aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Chalko <nchalko@google.com>2015-08-03 15:39:56 -0700
committerNick Chalko <nchalko@google.com>2015-08-03 15:53:37 -0700
commit816a4be1a0f34f6a48877c8afd3dbbca19eac435 (patch)
tree4f18dda269764494942f5313acc93db4a35d47db
parent6edd2b09e5d16a29c703a5fcbd2e88c5cf5e55b7 (diff)
downloadTV-816a4be1a0f34f6a48877c8afd3dbbca19eac435.tar.gz
Migrate Live Channels App Src to AOSP branch
Bug: 21625152 Change-Id: I07e2830b27440556dc757e6340b4f77d1c0cbc66
-rw-r--r--Android.mk37
-rw-r--r--AndroidManifest.xml114
-rw-r--r--LiveChannelsAndroidStyle.xml247
-rw-r--r--assets/rating_sources.html92
-rw-r--r--common/Android.mk11
-rw-r--r--common/src/com/android/tv/common/TvCommonConstants.java58
-rw-r--r--hwsample/AndroidManifest.xml41
-rw-r--r--hwsample/res/drawable-hdpi/fake_icon0.pngbin6415 -> 0 bytes
-rw-r--r--hwsample/res/drawable-hdpi/fake_icon1.pngbin10457 -> 0 bytes
-rw-r--r--hwsample/res/drawable-hdpi/fake_icon2.pngbin16904 -> 0 bytes
-rw-r--r--hwsample/res/drawable-hdpi/fake_icon3.pngbin10986 -> 0 bytes
-rw-r--r--hwsample/res/values/strings.xml21
-rw-r--r--hwsample/res/xml/hdmiservice.xml18
-rw-r--r--hwsample/src/com/example/android/fakehardwaretvinput/FakeHdmiService.java287
-rw-r--r--hwsample/src/com/example/android/fakehardwaretvinput/SettingsActivity.java52
-rw-r--r--open_source_project.LICENSE202
-rw-r--r--open_source_project.README13
-rw-r--r--proguard.flags (renamed from hwsample/Android.mk)25
-rw-r--r--res/anim/pin_dialog_enter.xml29
-rw-r--r--res/anim/pin_dialog_exit.xml29
-rw-r--r--res/animator/channel_banner_enter.xml31
-rw-r--r--res/animator/channel_banner_exit.xml31
-rw-r--r--res/animator/channel_banner_program_description_fade_in.xml25
-rw-r--r--res/animator/channel_banner_program_description_fade_out.xml25
-rw-r--r--res/animator/menu_enter.xml31
-rw-r--r--res/animator/menu_exit.xml31
-rw-r--r--res/animator/pin_adjacent_number_enter.xml23
-rw-r--r--res/animator/pin_adjacent_number_exit.xml23
-rw-r--r--res/animator/pin_focused_number_enter.xml23
-rw-r--r--res/animator/pin_focused_number_exit.xml23
-rw-r--r--res/animator/program_guide_side_panel_enter_full.xml25
-rw-r--r--res/animator/program_guide_side_panel_enter_partial.xml25
-rw-r--r--res/animator/program_guide_side_panel_exit.xml24
-rw-r--r--res/animator/program_guide_side_panel_grid_fade_in.xml25
-rw-r--r--res/animator/program_guide_side_panel_grid_fade_out.xml25
-rw-r--r--res/animator/program_guide_side_panel_hide.xml25
-rw-r--r--res/animator/program_guide_side_panel_reveal.xml25
-rw-r--r--res/animator/program_guide_side_panel_text_state_animator.xml62
-rw-r--r--res/animator/program_guide_table_enter_full.xml31
-rw-r--r--res/animator/program_guide_table_enter_partial.xml31
-rw-r--r--res/animator/program_guide_table_exit.xml29
-rw-r--r--res/animator/program_guide_table_fade_in.xml25
-rw-r--r--res/animator/program_guide_table_fade_out.xml24
-rw-r--r--res/animator/program_guide_table_full_to_partial.xml25
-rw-r--r--res/animator/program_guide_table_partial_to_full.xml25
-rw-r--r--res/animator/side_panel_enter.xml31
-rw-r--r--res/animator/side_panel_exit.xml31
-rw-r--r--res/animator/side_panel_fragment_enter.xml48
-rw-r--r--res/animator/side_panel_fragment_exit.xml31
-rw-r--r--res/animator/side_panel_fragment_pop_enter.xml48
-rw-r--r--res/animator/side_panel_fragment_pop_exit.xml31
-rw-r--r--res/animator/tvview_block_screen_fade_in.xml25
-rw-r--r--res/animator/tvview_block_screen_fade_out.xml25
-rw-r--r--res/color/card_meta_text_color.xml23
-rw-r--r--res/color/option_item_secondary_text_color.xml23
-rw-r--r--res/color/option_item_text_color.xml23
-rw-r--r--res/color/program_guide_table_item_grayed_out_program_text_color.xml23
-rw-r--r--res/color/side_panel_item_text.xml21
-rw-r--r--res/drawable-hdpi/app_icon.pngbin6797 -> 0 bytes
-rw-r--r--res/drawable-hdpi/bg_protection.pngbin1175436 -> 0 bytes
-rw-r--r--res/drawable-hdpi/btn_white_matte.9.pngbin4105 -> 0 bytes
-rw-r--r--res/drawable-hdpi/card_shadow_focused.9.pngbin2044 -> 0 bytes
-rw-r--r--res/drawable-hdpi/card_shadow_normal.9.pngbin1085 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_channel_guide.pngbin1207 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_dolby.pngbin633 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_launcher.pngbin4069 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_launcher_s.pngbin1345 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_tvoption_cc.pngbin2007 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_tvoption_display_mode.pngbin2184 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_tvoption_more.pngbin840 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_tvoption_pip.pngbin1390 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_tvoptions_edit_channels.pngbin1439 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_tvoptions_input.pngbin1915 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_tvoptions_input_settings.pngbin3446 -> 0 bytes
-rw-r--r--res/drawable-hdpi/ic_tvoptions_scan.pngbin3179 -> 0 bytes
-rw-r--r--res/drawable-ldpi/app_icon.pngbin3949 -> 0 bytes
-rw-r--r--res/drawable-ldpi/btn_white_matte.9.pngbin4105 -> 0 bytes
-rw-r--r--res/drawable-mdpi/bg_protection.pngbin452196 -> 0 bytes
-rw-r--r--res/drawable-mdpi/card_shadow_focused.9.pngbin1232 -> 0 bytes
-rw-r--r--res/drawable-mdpi/card_shadow_normal.9.pngbin701 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_channel_guide.pngbin1037 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_dolby.pngbin510 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_launcher.pngbin2729 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_launcher_s.pngbin879 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_tvoption_cc.pngbin1479 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_tvoption_display_mode.pngbin1519 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_tvoption_more.pngbin627 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_tvoption_pip.pngbin1106 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_tvoptions_edit_channels.pngbin1434 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_tvoptions_input.pngbin1472 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_tvoptions_input_settings.pngbin2448 -> 0 bytes
-rw-r--r--res/drawable-mdpi/ic_tvoptions_scan.pngbin2205 -> 0 bytes
-rw-r--r--res/drawable-nodpi/program_guide_table_item_background_focused.9.pngbin0 -> 93 bytes
-rw-r--r--res/drawable-nodpi/program_guide_table_item_background_focused_enabled.9.pngbin0 -> 93 bytes
-rw-r--r--res/drawable-nodpi/program_guide_table_item_background_normal.9.pngbin0 -> 92 bytes
-rw-r--r--res/drawable-xhdpi/app_icon.pngbin10040 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/banner.pngbin6367 -> 8223 bytes
-rw-r--r--res/drawable-xhdpi/bg_protection.pngbin2024593 -> 152456 bytes
-rw-r--r--res/drawable-xhdpi/bg_protection_right.pngbin2222580 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/btn_white_matte.9.pngbin4105 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/card_shadow_focused.9.pngbin3246 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/card_shadow_normal.9.pngbin1547 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_channel_guide.pngbin1527 -> 1527 bytes
-rw-r--r--res/drawable-xhdpi/ic_dolby.pngbin790 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_guide_lock.pngbin0 -> 1265 bytes
-rw-r--r--res/drawable-xhdpi/ic_launcher.pngbin3531 -> 3822 bytes
-rw-r--r--res/drawable-xhdpi/ic_launcher_s.pngbin1834 -> 1437 bytes
-rw-r--r--res/drawable-xhdpi/ic_message_lock.pngbin0 -> 7909 bytes
-rw-r--r--res/drawable-xhdpi/ic_message_lock_preview.pngbin0 -> 3093 bytes
-rw-r--r--res/drawable-xhdpi/ic_pip_loc_bottom_left.pngbin1353 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_pip_loc_bottom_right.pngbin2206 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_pip_loc_top_left.pngbin1352 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_pip_loc_top_right.pngbin1351 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_pip_option_input.pngbin0 -> 445 bytes
-rw-r--r--res/drawable-xhdpi/ic_pip_option_layout1.pngbin0 -> 302 bytes
-rw-r--r--res/drawable-xhdpi/ic_pip_option_layout2.pngbin0 -> 300 bytes
-rw-r--r--res/drawable-xhdpi/ic_pip_option_layout3.pngbin0 -> 299 bytes
-rw-r--r--res/drawable-xhdpi/ic_pip_option_layout4.pngbin0 -> 296 bytes
-rw-r--r--res/drawable-xhdpi/ic_pip_option_layout5.pngbin0 -> 308 bytes
-rw-r--r--res/drawable-xhdpi/ic_pip_option_size.pngbin0 -> 262 bytes
-rw-r--r--res/drawable-xhdpi/ic_pip_option_swap.pngbin0 -> 388 bytes
-rw-r--r--res/drawable-xhdpi/ic_pip_option_swap_audio.pngbin0 -> 846 bytes
-rw-r--r--res/drawable-xhdpi/ic_recent_thumbnail_default.pngbin0 -> 3072 bytes
-rw-r--r--res/drawable-xhdpi/ic_related_actor.pngbin0 -> 3073 bytes
-rw-r--r--res/drawable-xhdpi/ic_related_search.pngbin0 -> 4701 bytes
-rw-r--r--res/drawable-xhdpi/ic_setup_channels.pngbin0 -> 2429 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoption_about.pngbin0 -> 932 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoption_aspect.pngbin0 -> 333 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoption_cc.pngbin2419 -> 500 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoption_channel_sources.pngbin0 -> 423 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoption_display_mode.pngbin2744 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoption_locate_pip.pngbin1840 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoption_more.pngbin1054 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoption_multi_track.pngbin0 -> 862 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoption_parental.pngbin0 -> 751 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoption_pip.pngbin1681 -> 347 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoption_pip_off.pngbin0 -> 469 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoptions_edit_channels.pngbin2316 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoptions_input.pngbin2394 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoptions_input_settings.pngbin4633 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvoptions_scan.pngbin4130 -> 0 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_000.pngbin0 -> 1699 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_001.pngbin0 -> 1674 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_002.pngbin0 -> 1583 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_003.pngbin0 -> 1512 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_004.pngbin0 -> 1479 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_005.pngbin0 -> 1660 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_006.pngbin0 -> 1997 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_007.pngbin0 -> 2293 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_008.pngbin0 -> 2199 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_009.pngbin0 -> 1968 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_010.pngbin0 -> 1959 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_011.pngbin0 -> 1874 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_012.pngbin0 -> 1844 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_013.pngbin0 -> 1892 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_014.pngbin0 -> 1860 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_015.pngbin0 -> 1850 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_000.pngbin0 -> 1783 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_001.pngbin0 -> 1865 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_002.pngbin0 -> 1927 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_003.pngbin0 -> 1889 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_004.pngbin0 -> 1810 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_005.pngbin0 -> 1691 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_006.pngbin0 -> 1649 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_007.pngbin0 -> 1620 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_008.pngbin0 -> 1593 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_009.pngbin0 -> 1558 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_010.pngbin0 -> 1534 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_011.pngbin0 -> 1516 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_012.pngbin0 -> 1492 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_013.pngbin0 -> 1494 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_014.pngbin0 -> 1483 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_015.pngbin0 -> 1488 bytes
-rw-r--r--res/drawable-xhdpi/ic_tvsidepanel_partial_locked.pngbin0 -> 1242 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_remote.pngbin0 -> 10193 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_000.pngbin0 -> 2852 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_001.pngbin0 -> 3528 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_002.pngbin0 -> 3963 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_003.pngbin0 -> 4208 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_004.pngbin0 -> 4447 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_005.pngbin0 -> 4595 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_006.pngbin0 -> 4768 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_007.pngbin0 -> 4949 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_008.pngbin0 -> 5095 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_009.pngbin0 -> 5138 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_010.pngbin0 -> 5087 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_011.pngbin0 -> 5195 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_012.pngbin0 -> 5686 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_013.pngbin0 -> 6141 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_014.pngbin0 -> 6205 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_015.pngbin0 -> 5946 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_016.pngbin0 -> 6072 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_017.pngbin0 -> 6219 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_018.pngbin0 -> 6231 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_019.pngbin0 -> 6261 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_020.pngbin0 -> 6459 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_021.pngbin0 -> 6494 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_022.pngbin0 -> 6512 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_023.pngbin0 -> 6468 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_024.pngbin0 -> 6431 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_025.pngbin0 -> 6519 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_026.pngbin0 -> 6360 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_027.pngbin0 -> 5989 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_028.pngbin0 -> 5795 bytes
-rw-r--r--res/drawable-xhdpi/ic_welcome_ripple_029.pngbin0 -> 5247 bytes
-rw-r--r--res/drawable-xhdpi/ic_youtube.pngbin0 -> 1008 bytes
-rw-r--r--res/drawable-xxhdpi/card_shadow_focused.9.pngbin6113 -> 0 bytes
-rw-r--r--res/drawable-xxhdpi/card_shadow_normal.9.pngbin3174 -> 0 bytes
-rw-r--r--res/drawable-xxhdpi/ic_channel_guide.pngbin1648 -> 0 bytes
-rw-r--r--res/drawable-xxhdpi/ic_dolby.pngbin1360 -> 0 bytes
-rw-r--r--res/drawable-xxhdpi/ic_tvoption_cc.pngbin2794 -> 0 bytes
-rw-r--r--res/drawable-xxhdpi/ic_tvoption_display_mode.pngbin3238 -> 0 bytes
-rw-r--r--res/drawable-xxhdpi/ic_tvoption_more.pngbin2106 -> 0 bytes
-rw-r--r--res/drawable-xxhdpi/ic_tvoption_pip.pngbin2206 -> 0 bytes
-rw-r--r--res/drawable-xxhdpi/ic_tvoptions_edit_channels.pngbin2393 -> 0 bytes
-rw-r--r--res/drawable-xxhdpi/ic_tvoptions_input.pngbin2656 -> 0 bytes
-rw-r--r--res/drawable-xxhdpi/ic_tvoptions_input_settings.pngbin4519 -> 0 bytes
-rw-r--r--res/drawable-xxhdpi/ic_tvoptions_scan.pngbin4298 -> 0 bytes
-rw-r--r--res/drawable/action_card_background.xml35
-rw-r--r--res/drawable/btn_lock_material.xml20
-rw-r--r--res/drawable/btn_lock_material_anim.xml145
-rw-r--r--res/drawable/btn_partial_lock_material.xml20
-rw-r--r--res/drawable/btn_unlock_material.xml20
-rw-r--r--res/drawable/card_image_gradient.xml24
-rw-r--r--res/drawable/channel_banner_tile_border.xml24
-rw-r--r--res/drawable/channel_card_progress_bar.xml31
-rw-r--r--res/drawable/channel_tile_bottom.xml21
-rw-r--r--res/drawable/channel_tile_guide_bottom.xml21
-rw-r--r--res/drawable/channel_tile_guide_top.xml20
-rw-r--r--res/drawable/channel_tile_top.xml20
-rw-r--r--res/drawable/channel_tile_top_on_image.xml24
-rw-r--r--res/drawable/debug_option_background.xml22
-rw-r--r--res/drawable/ic_welcome_ripple_anim.xml51
-rw-r--r--res/drawable/info_banner_background.xml21
-rw-r--r--res/drawable/key_blue.xml25
-rw-r--r--res/drawable/key_green.xml25
-rw-r--r--res/drawable/key_red.xml25
-rw-r--r--res/drawable/key_yellow.xml25
-rw-r--r--res/drawable/keypad_background.xml24
-rw-r--r--res/drawable/menu_background.xml21
-rw-r--r--res/drawable/options_selection_circle.xml23
-rw-r--r--res/drawable/pin_dialog_background.xml21
-rw-r--r--res/drawable/pin_number_picker_focused_background.xml21
-rw-r--r--res/drawable/play_controls_body_background.xml29
-rw-r--r--res/drawable/play_controls_body_shape.xml20
-rw-r--r--res/drawable/play_controls_button_background.xml30
-rw-r--r--res/drawable/play_controls_time_indicator.xml23
-rw-r--r--res/drawable/program_guide_table_header_column_item_background.xml25
-rw-r--r--res/drawable/program_guide_table_header_column_last_item_background.xml25
-rw-r--r--res/drawable/program_guide_table_header_row_background.xml26
-rw-r--r--res/drawable/program_guide_table_item_background.xml56
-rw-r--r--res/drawable/program_guide_time_indicator.xml31
-rw-r--r--res/drawable/progress_horizontal.xml29
-rw-r--r--res/drawable/setup_item_background.xml28
-rw-r--r--res/drawable/track_meta_border.xml29
-rw-r--r--res/layout/action_tile.xml58
-rw-r--r--res/layout/activity_fragment.xml21
-rw-r--r--res/layout/activity_passthrough_tv.xml28
-rw-r--r--res/layout/activity_tv.xml112
-rw-r--r--res/layout/block_screen.xml67
-rw-r--r--res/layout/channel_banner.xml290
-rw-r--r--res/layout/channel_item.xml31
-rw-r--r--res/layout/channel_number.xml44
-rw-r--r--res/layout/channel_tile.xml83
-rw-r--r--res/layout/dialog_edit_input.xml35
-rw-r--r--res/layout/edit_channels_item.xml78
-rw-r--r--res/layout/empty_info_banner.xml25
-rw-r--r--res/layout/input_banner.xml52
-rw-r--r--res/layout/intro_dialog.xml68
-rw-r--r--res/layout/item_list.xml76
-rw-r--r--res/layout/keypad_channel_switch.xml57
-rw-r--r--res/layout/keypad_channel_switch_item.xml48
-rw-r--r--res/layout/keypad_view.xml216
-rw-r--r--res/layout/list_item_watched_program.xml48
-rw-r--r--res/layout/main_menu.xml37
-rw-r--r--res/layout/menu.xml43
-rw-r--r--res/layout/menu_card_action.xml68
-rw-r--r--res/layout/menu_card_app_link.xml86
-rw-r--r--res/layout/menu_card_channel.xml102
-rw-r--r--res/layout/menu_card_guide.xml52
-rw-r--r--res/layout/menu_card_setup.xml51
-rw-r--r--res/layout/option_container.xml44
-rw-r--r--res/layout/option_fragment.xml97
-rw-r--r--res/layout/option_item.xml36
-rw-r--r--res/layout/option_item_action.xml60
-rw-r--r--res/layout/option_item_attribution.xml40
-rw-r--r--res/layout/option_item_channel_check.xml40
-rw-r--r--res/layout/option_item_channel_common.xml68
-rw-r--r--res/layout/option_item_channel_lock.xml41
-rw-r--r--res/layout/option_item_check_box.xml57
-rw-r--r--res/layout/option_item_common.xml49
-rw-r--r--res/layout/option_item_divider.xml56
-rw-r--r--res/layout/option_item_radio_button.xml54
-rw-r--r--res/layout/option_item_rating.xml81
-rw-r--r--res/layout/option_item_simple.xml33
-rw-r--r--res/layout/option_item_sub_menu.xml36
-rw-r--r--res/layout/option_item_switch.xml40
-rw-r--r--res/layout/overlay_root_view.xml43
-rw-r--r--res/layout/pin_dialog.xml89
-rw-r--r--res/layout/pin_number_picker.xml51
-rw-r--r--res/layout/pip_location_item.xml41
-rw-r--r--res/layout/play_controls.xml53
-rw-r--r--res/layout/play_controls_button.xml40
-rw-r--r--res/layout/play_controls_contents.xml179
-rw-r--r--res/layout/program_guide.xml28
-rw-r--r--res/layout/program_guide_side_panel.xml55
-rw-r--r--res/layout/program_guide_side_panel_row.xml34
-rw-r--r--res/layout/program_guide_table.xml90
-rw-r--r--res/layout/program_guide_table_header_column_item.xml67
-rw-r--r--res/layout/program_guide_table_header_row_item.xml25
-rw-r--r--res/layout/program_guide_table_item.xml33
-rw-r--r--res/layout/program_guide_table_row.xml39
-rw-r--r--res/layout/program_guide_table_row_detail.xml123
-rw-r--r--res/layout/program_track_meta.xml38
-rw-r--r--res/layout/select_input.xml31
-rw-r--r--res/layout/select_input_item.xml44
-rw-r--r--res/layout/setup_dialog.xml64
-rw-r--r--res/layout/setup_item_action.xml42
-rw-r--r--res/layout/setup_item_divider.xml27
-rw-r--r--res/layout/setup_item_input.xml51
-rw-r--r--res/layout/shadow_focused.xml6
-rw-r--r--res/layout/shadow_normal.xml5
-rw-r--r--res/layout/show_only_item.xml34
-rw-r--r--res/layout/simple_guide_item.xml137
-rw-r--r--res/layout/tunable_tv_view.xml61
-rw-r--r--res/transition/transition_between_scenes.xml32
-rw-r--r--res/values-af/arrays.xml45
-rw-r--r--res/values-af/rating_system_strings.xml181
-rw-r--r--res/values-af/strings.xml182
-rw-r--r--res/values-am/arrays.xml45
-rw-r--r--res/values-am/rating_system_strings.xml181
-rw-r--r--res/values-am/strings.xml182
-rw-r--r--res/values-ar/arrays.xml45
-rw-r--r--res/values-ar/rating_system_strings.xml181
-rw-r--r--res/values-ar/strings.xml182
-rw-r--r--res/values-bg/arrays.xml45
-rw-r--r--res/values-bg/rating_system_strings.xml181
-rw-r--r--res/values-bg/strings.xml182
-rw-r--r--res/values-bn-rBD/arrays.xml45
-rw-r--r--res/values-bn-rBD/rating_system_strings.xml181
-rw-r--r--res/values-bn-rBD/strings.xml182
-rw-r--r--res/values-ca/arrays.xml45
-rw-r--r--res/values-ca/rating_system_strings.xml181
-rw-r--r--res/values-ca/strings.xml182
-rw-r--r--res/values-cs/arrays.xml45
-rw-r--r--res/values-cs/rating_system_strings.xml181
-rw-r--r--res/values-cs/strings.xml182
-rw-r--r--res/values-da/arrays.xml45
-rw-r--r--res/values-da/rating_system_strings.xml181
-rw-r--r--res/values-da/strings.xml182
-rw-r--r--res/values-de/arrays.xml45
-rw-r--r--res/values-de/rating_system_strings.xml181
-rw-r--r--res/values-de/strings.xml182
-rw-r--r--res/values-el/arrays.xml45
-rw-r--r--res/values-el/rating_system_strings.xml181
-rw-r--r--res/values-el/strings.xml180
-rw-r--r--res/values-en-rAU/arrays.xml45
-rw-r--r--res/values-en-rAU/rating_system_strings.xml181
-rw-r--r--res/values-en-rAU/strings.xml182
-rw-r--r--res/values-en-rGB/arrays.xml45
-rw-r--r--res/values-en-rGB/rating_system_strings.xml181
-rw-r--r--res/values-en-rGB/strings.xml182
-rw-r--r--res/values-en-rIN/arrays.xml45
-rw-r--r--res/values-en-rIN/rating_system_strings.xml181
-rw-r--r--res/values-en-rIN/strings.xml182
-rw-r--r--res/values-es-rUS/arrays.xml45
-rw-r--r--res/values-es-rUS/rating_system_strings.xml181
-rw-r--r--res/values-es-rUS/strings.xml182
-rw-r--r--res/values-es/arrays.xml45
-rw-r--r--res/values-es/rating_system_strings.xml181
-rw-r--r--res/values-es/strings.xml182
-rw-r--r--res/values-et-rEE/arrays.xml45
-rw-r--r--res/values-et-rEE/rating_system_strings.xml181
-rw-r--r--res/values-et-rEE/strings.xml182
-rw-r--r--res/values-eu-rES/arrays.xml45
-rw-r--r--res/values-eu-rES/rating_system_strings.xml181
-rw-r--r--res/values-eu-rES/strings.xml182
-rw-r--r--res/values-fa/arrays.xml45
-rw-r--r--res/values-fa/rating_system_strings.xml181
-rw-r--r--res/values-fa/strings.xml182
-rw-r--r--res/values-fi/arrays.xml45
-rw-r--r--res/values-fi/rating_system_strings.xml181
-rw-r--r--res/values-fi/strings.xml182
-rw-r--r--res/values-fr-rCA/arrays.xml45
-rw-r--r--res/values-fr-rCA/rating_system_strings.xml181
-rw-r--r--res/values-fr-rCA/strings.xml182
-rw-r--r--res/values-fr/arrays.xml45
-rw-r--r--res/values-fr/rating_system_strings.xml181
-rw-r--r--res/values-fr/strings.xml182
-rw-r--r--res/values-gl-rES/arrays.xml45
-rw-r--r--res/values-gl-rES/rating_system_strings.xml181
-rw-r--r--res/values-gl-rES/strings.xml182
-rw-r--r--res/values-hi/arrays.xml45
-rw-r--r--res/values-hi/rating_system_strings.xml181
-rw-r--r--res/values-hi/strings.xml182
-rw-r--r--res/values-hr/arrays.xml45
-rw-r--r--res/values-hr/rating_system_strings.xml181
-rw-r--r--res/values-hr/strings.xml182
-rw-r--r--res/values-hu/arrays.xml45
-rw-r--r--res/values-hu/rating_system_strings.xml181
-rw-r--r--res/values-hu/strings.xml182
-rw-r--r--res/values-hy-rAM/arrays.xml45
-rw-r--r--res/values-hy-rAM/rating_system_strings.xml181
-rw-r--r--res/values-hy-rAM/strings.xml182
-rw-r--r--res/values-in/arrays.xml45
-rw-r--r--res/values-in/rating_system_strings.xml181
-rw-r--r--res/values-in/strings.xml182
-rw-r--r--res/values-is-rIS/arrays.xml45
-rw-r--r--res/values-is-rIS/rating_system_strings.xml181
-rw-r--r--res/values-is-rIS/strings.xml182
-rw-r--r--res/values-it/arrays.xml45
-rw-r--r--res/values-it/rating_system_strings.xml181
-rw-r--r--res/values-it/strings.xml184
-rw-r--r--res/values-iw/arrays.xml45
-rw-r--r--res/values-iw/rating_system_strings.xml181
-rw-r--r--res/values-iw/strings.xml182
-rw-r--r--res/values-ja/arrays.xml45
-rw-r--r--res/values-ja/rating_system_strings.xml181
-rw-r--r--res/values-ja/strings.xml182
-rw-r--r--res/values-ka-rGE/arrays.xml45
-rw-r--r--res/values-ka-rGE/rating_system_strings.xml181
-rw-r--r--res/values-ka-rGE/strings.xml182
-rw-r--r--res/values-kk-rKZ/arrays.xml45
-rw-r--r--res/values-kk-rKZ/rating_system_strings.xml181
-rw-r--r--res/values-kk-rKZ/strings.xml182
-rw-r--r--res/values-km-rKH/arrays.xml45
-rw-r--r--res/values-km-rKH/rating_system_strings.xml181
-rw-r--r--res/values-km-rKH/strings.xml182
-rw-r--r--res/values-kn-rIN/arrays.xml45
-rw-r--r--res/values-kn-rIN/rating_system_strings.xml181
-rw-r--r--res/values-kn-rIN/strings.xml182
-rw-r--r--res/values-ko/arrays.xml45
-rw-r--r--res/values-ko/rating_system_strings.xml181
-rw-r--r--res/values-ko/strings.xml184
-rw-r--r--res/values-ky-rKG/arrays.xml45
-rw-r--r--res/values-ky-rKG/rating_system_strings.xml181
-rw-r--r--res/values-ky-rKG/strings.xml182
-rw-r--r--res/values-ldrtl/dimens.xml37
-rw-r--r--res/values-ldrtl/integers.xml21
-rw-r--r--res/values-lo-rLA/arrays.xml45
-rw-r--r--res/values-lo-rLA/rating_system_strings.xml181
-rw-r--r--res/values-lo-rLA/strings.xml182
-rw-r--r--res/values-lt/arrays.xml45
-rw-r--r--res/values-lt/rating_system_strings.xml181
-rw-r--r--res/values-lt/strings.xml182
-rw-r--r--res/values-lv/arrays.xml45
-rw-r--r--res/values-lv/rating_system_strings.xml181
-rw-r--r--res/values-lv/strings.xml182
-rw-r--r--res/values-mk-rMK/arrays.xml45
-rw-r--r--res/values-mk-rMK/rating_system_strings.xml181
-rw-r--r--res/values-mk-rMK/strings.xml182
-rw-r--r--res/values-ml-rIN/arrays.xml45
-rw-r--r--res/values-ml-rIN/rating_system_strings.xml181
-rw-r--r--res/values-ml-rIN/strings.xml182
-rw-r--r--res/values-mn-rMN/arrays.xml45
-rw-r--r--res/values-mn-rMN/rating_system_strings.xml181
-rw-r--r--res/values-mn-rMN/strings.xml182
-rw-r--r--res/values-mr-rIN/arrays.xml45
-rw-r--r--res/values-mr-rIN/rating_system_strings.xml181
-rw-r--r--res/values-mr-rIN/strings.xml182
-rw-r--r--res/values-ms-rMY/arrays.xml45
-rw-r--r--res/values-ms-rMY/rating_system_strings.xml181
-rw-r--r--res/values-ms-rMY/strings.xml182
-rw-r--r--res/values-my-rMM/arrays.xml45
-rw-r--r--res/values-my-rMM/rating_system_strings.xml181
-rw-r--r--res/values-my-rMM/strings.xml182
-rw-r--r--res/values-nb/arrays.xml45
-rw-r--r--res/values-nb/rating_system_strings.xml181
-rw-r--r--res/values-nb/strings.xml182
-rw-r--r--res/values-ne-rNP/arrays.xml45
-rw-r--r--res/values-ne-rNP/rating_system_strings.xml181
-rw-r--r--res/values-ne-rNP/strings.xml182
-rw-r--r--res/values-nl/arrays.xml45
-rw-r--r--res/values-nl/rating_system_strings.xml181
-rw-r--r--res/values-nl/strings.xml182
-rw-r--r--res/values-pl/arrays.xml45
-rw-r--r--res/values-pl/rating_system_strings.xml181
-rw-r--r--res/values-pl/strings.xml182
-rw-r--r--res/values-pt-rPT/arrays.xml45
-rw-r--r--res/values-pt-rPT/rating_system_strings.xml181
-rw-r--r--res/values-pt-rPT/strings.xml182
-rw-r--r--res/values-pt/arrays.xml45
-rw-r--r--res/values-pt/rating_system_strings.xml181
-rw-r--r--res/values-pt/strings.xml182
-rw-r--r--res/values-ro/arrays.xml45
-rw-r--r--res/values-ro/rating_system_strings.xml181
-rw-r--r--res/values-ro/strings.xml182
-rw-r--r--res/values-ru/arrays.xml45
-rw-r--r--res/values-ru/rating_system_strings.xml181
-rw-r--r--res/values-ru/strings.xml182
-rw-r--r--res/values-si-rLK/arrays.xml45
-rw-r--r--res/values-si-rLK/rating_system_strings.xml181
-rw-r--r--res/values-si-rLK/strings.xml182
-rw-r--r--res/values-sk/arrays.xml45
-rw-r--r--res/values-sk/rating_system_strings.xml181
-rw-r--r--res/values-sk/strings.xml182
-rw-r--r--res/values-sl/arrays.xml45
-rw-r--r--res/values-sl/rating_system_strings.xml181
-rw-r--r--res/values-sl/strings.xml182
-rw-r--r--res/values-sr/arrays.xml45
-rw-r--r--res/values-sr/rating_system_strings.xml181
-rw-r--r--res/values-sr/strings.xml182
-rw-r--r--res/values-sv/arrays.xml45
-rw-r--r--res/values-sv/rating_system_strings.xml181
-rw-r--r--res/values-sv/strings.xml182
-rw-r--r--res/values-sw/arrays.xml45
-rw-r--r--res/values-sw/rating_system_strings.xml181
-rw-r--r--res/values-sw/strings.xml182
-rw-r--r--res/values-ta-rIN/arrays.xml45
-rw-r--r--res/values-ta-rIN/rating_system_strings.xml181
-rw-r--r--res/values-ta-rIN/strings.xml182
-rw-r--r--res/values-te-rIN/arrays.xml45
-rw-r--r--res/values-te-rIN/rating_system_strings.xml181
-rw-r--r--res/values-te-rIN/strings.xml182
-rw-r--r--res/values-th/arrays.xml45
-rw-r--r--res/values-th/rating_system_strings.xml181
-rw-r--r--res/values-th/strings.xml182
-rw-r--r--res/values-tl/arrays.xml45
-rw-r--r--res/values-tl/rating_system_strings.xml181
-rw-r--r--res/values-tl/strings.xml182
-rw-r--r--res/values-tr/arrays.xml45
-rw-r--r--res/values-tr/rating_system_strings.xml181
-rw-r--r--res/values-tr/strings.xml182
-rw-r--r--res/values-uk/arrays.xml45
-rw-r--r--res/values-uk/rating_system_strings.xml181
-rw-r--r--res/values-uk/strings.xml182
-rw-r--r--res/values-ur-rPK/arrays.xml45
-rw-r--r--res/values-ur-rPK/rating_system_strings.xml181
-rw-r--r--res/values-ur-rPK/strings.xml182
-rw-r--r--res/values-uz-rUZ/arrays.xml45
-rw-r--r--res/values-uz-rUZ/rating_system_strings.xml181
-rw-r--r--res/values-uz-rUZ/strings.xml182
-rw-r--r--res/values-vi/arrays.xml45
-rw-r--r--res/values-vi/rating_system_strings.xml181
-rw-r--r--res/values-vi/strings.xml182
-rw-r--r--res/values-zh-rCN/arrays.xml45
-rw-r--r--res/values-zh-rCN/rating_system_strings.xml181
-rw-r--r--res/values-zh-rCN/strings.xml182
-rw-r--r--res/values-zh-rHK/arrays.xml45
-rw-r--r--res/values-zh-rHK/rating_system_strings.xml181
-rw-r--r--res/values-zh-rHK/strings.xml182
-rw-r--r--res/values-zh-rTW/arrays.xml45
-rw-r--r--res/values-zh-rTW/rating_system_strings.xml181
-rw-r--r--res/values-zh-rTW/strings.xml182
-rw-r--r--res/values-zu/arrays.xml45
-rw-r--r--res/values-zu/rating_system_strings.xml181
-rw-r--r--res/values-zu/strings.xml182
-rw-r--r--res/values/arrays.xml50
-rw-r--r--res/values/attr.xml25
-rw-r--r--res/values/colors.xml157
-rw-r--r--res/values/dimens.xml347
-rw-r--r--res/values/floats.xml34
-rw-r--r--res/values/ids.xml34
-rw-r--r--res/values/integers.xml92
-rw-r--r--res/values/rating_system_strings.xml153
-rw-r--r--res/values/strings.xml640
-rw-r--r--res/values/styles.xml66
-rw-r--r--res/values/tags.xml29
-rw-r--r--res/values/themes.xml49
-rw-r--r--res/xml/searchable.xml33
-rw-r--r--res/xml/tv_content_rating_systems.xml819
-rw-r--r--sample/Android.mk16
-rw-r--r--sample/AndroidManifest.xml103
-rw-r--r--sample/res/drawable/layout_border.xml21
-rw-r--r--sample/res/layout/overlay.xml65
-rw-r--r--sample/res/raw/hls_channels.xml78
-rw-r--r--sample/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gpbin584488 -> 0 bytes
-rw-r--r--sample/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4bin1218098 -> 0 bytes
-rw-r--r--sample/res/values/strings.xml29
-rw-r--r--sample/src/com/example/android/sampleproxyservice/ExternalSettopBox.java75
-rw-r--r--sample/src/com/example/android/sampleproxyservice/FakeHdmiTvInputService.java66
-rw-r--r--sample/src/com/example/android/sampleproxyservice/SampleProxyService.java117
-rw-r--r--sample/src/com/example/android/sampleproxyservice/SampleProxySetupActivity.java86
-rw-r--r--sample/src/com/example/sampletvinput/BaseTvInputService.java493
-rw-r--r--sample/src/com/example/sampletvinput/ChannelUtils.java74
-rw-r--r--sample/src/com/example/sampletvinput/ChannelXMLParser.java161
-rw-r--r--sample/src/com/example/sampletvinput/ExternalFileTvInputService.java66
-rw-r--r--sample/src/com/example/sampletvinput/ExternalFileTvInputSettingsActivity.java77
-rw-r--r--sample/src/com/example/sampletvinput/HlsTvInputService.java137
-rw-r--r--sample/src/com/example/sampletvinput/LocalTvInputService.java162
-rw-r--r--sample/src/com/example/sampletvinput/SampleTvInputSettingsActivity.java53
-rw-r--r--sample/src/com/example/sampletvinput/SampleTvInputSetupActivity.java91
-rw-r--r--src/com/android/tv/ChannelTuner.java420
-rw-r--r--src/com/android/tv/LauncherActivity.java113
-rw-r--r--src/com/android/tv/MainActivity.java2757
-rw-r--r--src/com/android/tv/PassthroughTvActivity.java143
-rw-r--r--src/com/android/tv/TimeShiftManager.java1295
-rw-r--r--src/com/android/tv/TvActivity.java1581
-rw-r--r--src/com/android/tv/TvApplication.java135
-rw-r--r--src/com/android/tv/TvOptionsManager.java194
-rw-r--r--src/com/android/tv/analytics/Analytics.java (renamed from sample/res/xml/sampleproxyservice.xml)19
-rw-r--r--src/com/android/tv/analytics/DurationTimer.java62
-rw-r--r--src/com/android/tv/analytics/HasTrackerLabel.java (renamed from sample/res/xml/tvinputservice.xml)26
-rw-r--r--src/com/android/tv/analytics/StubAnalytics.java39
-rw-r--r--src/com/android/tv/analytics/StubTracker.java105
-rw-r--r--src/com/android/tv/analytics/Tracker.java198
-rw-r--r--src/com/android/tv/customization/CustomAction.java86
-rw-r--r--src/com/android/tv/customization/TvCustomizationManager.java181
-rw-r--r--src/com/android/tv/data/Channel.java661
-rw-r--r--src/com/android/tv/data/ChannelDataManager.java644
-rw-r--r--src/com/android/tv/data/ChannelLogoFetcher.java319
-rw-r--r--src/com/android/tv/data/ChannelMap.java312
-rw-r--r--src/com/android/tv/data/ChannelNumber.java122
-rw-r--r--src/com/android/tv/data/DisplayMode.java6
-rw-r--r--src/com/android/tv/data/GenreItems.java90
-rw-r--r--src/com/android/tv/data/OnCurrentProgramUpdatedListener.java24
-rw-r--r--src/com/android/tv/data/Program.java395
-rw-r--r--src/com/android/tv/data/ProgramDataManager.java695
-rw-r--r--src/com/android/tv/data/StreamInfo.java17
-rw-r--r--src/com/android/tv/dialog/EditInputDialogFragment.java176
-rw-r--r--src/com/android/tv/dialog/FullscreenDialogFragment.java112
-rw-r--r--src/com/android/tv/dialog/PinDialogFragment.java672
-rw-r--r--src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java36
-rw-r--r--src/com/android/tv/dialog/SafeDismissDialogFragment.java111
-rw-r--r--src/com/android/tv/dialog/WebDialogFragment.java96
-rw-r--r--src/com/android/tv/guide/GenreListAdapter.java116
-rw-r--r--src/com/android/tv/guide/GuideUtils.java56
-rw-r--r--src/com/android/tv/guide/ProgramGrid.java340
-rw-r--r--src/com/android/tv/guide/ProgramGuide.java899
-rw-r--r--src/com/android/tv/guide/ProgramItemView.java365
-rw-r--r--src/com/android/tv/guide/ProgramListAdapter.java131
-rw-r--r--src/com/android/tv/guide/ProgramManager.java603
-rw-r--r--src/com/android/tv/guide/ProgramRow.java321
-rw-r--r--src/com/android/tv/guide/ProgramTableAdapter.java509
-rw-r--r--src/com/android/tv/guide/TimeListAdapter.java98
-rw-r--r--src/com/android/tv/guide/TimelineGridView.java58
-rw-r--r--src/com/android/tv/guide/TimelineRow.java88
-rw-r--r--src/com/android/tv/input/TisTvInput.java95
-rw-r--r--src/com/android/tv/input/TvInput.java51
-rw-r--r--src/com/android/tv/input/UnifiedTvInput.java104
-rw-r--r--src/com/android/tv/license/LicenseUtils.java58
-rw-r--r--src/com/android/tv/menu/ActionCardView.java95
-rw-r--r--src/com/android/tv/menu/AppLinkCardView.java305
-rw-r--r--src/com/android/tv/menu/BaseCardView.java175
-rw-r--r--src/com/android/tv/menu/ChannelCardView.java235
-rw-r--r--src/com/android/tv/menu/ChannelsPosterPrefetcher.java109
-rw-r--r--src/com/android/tv/menu/ChannelsRow.java81
-rw-r--r--src/com/android/tv/menu/ChannelsRowAdapter.java193
-rw-r--r--src/com/android/tv/menu/CustomizableOptionsRowAdapter.java83
-rw-r--r--src/com/android/tv/menu/GuideCardView.java50
-rw-r--r--src/com/android/tv/menu/ItemListRow.java74
-rw-r--r--src/com/android/tv/menu/ItemListRowView.java215
-rw-r--r--src/com/android/tv/menu/MenuAction.java147
-rw-r--r--src/com/android/tv/menu/MenuRow.java99
-rw-r--r--src/com/android/tv/menu/MenuRowView.java340
-rw-r--r--src/com/android/tv/menu/MenuView.java665
-rw-r--r--src/com/android/tv/menu/OptionsRowAdapter.java152
-rw-r--r--src/com/android/tv/menu/PartnerOptionsRowAdapter.java45
-rw-r--r--src/com/android/tv/menu/PipOptionsRowAdapter.java137
-rw-r--r--src/com/android/tv/menu/PlayControlsButton.java100
-rw-r--r--src/com/android/tv/menu/PlayControlsRow.java54
-rw-r--r--src/com/android/tv/menu/PlayControlsRowView.java504
-rw-r--r--src/com/android/tv/menu/SetupCardView.java53
-rw-r--r--src/com/android/tv/menu/TvOptionsRowAdapter.java194
-rw-r--r--src/com/android/tv/notification/NotificationService.java325
-rw-r--r--src/com/android/tv/parental/ContentRatingLevelPolicy.java92
-rw-r--r--src/com/android/tv/parental/ContentRatingSystem.java393
-rw-r--r--src/com/android/tv/parental/ContentRatingsManager.java84
-rw-r--r--src/com/android/tv/parental/ContentRatingsParser.java453
-rw-r--r--src/com/android/tv/parental/ParentalControlSettings.java280
-rw-r--r--src/com/android/tv/receiver/AudioCapabilitiesReceiver.java100
-rw-r--r--src/com/android/tv/receiver/GlobalKeyReceiver.java22
-rw-r--r--src/com/android/tv/receiver/NotificationReceiver.java4
-rw-r--r--src/com/android/tv/receiver/PackageIntentsReceiver.java86
-rw-r--r--src/com/android/tv/recommendation/ChannelRecord.java92
-rw-r--r--src/com/android/tv/recommendation/FavoriteChannelEvaluator.java52
-rw-r--r--src/com/android/tv/recommendation/NotificationService.java459
-rw-r--r--src/com/android/tv/recommendation/RandomEvaluator.java (renamed from src/com/android/tv/recommendation/RandomRecommender.java)9
-rw-r--r--src/com/android/tv/recommendation/RecentChannelEvaluator.java64
-rw-r--r--src/com/android/tv/recommendation/RecentChannelRecommender.java47
-rw-r--r--src/com/android/tv/recommendation/RecommendationDataManager.java583
-rw-r--r--src/com/android/tv/recommendation/Recommender.java336
-rw-r--r--src/com/android/tv/recommendation/RoutineWatchEvaluator.java246
-rw-r--r--src/com/android/tv/recommendation/RoutineWatchRecommender.java107
-rw-r--r--src/com/android/tv/recommendation/SampleRecommender.java49
-rw-r--r--src/com/android/tv/recommendation/TvRecommendation.java481
-rw-r--r--src/com/android/tv/recommendation/WatchedProgram.java47
-rw-r--r--src/com/android/tv/search/LocalSearchProvider.java91
-rw-r--r--src/com/android/tv/search/ProgramGuideSearchFragment.java220
-rw-r--r--src/com/android/tv/search/SearchResult.java100
-rw-r--r--src/com/android/tv/search/TvProviderSearch.java535
-rw-r--r--src/com/android/tv/ui/ActionTileView.java124
-rw-r--r--src/com/android/tv/ui/AppLayerTvView.java49
-rw-r--r--src/com/android/tv/ui/ChannelBannerView.java662
-rw-r--r--src/com/android/tv/ui/ChannelListAdapter.java140
-rw-r--r--src/com/android/tv/ui/ChannelNumberView.java353
-rw-r--r--src/com/android/tv/ui/ChannelTileView.java189
-rw-r--r--src/com/android/tv/ui/FullscreenDialogView.java202
-rw-r--r--src/com/android/tv/ui/HardwareLayerAnimatorListenerAdapter.java50
-rw-r--r--src/com/android/tv/ui/InputBannerView.java95
-rw-r--r--src/com/android/tv/ui/IntroView.java84
-rw-r--r--src/com/android/tv/ui/ItemListView.java196
-rw-r--r--src/com/android/tv/ui/KeypadChannelSwitchView.java393
-rw-r--r--src/com/android/tv/ui/KeypadView.java130
-rw-r--r--src/com/android/tv/ui/LocaleAwareTextClock.java44
-rw-r--r--src/com/android/tv/ui/MainMenuView.java421
-rw-r--r--src/com/android/tv/ui/MenuAction.java82
-rw-r--r--src/com/android/tv/ui/OptionsAdapter.java83
-rw-r--r--src/com/android/tv/ui/OverlayRootView.java51
-rw-r--r--src/com/android/tv/ui/RecommendationListAdapter.java147
-rw-r--r--src/com/android/tv/ui/SelectInputView.java437
-rw-r--r--src/com/android/tv/ui/SetupView.java307
-rw-r--r--src/com/android/tv/ui/ShadowContainer.java122
-rw-r--r--src/com/android/tv/ui/TunableTvView.java1001
-rw-r--r--src/com/android/tv/ui/TvOverlayManager.java595
-rw-r--r--src/com/android/tv/ui/TvTransitionManager.java315
-rw-r--r--src/com/android/tv/ui/TvViewUiManager.java797
-rw-r--r--src/com/android/tv/ui/ViewUtils.java45
-rw-r--r--src/com/android/tv/ui/sidepanel/AboutFragment.java101
-rw-r--r--src/com/android/tv/ui/sidepanel/ActionItem.java50
-rw-r--r--src/com/android/tv/ui/sidepanel/BaseOptionFragment.java104
-rw-r--r--src/com/android/tv/ui/sidepanel/BaseSideFragment.java191
-rw-r--r--src/com/android/tv/ui/sidepanel/ChannelCheckItem.java130
-rw-r--r--src/com/android/tv/ui/sidepanel/ChannelSourcesFragment.java97
-rw-r--r--src/com/android/tv/ui/sidepanel/CheckBoxItem.java71
-rw-r--r--src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java210
-rw-r--r--src/com/android/tv/ui/sidepanel/ClosedCaptionOptionFragment.java92
-rw-r--r--src/com/android/tv/ui/sidepanel/CompoundButtonItem.java97
-rw-r--r--src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java359
-rw-r--r--src/com/android/tv/ui/sidepanel/DebugOptionFragment.java48
-rw-r--r--src/com/android/tv/ui/sidepanel/DisplayModeFragment.java89
-rw-r--r--src/com/android/tv/ui/sidepanel/DisplayModeOptionFragment.java88
-rw-r--r--src/com/android/tv/ui/sidepanel/DividerItem.java39
-rw-r--r--src/com/android/tv/ui/sidepanel/EditChannelsFragment.java209
-rw-r--r--src/com/android/tv/ui/sidepanel/InputPickerFragment.java115
-rw-r--r--src/com/android/tv/ui/sidepanel/Item.java74
-rw-r--r--src/com/android/tv/ui/sidepanel/MultiAudioFragment.java113
-rw-r--r--src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java167
-rw-r--r--src/com/android/tv/ui/sidepanel/PipLocationFragment.java96
-rw-r--r--src/com/android/tv/ui/sidepanel/RadioButtonItem.java43
-rw-r--r--src/com/android/tv/ui/sidepanel/SideFragment.java315
-rw-r--r--src/com/android/tv/ui/sidepanel/SideFragmentManager.java222
-rw-r--r--src/com/android/tv/ui/sidepanel/SidePanelContainer.java51
-rw-r--r--src/com/android/tv/ui/sidepanel/SimpleGuideFragment.java281
-rw-r--r--src/com/android/tv/ui/sidepanel/SimpleGuideShowOnlyFragment.java118
-rw-r--r--src/com/android/tv/ui/sidepanel/SubMenuItem.java66
-rw-r--r--src/com/android/tv/ui/sidepanel/SwitchItem.java44
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java230
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java159
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java85
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/RatingSystemsFragment.java173
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java286
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java193
-rw-r--r--src/com/android/tv/util/AsyncDbTask.java275
-rw-r--r--src/com/android/tv/util/BitmapUtils.java297
-rw-r--r--src/com/android/tv/util/BooleanSystemProperty.java90
-rw-r--r--src/com/android/tv/util/CaptionSettings.java100
-rw-r--r--src/com/android/tv/util/Clock.java (renamed from sample/res/xml/externalfiletvinputservice.xml)30
-rw-r--r--src/com/android/tv/util/ImageCache.java171
-rw-r--r--src/com/android/tv/util/ImageLoader.java322
-rw-r--r--src/com/android/tv/util/PipInputManager.java411
-rw-r--r--src/com/android/tv/util/SearchManagerHelper.java75
-rw-r--r--src/com/android/tv/util/SetupUtils.java206
-rw-r--r--src/com/android/tv/util/SystemProperties.java59
-rw-r--r--src/com/android/tv/util/TvInputManagerHelper.java262
-rw-r--r--src/com/android/tv/util/TvSettings.java235
-rw-r--r--src/com/android/tv/util/TvTrackInfoUtils.java111
-rw-r--r--src/com/android/tv/util/Utils.java571
-rw-r--r--tests/Android.mk16
-rw-r--r--tests/AndroidManifest.xml15
-rw-r--r--tests/common/Android.mk23
-rw-r--r--tests/common/AndroidManifest.xml23
-rw-r--r--tests/common/res/drawable-xhdpi/blue.pngbin0 -> 3253 bytes
-rw-r--r--tests/common/res/drawable-xhdpi/blue_small.pngbin0 -> 1158 bytes
-rw-r--r--tests/common/res/drawable-xhdpi/crash_test_android_logo.pngbin0 -> 18721 bytes
-rw-r--r--tests/common/res/drawable-xhdpi/green.pngbin0 -> 3253 bytes
-rw-r--r--tests/common/res/drawable-xhdpi/green_large.pngbin0 -> 10263 bytes
-rw-r--r--tests/common/res/drawable-xhdpi/red.pngbin0 -> 3252 bytes
-rw-r--r--tests/common/res/drawable-xhdpi/red_large.pngbin0 -> 10262 bytes
-rw-r--r--tests/common/src/com/android/tv/testing/ChannelInfo.java268
-rw-r--r--tests/common/src/com/android/tv/testing/ChannelUtils.java186
-rw-r--r--tests/common/src/com/android/tv/testing/ComparableTester.java115
-rw-r--r--tests/common/src/com/android/tv/testing/ComparatorTester.java128
-rw-r--r--tests/common/src/com/android/tv/testing/Constants.java44
-rw-r--r--tests/common/src/com/android/tv/testing/ProgramInfo.java282
-rw-r--r--tests/common/src/com/android/tv/testing/ProgramUtils.java100
-rw-r--r--tests/common/src/com/android/tv/testing/TvContentRatingConstants.java57
-rw-r--r--tests/common/src/com/android/tv/testing/Utils.java (renamed from sample/src/com/example/sampletvinput/Utils.java)42
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/ChannelState.java114
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/ChannelStateData.aidl3
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/ChannelStateData.java79
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/ITestInputControl.aidl9
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java80
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/TestInputControlUtils.java33
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java39
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/BaseUiDeviceHelper.java32
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/ByResource.java49
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/Constants.java35
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/DialogHelper.java69
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java43
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java190
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java63
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java146
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java70
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/UiObject2Asserts.java60
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/UiObject2Utils.java61
-rw-r--r--tests/func/Android.mk21
-rw-r--r--tests/func/AndroidManifest.xml32
-rw-r--r--tests/func/src/com/android/tv/tests/ui/ChannelBannerViewTest.java52
-rw-r--r--tests/func/src/com/android/tv/tests/ui/ChannelSourcesTest.java58
-rw-r--r--tests/func/src/com/android/tv/tests/ui/LiveChannelsAppTest.java115
-rw-r--r--tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java96
-rw-r--r--tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java159
-rw-r--r--tests/func/src/com/android/tv/tests/ui/ProgramGuidePerformanceTest.java59
-rw-r--r--tests/func/src/com/android/tv/tests/ui/ProgramGuideTest.java42
-rw-r--r--tests/func/src/com/android/tv/tests/ui/TimeoutTest.java56
-rw-r--r--tests/input/Android.mk25
-rw-r--r--tests/input/AndroidManifest.xml70
-rw-r--r--tests/input/res/drawable-xhdpi/android_48dp.pngbin0 -> 5985 bytes
-rw-r--r--tests/input/res/drawable-xhdpi/icon.png (renamed from sample/res/drawable-xhdpi/icon.png)bin3641 -> 3641 bytes
-rw-r--r--tests/input/res/values/strings.xml24
-rw-r--r--tests/input/res/xml/testtvinputservice.xml18
-rw-r--r--tests/input/src/com/android/tv/testinput/TestInputControl.java98
-rw-r--r--tests/input/src/com/android/tv/testinput/TestInputControlService.java32
-rw-r--r--tests/input/src/com/android/tv/testinput/TestTvInputService.java345
-rw-r--r--tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java119
-rw-r--r--tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java134
-rw-r--r--tests/jank/Android.mk22
-rw-r--r--tests/jank/AndroidManifest.xml32
-rw-r--r--tests/jank/src/com/android/tv/tests/jank/ProgramGuideJankTest.java184
-rw-r--r--tests/unit/Android.mk21
-rw-r--r--tests/unit/AndroidManifest.xml32
-rw-r--r--tests/unit/src/com/android/tv/BaseMainActivityTestCase.java135
-rw-r--r--tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java79
-rw-r--r--tests/unit/src/com/android/tv/MainActivityTest.java106
-rw-r--r--tests/unit/src/com/android/tv/TimeShiftManagerTest.java100
-rw-r--r--tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java646
-rw-r--r--tests/unit/src/com/android/tv/data/ChannelNumberTest.java87
-rw-r--r--tests/unit/src/com/android/tv/data/ChannelTest.java222
-rw-r--r--tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java533
-rw-r--r--tests/unit/src/com/android/tv/data/ProgramTest.java98
-rw-r--r--tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java109
-rw-r--r--tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java118
-rw-r--r--tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java128
-rw-r--r--tests/unit/src/com/android/tv/recommendation/FavoriteChannelEvaluatorTest.java144
-rw-r--r--tests/unit/src/com/android/tv/recommendation/RecentChannelEvaluatorTest.java140
-rw-r--r--tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java180
-rw-r--r--tests/unit/src/com/android/tv/recommendation/RecommenderTest.java324
-rw-r--r--tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java205
-rw-r--r--tests/unit/src/com/android/tv/tests/TvActivityTest.java (renamed from tests/src/com/android/tv/tests/TvActivityTest.java)4
-rw-r--r--tests/unit/src/com/android/tv/ui/SetupViewTest.java86
-rw-r--r--tests/unit/src/com/android/tv/util/FakeClock.java34
-rw-r--r--tests/unit/src/com/android/tv/util/ImageCacheTest.java75
-rw-r--r--tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java52
-rw-r--r--tests/unit/src/com/android/tv/util/TestUtils.java65
-rw-r--r--tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java72
-rw-r--r--tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java98
-rw-r--r--tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java250
-rw-r--r--tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java61
-rw-r--r--version.mk120
850 files changed, 77642 insertions, 13323 deletions
diff --git a/Android.mk b/Android.mk
index 5012f590..fbeea89b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1,3 +1,19 @@
+#
+# 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.
+#
+
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
@@ -6,19 +22,32 @@ LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := TV
-# It is required for android.permission.ALL_EPG_DATA
+# It is required for com.android.providers.tv.permission.ALL_EPG_DATA
LOCAL_PRIVILEGED_MODULE := true
+LOCAL_SDK_VERSION := system_current
LOCAL_RESOURCE_DIR := \
- $(TOP)/frameworks/support/v17/leanback/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-v7-palette \
android-support-v7-recyclerview \
- android-support-v17-leanback
+ android-support-v17-leanback \
+ tv-common
+
+LOCAL_JAVACFLAGS := -Xlint:deprecation -Xlint:unchecked
-LOCAL_AAPT_FLAGS += --auto-add-overlay --extra-packages android.support.v17.leanback
+include $(LOCAL_PATH)/version.mk
+LOCAL_AAPT_FLAGS := --auto-add-overlay \
+ --extra-packages android.support.v7.recyclerview \
+ --extra-packages android.support.v17.leanback \
+ --version-name "$(version_name_package)" \
+ --version-code $(version_code_package) \
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 78f2d377..427ff3b1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,58 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tv"
- android:versionCode="1"
- android:versionName="1.0" >
+ package="com.android.tv" xmlns:tools="http://schemas.android.com/tools">
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.GLOBAL_SEARCH" tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.ALL_EPG_DATA" />
- <uses-permission android:name="android.permission.READ_EPG_DATA" />
- <uses-permission android:name="android.permission.WRITE_EPG_DATA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.MODIFY_PARENTAL_CONTROLS" />
+ <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
+ <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+ <uses-permission android:name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA" />
+ <uses-permission android:name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS" />
+
+ <!-- Permissions/feature for USB tuner -->
+ <uses-permission android:name="android.permission.DVB_DEVICE" />
+ <uses-feature android:name="android.hardware.usb.host" android:required="false" />
- <!-- Receives input events from TV app. -->
- <permission android:name="android.permission.RECEIVE_INPUT_EVENT"
+ <!-- Limit only for Android TV -->
+ <uses-feature android:name="android.software.leanback" android:required="true" />
+ <uses-feature android:name="android.software.live_tv" android:required="true" />
+ <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
+
+ <!-- Receives input events from the TV app. -->
+ <permission android:name="com.android.tv.permission.RECEIVE_INPUT_EVENT"
android:protectionLevel="signatureOrSystem"
android:label="@string/permlab_receiveInputEvent"
- android:description="@string/permdesc_receiveInputEvent" />
+ android:description="@string/permdesc_receiveInputEvent" tools:ignore="SignatureOrSystemPermissions"/>
+ <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21"/>
- <application android:label="@string/tv_app"
+ <application android:label="@string/app_name"
+ android:name=".TvApplication"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:banner="@drawable/banner"
+ android:supportsRtl="true"
android:theme="@style/Theme.TV">
- <activity android:name="com.android.tv.TvActivity"
- android:configChanges="keyboard|keyboardHidden"
- android:screenOrientation="landscape"
- android:launchMode="singleTask" >
+ <activity android:name="com.android.tv.TvActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
+ </activity>
+
+ <activity android:name="com.android.tv.MainActivity"
+ android:configChanges="keyboard|keyboardHidden"
+ android:screenOrientation="landscape"
+ android:launchMode="singleTask" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.cursor.item/channel"
- android:scheme="content"
- android:host="android.media.tv"
- android:pathPrefix="/channel" />
+ <data android:mimeType="vnd.android.cursor.item/channel" />
+ <data android:mimeType="vnd.android.cursor.dir/channel" />
+ <data android:mimeType="vnd.android.cursor.item/program" />
+ <data android:mimeType="vnd.android.cursor.dir/program" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
@@ -62,27 +80,18 @@
android:resource="@xml/searchable" />
</activity>
- <activity android:name="com.android.tv.PassthroughTvActivity"
- android:configChanges="keyboard|keyboardHidden"
- android:screenOrientation="landscape"
- android:launchMode="singleTask" >
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.cursor.item/channel"
- android:scheme="content"
- android:host="android.media.tv"
- android:pathPrefix="/input" />
- </intent-filter>
- </activity>
+ <activity android:name=".LauncherActivity"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<provider android:name="com.android.tv.search.LocalSearchProvider"
android:authorities="com.android.tv.search"
android:exported="true"
- android:enabled="true">
+ android:enabled="true" tools:ignore="ExportedContentProvider">
+ <meta-data android:name="SupportedSwitchActionType" android:value="CHANNEL|TVINPUT" />
</provider>
- <service android:name="com.android.tv.notification.NotificationService" />
+ <service android:name="com.android.tv.recommendation.NotificationService"
+ android:exported="false" />
<receiver android:name="com.android.tv.receiver.NotificationReceiver">
<intent-filter>
@@ -92,6 +101,8 @@
<receiver android:name="com.android.tv.receiver.PackageIntentsReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
+ <!-- PACKAGE_CHANGED for package enabled/disabled notification-->
+ <action android:name="android.intent.action.PACKAGE_CHANGED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package"/>
</intent-filter>
@@ -103,6 +114,15 @@
<intent-filter>
<action android:name="android.intent.action.GLOBAL_BUTTON" />
</intent-filter>
+
+ <!-- Not directly related to GlobalKeyReceiver but needed to be able to provide our
+ content rating definitions to the system service. -->
+ <intent-filter>
+ <action android:name="android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" />
+ </intent-filter>
+ <meta-data android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS"
+ android:resource="@xml/tv_content_rating_systems" />
</receiver>
+
</application>
</manifest>
diff --git a/LiveChannelsAndroidStyle.xml b/LiveChannelsAndroidStyle.xml
new file mode 100644
index 00000000..10b9384d
--- /dev/null
+++ b/LiveChannelsAndroidStyle.xml
@@ -0,0 +1,247 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<code_scheme name="LiveChannelsAndroidStyle">
+ <option name="JAVA_INDENT_OPTIONS">
+ <value>
+ <option name="INDENT_SIZE" value="4" />
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ <option name="TAB_SIZE" value="8" />
+ <option name="USE_TAB_CHARACTER" value="false" />
+ <option name="SMART_TABS" value="false" />
+ <option name="LABEL_INDENT_SIZE" value="0" />
+ <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+ <option name="USE_RELATIVE_INDENTS" value="false" />
+ </value>
+ </option>
+ <option name="FIELD_NAME_PREFIX" value="m" />
+ <option name="STATIC_FIELD_NAME_PREFIX" value="s" />
+ <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
+ <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
+ <option name="IMPORT_LAYOUT_TABLE">
+ <value>
+ <package name="" withSubpackages="true" static="true" />
+ <emptyLine />
+ <package name="android" withSubpackages="true" static="false" />
+ <emptyLine />
+ <package name="com.google" withSubpackages="true" static="false" />
+ <emptyLine />
+ <package name="com" withSubpackages="true" static="false" />
+ <emptyLine />
+ <package name="junit" withSubpackages="true" static="false" />
+ <emptyLine />
+ <package name="net" withSubpackages="true" static="false" />
+ <emptyLine />
+ <package name="org" withSubpackages="true" static="false" />
+ <emptyLine />
+ <package name="java" withSubpackages="true" static="false" />
+ <emptyLine />
+ <package name="javax" withSubpackages="true" static="false" />
+ <emptyLine />
+ <package name="" withSubpackages="true" static="false" />
+ </value>
+ </option>
+ <option name="RIGHT_MARGIN" value="100" />
+ <option name="JD_P_AT_EMPTY_LINES" value="false" />
+ <option name="JD_DO_NOT_WRAP_ONE_LINE_COMMENTS" value="true" />
+ <option name="JD_KEEP_EMPTY_PARAMETER" value="false" />
+ <option name="JD_KEEP_EMPTY_EXCEPTION" value="false" />
+ <option name="JD_KEEP_EMPTY_RETURN" value="false" />
+ <option name="JD_PRESERVE_LINE_FEEDS" value="true" />
+ <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
+ <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
+ <option name="BLANK_LINES_AROUND_FIELD" value="1" />
+ <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
+ <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
+ <option name="ALIGN_MULTILINE_FOR" value="false" />
+ <option name="CALL_PARAMETERS_WRAP" value="1" />
+ <option name="METHOD_PARAMETERS_WRAP" value="1" />
+ <option name="EXTENDS_LIST_WRAP" value="1" />
+ <option name="THROWS_LIST_WRAP" value="1" />
+ <option name="EXTENDS_KEYWORD_WRAP" value="1" />
+ <option name="THROWS_KEYWORD_WRAP" value="1" />
+ <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
+ <option name="BINARY_OPERATION_WRAP" value="1" />
+ <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
+ <option name="TERNARY_OPERATION_WRAP" value="1" />
+ <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+ <option name="FOR_STATEMENT_WRAP" value="1" />
+ <option name="ARRAY_INITIALIZER_WRAP" value="1" />
+ <option name="ASSIGNMENT_WRAP" value="1" />
+ <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
+ <option name="WRAP_COMMENTS" value="true" />
+ <option name="IF_BRACE_FORCE" value="3" />
+ <option name="DOWHILE_BRACE_FORCE" value="3" />
+ <option name="WHILE_BRACE_FORCE" value="3" />
+ <option name="FOR_BRACE_FORCE" value="3" />
+ <XML>
+ <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
+ </XML>
+ <codeStyleSettings language="CFML">
+ <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
+ <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
+ <option name="ALIGN_MULTILINE_FOR" value="false" />
+ <option name="CALL_PARAMETERS_WRAP" value="1" />
+ <option name="METHOD_PARAMETERS_WRAP" value="1" />
+ <option name="BINARY_OPERATION_WRAP" value="1" />
+ <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
+ <option name="TERNARY_OPERATION_WRAP" value="1" />
+ <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+ <option name="FOR_STATEMENT_WRAP" value="1" />
+ <option name="ASSIGNMENT_WRAP" value="1" />
+ <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
+ <option name="PARENT_SETTINGS_INSTALLED" value="true" />
+ </codeStyleSettings>
+ <codeStyleSettings language="CoffeeScript">
+ <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
+ <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
+ <option name="METHOD_PARAMETERS_WRAP" value="1" />
+ <option name="PARENT_SETTINGS_INSTALLED" value="true" />
+ </codeStyleSettings>
+ <codeStyleSettings language="ECMA Script Level 4">
+ <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
+ <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
+ <option name="ALIGN_MULTILINE_FOR" value="false" />
+ <option name="CALL_PARAMETERS_WRAP" value="1" />
+ <option name="METHOD_PARAMETERS_WRAP" value="1" />
+ <option name="EXTENDS_LIST_WRAP" value="1" />
+ <option name="EXTENDS_KEYWORD_WRAP" value="1" />
+ <option name="BINARY_OPERATION_WRAP" value="1" />
+ <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
+ <option name="TERNARY_OPERATION_WRAP" value="1" />
+ <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+ <option name="FOR_STATEMENT_WRAP" value="1" />
+ <option name="ARRAY_INITIALIZER_WRAP" value="1" />
+ <option name="ASSIGNMENT_WRAP" value="1" />
+ <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
+ <option name="WRAP_COMMENTS" value="true" />
+ <option name="IF_BRACE_FORCE" value="3" />
+ <option name="DOWHILE_BRACE_FORCE" value="3" />
+ <option name="WHILE_BRACE_FORCE" value="3" />
+ <option name="FOR_BRACE_FORCE" value="3" />
+ <option name="PARENT_SETTINGS_INSTALLED" value="true" />
+ </codeStyleSettings>
+ <codeStyleSettings language="Groovy">
+ <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
+ <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
+ <option name="BLANK_LINES_AROUND_FIELD" value="1" />
+ <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
+ <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
+ <option name="ALIGN_MULTILINE_FOR" value="false" />
+ <option name="CALL_PARAMETERS_WRAP" value="1" />
+ <option name="METHOD_PARAMETERS_WRAP" value="1" />
+ <option name="EXTENDS_LIST_WRAP" value="1" />
+ <option name="THROWS_LIST_WRAP" value="1" />
+ <option name="EXTENDS_KEYWORD_WRAP" value="1" />
+ <option name="THROWS_KEYWORD_WRAP" value="1" />
+ <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
+ <option name="BINARY_OPERATION_WRAP" value="1" />
+ <option name="TERNARY_OPERATION_WRAP" value="1" />
+ <option name="FOR_STATEMENT_WRAP" value="1" />
+ <option name="ASSIGNMENT_WRAP" value="1" />
+ <option name="IF_BRACE_FORCE" value="3" />
+ <option name="WHILE_BRACE_FORCE" value="3" />
+ <option name="FOR_BRACE_FORCE" value="3" />
+ <option name="PARENT_SETTINGS_INSTALLED" value="true" />
+ </codeStyleSettings>
+ <codeStyleSettings language="JAVA">
+ <option name="KEEP_LINE_BREAKS" value="false" />
+ <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
+ <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
+ <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
+ <option name="ALIGN_MULTILINE_FOR" value="false" />
+ <option name="CALL_PARAMETERS_WRAP" value="1" />
+ <option name="METHOD_PARAMETERS_WRAP" value="1" />
+ <option name="EXTENDS_LIST_WRAP" value="1" />
+ <option name="THROWS_LIST_WRAP" value="1" />
+ <option name="EXTENDS_KEYWORD_WRAP" value="1" />
+ <option name="THROWS_KEYWORD_WRAP" value="1" />
+ <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
+ <option name="BINARY_OPERATION_WRAP" value="1" />
+ <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
+ <option name="TERNARY_OPERATION_WRAP" value="1" />
+ <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+ <option name="FOR_STATEMENT_WRAP" value="1" />
+ <option name="ARRAY_INITIALIZER_WRAP" value="1" />
+ <option name="ASSIGNMENT_WRAP" value="1" />
+ <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
+ <option name="IF_BRACE_FORCE" value="3" />
+ <option name="DOWHILE_BRACE_FORCE" value="3" />
+ <option name="WHILE_BRACE_FORCE" value="3" />
+ <option name="FOR_BRACE_FORCE" value="3" />
+ <option name="PARENT_SETTINGS_INSTALLED" value="true" />
+ <indentOptions>
+ <option name="TAB_SIZE" value="8" />
+ </indentOptions>
+ </codeStyleSettings>
+ <codeStyleSettings language="JSON">
+ <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
+ <option name="PARENT_SETTINGS_INSTALLED" value="true" />
+ </codeStyleSettings>
+ <codeStyleSettings language="JavaScript">
+ <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
+ <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
+ <option name="ALIGN_MULTILINE_FOR" value="false" />
+ <option name="CALL_PARAMETERS_WRAP" value="1" />
+ <option name="METHOD_PARAMETERS_WRAP" value="1" />
+ <option name="BINARY_OPERATION_WRAP" value="1" />
+ <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
+ <option name="TERNARY_OPERATION_WRAP" value="1" />
+ <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+ <option name="FOR_STATEMENT_WRAP" value="1" />
+ <option name="ARRAY_INITIALIZER_WRAP" value="1" />
+ <option name="ASSIGNMENT_WRAP" value="1" />
+ <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
+ <option name="WRAP_COMMENTS" value="true" />
+ <option name="IF_BRACE_FORCE" value="3" />
+ <option name="DOWHILE_BRACE_FORCE" value="3" />
+ <option name="WHILE_BRACE_FORCE" value="3" />
+ <option name="FOR_BRACE_FORCE" value="3" />
+ <option name="PARENT_SETTINGS_INSTALLED" value="true" />
+ </codeStyleSettings>
+ <codeStyleSettings language="SQL">
+ <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
+ <option name="PARENT_SETTINGS_INSTALLED" value="true" />
+ <indentOptions>
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ <option name="TAB_SIZE" value="4" />
+ </indentOptions>
+ </codeStyleSettings>
+ <codeStyleSettings language="TypeScript">
+ <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
+ <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
+ <option name="ALIGN_MULTILINE_FOR" value="false" />
+ <option name="CALL_PARAMETERS_WRAP" value="1" />
+ <option name="METHOD_PARAMETERS_WRAP" value="1" />
+ <option name="EXTENDS_LIST_WRAP" value="1" />
+ <option name="EXTENDS_KEYWORD_WRAP" value="1" />
+ <option name="BINARY_OPERATION_WRAP" value="1" />
+ <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
+ <option name="TERNARY_OPERATION_WRAP" value="1" />
+ <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+ <option name="FOR_STATEMENT_WRAP" value="1" />
+ <option name="ARRAY_INITIALIZER_WRAP" value="1" />
+ <option name="ASSIGNMENT_WRAP" value="1" />
+ <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
+ <option name="WRAP_COMMENTS" value="true" />
+ <option name="IF_BRACE_FORCE" value="3" />
+ <option name="DOWHILE_BRACE_FORCE" value="3" />
+ <option name="WHILE_BRACE_FORCE" value="3" />
+ <option name="FOR_BRACE_FORCE" value="3" />
+ <option name="PARENT_SETTINGS_INSTALLED" value="true" />
+ </codeStyleSettings>
+</code_scheme>
diff --git a/assets/rating_sources.html b/assets/rating_sources.html
new file mode 100644
index 00000000..50da7cc7
--- /dev/null
+++ b/assets/rating_sources.html
@@ -0,0 +1,92 @@
+<!--
+ ~ 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.
+ -->
+
+<html><head><style> body { font-family: sans-serif; } pre { background-color: #eeeeee; padding: 1em; white-space: pre-wrap; } </style></head><body>
+<h3>Sources for content rating systems</h3>
+<ul>
+ <li>TV content rating system strings for Argentina</li>
+</ul>
+<pre>
+ Source: http://en.wikipedia.org/wiki/Television_content_rating_systems
+</pre>
+<ul>
+ <li>TV content rating system for Australia</li>
+</ul>
+<pre>
+ Sources:
+ ACB: http://www.classification.gov.au/Industry/Journey/Pages/TV/Step1.aspx
+ ABC classification: Appendix 4 of http://www.abc.net.au/mediawatch/transcripts/0935_industry.pdf
+ SBS classification: http://www.sbs.com.au/aboutus/faqs/index/id/115/h/Classification
+ Pay TV: http://www.astra.org.au/advocacy/classification
+ Free TV Code of Practice: Appendix 1 of Part B in http://www.freetv.com.au/media/Code_of_Practice/
+ Commercial_Television_Industry_Code_of_Practice_2015_-_New_Code_and_Explanatory_Materials.pdf
+</pre>
+<ul>
+ <li>TV content rating system for Brazil</li>
+</ul>
+<pre>
+ Source: http://www.justica.gov.br/seus-direitos/classificacao/guia-pratico/practical-guide.pdf/view
+</pre>
+<ul>
+ <li>TV content rating system for Canada</li>
+</ul>
+<pre>
+ Source: http://www.cbsc.ca/english/agvot/englishsystem.php
+</pre>
+<ul>
+ <li>TV content rating system for Canada (French) </li>
+</ul>
+<pre>
+ Source: http://www.cbsc.ca/english/agvot/frenchsystem.php
+</pre>
+<ul>
+ <li>TV content rating system for DVB</li>
+</ul>
+<pre>
+ Source: Table 81 of DVB SI (EN 300 468 V1.14.1) in https://www.dvb.org/standards
+</pre>
+<ul>
+ <li>TV content rating system for ISDB</li>
+</ul>
+<pre>
+ Source: STD-B10 in http://www.dibeg.org/techp/aribstd/aribstd.html
+</pre>
+<ul>
+ <li>TV content rating system for South Korea</li>
+</ul>
+<pre>
+ Source: http://www.law.go.kr/admRulLsInfoP.do?admRulSeq=2000000118507
+</pre>
+<ul>
+ <li>TV content rating system for Singapore</li>
+</ul>
+<pre>
+ Source: http://www.mda.gov.sg/RegulationsAndLicensing/ContentStandardsAndClassification/FilmsAndVideos/
+ Pages/default.aspx
+</pre>
+<ul>
+ <li>TV content rating system for the United States of America</li>
+</ul>
+<pre>
+ Source: http://www.tvguidelines.org/ratings.htm
+</pre>
+<ul>
+ <li>Movie content rating system for the United States of America</li>
+</ul>
+<pre>
+ Source: http://www.mpaa.org/film-ratings/
+</pre>
+</body></html>
diff --git a/common/Android.mk b/common/Android.mk
new file mode 100644
index 00000000..71d94781
--- /dev/null
+++ b/common/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := tv-common
+LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/common/src/com/android/tv/common/TvCommonConstants.java b/common/src/com/android/tv/common/TvCommonConstants.java
new file mode 100644
index 00000000..6dd67071
--- /dev/null
+++ b/common/src/com/android/tv/common/TvCommonConstants.java
@@ -0,0 +1,58 @@
+/*
+ * 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.os.Build;
+
+/**
+ * Constants for common use in TV app and tests.
+ */
+public final class TvCommonConstants {
+ /**
+ * A constant for the key of the extra data for the app linking intent.
+ */
+ public static final String EXTRA_APP_LINK_CHANNEL_URI = "app_link_channel_uri";
+
+ private static int sPreviewSdkInt;
+ static {
+ try {
+ sPreviewSdkInt = Build.VERSION.PREVIEW_SDK_INT;
+ } catch (java.lang.NoSuchFieldError e) {
+ sPreviewSdkInt = 0;
+ }
+ }
+
+ /**
+ * A flag whether this platform is MNC Preview or not.
+ */
+ public static final boolean IS_MNC_PREVIEW =
+ Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1 && sPreviewSdkInt > 0;
+
+ /**
+ * A flag whether this platform is after MNC Preview or not.
+ */
+ public static final boolean IS_MNC_OR_HIGHER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.MNC;
+
+ /**
+ * A flag whether this platform supports time shifting API or not.
+ * TODO: Stop supporting mnc preview if possible.
+ */
+ public static final boolean HAS_TIME_SHIFT_API = IS_MNC_PREVIEW || IS_MNC_OR_HIGHER;
+
+ private TvCommonConstants() {
+ }
+}
diff --git a/hwsample/AndroidManifest.xml b/hwsample/AndroidManifest.xml
deleted file mode 100644
index a4bacb88..00000000
--- a/hwsample/AndroidManifest.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.android.fakehardwaretvinput">
-
- <uses-permission android:name="android.permission.TV_INPUT_HARDWARE" />
- <uses-permission android:name="android.permission.HDMI_CEC" />
-
- <application android:label="@string/app_name">
- <activity android:name=".SettingsActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <service android:name=".FakeHdmiService"
- android:permission="android.permission.BIND_TV_INPUT"
- android:label="@string/hdmi_service_label"
- android:process=":remoteFakeHdmiService">
- <intent-filter>
- <action android:name="android.media.tv.TvInputService" />
- </intent-filter>
- <meta-data android:name="android.media.tv.input"
- android:resource="@xml/hdmiservice" />
- </service>
- </application>
-</manifest>
diff --git a/hwsample/res/drawable-hdpi/fake_icon0.png b/hwsample/res/drawable-hdpi/fake_icon0.png
deleted file mode 100644
index e39cb6b4..00000000
--- a/hwsample/res/drawable-hdpi/fake_icon0.png
+++ /dev/null
Binary files differ
diff --git a/hwsample/res/drawable-hdpi/fake_icon1.png b/hwsample/res/drawable-hdpi/fake_icon1.png
deleted file mode 100644
index 7634404c..00000000
--- a/hwsample/res/drawable-hdpi/fake_icon1.png
+++ /dev/null
Binary files differ
diff --git a/hwsample/res/drawable-hdpi/fake_icon2.png b/hwsample/res/drawable-hdpi/fake_icon2.png
deleted file mode 100644
index 0eabb7e0..00000000
--- a/hwsample/res/drawable-hdpi/fake_icon2.png
+++ /dev/null
Binary files differ
diff --git a/hwsample/res/drawable-hdpi/fake_icon3.png b/hwsample/res/drawable-hdpi/fake_icon3.png
deleted file mode 100644
index cbd53218..00000000
--- a/hwsample/res/drawable-hdpi/fake_icon3.png
+++ /dev/null
Binary files differ
diff --git a/hwsample/res/values/strings.xml b/hwsample/res/values/strings.xml
deleted file mode 100644
index 700c3122..00000000
--- a/hwsample/res/values/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 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.
--->
-
-<resources>
- <string name="app_name">FakeHardwareTvInput</string>
- <string name="hdmi_service_label">HDMI service</string>
- <string name="settings_button_label">Settings</string>
-</resources>
diff --git a/hwsample/res/xml/hdmiservice.xml b/hwsample/res/xml/hdmiservice.xml
deleted file mode 100644
index 74a9dc98..00000000
--- a/hwsample/res/xml/hdmiservice.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright 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.
--->
-
-<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
- android:settingsActivity="com.example.android.fakehardwaretvinput.SettingsActivity" />
diff --git a/hwsample/src/com/example/android/fakehardwaretvinput/FakeHdmiService.java b/hwsample/src/com/example/android/fakehardwaretvinput/FakeHdmiService.java
deleted file mode 100644
index 2690a870..00000000
--- a/hwsample/src/com/example/android/fakehardwaretvinput/FakeHdmiService.java
+++ /dev/null
@@ -1,287 +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.example.android.fakehardwaretvinput;
-
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.hardware.hdmi.HdmiCecDeviceInfo;
-import android.hardware.hdmi.IHdmiControlService;
-import android.media.tv.ITvInputManager;
-import android.media.tv.TvContentRating;
-import android.media.tv.TvInputHardwareInfo;
-import android.media.tv.TvInputInfo;
-import android.media.tv.TvInputService;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.Surface;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-/**
- * Demonstrates a simple HDMI input.
- *
- * Note that the service doesn't need to communicate with HdmiControlService directly unless it is
- * dealing with custom CEC or MHL signals.
- */
-public class FakeHdmiService extends TvInputService {
- private static final boolean DEBUG = true;
- private static final String TAG = FakeHdmiService.class.getSimpleName();
- private static final int[] COLORS = {
- 0xFF888888, 0xFF999999, 0xFFAAAAAA, 0xFFBBBBBB, 0xFFAAAAAA, 0xFF999999};
- private static final int[] ICONS = { R.drawable.fake_icon0, R.drawable.fake_icon1,
- R.drawable.fake_icon2, R.drawable.fake_icon3 };
-
- private ITvInputManager mManager = null;
- private IHdmiControlService mHdmiControlService = null;
-
- private final SparseArray<String> mHardwareInputIdMap = new SparseArray<String>();
- private final SparseArray<String> mCecInputIdMap = new SparseArray<String>();
- private final Map<String, TvInputInfo> mInputMap = new HashMap<String, TvInputInfo>();
- private ResolveInfo mResolveInfo;
- private final Random mRandom = new Random();
-
- private static class PortInfo {
- private final int mPortId;
- private final int mHardwareDeviceId;
- private final int mCecLogicalAddress;
-
- PortInfo(int portId, int hardwareDeviceId, int cecLogicalAddress) {
- this.mPortId = portId;
- this.mHardwareDeviceId = hardwareDeviceId;
- this.mCecLogicalAddress = cecLogicalAddress;
- }
- }
- private final SparseArray<PortInfo> mPortInfos = new SparseArray<PortInfo>();
-
- @Override
- public void onCreate() {
- super.onCreate();
- mResolveInfo = getPackageManager().resolveService(
- new Intent(SERVICE_INTERFACE).setClass(this, getClass()),
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
- mManager = ITvInputManager.Stub.asInterface(
- ServiceManager.getService(Context.TV_INPUT_SERVICE));
- mHdmiControlService = IHdmiControlService.Stub.asInterface(
- ServiceManager.getService(Context.HDMI_CONTROL_SERVICE));
- }
-
- @Override
- public Session onCreateSession(String inputId) {
- TvInputInfo info = mInputMap.get(inputId);
- if (info == null) {
- throw new IllegalArgumentException("Unknown inputId: " + inputId
- + " ; this should not happen.");
- }
- return new HdmiInputSessionImpl(info, this);
- }
-
- @Override
- public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) {
- if (hardwareInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
- return null;
- }
- int deviceId = hardwareInfo.getDeviceId();
- if (mHardwareInputIdMap.indexOfKey(deviceId) >= 0) {
- Log.e(TAG, "Already created TvInputInfo for deviceId=" + deviceId);
- return null;
- }
- int portId = hardwareInfo.getHdmiPortId();
- if (portId < 0) {
- Log.e(TAG, "Failed to get HDMI port for deviceId=" + deviceId);
- return null;
- }
- if (mPortInfos.get(portId) != null) {
- Log.e(TAG, "Already have port " + portId + " for deviceId=" + deviceId);
- return null;
- }
- TvInputInfo info = null;
- try {
- info = TvInputInfo.createTvInputInfo(this, mResolveInfo, hardwareInfo,
- "HDMI " + hardwareInfo.getHdmiPortId(), null);
- } catch (XmlPullParserException | IOException e) {
- Log.e(TAG, "Error while creating TvInputInfo", e);
- return null;
- }
- mHardwareInputIdMap.put(deviceId, info.getId());
- mInputMap.put(info.getId(), info);
- mPortInfos.put(portId, new PortInfo(portId, deviceId, -1));
- if (DEBUG) Log.d(TAG, "onHardwareAdded returns " + info);
- return info;
- }
-
- @Override
- public String onHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
- int deviceId = hardwareInfo.getDeviceId();
- String inputId = mHardwareInputIdMap.get(deviceId);
- if (inputId == null) {
- if (DEBUG) Log.d(TAG, "TvInputInfo for deviceId=" + deviceId + " does not exist.");
- return null;
- }
- int portId = getPortInfoForDeviceId(deviceId);
- if (portId == -1) {
- Log.w(TAG, "Port not exists for deviceId=" + deviceId);
- }
- mPortInfos.remove(portId);
- mHardwareInputIdMap.remove(deviceId);
- mInputMap.remove(inputId);
- if (DEBUG) Log.d(TAG, "onHardwareRemoved returns " + inputId);
- return inputId;
- }
-
- private int getPortInfoForDeviceId(int deviceId) {
- for (int i = 0; i < mPortInfos.size(); i++) {
- PortInfo portInfo = mPortInfos.valueAt(i);
- if (portInfo.mHardwareDeviceId == deviceId) {
- return portInfo.mPortId;
- }
- }
- return -1;
- }
-
- @Override
- public TvInputInfo onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDeviceInfo) {
- int logicalAddress = cecDeviceInfo.getLogicalAddress();
- if (mCecInputIdMap.indexOfKey(logicalAddress) >= 0) {
- Log.e(TAG, "Already created TvInputInfo for logicalAddress=" + logicalAddress);
- return null;
- }
- int portId = cecDeviceInfo.getPortId();
- if (portId < 0) {
- Log.e(TAG, "Failed to get HDMI port for logicalAddress=" + logicalAddress);
- return null;
- }
- PortInfo portInfo = mPortInfos.get(portId);
- if (portInfo == null) {
- Log.e(TAG, "Unknown HDMI port " + portId + " for logicalAddress=" + logicalAddress);
- return null;
- }
- TvInputInfo parentInfo =
- mInputMap.get(mHardwareInputIdMap.get(portInfo.mHardwareDeviceId));
- TvInputInfo info = null;
- try {
- info = TvInputInfo.createTvInputInfo(this, mResolveInfo, cecDeviceInfo,
- parentInfo.getId(), cecDeviceInfo.getDisplayName(),
- Uri.parse("android.resource://" + getPackageName() + "/"
- + ICONS[mRandom.nextInt(ICONS.length)]));
- } catch (XmlPullParserException | IOException e) {
- Log.e(TAG, "Error while creating TvInputInfo", e);
- return null;
- }
- mCecInputIdMap.put(logicalAddress, info.getId());
- mInputMap.put(info.getId(), info);
- if (DEBUG) Log.d(TAG, "onHdmiCecDeviceAdded returns " + info);
- return info;
- }
-
- @Override
- public String onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDeviceInfo) {
- int logicalAddress = cecDeviceInfo.getLogicalAddress();
- String inputId = mCecInputIdMap.get(logicalAddress);
- if (inputId == null) {
- if (DEBUG) {
- Log.d(TAG, "TvInputInfo for logicalAddress=" + logicalAddress + " does not exist.");
- }
- return null;
- }
- mCecInputIdMap.remove(logicalAddress);
- mInputMap.remove(inputId);
- if (DEBUG) Log.d(TAG, "onHdmiCecDeviceRemoved returns " + inputId);
- return inputId;
- }
-
- private class HdmiInputSessionImpl extends Session {
- private final TvInputInfo mInfo;
- private final Object mImplLock = new Object();
- private Surface mSurface = null;
- private final Paint mTextPaint = new Paint();
- private final Handler mHandler = new Handler();
- private final String mLabel;
-
- private final Runnable mDrawTask = new Runnable() {
- private int mIndex = 0;
-
- @Override
- public void run() {
- synchronized (mImplLock) {
- if (mSurface != null) {
- Canvas c = mSurface.lockCanvas(null);
- c.drawColor(COLORS[mIndex]);
- c.drawText(mLabel, 100f, 200f, mTextPaint);
- mSurface.unlockCanvasAndPost(c);
- }
- }
- ++mIndex;
- if (mIndex >= COLORS.length) {
- mIndex = 0;
- }
- mHandler.postDelayed(this, 1000);
- }
- };
-
- HdmiInputSessionImpl(TvInputInfo info, Context context) {
- super(context);
- mInfo = info;
- mLabel = info.loadLabel(FakeHdmiService.this).toString();
- mTextPaint.setColor(Color.BLACK);
- mTextPaint.setTextSize(200);
- mHandler.post(mDrawTask);
- }
-
- @Override
- public void onRelease() {
- mHandler.removeCallbacks(mDrawTask);
- }
-
- @Override
- public boolean onSetSurface(Surface surface) {
- synchronized (mImplLock) {
- mSurface = surface;
- return true;
- }
- }
-
- @Override
- public void onSetStreamVolume(float volume) {
- // No-op
- }
-
- @Override
- public boolean onTune(Uri channelUri) {
- return true;
- }
-
- @Override
- public void onSetCaptionEnabled(boolean enabled) {
- // No-op
- }
- }
-}
diff --git a/hwsample/src/com/example/android/fakehardwaretvinput/SettingsActivity.java b/hwsample/src/com/example/android/fakehardwaretvinput/SettingsActivity.java
deleted file mode 100644
index fec8859f..00000000
--- a/hwsample/src/com/example/android/fakehardwaretvinput/SettingsActivity.java
+++ /dev/null
@@ -1,52 +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.example.android.fakehardwaretvinput;
-
-import android.app.Activity;
-import android.media.tv.TvInputInfo;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.LinearLayout;
-
-/**
- * The settings activity for demonstrating TvInput app.
- */
-public class SettingsActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- LinearLayout layout = new LinearLayout(this);
- layout.setOrientation(LinearLayout.VERTICAL);
- setContentView(layout);
-
- String inputId = getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
- Button btn = new Button(this);
- btn.setText(R.string.settings_button_label);
- btn.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- setResult(Activity.RESULT_OK);
- finish();
- }
- });
-
- layout.addView(btn);
- }
-}
diff --git a/open_source_project.LICENSE b/open_source_project.LICENSE
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/open_source_project.LICENSE
@@ -0,0 +1,202 @@
+
+ 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.
diff --git a/open_source_project.README b/open_source_project.README
new file mode 100644
index 00000000..31532f03
--- /dev/null
+++ b/open_source_project.README
@@ -0,0 +1,13 @@
+Live TV is an application to provide TV watching experience through Android TV input framework.
+
+Live TV is a system app for Android TV. It should be compiled with Android TV platform.
+
+How to build:
+1. Download AOSP (equal or above L version).
+ https://source.android.com/source/building.html
+ (Developers using PDK can skip the step 1.)
+2. Enable the feature PackageManager.FEATURE_LIVE_TV.
+3. Put this project under Android platform repository.
+4. Include this package inside platform build.
+5. Build the platform.
+ https://source.android.com/source/building.html
diff --git a/hwsample/Android.mk b/proguard.flags
index 818b7276..a72b60f4 100644
--- a/hwsample/Android.mk
+++ b/proguard.flags
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2014 The Android Open Source Project
+# 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.
@@ -14,21 +14,8 @@
# limitations under the License.
#
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := FakeHardwareTvInput
-
-LOCAL_PRIVILEGED_MODULE := true
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_PACKAGE)
-
-ifeq ($(PRODUCT_IS_ATV),true)
- ifneq ($(PRODUCT_IS_ATV_SDK),true)
- $(call dist-for-goals,dist_files,$(LOCAL_BUILT_MODULE):FakeHardwareTvInput.apk)
- endif
-endif
+# For tests
+-keep @android.support.annotation.VisibleForTesting class *
+-keepclasseswithmembers class * {
+ @android.support.annotation.VisibleForTesting <methods>;
+}
diff --git a/res/anim/pin_dialog_enter.xml b/res/anim/pin_dialog_enter.xml
new file mode 100644
index 00000000..49266f60
--- /dev/null
+++ b/res/anim/pin_dialog_enter.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <translate
+ android:fromYDelta="@integer/pin_dialog_enter_offset_y"
+ android:toYDelta="0"
+ android:duration="@integer/pin_dialog_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+ <alpha
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"
+ android:duration="@integer/pin_dialog_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/anim/pin_dialog_exit.xml b/res/anim/pin_dialog_exit.xml
new file mode 100644
index 00000000..9356cd75
--- /dev/null
+++ b/res/anim/pin_dialog_exit.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <translate
+ android:fromYDelta="0"
+ android:toYDelta="@integer/pin_dialog_exit_offset_y"
+ android:duration="@integer/pin_dialog_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+ android:duration="@integer/pin_dialog_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/animator/channel_banner_enter.xml b/res/animator/channel_banner_enter.xml
new file mode 100644
index 00000000..1594b2d5
--- /dev/null
+++ b/res/animator/channel_banner_enter.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationY"
+ android:valueFrom="@dimen/channel_banner_enter_offset_y"
+ android:valueTo="0dp"
+ android:duration="@integer/channel_banner_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="@integer/channel_banner_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/animator/channel_banner_exit.xml b/res/animator/channel_banner_exit.xml
new file mode 100644
index 00000000..9ee407fa
--- /dev/null
+++ b/res/animator/channel_banner_exit.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationY"
+ android:valueFrom="0dp"
+ android:valueTo="@dimen/channel_banner_exit_offset_y"
+ android:duration="@integer/channel_banner_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="1.0"
+ android:valueTo="0.0"
+ android:duration="@integer/channel_banner_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/animator/channel_banner_program_description_fade_in.xml b/res/animator/channel_banner_program_description_fade_in.xml
new file mode 100644
index 00000000..0774f747
--- /dev/null
+++ b/res/animator/channel_banner_program_description_fade_in.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="@integer/channel_banner_fast_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/animator/channel_banner_program_description_fade_out.xml b/res/animator/channel_banner_program_description_fade_out.xml
new file mode 100644
index 00000000..2f5f0d46
--- /dev/null
+++ b/res/animator/channel_banner_program_description_fade_out.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="1.0"
+ android:valueTo="0.0"
+ android:duration="@integer/channel_banner_fast_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/animator/menu_enter.xml b/res/animator/menu_enter.xml
new file mode 100644
index 00000000..b277ff53
--- /dev/null
+++ b/res/animator/menu_enter.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationY"
+ android:valueFrom="@dimen/menu_enter_offset_y"
+ android:valueTo="0dp"
+ android:duration="@integer/menu_enter_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="@integer/menu_enter_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/animator/menu_exit.xml b/res/animator/menu_exit.xml
new file mode 100644
index 00000000..f08643ec
--- /dev/null
+++ b/res/animator/menu_exit.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationY"
+ android:valueFrom="0dp"
+ android:valueTo="@dimen/menu_exit_offset_y"
+ android:duration="@integer/menu_exit_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="1.0"
+ android:valueTo="0.0"
+ android:duration="@integer/menu_exit_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/animator/pin_adjacent_number_enter.xml b/res/animator/pin_adjacent_number_enter.xml
new file mode 100644
index 00000000..3e54fb73
--- /dev/null
+++ b/res/animator/pin_adjacent_number_enter.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="@dimen/pin_alpha_for_adjacent_number"
+ android:duration="@integer/pin_number_scroll_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
diff --git a/res/animator/pin_adjacent_number_exit.xml b/res/animator/pin_adjacent_number_exit.xml
new file mode 100644
index 00000000..f2348f3c
--- /dev/null
+++ b/res/animator/pin_adjacent_number_exit.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:propertyName="alpha"
+ android:valueFrom="@dimen/pin_alpha_for_adjacent_number"
+ android:valueTo="0.0"
+ android:duration="@integer/pin_number_scroll_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
diff --git a/res/animator/pin_focused_number_enter.xml b/res/animator/pin_focused_number_enter.xml
new file mode 100644
index 00000000..2ab69e1a
--- /dev/null
+++ b/res/animator/pin_focused_number_enter.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:propertyName="alpha"
+ android:valueFrom="@dimen/pin_alpha_for_adjacent_number"
+ android:valueTo="@dimen/pin_alpha_for_focused_number"
+ android:duration="@integer/pin_number_scroll_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
diff --git a/res/animator/pin_focused_number_exit.xml b/res/animator/pin_focused_number_exit.xml
new file mode 100644
index 00000000..35a0cbb6
--- /dev/null
+++ b/res/animator/pin_focused_number_exit.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:propertyName="alpha"
+ android:valueFrom="@dimen/pin_alpha_for_focused_number"
+ android:valueTo="@dimen/pin_alpha_for_adjacent_number"
+ android:duration="@integer/pin_number_scroll_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
diff --git a/res/animator/program_guide_side_panel_enter_full.xml b/res/animator/program_guide_side_panel_enter_full.xml
new file mode 100644
index 00000000..bb6cdd89
--- /dev/null
+++ b/res/animator/program_guide_side_panel_enter_full.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="@dimen/program_guide_side_panel_exit_x"
+ android:valueTo="0dp"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/animator/program_guide_side_panel_enter_partial.xml b/res/animator/program_guide_side_panel_enter_partial.xml
new file mode 100644
index 00000000..8f770089
--- /dev/null
+++ b/res/animator/program_guide_side_panel_enter_partial.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="@dimen/program_guide_side_panel_exit_x"
+ android:valueTo="@dimen/program_guide_shift_start_to_end_x"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/animator/program_guide_side_panel_exit.xml b/res/animator/program_guide_side_panel_exit.xml
new file mode 100644
index 00000000..776b3969
--- /dev/null
+++ b/res/animator/program_guide_side_panel_exit.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueTo="@dimen/program_guide_side_panel_exit_x"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/animator/program_guide_side_panel_grid_fade_in.xml b/res/animator/program_guide_side_panel_grid_fade_in.xml
new file mode 100644
index 00000000..01e9b830
--- /dev/null
+++ b/res/animator/program_guide_side_panel_grid_fade_in.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/animator/program_guide_side_panel_grid_fade_out.xml b/res/animator/program_guide_side_panel_grid_fade_out.xml
new file mode 100644
index 00000000..2c508cdb
--- /dev/null
+++ b/res/animator/program_guide_side_panel_grid_fade_out.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="1.0"
+ android:valueTo="0.0"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/animator/program_guide_side_panel_hide.xml b/res/animator/program_guide_side_panel_hide.xml
new file mode 100644
index 00000000..1c100d5f
--- /dev/null
+++ b/res/animator/program_guide_side_panel_hide.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="@dimen/program_guide_shift_start_to_end_x"
+ android:valueTo="0dp"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/animator/program_guide_side_panel_reveal.xml b/res/animator/program_guide_side_panel_reveal.xml
new file mode 100644
index 00000000..e240e0a4
--- /dev/null
+++ b/res/animator/program_guide_side_panel_reveal.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="0dp"
+ android:valueTo="@dimen/program_guide_shift_start_to_end_x"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/animator/program_guide_side_panel_text_state_animator.xml b/res/animator/program_guide_side_panel_text_state_animator.xml
new file mode 100644
index 00000000..5425fe5b
--- /dev/null
+++ b/res/animator/program_guide_side_panel_text_state_animator.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true">
+ <set>
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:valueFrom="1.0"
+ android:valueTo="@dimen/lb_browse_header_select_scale"
+ android:duration="@dimen/lb_browse_header_select_duration"
+ android:interpolator="@android:interpolator/accelerate_decelerate" />
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:valueFrom="1.0"
+ android:valueTo="@dimen/lb_browse_header_select_scale"
+ android:duration="@dimen/lb_browse_header_select_duration"
+ android:interpolator="@android:interpolator/accelerate_decelerate" />
+ <objectAnimator
+ android:propertyName="textColor"
+ android:valueFrom="@color/program_guide_side_panel_text_color_unfocused"
+ android:valueTo="@color/program_guide_side_panel_text_color"
+ android:duration="@dimen/lb_browse_header_select_duration"
+ android:interpolator="@android:interpolator/accelerate_decelerate" />
+ </set>
+ </item>
+ <item>
+ <set>
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:valueFrom="@dimen/lb_browse_header_select_scale"
+ android:valueTo="1.0"
+ android:duration="@dimen/lb_browse_header_select_duration"
+ android:interpolator="@android:interpolator/accelerate_decelerate" />
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:valueFrom="@dimen/lb_browse_header_select_scale"
+ android:valueTo="1.0"
+ android:duration="@dimen/lb_browse_header_select_duration"
+ android:interpolator="@android:interpolator/accelerate_decelerate" />
+ <objectAnimator
+ android:propertyName="textColor"
+ android:valueFrom="@color/program_guide_side_panel_text_color"
+ android:valueTo="@color/program_guide_side_panel_text_color_unfocused"
+ android:duration="@dimen/lb_browse_header_select_duration"
+ android:interpolator="@android:interpolator/accelerate_decelerate" />
+ </set>
+ </item>
+</selector>
diff --git a/res/animator/program_guide_table_enter_full.xml b/res/animator/program_guide_table_enter_full.xml
new file mode 100644
index 00000000..146ceb9a
--- /dev/null
+++ b/res/animator/program_guide_table_enter_full.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="@dimen/program_guide_table_exit_x"
+ android:valueTo="0dp"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/animator/program_guide_table_enter_partial.xml b/res/animator/program_guide_table_enter_partial.xml
new file mode 100644
index 00000000..d76ee1b5
--- /dev/null
+++ b/res/animator/program_guide_table_enter_partial.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="@dimen/program_guide_table_exit_x"
+ android:valueTo="@dimen/program_guide_shift_start_to_end_x"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/animator/program_guide_table_exit.xml b/res/animator/program_guide_table_exit.xml
new file mode 100644
index 00000000..3d6cf772
--- /dev/null
+++ b/res/animator/program_guide_table_exit.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueTo="@dimen/program_guide_table_exit_x"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueTo="0.0"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/animator/program_guide_table_fade_in.xml b/res/animator/program_guide_table_fade_in.xml
new file mode 100644
index 00000000..6a7ba1c1
--- /dev/null
+++ b/res/animator/program_guide_table_fade_in.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="@integer/program_guide_table_fade_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/animator/program_guide_table_fade_out.xml b/res/animator/program_guide_table_fade_out.xml
new file mode 100644
index 00000000..870f654f
--- /dev/null
+++ b/res/animator/program_guide_table_fade_out.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueTo="0.0"
+ android:duration="@integer/program_guide_table_fade_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/animator/program_guide_table_full_to_partial.xml b/res/animator/program_guide_table_full_to_partial.xml
new file mode 100644
index 00000000..e240e0a4
--- /dev/null
+++ b/res/animator/program_guide_table_full_to_partial.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="0dp"
+ android:valueTo="@dimen/program_guide_shift_start_to_end_x"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/animator/program_guide_table_partial_to_full.xml b/res/animator/program_guide_table_partial_to_full.xml
new file mode 100644
index 00000000..1c100d5f
--- /dev/null
+++ b/res/animator/program_guide_table_partial_to_full.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="@dimen/program_guide_shift_start_to_end_x"
+ android:valueTo="0dp"
+ android:duration="@integer/program_guide_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/animator/side_panel_enter.xml b/res/animator/side_panel_enter.xml
new file mode 100644
index 00000000..6b78211e
--- /dev/null
+++ b/res/animator/side_panel_enter.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="@dimen/side_panel_enter_offset_x"
+ android:valueTo="0dp"
+ android:duration="@integer/side_panel_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="@integer/side_panel_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/animator/side_panel_exit.xml b/res/animator/side_panel_exit.xml
new file mode 100644
index 00000000..42ea9abc
--- /dev/null
+++ b/res/animator/side_panel_exit.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="0dp"
+ android:valueTo="@dimen/side_panel_exit_offset_x"
+ android:duration="@integer/side_panel_anim_short_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="1.0"
+ android:valueTo="0.0"
+ android:duration="@integer/side_panel_anim_short_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/animator/side_panel_fragment_enter.xml b/res/animator/side_panel_fragment_enter.xml
new file mode 100644
index 00000000..c3270a85
--- /dev/null
+++ b/res/animator/side_panel_fragment_enter.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:ordering="sequentially">
+ <!-- Set inital values and stay still until exit animation ends -->
+ <set>
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="@dimen/side_panel_fragment_enter_offset_x"
+ android:valueTo="@dimen/side_panel_fragment_enter_offset_x"
+ android:duration="@integer/side_panel_anim_duration" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="0.0"
+ android:duration="@integer/side_panel_anim_duration" />
+ </set>
+ <!-- Entering animation starts after exit animation -->
+ <set>
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="@dimen/side_panel_fragment_enter_offset_x"
+ android:valueTo="0dp"
+ android:duration="@integer/side_panel_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="@integer/side_panel_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+ </set>
+</set>
diff --git a/res/animator/side_panel_fragment_exit.xml b/res/animator/side_panel_fragment_exit.xml
new file mode 100644
index 00000000..0097cd85
--- /dev/null
+++ b/res/animator/side_panel_fragment_exit.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="0dp"
+ android:valueTo="@dimen/side_panel_fragment_exit_offset_x"
+ android:duration="@integer/side_panel_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="1.0"
+ android:valueTo="0.0"
+ android:duration="@integer/side_panel_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/animator/side_panel_fragment_pop_enter.xml b/res/animator/side_panel_fragment_pop_enter.xml
new file mode 100644
index 00000000..36776f83
--- /dev/null
+++ b/res/animator/side_panel_fragment_pop_enter.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:ordering="sequentially">
+ <!-- Set inital values and stay still until exit animation ends -->
+ <set>
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="@dimen/side_panel_fragment_pop_enter_offset_x"
+ android:valueTo="@dimen/side_panel_fragment_pop_enter_offset_x"
+ android:duration="@integer/side_panel_anim_duration" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="0.0"
+ android:duration="@integer/side_panel_anim_duration" />
+ </set>
+ <!-- Entering animation starts with delay -->
+ <set>
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="@dimen/side_panel_fragment_pop_enter_offset_x"
+ android:valueTo="0dp"
+ android:duration="@integer/side_panel_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="@integer/side_panel_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+ </set>
+</set>
diff --git a/res/animator/side_panel_fragment_pop_exit.xml b/res/animator/side_panel_fragment_pop_exit.xml
new file mode 100644
index 00000000..66444edc
--- /dev/null
+++ b/res/animator/side_panel_fragment_pop_exit.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="0dp"
+ android:valueTo="@dimen/side_panel_fragment_pop_exit_offset_x"
+ android:duration="@integer/side_panel_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="1.0"
+ android:valueTo="0.0"
+ android:duration="@integer/side_panel_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/animator/tvview_block_screen_fade_in.xml b/res/animator/tvview_block_screen_fade_in.xml
new file mode 100644
index 00000000..6dca03af
--- /dev/null
+++ b/res/animator/tvview_block_screen_fade_in.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="@integer/tvview_block_icon_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/animator/tvview_block_screen_fade_out.xml b/res/animator/tvview_block_screen_fade_out.xml
new file mode 100644
index 00000000..340d0b7d
--- /dev/null
+++ b/res/animator/tvview_block_screen_fade_out.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="1.0"
+ android:valueTo="0.0"
+ android:duration="@integer/tvview_block_icon_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+</set>
diff --git a/res/color/card_meta_text_color.xml b/res/color/card_meta_text_color.xml
new file mode 100644
index 00000000..3c670146
--- /dev/null
+++ b/res/color/card_meta_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:state_enabled="false"
+ android:color="@color/card_meta_text_color_disabled"/>
+ <item
+ android:color="@color/card_meta_text_color_enabled"/>
+</selector>
diff --git a/res/color/option_item_secondary_text_color.xml b/res/color/option_item_secondary_text_color.xml
new file mode 100644
index 00000000..9d5e9d4e
--- /dev/null
+++ b/res/color/option_item_secondary_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:state_enabled="false"
+ android:color="@color/option_item_secondary_text_color_disabled"/>
+ <item
+ android:color="@color/option_item_secondary_text_color_enabled"/>
+</selector>
diff --git a/res/color/option_item_text_color.xml b/res/color/option_item_text_color.xml
new file mode 100644
index 00000000..81706dba
--- /dev/null
+++ b/res/color/option_item_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:state_enabled="false"
+ android:color="@color/option_item_text_color_disabled"/>
+ <item
+ android:color="@color/option_item_text_color_enabled"/>
+</selector>
diff --git a/res/color/program_guide_table_item_grayed_out_program_text_color.xml b/res/color/program_guide_table_item_grayed_out_program_text_color.xml
new file mode 100644
index 00000000..718e6208
--- /dev/null
+++ b/res/color/program_guide_table_item_grayed_out_program_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:state_focused="true"
+ android:color="@color/program_guide_table_item_grayed_out_program_text_color_focused"/>
+ <item android:state_focused="false"
+ android:color="@color/program_guide_table_item_grayed_out_program_text_color_not_focused"/>
+</selector>
diff --git a/res/color/side_panel_item_text.xml b/res/color/side_panel_item_text.xml
deleted file mode 100644
index edd170cd..00000000
--- a/res/color/side_panel_item_text.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- disabled state -->
- <item android:state_enabled="false" android:color="@color/menu_default_disabled_text_color" />
- <item android:color="@color/menu_default_text_color" />
-</selector> \ No newline at end of file
diff --git a/res/drawable-hdpi/app_icon.png b/res/drawable-hdpi/app_icon.png
deleted file mode 100644
index 04b7f30a..00000000
--- a/res/drawable-hdpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/bg_protection.png b/res/drawable-hdpi/bg_protection.png
deleted file mode 100644
index 1caa98c1..00000000
--- a/res/drawable-hdpi/bg_protection.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/btn_white_matte.9.png b/res/drawable-hdpi/btn_white_matte.9.png
deleted file mode 100644
index a5d1184c..00000000
--- a/res/drawable-hdpi/btn_white_matte.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/card_shadow_focused.9.png b/res/drawable-hdpi/card_shadow_focused.9.png
deleted file mode 100644
index 9f903d56..00000000
--- a/res/drawable-hdpi/card_shadow_focused.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/card_shadow_normal.9.png b/res/drawable-hdpi/card_shadow_normal.9.png
deleted file mode 100644
index f41a0612..00000000
--- a/res/drawable-hdpi/card_shadow_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_channel_guide.png b/res/drawable-hdpi/ic_channel_guide.png
deleted file mode 100644
index a00a54af..00000000
--- a/res/drawable-hdpi/ic_channel_guide.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_dolby.png b/res/drawable-hdpi/ic_dolby.png
deleted file mode 100644
index 2eec6882..00000000
--- a/res/drawable-hdpi/ic_dolby.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
deleted file mode 100644
index c5f17f9a..00000000
--- a/res/drawable-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_s.png b/res/drawable-hdpi/ic_launcher_s.png
deleted file mode 100644
index 5510beb8..00000000
--- a/res/drawable-hdpi/ic_launcher_s.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_tvoption_cc.png b/res/drawable-hdpi/ic_tvoption_cc.png
deleted file mode 100644
index e9dba256..00000000
--- a/res/drawable-hdpi/ic_tvoption_cc.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_tvoption_display_mode.png b/res/drawable-hdpi/ic_tvoption_display_mode.png
deleted file mode 100644
index 05ea029b..00000000
--- a/res/drawable-hdpi/ic_tvoption_display_mode.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_tvoption_more.png b/res/drawable-hdpi/ic_tvoption_more.png
deleted file mode 100644
index bf5f4cb6..00000000
--- a/res/drawable-hdpi/ic_tvoption_more.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_tvoption_pip.png b/res/drawable-hdpi/ic_tvoption_pip.png
deleted file mode 100644
index aba8a038..00000000
--- a/res/drawable-hdpi/ic_tvoption_pip.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_tvoptions_edit_channels.png b/res/drawable-hdpi/ic_tvoptions_edit_channels.png
deleted file mode 100644
index e24b2c22..00000000
--- a/res/drawable-hdpi/ic_tvoptions_edit_channels.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_tvoptions_input.png b/res/drawable-hdpi/ic_tvoptions_input.png
deleted file mode 100644
index ef3390d0..00000000
--- a/res/drawable-hdpi/ic_tvoptions_input.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_tvoptions_input_settings.png b/res/drawable-hdpi/ic_tvoptions_input_settings.png
deleted file mode 100644
index 5f73f07c..00000000
--- a/res/drawable-hdpi/ic_tvoptions_input_settings.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_tvoptions_scan.png b/res/drawable-hdpi/ic_tvoptions_scan.png
deleted file mode 100644
index 893720ae..00000000
--- a/res/drawable-hdpi/ic_tvoptions_scan.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-ldpi/app_icon.png b/res/drawable-ldpi/app_icon.png
deleted file mode 100644
index 1b308739..00000000
--- a/res/drawable-ldpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-ldpi/btn_white_matte.9.png b/res/drawable-ldpi/btn_white_matte.9.png
deleted file mode 100644
index a5d1184c..00000000
--- a/res/drawable-ldpi/btn_white_matte.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/bg_protection.png b/res/drawable-mdpi/bg_protection.png
deleted file mode 100644
index 220fad89..00000000
--- a/res/drawable-mdpi/bg_protection.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/card_shadow_focused.9.png b/res/drawable-mdpi/card_shadow_focused.9.png
deleted file mode 100644
index 02791477..00000000
--- a/res/drawable-mdpi/card_shadow_focused.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/card_shadow_normal.9.png b/res/drawable-mdpi/card_shadow_normal.9.png
deleted file mode 100644
index 9460800b..00000000
--- a/res/drawable-mdpi/card_shadow_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_channel_guide.png b/res/drawable-mdpi/ic_channel_guide.png
deleted file mode 100644
index 6c15f95a..00000000
--- a/res/drawable-mdpi/ic_channel_guide.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_dolby.png b/res/drawable-mdpi/ic_dolby.png
deleted file mode 100644
index 2ac2d931..00000000
--- a/res/drawable-mdpi/ic_dolby.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
deleted file mode 100644
index ce25063f..00000000
--- a/res/drawable-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_s.png b/res/drawable-mdpi/ic_launcher_s.png
deleted file mode 100644
index d49fd31d..00000000
--- a/res/drawable-mdpi/ic_launcher_s.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_tvoption_cc.png b/res/drawable-mdpi/ic_tvoption_cc.png
deleted file mode 100644
index 3360cba9..00000000
--- a/res/drawable-mdpi/ic_tvoption_cc.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_tvoption_display_mode.png b/res/drawable-mdpi/ic_tvoption_display_mode.png
deleted file mode 100644
index 797537d4..00000000
--- a/res/drawable-mdpi/ic_tvoption_display_mode.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_tvoption_more.png b/res/drawable-mdpi/ic_tvoption_more.png
deleted file mode 100644
index 730b52e8..00000000
--- a/res/drawable-mdpi/ic_tvoption_more.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_tvoption_pip.png b/res/drawable-mdpi/ic_tvoption_pip.png
deleted file mode 100644
index 03804934..00000000
--- a/res/drawable-mdpi/ic_tvoption_pip.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_tvoptions_edit_channels.png b/res/drawable-mdpi/ic_tvoptions_edit_channels.png
deleted file mode 100644
index 75c27018..00000000
--- a/res/drawable-mdpi/ic_tvoptions_edit_channels.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_tvoptions_input.png b/res/drawable-mdpi/ic_tvoptions_input.png
deleted file mode 100644
index ef5bdacb..00000000
--- a/res/drawable-mdpi/ic_tvoptions_input.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_tvoptions_input_settings.png b/res/drawable-mdpi/ic_tvoptions_input_settings.png
deleted file mode 100644
index af1a3467..00000000
--- a/res/drawable-mdpi/ic_tvoptions_input_settings.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_tvoptions_scan.png b/res/drawable-mdpi/ic_tvoptions_scan.png
deleted file mode 100644
index 2b03fb10..00000000
--- a/res/drawable-mdpi/ic_tvoptions_scan.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/program_guide_table_item_background_focused.9.png b/res/drawable-nodpi/program_guide_table_item_background_focused.9.png
new file mode 100644
index 00000000..7c69c49e
--- /dev/null
+++ b/res/drawable-nodpi/program_guide_table_item_background_focused.9.png
Binary files differ
diff --git a/res/drawable-nodpi/program_guide_table_item_background_focused_enabled.9.png b/res/drawable-nodpi/program_guide_table_item_background_focused_enabled.9.png
new file mode 100644
index 00000000..bb3a4a8d
--- /dev/null
+++ b/res/drawable-nodpi/program_guide_table_item_background_focused_enabled.9.png
Binary files differ
diff --git a/res/drawable-nodpi/program_guide_table_item_background_normal.9.png b/res/drawable-nodpi/program_guide_table_item_background_normal.9.png
new file mode 100644
index 00000000..0aa70f9f
--- /dev/null
+++ b/res/drawable-nodpi/program_guide_table_item_background_normal.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/app_icon.png b/res/drawable-xhdpi/app_icon.png
deleted file mode 100644
index 348c7d99..00000000
--- a/res/drawable-xhdpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/banner.png b/res/drawable-xhdpi/banner.png
index 95049156..a15c9123 100644
--- a/res/drawable-xhdpi/banner.png
+++ b/res/drawable-xhdpi/banner.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_protection.png b/res/drawable-xhdpi/bg_protection.png
index 8f42155d..e3226520 100644
--- a/res/drawable-xhdpi/bg_protection.png
+++ b/res/drawable-xhdpi/bg_protection.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_protection_right.png b/res/drawable-xhdpi/bg_protection_right.png
deleted file mode 100644
index 68a9ddf1..00000000
--- a/res/drawable-xhdpi/bg_protection_right.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/btn_white_matte.9.png b/res/drawable-xhdpi/btn_white_matte.9.png
deleted file mode 100644
index a5d1184c..00000000
--- a/res/drawable-xhdpi/btn_white_matte.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/card_shadow_focused.9.png b/res/drawable-xhdpi/card_shadow_focused.9.png
deleted file mode 100644
index 5685a7e5..00000000
--- a/res/drawable-xhdpi/card_shadow_focused.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/card_shadow_normal.9.png b/res/drawable-xhdpi/card_shadow_normal.9.png
deleted file mode 100644
index 39bd79fb..00000000
--- a/res/drawable-xhdpi/card_shadow_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_channel_guide.png b/res/drawable-xhdpi/ic_channel_guide.png
index d9ac89cd..081027c3 100644
--- a/res/drawable-xhdpi/ic_channel_guide.png
+++ b/res/drawable-xhdpi/ic_channel_guide.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_dolby.png b/res/drawable-xhdpi/ic_dolby.png
deleted file mode 100644
index 2ddba2d6..00000000
--- a/res/drawable-xhdpi/ic_dolby.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_guide_lock.png b/res/drawable-xhdpi/ic_guide_lock.png
new file mode 100644
index 00000000..62832efd
--- /dev/null
+++ b/res/drawable-xhdpi/ic_guide_lock.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
index 5600f919..6b092ce7 100644
--- a/res/drawable-xhdpi/ic_launcher.png
+++ b/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_s.png b/res/drawable-xhdpi/ic_launcher_s.png
index ad26d614..b5808345 100644
--- a/res/drawable-xhdpi/ic_launcher_s.png
+++ b/res/drawable-xhdpi/ic_launcher_s.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_message_lock.png b/res/drawable-xhdpi/ic_message_lock.png
new file mode 100644
index 00000000..e5741ffe
--- /dev/null
+++ b/res/drawable-xhdpi/ic_message_lock.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_message_lock_preview.png b/res/drawable-xhdpi/ic_message_lock_preview.png
new file mode 100644
index 00000000..cc1f5289
--- /dev/null
+++ b/res/drawable-xhdpi/ic_message_lock_preview.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pip_loc_bottom_left.png b/res/drawable-xhdpi/ic_pip_loc_bottom_left.png
deleted file mode 100644
index 2e28fc38..00000000
--- a/res/drawable-xhdpi/ic_pip_loc_bottom_left.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pip_loc_bottom_right.png b/res/drawable-xhdpi/ic_pip_loc_bottom_right.png
deleted file mode 100644
index db0727ef..00000000
--- a/res/drawable-xhdpi/ic_pip_loc_bottom_right.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pip_loc_top_left.png b/res/drawable-xhdpi/ic_pip_loc_top_left.png
deleted file mode 100644
index 539fcc3a..00000000
--- a/res/drawable-xhdpi/ic_pip_loc_top_left.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pip_loc_top_right.png b/res/drawable-xhdpi/ic_pip_loc_top_right.png
deleted file mode 100644
index bae9ef2f..00000000
--- a/res/drawable-xhdpi/ic_pip_loc_top_right.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pip_option_input.png b/res/drawable-xhdpi/ic_pip_option_input.png
new file mode 100644
index 00000000..2638b67b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pip_option_input.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pip_option_layout1.png b/res/drawable-xhdpi/ic_pip_option_layout1.png
new file mode 100644
index 00000000..ade3cfc5
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pip_option_layout1.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pip_option_layout2.png b/res/drawable-xhdpi/ic_pip_option_layout2.png
new file mode 100644
index 00000000..1a59e474
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pip_option_layout2.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pip_option_layout3.png b/res/drawable-xhdpi/ic_pip_option_layout3.png
new file mode 100644
index 00000000..f43a57ed
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pip_option_layout3.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pip_option_layout4.png b/res/drawable-xhdpi/ic_pip_option_layout4.png
new file mode 100644
index 00000000..bdf3f065
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pip_option_layout4.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pip_option_layout5.png b/res/drawable-xhdpi/ic_pip_option_layout5.png
new file mode 100644
index 00000000..4868c9f0
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pip_option_layout5.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pip_option_size.png b/res/drawable-xhdpi/ic_pip_option_size.png
new file mode 100644
index 00000000..f7e32591
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pip_option_size.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pip_option_swap.png b/res/drawable-xhdpi/ic_pip_option_swap.png
new file mode 100644
index 00000000..fd9d8895
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pip_option_swap.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pip_option_swap_audio.png b/res/drawable-xhdpi/ic_pip_option_swap_audio.png
new file mode 100644
index 00000000..8f62ab0c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pip_option_swap_audio.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_recent_thumbnail_default.png b/res/drawable-xhdpi/ic_recent_thumbnail_default.png
new file mode 100644
index 00000000..9b4abd58
--- /dev/null
+++ b/res/drawable-xhdpi/ic_recent_thumbnail_default.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_related_actor.png b/res/drawable-xhdpi/ic_related_actor.png
new file mode 100644
index 00000000..a34d2cc0
--- /dev/null
+++ b/res/drawable-xhdpi/ic_related_actor.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_related_search.png b/res/drawable-xhdpi/ic_related_search.png
new file mode 100644
index 00000000..c7c52f9a
--- /dev/null
+++ b/res/drawable-xhdpi/ic_related_search.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_setup_channels.png b/res/drawable-xhdpi/ic_setup_channels.png
new file mode 100644
index 00000000..424f08ad
--- /dev/null
+++ b/res/drawable-xhdpi/ic_setup_channels.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoption_about.png b/res/drawable-xhdpi/ic_tvoption_about.png
new file mode 100644
index 00000000..b242df7d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvoption_about.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoption_aspect.png b/res/drawable-xhdpi/ic_tvoption_aspect.png
new file mode 100644
index 00000000..526908f8
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvoption_aspect.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoption_cc.png b/res/drawable-xhdpi/ic_tvoption_cc.png
index aaaa5f79..2c7ce075 100644
--- a/res/drawable-xhdpi/ic_tvoption_cc.png
+++ b/res/drawable-xhdpi/ic_tvoption_cc.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoption_channel_sources.png b/res/drawable-xhdpi/ic_tvoption_channel_sources.png
new file mode 100644
index 00000000..f5df5bcd
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvoption_channel_sources.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoption_display_mode.png b/res/drawable-xhdpi/ic_tvoption_display_mode.png
deleted file mode 100644
index d57ffa62..00000000
--- a/res/drawable-xhdpi/ic_tvoption_display_mode.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoption_locate_pip.png b/res/drawable-xhdpi/ic_tvoption_locate_pip.png
deleted file mode 100644
index 0faf2dfd..00000000
--- a/res/drawable-xhdpi/ic_tvoption_locate_pip.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoption_more.png b/res/drawable-xhdpi/ic_tvoption_more.png
deleted file mode 100644
index 50a37c40..00000000
--- a/res/drawable-xhdpi/ic_tvoption_more.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoption_multi_track.png b/res/drawable-xhdpi/ic_tvoption_multi_track.png
new file mode 100644
index 00000000..ef06dff0
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvoption_multi_track.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoption_parental.png b/res/drawable-xhdpi/ic_tvoption_parental.png
new file mode 100644
index 00000000..d3499a63
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvoption_parental.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoption_pip.png b/res/drawable-xhdpi/ic_tvoption_pip.png
index c2ae77bc..59b972e1 100644
--- a/res/drawable-xhdpi/ic_tvoption_pip.png
+++ b/res/drawable-xhdpi/ic_tvoption_pip.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoption_pip_off.png b/res/drawable-xhdpi/ic_tvoption_pip_off.png
new file mode 100644
index 00000000..d3a8e7a5
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvoption_pip_off.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoptions_edit_channels.png b/res/drawable-xhdpi/ic_tvoptions_edit_channels.png
deleted file mode 100644
index ca920769..00000000
--- a/res/drawable-xhdpi/ic_tvoptions_edit_channels.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoptions_input.png b/res/drawable-xhdpi/ic_tvoptions_input.png
deleted file mode 100644
index 8dff37e5..00000000
--- a/res/drawable-xhdpi/ic_tvoptions_input.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoptions_input_settings.png b/res/drawable-xhdpi/ic_tvoptions_input_settings.png
deleted file mode 100644
index e480b1d0..00000000
--- a/res/drawable-xhdpi/ic_tvoptions_input_settings.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvoptions_scan.png b/res/drawable-xhdpi/ic_tvoptions_scan.png
deleted file mode 100644
index 103ba15e..00000000
--- a/res/drawable-xhdpi/ic_tvoptions_scan.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_000.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_000.png
new file mode 100644
index 00000000..070ff819
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_000.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_001.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_001.png
new file mode 100644
index 00000000..35337864
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_001.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_002.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_002.png
new file mode 100644
index 00000000..70d2dbe9
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_002.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_003.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_003.png
new file mode 100644
index 00000000..681ebd1d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_003.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_004.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_004.png
new file mode 100644
index 00000000..8278a246
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_004.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_005.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_005.png
new file mode 100644
index 00000000..fb47c206
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_005.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_006.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_006.png
new file mode 100644
index 00000000..8999b1e1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_006.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_007.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_007.png
new file mode 100644
index 00000000..c730dabe
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_007.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_008.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_008.png
new file mode 100644
index 00000000..ab3483e6
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_008.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_009.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_009.png
new file mode 100644
index 00000000..5ddd8b45
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_009.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_010.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_010.png
new file mode 100644
index 00000000..3d7cbb56
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_010.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_011.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_011.png
new file mode 100644
index 00000000..03dba03f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_011.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_012.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_012.png
new file mode 100644
index 00000000..5a9fa39c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_012.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_013.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_013.png
new file mode 100644
index 00000000..858591cf
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_013.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_014.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_014.png
new file mode 100644
index 00000000..02355c95
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_014.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_015.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_015.png
new file mode 100644
index 00000000..8b0ed03e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_off_015.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_000.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_000.png
new file mode 100644
index 00000000..771489be
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_000.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_001.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_001.png
new file mode 100644
index 00000000..b125b7fd
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_001.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_002.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_002.png
new file mode 100644
index 00000000..8e37be85
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_002.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_003.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_003.png
new file mode 100644
index 00000000..5212f90f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_003.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_004.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_004.png
new file mode 100644
index 00000000..48f5e263
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_004.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_005.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_005.png
new file mode 100644
index 00000000..de55a996
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_005.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_006.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_006.png
new file mode 100644
index 00000000..ee265564
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_006.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_007.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_007.png
new file mode 100644
index 00000000..ada6b569
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_007.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_008.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_008.png
new file mode 100644
index 00000000..c3f7e91b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_008.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_009.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_009.png
new file mode 100644
index 00000000..aefffe33
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_009.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_010.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_010.png
new file mode 100644
index 00000000..55229c21
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_010.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_011.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_011.png
new file mode 100644
index 00000000..ddc1140c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_011.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_012.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_012.png
new file mode 100644
index 00000000..3585fb96
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_012.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_013.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_013.png
new file mode 100644
index 00000000..7834d5f8
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_013.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_014.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_014.png
new file mode 100644
index 00000000..218b400d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_014.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_015.png b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_015.png
new file mode 100644
index 00000000..c15fc8ab
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_lock_to_on_015.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tvsidepanel_partial_locked.png b/res/drawable-xhdpi/ic_tvsidepanel_partial_locked.png
new file mode 100644
index 00000000..522db08f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tvsidepanel_partial_locked.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_remote.png b/res/drawable-xhdpi/ic_welcome_remote.png
new file mode 100644
index 00000000..e4dee093
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_remote.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_000.png b/res/drawable-xhdpi/ic_welcome_ripple_000.png
new file mode 100644
index 00000000..74b5e47d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_000.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_001.png b/res/drawable-xhdpi/ic_welcome_ripple_001.png
new file mode 100644
index 00000000..d33ab76f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_001.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_002.png b/res/drawable-xhdpi/ic_welcome_ripple_002.png
new file mode 100644
index 00000000..7f128b24
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_002.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_003.png b/res/drawable-xhdpi/ic_welcome_ripple_003.png
new file mode 100644
index 00000000..f62238ea
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_003.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_004.png b/res/drawable-xhdpi/ic_welcome_ripple_004.png
new file mode 100644
index 00000000..e64053a7
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_004.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_005.png b/res/drawable-xhdpi/ic_welcome_ripple_005.png
new file mode 100644
index 00000000..b9ec6e0e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_005.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_006.png b/res/drawable-xhdpi/ic_welcome_ripple_006.png
new file mode 100644
index 00000000..a0c3fefa
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_006.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_007.png b/res/drawable-xhdpi/ic_welcome_ripple_007.png
new file mode 100644
index 00000000..43e141f3
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_007.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_008.png b/res/drawable-xhdpi/ic_welcome_ripple_008.png
new file mode 100644
index 00000000..96e92a71
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_008.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_009.png b/res/drawable-xhdpi/ic_welcome_ripple_009.png
new file mode 100644
index 00000000..9cb63d14
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_009.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_010.png b/res/drawable-xhdpi/ic_welcome_ripple_010.png
new file mode 100644
index 00000000..9bff246c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_010.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_011.png b/res/drawable-xhdpi/ic_welcome_ripple_011.png
new file mode 100644
index 00000000..71bd628a
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_011.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_012.png b/res/drawable-xhdpi/ic_welcome_ripple_012.png
new file mode 100644
index 00000000..e7198381
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_012.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_013.png b/res/drawable-xhdpi/ic_welcome_ripple_013.png
new file mode 100644
index 00000000..de52b73b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_013.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_014.png b/res/drawable-xhdpi/ic_welcome_ripple_014.png
new file mode 100644
index 00000000..ad6d1ef1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_014.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_015.png b/res/drawable-xhdpi/ic_welcome_ripple_015.png
new file mode 100644
index 00000000..324bbe26
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_015.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_016.png b/res/drawable-xhdpi/ic_welcome_ripple_016.png
new file mode 100644
index 00000000..9c61ef43
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_016.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_017.png b/res/drawable-xhdpi/ic_welcome_ripple_017.png
new file mode 100644
index 00000000..1d6518c1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_017.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_018.png b/res/drawable-xhdpi/ic_welcome_ripple_018.png
new file mode 100644
index 00000000..262ef9fb
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_018.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_019.png b/res/drawable-xhdpi/ic_welcome_ripple_019.png
new file mode 100644
index 00000000..d8a1e4cc
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_019.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_020.png b/res/drawable-xhdpi/ic_welcome_ripple_020.png
new file mode 100644
index 00000000..cc4a8179
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_020.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_021.png b/res/drawable-xhdpi/ic_welcome_ripple_021.png
new file mode 100644
index 00000000..a4bb8f3c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_021.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_022.png b/res/drawable-xhdpi/ic_welcome_ripple_022.png
new file mode 100644
index 00000000..1f4a4359
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_022.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_023.png b/res/drawable-xhdpi/ic_welcome_ripple_023.png
new file mode 100644
index 00000000..4dea699e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_023.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_024.png b/res/drawable-xhdpi/ic_welcome_ripple_024.png
new file mode 100644
index 00000000..04d8f95a
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_024.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_025.png b/res/drawable-xhdpi/ic_welcome_ripple_025.png
new file mode 100644
index 00000000..9934fe45
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_025.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_026.png b/res/drawable-xhdpi/ic_welcome_ripple_026.png
new file mode 100644
index 00000000..774862e0
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_026.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_027.png b/res/drawable-xhdpi/ic_welcome_ripple_027.png
new file mode 100644
index 00000000..007d0c20
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_027.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_028.png b/res/drawable-xhdpi/ic_welcome_ripple_028.png
new file mode 100644
index 00000000..f2738f79
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_028.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_welcome_ripple_029.png b/res/drawable-xhdpi/ic_welcome_ripple_029.png
new file mode 100644
index 00000000..b6797543
--- /dev/null
+++ b/res/drawable-xhdpi/ic_welcome_ripple_029.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_youtube.png b/res/drawable-xhdpi/ic_youtube.png
new file mode 100644
index 00000000..653b270d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_youtube.png
Binary files differ
diff --git a/res/drawable-xxhdpi/card_shadow_focused.9.png b/res/drawable-xxhdpi/card_shadow_focused.9.png
deleted file mode 100644
index 619cfb27..00000000
--- a/res/drawable-xxhdpi/card_shadow_focused.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/card_shadow_normal.9.png b/res/drawable-xxhdpi/card_shadow_normal.9.png
deleted file mode 100644
index a284fa21..00000000
--- a/res/drawable-xxhdpi/card_shadow_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_channel_guide.png b/res/drawable-xxhdpi/ic_channel_guide.png
deleted file mode 100644
index fc6acaa9..00000000
--- a/res/drawable-xxhdpi/ic_channel_guide.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_dolby.png b/res/drawable-xxhdpi/ic_dolby.png
deleted file mode 100644
index 54f50f3e..00000000
--- a/res/drawable-xxhdpi/ic_dolby.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_tvoption_cc.png b/res/drawable-xxhdpi/ic_tvoption_cc.png
deleted file mode 100644
index 6ca8e2da..00000000
--- a/res/drawable-xxhdpi/ic_tvoption_cc.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_tvoption_display_mode.png b/res/drawable-xxhdpi/ic_tvoption_display_mode.png
deleted file mode 100644
index 0675d7c1..00000000
--- a/res/drawable-xxhdpi/ic_tvoption_display_mode.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_tvoption_more.png b/res/drawable-xxhdpi/ic_tvoption_more.png
deleted file mode 100644
index 8ad33318..00000000
--- a/res/drawable-xxhdpi/ic_tvoption_more.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_tvoption_pip.png b/res/drawable-xxhdpi/ic_tvoption_pip.png
deleted file mode 100644
index db0727ef..00000000
--- a/res/drawable-xxhdpi/ic_tvoption_pip.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_tvoptions_edit_channels.png b/res/drawable-xxhdpi/ic_tvoptions_edit_channels.png
deleted file mode 100644
index c43bfa8d..00000000
--- a/res/drawable-xxhdpi/ic_tvoptions_edit_channels.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_tvoptions_input.png b/res/drawable-xxhdpi/ic_tvoptions_input.png
deleted file mode 100644
index f51b9070..00000000
--- a/res/drawable-xxhdpi/ic_tvoptions_input.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_tvoptions_input_settings.png b/res/drawable-xxhdpi/ic_tvoptions_input_settings.png
deleted file mode 100644
index c99df536..00000000
--- a/res/drawable-xxhdpi/ic_tvoptions_input_settings.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_tvoptions_scan.png b/res/drawable-xxhdpi/ic_tvoptions_scan.png
deleted file mode 100644
index fdac164d..00000000
--- a/res/drawable-xxhdpi/ic_tvoptions_scan.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/action_card_background.xml b/res/drawable/action_card_background.xml
new file mode 100644
index 00000000..0d7c46c3
--- /dev/null
+++ b/res/drawable/action_card_background.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:enterFadeDuration="@android:integer/config_shortAnimTime"
+ android:exitFadeDuration="@android:integer/config_shortAnimTime">
+
+ <item android:state_focused="true">
+ <shape>
+ <solid android:color="@color/action_card_selected_background"/>
+ <corners android:radius="2dp" />
+ </shape>
+ </item>
+
+ <item android:state_focused="false">
+ <shape>
+ <solid android:color="@color/action_card_deselected_background"/>
+ <corners android:radius="2dp" />
+ </shape>
+ </item>
+
+</selector>
diff --git a/res/drawable/btn_lock_material.xml b/res/drawable/btn_lock_material.xml
new file mode 100644
index 00000000..2c555cdd
--- /dev/null
+++ b/res/drawable/btn_lock_material.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:src="@drawable/ic_tvsidepanel_lock_to_on_015"
+ android:tint="?android:attr/colorControlActivated" />
diff --git a/res/drawable/btn_lock_material_anim.xml b/res/drawable/btn_lock_material_anim.xml
new file mode 100644
index 00000000..1180614d
--- /dev/null
+++ b/res/drawable/btn_lock_material_anim.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:state_checked="true">
+ <bitmap
+ android:src="@drawable/ic_tvsidepanel_lock_to_on_015"
+ android:tint="?android:attr/colorControlActivated"
+ android:alpha="?android:attr/disabledAlpha" />
+ </item>
+ <item android:state_enabled="false">
+ <bitmap
+ android:src="@drawable/ic_tvsidepanel_lock_to_on_000"
+ android:tint="?android:attr/colorControlNormal"
+ android:alpha="?android:attr/disabledAlpha" />
+ </item>
+ <item android:state_checked="true" android:id="@+id/on">
+ <bitmap
+ android:src="@drawable/ic_tvsidepanel_lock_to_on_015"
+ android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:id="@+id/off">
+ <bitmap
+ android:src="@drawable/ic_tvsidepanel_lock_to_on_000"
+ android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <transition android:fromId="@+id/off" android:toId="@+id/on">
+ <animation-list>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_000" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_001" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_002" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_003" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_004" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_005" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_006" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_007" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_008" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_009" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_010" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_011" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_012" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_013" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_014" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_on_015" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ </animation-list>
+ </transition>
+ <transition android:fromId="@+id/on" android:toId="@+id/off">
+ <animation-list>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_000" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_001" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_002" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_003" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_004" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_005" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_006" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_007" android:tint="?android:attr/colorControlActivated" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_008" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_009" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_010" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_011" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_012" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_013" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_014" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:duration="15">
+ <bitmap android:src="@drawable/ic_tvsidepanel_lock_to_off_015" android:tint="?android:attr/colorControlNormal" />
+ </item>
+ </animation-list>
+ </transition>
+</animated-selector>
diff --git a/res/drawable/btn_partial_lock_material.xml b/res/drawable/btn_partial_lock_material.xml
new file mode 100644
index 00000000..d978838e
--- /dev/null
+++ b/res/drawable/btn_partial_lock_material.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:src="@drawable/ic_tvsidepanel_partial_locked"
+ android:tint="?android:attr/colorControlActivated" />
diff --git a/res/drawable/btn_unlock_material.xml b/res/drawable/btn_unlock_material.xml
new file mode 100644
index 00000000..da0fba34
--- /dev/null
+++ b/res/drawable/btn_unlock_material.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:src="@drawable/ic_tvsidepanel_lock_to_on_000"
+ android:tint="?android:attr/colorControlNormal" />
diff --git a/res/drawable/card_image_gradient.xml b/res/drawable/card_image_gradient.xml
new file mode 100644
index 00000000..7bb6814b
--- /dev/null
+++ b/res/drawable/card_image_gradient.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <gradient
+ android:startColor="#99000000"
+ android:endColor="#00000000"
+ android:angle="90"
+ android:top="@dimen/card_image_gradient_padding_top" />
+</shape>
diff --git a/res/drawable/channel_banner_tile_border.xml b/res/drawable/channel_banner_tile_border.xml
deleted file mode 100644
index fa4df759..00000000
--- a/res/drawable/channel_banner_tile_border.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="#455A64"/>
- <corners android:radius="2dp"
- android:topLeftRadius="2dp"
- android:topRightRadius="2dp"
- android:bottomLeftRadius="2dp"
- android:bottomRightRadius="2dp"/>
-</shape> \ No newline at end of file
diff --git a/res/drawable/channel_card_progress_bar.xml b/res/drawable/channel_card_progress_bar.xml
new file mode 100644
index 00000000..2f020fb7
--- /dev/null
+++ b/res/drawable/channel_card_progress_bar.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@android:id/background">
+ <shape>
+ <solid android:color="@color/channel_card_progress_bar_background" />
+ </shape>
+ </item>
+ <item
+ android:id="@android:id/progress">
+ <clip>
+ <shape>
+ <solid android:color="@color/channel_card_progress_bar_foreground" />
+ </shape>
+ </clip>
+ </item>
+</layer-list>
diff --git a/res/drawable/channel_tile_bottom.xml b/res/drawable/channel_tile_bottom.xml
deleted file mode 100644
index f87115dc..00000000
--- a/res/drawable/channel_tile_bottom.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/channel_tile_bottom" />
- <corners android:bottomLeftRadius="@dimen/channel_tile_round_radius"
- android:bottomRightRadius="@dimen/channel_tile_round_radius"/>
-</shape> \ No newline at end of file
diff --git a/res/drawable/channel_tile_guide_bottom.xml b/res/drawable/channel_tile_guide_bottom.xml
deleted file mode 100644
index 0647bcfe..00000000
--- a/res/drawable/channel_tile_guide_bottom.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/channel_tile_guide_bottom" />
- <corners android:bottomLeftRadius="@dimen/channel_tile_round_radius"
- android:bottomRightRadius="@dimen/channel_tile_round_radius"/>
-</shape> \ No newline at end of file
diff --git a/res/drawable/channel_tile_guide_top.xml b/res/drawable/channel_tile_guide_top.xml
deleted file mode 100644
index 93594693..00000000
--- a/res/drawable/channel_tile_guide_top.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/channel_tile_guide_top" />
- <corners android:radius="@dimen/channel_tile_round_radius" />
-</shape> \ No newline at end of file
diff --git a/res/drawable/channel_tile_top.xml b/res/drawable/channel_tile_top.xml
deleted file mode 100644
index ed309fce..00000000
--- a/res/drawable/channel_tile_top.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/channel_tile_top" />
- <corners android:radius="@dimen/channel_tile_round_radius" />
-</shape> \ No newline at end of file
diff --git a/res/drawable/channel_tile_top_on_image.xml b/res/drawable/channel_tile_top_on_image.xml
deleted file mode 100644
index bca65cf9..00000000
--- a/res/drawable/channel_tile_top_on_image.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/channel_tile_top_on_image"/>
- <corners android:radius="@dimen/channel_tile_round_radius"
- android:topLeftRadius="@dimen/channel_tile_round_radius"
- android:topRightRadius="@dimen/channel_tile_round_radius"
- android:bottomLeftRadius="@dimen/channel_tile_round_radius"
- android:bottomRightRadius="@dimen/channel_tile_round_radius"/>
-</shape> \ No newline at end of file
diff --git a/res/drawable/debug_option_background.xml b/res/drawable/debug_option_background.xml
deleted file mode 100644
index 284fd801..00000000
--- a/res/drawable/debug_option_background.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android" >
- <item android:state_focused="true"
- android:drawable="@color/option_item_focused_background"/>
- <item
- android:drawable="@color/option_item_background"/>
-</selector> \ No newline at end of file
diff --git a/res/drawable/ic_welcome_ripple_anim.xml b/res/drawable/ic_welcome_ripple_anim.xml
new file mode 100644
index 00000000..90d56e66
--- /dev/null
+++ b/res/drawable/ic_welcome_ripple_anim.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:oneshot="false">
+ <item android:drawable="@drawable/ic_welcome_ripple_000" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_001" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_002" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_003" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_004" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_005" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_006" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_007" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_008" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_009" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_010" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_011" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_012" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_013" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_014" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_015" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_016" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_017" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_018" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_019" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_020" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_021" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_022" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_023" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_024" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_025" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_026" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_027" android:duration="33" />
+ <item android:drawable="@drawable/ic_welcome_ripple_028" android:duration="33" />
+ <!-- Once the last frame is drawn, 1 sec interval is given before repetition. -->
+ <item android:drawable="@drawable/ic_welcome_ripple_029" android:duration="1033" />
+</animation-list>
diff --git a/res/drawable/info_banner_background.xml b/res/drawable/info_banner_background.xml
new file mode 100644
index 00000000..dee6189b
--- /dev/null
+++ b/res/drawable/info_banner_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#455A64"/>
+ <corners android:radius="2dp" />
+</shape>
diff --git a/res/drawable/key_blue.xml b/res/drawable/key_blue.xml
deleted file mode 100644
index 0bc4732e..00000000
--- a/res/drawable/key_blue.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval" >
- <size
- android:width="12dp"
- android:height="12dp" />
- <solid
- android:color="@color/key_blue" />
-</shape> \ No newline at end of file
diff --git a/res/drawable/key_green.xml b/res/drawable/key_green.xml
deleted file mode 100644
index b6dc72e0..00000000
--- a/res/drawable/key_green.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval" >
- <size
- android:width="12dp"
- android:height="12dp" />
- <solid
- android:color="@color/key_green" />
-</shape> \ No newline at end of file
diff --git a/res/drawable/key_red.xml b/res/drawable/key_red.xml
deleted file mode 100644
index 57f83192..00000000
--- a/res/drawable/key_red.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval" >
- <size
- android:width="12dp"
- android:height="12dp" />
- <solid
- android:color="@color/key_red" />
-</shape> \ No newline at end of file
diff --git a/res/drawable/key_yellow.xml b/res/drawable/key_yellow.xml
deleted file mode 100644
index aa8fbaed..00000000
--- a/res/drawable/key_yellow.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval" >
- <size
- android:width="12dp"
- android:height="12dp" />
- <solid
- android:color="@color/key_yellow" />
-</shape> \ No newline at end of file
diff --git a/res/drawable/keypad_background.xml b/res/drawable/keypad_background.xml
deleted file mode 100644
index 39375336..00000000
--- a/res/drawable/keypad_background.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners
- android:radius="2dp" />
- <solid
- android:color="@color/keypad_background" />
-</shape> \ No newline at end of file
diff --git a/res/drawable/menu_background.xml b/res/drawable/menu_background.xml
new file mode 100644
index 00000000..bdd55c8c
--- /dev/null
+++ b/res/drawable/menu_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:src="@drawable/bg_protection"
+ android:tileModeX="repeat"
+ android:tileModeY="mirror" />
diff --git a/res/drawable/options_selection_circle.xml b/res/drawable/options_selection_circle.xml
deleted file mode 100644
index da67869b..00000000
--- a/res/drawable/options_selection_circle.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval">
-
- <solid
- android:color="@color/options_selection_indicator"/>
-</shape> \ No newline at end of file
diff --git a/res/drawable/pin_dialog_background.xml b/res/drawable/pin_dialog_background.xml
new file mode 100644
index 00000000..d75ab4c9
--- /dev/null
+++ b/res/drawable/pin_dialog_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/pin_dialog_background"/>
+ <corners android:radius="2dp" />
+</shape>
diff --git a/res/drawable/pin_number_picker_focused_background.xml b/res/drawable/pin_number_picker_focused_background.xml
new file mode 100644
index 00000000..85867ea6
--- /dev/null
+++ b/res/drawable/pin_number_picker_focused_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/pin_number_picker_focused_background"/>
+ <corners android:radius="2dp" />
+</shape>
diff --git a/res/drawable/play_controls_body_background.xml b/res/drawable/play_controls_body_background.xml
new file mode 100644
index 00000000..8e96356a
--- /dev/null
+++ b/res/drawable/play_controls_body_background.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="true">
+ <shape>
+ <solid android:color="@color/play_controls_body_background_enabled"/>
+ </shape>
+ </item>
+ <item android:state_enabled="false">
+ <shape>
+ <solid android:color="@color/play_controls_body_background_disabled"/>
+ </shape>
+ </item>
+</selector>
diff --git a/res/drawable/play_controls_body_shape.xml b/res/drawable/play_controls_body_shape.xml
new file mode 100644
index 00000000..52ec7533
--- /dev/null
+++ b/res/drawable/play_controls_body_shape.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <corners android:radius="2dp" />
+</shape>
diff --git a/res/drawable/play_controls_button_background.xml b/res/drawable/play_controls_button_background.xml
new file mode 100644
index 00000000..55f7ff72
--- /dev/null
+++ b/res/drawable/play_controls_button_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:enterFadeDuration="@android:integer/config_shortAnimTime"
+ android:exitFadeDuration="@android:integer/config_shortAnimTime"
+ android:color="@color/lb_control_button_color">
+ <item android:id="@android:id/mask" android:state_focused="true">
+ <shape android:shape="oval">
+ <!-- Color not used since this is a ripple mask -->
+ <solid android:color="@android:color/white" />
+ <size android:width="@dimen/play_controls_button_background_width"
+ android:height="@dimen/play_controls_button_background_height" />
+ </shape>
+ </item>
+</ripple>
diff --git a/res/drawable/play_controls_time_indicator.xml b/res/drawable/play_controls_time_indicator.xml
new file mode 100644
index 00000000..16acb107
--- /dev/null
+++ b/res/drawable/play_controls_time_indicator.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval" >
+ <size android:width="@dimen/play_controls_time_indicator_width"
+ android:height="@dimen/play_controls_time_indicator_height" />
+ <solid android:color="@color/play_controls_progress_time_indicator_color" />
+</shape>
diff --git a/res/drawable/program_guide_table_header_column_item_background.xml b/res/drawable/program_guide_table_header_column_item_background.xml
new file mode 100644
index 00000000..f4bb5372
--- /dev/null
+++ b/res/drawable/program_guide_table_header_column_item_background.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item><shape>
+ <solid android:color="@color/program_guide_table_header_column_background" />
+ </shape></item>
+ <item android:left="-0.5dp" android:right="-0.5dp" ><shape>
+ <stroke android:width="0.5dp" android:color="@color/program_guide_table_border_color" />
+ </shape></item>
+</layer-list>
diff --git a/res/drawable/program_guide_table_header_column_last_item_background.xml b/res/drawable/program_guide_table_header_column_last_item_background.xml
new file mode 100644
index 00000000..2ef699f6
--- /dev/null
+++ b/res/drawable/program_guide_table_header_column_last_item_background.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/program_guide_table_header_column_background" />
+ <corners
+ android:bottomLeftRadius="@dimen/program_guide_table_background_round_radius_left"
+ android:bottomRightRadius="@dimen/program_guide_table_background_round_radius_right"
+ android:topLeftRadius="0dp"
+ android:topRightRadius="0dp" />
+</shape>
diff --git a/res/drawable/program_guide_table_header_row_background.xml b/res/drawable/program_guide_table_header_row_background.xml
new file mode 100644
index 00000000..81d081c2
--- /dev/null
+++ b/res/drawable/program_guide_table_header_row_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/program_guide_table_header_row_background" />
+ <corners
+ android:bottomLeftRadius="0dp"
+ android:bottomRightRadius="0dp"
+ android:topLeftRadius="@dimen/program_guide_table_background_round_radius_left"
+ android:topRightRadius="@dimen/program_guide_table_background_round_radius_right" />
+</shape>
diff --git a/res/drawable/program_guide_table_item_background.xml b/res/drawable/program_guide_table_item_background.xml
new file mode 100644
index 00000000..299c20dc
--- /dev/null
+++ b/res/drawable/program_guide_table_item_background.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tv="http://schemas.android.com/apk/res-auto" >
+
+ <!-- This state is needed as a too wide ripple will try to allocate too much memory. -->
+ <item android:state_focused="true" tv:state_current_program="true"
+ tv:state_program_too_wide="true" >
+ <layer-list>
+ <item>
+ <nine-patch
+ android:src="@drawable/program_guide_table_item_background_focused_enabled" />
+ </item>
+ <item android:id="@+id/reverse_progress" >
+ <clip android:gravity="end" >
+ <color android:color="@color/program_guide_table_item_focus_progress_overlay" />
+ </clip>
+ </item>
+ </layer-list>
+ </item>
+
+ <item android:state_focused="true" tv:state_current_program="true" >
+ <ripple android:color="?android:attr/colorControlHighlight" >
+ <item>
+ <nine-patch
+ android:src="@drawable/program_guide_table_item_background_focused_enabled" />
+ </item>
+ <item android:id="@+id/reverse_progress" >
+ <clip android:gravity="end" >
+ <color android:color="@color/program_guide_table_item_focus_progress_overlay" />
+ </clip>
+ </item>
+ </ripple>
+ </item>
+ <item android:state_focused="true" >
+ <nine-patch android:src="@drawable/program_guide_table_item_background_focused" />
+ </item>
+ <item>
+ <nine-patch android:src="@drawable/program_guide_table_item_background_normal" />
+ </item>
+</selector>
diff --git a/res/drawable/program_guide_time_indicator.xml b/res/drawable/program_guide_time_indicator.xml
new file mode 100644
index 00000000..dcd74f16
--- /dev/null
+++ b/res/drawable/program_guide_time_indicator.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:top="4dp" android:left="3.5dp" android:bottom="0dp" android:right="3.5dp" >
+ <shape android:shape="rectangle" >
+ <size android:width="1dp" android:height="40dp" />
+ <solid android:color="@color/program_guide_time_indicator_color" />
+ </shape>
+ </item>
+ <item android:top="0dp" android:left="0dp" android:bottom="36dp" android:right="0dp">
+ <shape android:shape="oval" >
+ <size android:width="8dp" android:height="8dp" />
+ <solid android:color="@color/program_guide_time_indicator_color" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/res/drawable/progress_horizontal.xml b/res/drawable/progress_horizontal.xml
index d583e9e7..95b7a531 100644
--- a/res/drawable/progress_horizontal.xml
+++ b/res/drawable/progress_horizontal.xml
@@ -1,18 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
diff --git a/res/drawable/setup_item_background.xml b/res/drawable/setup_item_background.xml
new file mode 100644
index 00000000..fb3899aa
--- /dev/null
+++ b/res/drawable/setup_item_background.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:enterFadeDuration="@android:integer/config_shortAnimTime"
+ android:exitFadeDuration="@android:integer/config_shortAnimTime"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle" >
+ <solid android:color="@android:color/white" />
+ <corners android:radius="2dp" />
+ </shape>
+ </item>
+</ripple>
diff --git a/res/drawable/track_meta_border.xml b/res/drawable/track_meta_border.xml
index 649ca1e4..ee9241c5 100644
--- a/res/drawable/track_meta_border.xml
+++ b/res/drawable/track_meta_border.xml
@@ -1,18 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#CCEEEEEE"/>
diff --git a/res/layout/action_tile.xml b/res/layout/action_tile.xml
deleted file mode 100644
index 9247b4f9..00000000
--- a/res/layout/action_tile.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<com.android.tv.ui.ActionTileView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:focusable="true" >
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical" >
- <FrameLayout
- android:layout_width="@dimen/action_tile_outer_length"
- android:layout_height="@dimen/action_tile_outer_length" >
- <ImageView
- android:id="@+id/action_tile_selection_circle"
- android:layout_width="@dimen/action_tile_inner_length"
- android:layout_height="@dimen/action_tile_inner_length"
- android:layout_gravity="center"
- android:contentDescription="@null"
- android:scaleType="fitCenter"
- android:alpha="0.0"
- android:src="@drawable/options_selection_circle" />
- <ImageView
- android:id="@+id/action_tile_icon"
- android:layout_width="@dimen/action_tile_inner_length"
- android:layout_height="@dimen/action_tile_inner_length"
- android:layout_gravity="center"
- android:contentDescription="@null"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_tvoption_display_mode" />
- </FrameLayout>
-
- <TextView
- android:id="@+id/action_tile_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:fontFamily="@string/font"
- android:layout_gravity="center_horizontal"
- android:singleLine="true"
- android:textColor="@color/menu_default_text_color"
- android:textSize="@dimen/action_tile_label_font_size" />
- </LinearLayout>
-</com.android.tv.ui.ActionTileView> \ No newline at end of file
diff --git a/res/layout/activity_fragment.xml b/res/layout/activity_fragment.xml
deleted file mode 100644
index 32b2e5f5..00000000
--- a/res/layout/activity_fragment.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/fragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-</FrameLayout>
diff --git a/res/layout/activity_passthrough_tv.xml b/res/layout/activity_passthrough_tv.xml
deleted file mode 100644
index 07ad6fa0..00000000
--- a/res/layout/activity_passthrough_tv.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:foreground="@android:color/transparent"
- android:keepScreenOn="true" >
-
- <android.media.tv.TvView android:id="@+id/tv_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center" />
-</FrameLayout>
diff --git a/res/layout/activity_tv.xml b/res/layout/activity_tv.xml
index e2fd6280..b484ed60 100644
--- a/res/layout/activity_tv.xml
+++ b/res/layout/activity_tv.xml
@@ -1,91 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
+<!--
+ ~ 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.
+ -->
+
+<!-- Foreground is the transparent color. Without the foreground, UIs above SurfaceView
+ sometimes show some artifacts. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
android:foreground="@android:color/transparent"
- android:keepScreenOn="true" >
+ android:keepScreenOn="true">
- <com.android.tv.ui.TunableTvView android:id="@+id/main_tv_view"
+ <com.android.tv.ui.AppLayerTvView android:id="@+id/main_tv_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_gravity="center" />
+ android:layout_gravity="start|center_vertical" />
- <com.android.tv.ui.TunableTvView android:id="@+id/pip_tv_view"
- android:visibility="gone"
- android:layout_marginLeft="56dp"
- android:layout_marginRight="56dp"
- android:layout_marginTop="27dp"
- android:layout_marginBottom="27dp"
- android:layout_width="240dp"
- android:layout_height="135dp"
- android:layout_gravity="right|bottom" />
-
- <LinearLayout
- android:id="@+id/control_guide"
+ <com.android.tv.ui.TunableTvView android:id="@+id/main_tunable_tv_view"
android:layout_width="match_parent"
- android:layout_height="64dp"
- android:layout_gravity="center"
- android:orientation="horizontal" >
-
- <View
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_margin="8dp"
- android:background="@android:drawable/ic_media_previous" />
-
- <View
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="8" />
-
- <View
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_margin="8dp"
- android:layout_weight="1"
- android:background="@android:drawable/ic_media_next" />
- </LinearLayout>
-
- <include layout="@layout/channel_banner" />
-
- <include layout="@layout/main_menu" />
-
- <FrameLayout
- android:layout_width="150dp"
- android:layout_height="200dp"
- android:layout_gravity="left|top"
- android:layout_marginLeft="50dp"
- android:layout_marginTop="50dp">
- <include layout="@layout/channel_number" />
- </FrameLayout>
-
- <com.android.tv.ui.sidepanel.SidePanelContainer
- android:id="@+id/right_panel"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- <com.android.tv.ui.KeypadView
- android:id="@+id/keypad"
- android:visibility="gone"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="32dp"
- android:layout_gravity="left|bottom" />
+ android:layout_height="match_parent"
+ android:layout_gravity="start|center_vertical" />
+
+ <com.android.tv.ui.AppLayerTvView android:id="@+id/pip_tv_view"
+ android:layout_marginLeft="@dimen/pipview_margin_horizontal"
+ android:layout_marginRight="@dimen/pipview_margin_horizontal"
+ android:layout_marginTop="@dimen/pipview_margin_top"
+ android:layout_marginBottom="@dimen/pipview_margin_bottom"
+ android:layout_width="@dimen/pipview_small_size_width"
+ android:layout_height="@dimen/pipview_small_size_height" />
</FrameLayout>
diff --git a/res/layout/block_screen.xml b/res/layout/block_screen.xml
new file mode 100644
index 00000000..c355f5d8
--- /dev/null
+++ b/res/layout/block_screen.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/block_screen"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:visibility="gone"
+ android:background="@android:color/black" >
+
+ <LinearLayout
+ android:id="@+id/block_screen_description"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center" >
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center" >
+ <ImageView
+ android:id="@+id/block_screen_icon"
+ android:layout_width="@dimen/tvview_block_icon_width"
+ android:layout_height="@dimen/tvview_block_icon_height"
+ android:layout_gravity="center"
+ android:src="@drawable/ic_message_lock"
+ android:contentDescription="@null" />
+ <ImageView
+ android:id="@+id/block_screen_shrunken_icon"
+ android:layout_width="@dimen/shrunken_tvview_block_icon_width"
+ android:layout_height="@dimen/shrunken_tvview_block_icon_height"
+ android:layout_gravity="center"
+ android:src="@drawable/ic_message_lock_preview"
+ android:visibility="gone"
+ android:contentDescription="@null" />
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/block_screen_text"
+ android:layout_width="600dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:paddingTop="@dimen/tvview_block_text_padding_top"
+ android:fontFamily="@string/font"
+ android:textSize="@dimen/tvview_block_text_size"
+ android:lineSpacingExtra="4sp"
+ android:textColor="@color/tvview_block_text_color" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/channel_banner.xml b/res/layout/channel_banner.xml
index 79b87314..71264584 100644
--- a/res/layout/channel_banner.xml
+++ b/res/layout/channel_banner.xml
@@ -1,159 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
+<!--
+ ~ 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.
+ -->
+<!-- All info banners have the same id for use by TvTransitionManager. -->
<com.android.tv.ui.ChannelBannerView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/channel_banner"
- android:orientation="vertical"
- android:layout_width="match_parent"
+ android:id="@+id/scene_transition_common"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="27dp"
- android:layout_marginLeft="132dp"
- android:layout_marginRight="132dp"
- android:paddingTop="16dp"
- android:paddingLeft="16dp"
- android:paddingRight="32dp"
- android:clipToPadding="false"
- android:elevation="8dp"
- android:background="@drawable/channel_banner_tile_border">
-
- <TextView android:id="@+id/channel_text"
- android:layout_width="112dp"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:layout_marginTop="@dimen/channel_banner_title_large_margin_top"
- android:textColor="#EEEEEE"
- android:fontFamily="sans-serif-thin"
- android:textSize="@dimen/channel_banner_title_large_text_size" />
-
- <ImageView android:id="@+id/channel_logo"
- android:layout_width="64dp"
- android:layout_height="44dp"
- android:layout_alignParentTop="true"
- android:layout_alignParentRight="true" />
-
- <TextView android:id="@+id/program_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/channel_banner_program_large_margin_top"
- android:layout_alignParentTop="true"
- android:layout_toRightOf="@id/channel_text"
- android:layout_toLeftOf="@id/channel_logo"
- android:maxLines="2"
- android:lineSpacingExtra="0.5sp"
- android:ellipsize="end"
- android:textColor="#EEEEEE"
- android:fontFamily="sans-serif"
- android:textSize="@dimen/channel_banner_program_large_text_size" />
-
- <Space android:id="@+id/anchor"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_marginTop="@dimen/channel_banner_anchor_one_line_y"
- android:layout_marginBottom="16dp"
- android:layout_alignParentTop="true"
- android:layout_toRightOf="@id/channel_text" />
-
- <ImageView android:id="@+id/tvinput_logo"
- android:layout_width="16dp"
- android:layout_height="16dp"
- android:layout_marginRight="8dp"
- android:layout_marginBottom="-2dp"
- android:layout_alignBottom="@id/anchor"
- android:layout_toRightOf="@id/anchor"
- android:visibility="gone" />
-
- <TextView android:id="@+id/channel_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="12dp"
- android:layout_marginBottom="-4sp"
- android:layout_alignBottom="@id/anchor"
- android:layout_toRightOf="@id/tvinput_logo"
- android:singleLine="true"
- android:ellipsize="end"
- android:maxWidth="@dimen/channel_name_max_width"
- android:textColor="#EEEEEE"
- android:fontFamily="sans-serif-condensed"
- android:textSize="@dimen/channel_banner_small_text_size" />
-
- <TextView android:id="@+id/program_time_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="12dp"
- android:layout_alignBottom="@id/channel_name"
- android:layout_toRightOf="@id/channel_name"
- android:textColor="#80EEEEEE"
- android:fontFamily="sans-serif-condensed"
- android:textSize="@dimen/channel_banner_small_text_size" />
-
- <ProgressBar
- android:id="@+id/remaining_time"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="60dp"
- android:layout_height="4dp"
- android:layout_alignBottom="@id/anchor"
- android:layout_toRightOf="@id/program_time_text"
- android:layout_marginRight="12dp"
- android:layout_marginBottom="0.5dp"
- android:progressDrawable="@drawable/progress_horizontal"
- android:indeterminate="false"
- android:max="100" />
-
- <TextView android:id="@+id/video_status"
- style="@style/channel_banner_track_meta_text"
- android:layout_alignBottom="@id/anchor"
- android:layout_toRightOf="@id/remaining_time" />
-
- <TextView android:id="@+id/rating"
- style="@style/channel_banner_track_meta_text"
- android:layout_alignBottom="@id/anchor"
- android:layout_toRightOf="@id/video_status" />
-
- <TextView android:id="@+id/closed_caption"
- style="@style/channel_banner_track_meta_text"
- android:layout_alignBottom="@id/anchor"
- android:layout_toRightOf="@id/rating" />
-
- <TextView android:id="@+id/aspect_ratio"
- style="@style/channel_banner_track_meta_text"
- android:layout_alignBottom="@id/anchor"
- android:layout_toRightOf="@id/closed_caption" />
-
- <TextView android:id="@+id/resolution"
- style="@style/channel_banner_track_meta_text"
- android:layout_alignBottom="@id/anchor"
- android:layout_toRightOf="@id/aspect_ratio" />
-
- <TextView android:id="@+id/audio_channel"
- style="@style/channel_banner_track_meta_text"
- android:layout_alignBottom="@id/anchor"
- android:layout_toRightOf="@id/resolution" />
-
- <TextView android:id="@+id/program_description"
- android:layout_width="match_parent"
+ android:layout_marginStart="132dp"
+ android:layout_marginEnd="132dp"
+ android:background="@drawable/info_banner_background"
+ android:elevation="8dp" >
+
+ <RelativeLayout android:id="@+id/channel_banner_view"
+ android:orientation="vertical"
+ android:layout_width="@dimen/channel_banner_width"
android:layout_height="wrap_content"
- android:layout_toRightOf="@id/channel_text"
- android:layout_below="@id/anchor"
- android:layout_marginTop="-5dp"
- android:layout_marginBottom="12.5dp"
- android:maxLines="3"
- android:lineSpacingExtra="4sp"
- android:ellipsize="end"
- android:textColor="#80EEEEEE"
- android:textSize="12sp" />
+ android:paddingTop="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="32dp"
+ android:clipToPadding="false"
+ android:visibility="gone" >
+
+ <TextView android:id="@+id/channel_number"
+ android:layout_width="112dp"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginTop="@dimen/channel_banner_channel_number_large_margin_top"
+ android:textColor="@color/channel_banner_text_color"
+ android:fontFamily="@string/thin_font"
+ android:textSize="@dimen/channel_banner_channel_number_large_text_size" />
+
+ <ImageView android:id="@+id/channel_logo"
+ android:layout_width="@dimen/channel_banner_channel_logo_width"
+ android:layout_height="@dimen/channel_banner_channel_logo_height"
+ android:layout_marginStart="@dimen/channel_banner_channel_logo_margin_start"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true" />
+
+ <TextView android:id="@+id/program_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/channel_banner_program_large_margin_top"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@id/channel_number"
+ android:layout_toStartOf="@id/channel_logo"
+ android:maxLines="2"
+ android:lineSpacingExtra="0.5sp"
+ android:ellipsize="end"
+ android:textColor="@color/channel_banner_text_color"
+ android:fontFamily="@string/light_font"
+ android:textSize="@dimen/channel_banner_program_large_text_size" />
+
+ <Space android:id="@+id/anchor"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/channel_banner_anchor_one_line_y"
+ android:layout_marginBottom="16dp"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@id/channel_number" />
+
+ <ImageView android:id="@+id/tvinput_logo"
+ android:layout_width="@dimen/channel_banner_input_logo_size"
+ android:layout_height="@dimen/channel_banner_input_logo_size"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="-2dp"
+ android:layout_alignBottom="@id/anchor"
+ android:layout_toEndOf="@id/anchor"
+ android:visibility="gone" />
+
+ <TextView android:id="@+id/channel_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="12dp"
+ android:layout_marginBottom="-4sp"
+ android:layout_alignBottom="@id/anchor"
+ android:layout_toEndOf="@id/tvinput_logo"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:maxWidth="@dimen/channel_name_max_width"
+ android:textColor="@color/channel_banner_text_color"
+ android:fontFamily="@string/condensed_font"
+ android:textSize="@dimen/channel_banner_small_text_size" />
+
+ <TextView android:id="@+id/program_time_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="12dp"
+ android:layout_alignBottom="@id/channel_name"
+ android:layout_toEndOf="@id/channel_name"
+ android:textColor="@color/channel_banner_dim_text_color"
+ android:fontFamily="@string/condensed_font"
+ android:textSize="@dimen/channel_banner_small_text_size" />
+
+ <ProgressBar
+ android:id="@+id/remaining_time"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="60dp"
+ android:layout_height="4dp"
+ android:layout_alignBottom="@id/anchor"
+ android:layout_toEndOf="@id/program_time_text"
+ android:layout_marginEnd="12dp"
+ android:layout_marginBottom="0.5dp"
+ android:mirrorForRtl="false"
+ android:progressDrawable="@drawable/progress_horizontal"
+ android:indeterminate="false"
+ android:max="100" />
+
+ <include layout="@layout/program_track_meta"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignBottom="@id/anchor"
+ android:layout_toEndOf="@id/remaining_time" />
+
+ <TextView android:id="@+id/program_description"
+ android:layout_width="@dimen/channel_banner_program_description_width"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@id/channel_number"
+ android:layout_below="@id/anchor"
+ android:layout_marginTop="-9dp"
+ android:layout_marginBottom="12.5dp"
+ android:maxLines="3"
+ android:lineSpacingExtra="3sp"
+ android:ellipsize="end"
+ android:textColor="@color/channel_banner_dim_text_color"
+ android:textSize="12sp" />
+
+ </RelativeLayout>
</com.android.tv.ui.ChannelBannerView>
diff --git a/res/layout/channel_item.xml b/res/layout/channel_item.xml
deleted file mode 100644
index 855c0b9b..00000000
--- a/res/layout/channel_item.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="50dp"
- android:orientation="vertical"
- android:background="@color/option_item_background"
- android:focusable="true"
- android:clickable="true">
- <TextView
- android:id="@+id/channel_number"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:textColor="@color/side_panel_item_text"
- android:gravity="right|center_vertical"
- android:layout_margin="10dp" />
-</LinearLayout>
diff --git a/res/layout/channel_number.xml b/res/layout/channel_number.xml
deleted file mode 100644
index 1d075eac..00000000
--- a/res/layout/channel_number.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<com.android.tv.ui.ChannelNumberView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:lb="http://schemas.android.com/apk/res-auto"
- android:id="@+id/channel_number_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="invisible"
- android:background="@color/side_panel_background"
- android:orientation="vertical">
- <TextView
- android:id="@+id/typed_channel_number"
- android:layout_width="match_parent"
- android:layout_height="40dp"
- android:background="@color/side_panel_header_background"
- android:textColor="@color/menu_default_text_color"
- android:layout_gravity="center_vertical"
- android:gravity="right"
- android:padding="10dp" />
-
- <android.support.v17.leanback.widget.VerticalGridView
- android:id="@+id/channel_list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:focusable="true"
- android:paddingBottom="20dp"
- lb:verticalMargin="0dp" />
-</com.android.tv.ui.ChannelNumberView>
diff --git a/res/layout/channel_tile.xml b/res/layout/channel_tile.xml
deleted file mode 100644
index 842b2c92..00000000
--- a/res/layout/channel_tile.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<com.android.tv.ui.ChannelTileView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/channel_tile"
- android:layout_width="192dp"
- android:layout_height="match_parent"
- android:layoutMode="opticalBounds"
- android:clipChildren="false"
- android:focusable="true"
- android:clickable="true">
-
- <ImageView android:id="@+id/program_poster_art"
- android:layout_width="@dimen/channel_tile_poster_art_width"
- android:layout_height="@dimen/channel_tile_poster_art_height"
- android:layout_gravity="top" />
-
- <LinearLayout android:id="@+id/channel_infos"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:background="@drawable/channel_tile_top" >
-
- <FrameLayout android:id="@+id/channel"
- android:layout_width="match_parent"
- android:layout_height="90dp">
-
- <ImageView android:id="@+id/channel_logo"
- android:layout_width="88dp"
- android:layout_height="52dp"
- android:layout_gravity="center" />
-
- <TextView android:id="@+id/channel_name"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginLeft="12dp"
- android:layout_marginRight="12dp"
- android:gravity="center"
- android:singleLine="false"
- android:maxLines="2"
- android:ellipsize="end"
- android:fontFamily="@string/condensed_font"
- android:textColor="@color/channel_tile_text_color"
- android:textSize="18sp" />
-
- <TextView android:id="@+id/channel_number"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="left|bottom"
- android:layout_marginLeft="12dp"
- android:layout_marginBottom="8dp"
- android:fontFamily="@string/condensed_font"
- android:textColor="@color/channel_tile_text_color"
- android:textSize="12sp" />
- </FrameLayout>
-
- <TextView android:id="@+id/program_name"
- android:layout_width="match_parent"
- android:layout_height="38dp"
- android:gravity="center_vertical"
- android:paddingLeft="12dp"
- android:paddingRight="12dp"
- android:singleLine="true"
- android:ellipsize="end"
- android:fontFamily="@string/condensed_font"
- android:textColor="@color/channel_tile_text_color"
- android:textSize="12sp"
- android:background="@drawable/channel_tile_bottom" />
- </LinearLayout>
-</com.android.tv.ui.ChannelTileView>
diff --git a/res/layout/dialog_edit_input.xml b/res/layout/dialog_edit_input.xml
deleted file mode 100644
index 6e9fd5f3..00000000
--- a/res/layout/dialog_edit_input.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal" >
-
- <Spinner android:id="@+id/spinner"
- android:layout_weight="1"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:spinnerMode="dropdown" />
-
- <EditText android:id="@+id/input_edit_text"
- android:imeOptions="actionDone"
- android:inputType="text"
- android:layout_weight="1"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:fontFamily="sans-serif" />
-</LinearLayout>
diff --git a/res/layout/edit_channels_item.xml b/res/layout/edit_channels_item.xml
deleted file mode 100644
index 3dafa50e..00000000
--- a/res/layout/edit_channels_item.xml
+++ /dev/null
@@ -1,78 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<!-- A channel in the list. -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:focusable="true" >
-
- <LinearLayout
- android:id="@+id/item_container"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="@dimen/edit_channels_item_height"
- android:paddingLeft="@dimen/side_panel_padding_left"
- android:gravity="left|center_vertical" >
-
- <CheckBox
- android:id="@+id/check_box"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:gravity="left|center_vertical"
- android:paddingLeft="@dimen/option_item_padding_left"
- android:focusable="false"
- android:fontFamily="@string/font" />
-
- <!-- This is "gone" for now, since we don't have channel icons. -->
- <ImageView android:id="@+id/channel_image_view"
- android:layout_width="90dip"
- android:layout_height="match_parent"
- android:layout_weight="0"
- android:paddingLeft="10dip"
- android:paddingRight="0dip"
- android:visibility="gone" />
-
- <ImageView android:id="@+id/channel_source_view"
- android:layout_width="60dip"
- android:layout_height="match_parent"
- android:layout_weight="0"
- android:paddingLeft="10dip"
- android:paddingRight="0dip"
- android:scaleType="center"
- android:visibility="gone" />
-
- <TextView
- android:id="@+id/channel_text_view"
- android:layout_width="match_parent"
- android:layout_height="?android:attr/listPreferredItemHeight"
- android:gravity="center_vertical"
- android:paddingLeft="0dip"
- android:paddingRight="10dip"
- android:singleLine="true"
- android:ellipsize="end"
- android:marqueeRepeatLimit="1"
- android:textSize="@dimen/option_item_text_size"
- android:textColor="@color/side_panel_item_text" />
- </LinearLayout>
-
- <View
- android:id="@+id/divider"
- android:layout_width="match_parent"
- android:layout_height="1.5dp"
- android:background="@color/side_panel_divider" />
-</LinearLayout> \ No newline at end of file
diff --git a/res/layout/empty_info_banner.xml b/res/layout/empty_info_banner.xml
new file mode 100644
index 00000000..c4298661
--- /dev/null
+++ b/res/layout/empty_info_banner.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="start|top"
+ android:layout_marginTop="27dp"
+ android:elevation="8dp"
+ android:background="@drawable/info_banner_background" />
diff --git a/res/layout/input_banner.xml b/res/layout/input_banner.xml
new file mode 100644
index 00000000..cd8770c8
--- /dev/null
+++ b/res/layout/input_banner.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- All info banners have the same id for use by TvTransitionManager. -->
+<com.android.tv.ui.InputBannerView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/scene_transition_common"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/input_banner_item_height"
+ android:layout_gravity="start|top"
+ android:layout_marginStart="56dp"
+ android:layout_marginTop="27dp"
+ android:orientation="horizontal"
+ android:elevation="8dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:gravity="start|center"
+ android:background="@drawable/info_banner_background"
+ android:focusable="false" >
+
+ <TextView android:id="@+id/input_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@string/condensed_font"
+ android:textColor="@color/select_input_text_color_primary"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textSize="18sp" />
+
+ <TextView android:id="@+id/secondary_input_label"
+ android:layout_marginStart="16dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@string/condensed_font"
+ android:textColor="@color/select_input_text_color_secondary"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textSize="18sp" />
+</com.android.tv.ui.InputBannerView>
diff --git a/res/layout/intro_dialog.xml b/res/layout/intro_dialog.xml
new file mode 100644
index 00000000..4385a0ab
--- /dev/null
+++ b/res/layout/intro_dialog.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- id/intro is used by uitests -->
+<com.android.tv.ui.IntroView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/intro"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/setup_background">
+ <RelativeLayout
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="111dp"
+ android:paddingBottom="60dp">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentTop="true"
+ android:text="@string/intro_title"
+ android:fontFamily="@string/light_font"
+ android:textColor="@color/setup_title"
+ android:textSize="34sp" />
+ <TextView
+ android:id="@+id/setup_description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentBottom="true"
+ android:paddingTop="60dp"
+ android:text="@string/intro_description"
+ android:fontFamily="@string/font"
+ android:textColor="@color/setup_description"
+ android:textSize="14sp"/>
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_above="@id/setup_description">
+ <ImageView
+ android:layout_width="128dp"
+ android:layout_height="128dp"
+ android:src="@drawable/ic_welcome_remote" />
+ <View
+ android:id="@+id/welcome_ripple"
+ android:layout_width="51dp"
+ android:layout_height="51dp"
+ android:layout_gravity="center"
+ android:background="@drawable/ic_welcome_ripple_anim" />
+ </FrameLayout>
+ </RelativeLayout>
+</com.android.tv.ui.IntroView>
diff --git a/res/layout/item_list.xml b/res/layout/item_list.xml
index 33ec315a..f9492576 100644
--- a/res/layout/item_list.xml
+++ b/res/layout/item_list.xml
@@ -1,20 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+ ~ 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.
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<com.android.tv.ui.ItemListView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.tv.menu.ItemListRowView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:lb="http://schemas.android.com/apk/res-auto"
android:id="@+id/item_list"
android:layout_width="match_parent"
@@ -25,26 +26,45 @@
android:focusable="true"
android:descendantFocusability="afterDescendants">
+ <!-- Although the text leading can be changed accroding to the text size,
+ we decided to set fixed layout_height since it doesn't make a big difference,
+ and it makes code more simpler. -->
<TextView
android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="32dp"
- android:layout_gravity="left|bottom"
- android:gravity="left|top"
- android:singleLine="true"
- android:maxWidth="600dp"
- android:ellipsize="end"
- android:fontFamily="sans-serif"
- android:textColor="@color/unfocused_list_title"
- android:textSize="@dimen/unfocused_list_title_text_size" />
+ android:layout_width="@dimen/menu_row_title_width"
+ android:layout_height="@dimen/menu_row_title_height"
+ android:layout_gravity="start"
+ android:paddingBottom="@dimen/menu_row_title_padding_bottom"
+ android:fontFamily="@string/condensed_font"
+ android:gravity="start|bottom"
+ android:textAlignment="viewStart"
+ android:textColor="@color/menu_row_title"
+ android:textSize="@dimen/menu_row_title_text_size_deselected"
+ android:transformPivotX="@dimen/menu_row_title_transform_pivot_x"
+ android:transformPivotY="@dimen/menu_row_title_transform_pivot_y" />
+
+ <!-- The animation of this view includes an alpha animation, while one of its child is scaled
+ to out of bounds of this view. This results in visual artifacts while animating, because
+ alpha animation only occurs within the bounds of the view.
+
+ To work around, this view takes up extra space up to its neighbors using negative margins,
+ and compensate the same space with padding.
+ The accurate layout height is set in MenuRowView.onBind(). -->
<android.support.v17.leanback.widget.HorizontalGridView
android:id="@+id/list_view"
android:layout_width="match_parent"
- android:layout_height="115dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/menu_list_margin_top"
+ android:layout_marginBottom="@dimen/menu_list_margin_bottom"
+ android:layout_marginStart="@dimen/menu_list_margin_start"
+ android:layout_marginEnd="@dimen/menu_list_margin_end"
+ android:paddingTop="@dimen/menu_list_padding_top"
+ android:paddingStart="@dimen/menu_list_padding_start"
+ android:paddingEnd="@dimen/menu_list_padding_end"
android:clipChildren="false"
android:clipToPadding="false"
android:focusable="true"
- lb:horizontalMargin="@dimen/inter_card_spacing" />
+ lb:horizontalMargin="@dimen/menu_inter_card_spacing" />
-</com.android.tv.ui.ItemListView>
+</com.android.tv.menu.ItemListRowView>
diff --git a/res/layout/keypad_channel_switch.xml b/res/layout/keypad_channel_switch.xml
new file mode 100644
index 00000000..abc3eaa1
--- /dev/null
+++ b/res/layout/keypad_channel_switch.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- NOTE: When the height of KeypadChannelSwitchView is changed,
+ please update @dimen/keypad_channel_switch_base_height, too. -->
+<!-- All info banners have the same id for use by TvTransitionManager. -->
+<com.android.tv.ui.KeypadChannelSwitchView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/scene_transition_common"
+ android:orientation="vertical"
+ android:layout_width="260dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|top"
+ android:layout_marginStart="56dp"
+ android:layout_marginTop="27dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp"
+ android:elevation="8dp"
+ android:background="@drawable/info_banner_background">
+
+ <TextView
+ android:id="@+id/channel_number"
+ android:layout_width="match_parent"
+ android:layout_height="76dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginTop="@dimen/keypad_channel_switch_channel_number_margin_top"
+ android:layout_marginBottom="6.5dp"
+ android:fontFamily="@string/thin_font"
+ android:textColor="@color/keypad_channel_switch_text_color"
+ android:textSize="@dimen/keypad_channel_switch_channel_number_text_size" />
+
+ <ListView
+ android:id="@+id/channel_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:listSelector="#33FFFFFF"
+ android:divider="@null"
+ android:dividerHeight="0dp"
+ android:clickable="true" />
+
+</com.android.tv.ui.KeypadChannelSwitchView>
diff --git a/res/layout/keypad_channel_switch_item.xml b/res/layout/keypad_channel_switch_item.xml
new file mode 100644
index 00000000..62036324
--- /dev/null
+++ b/res/layout/keypad_channel_switch_item.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/keypad_channel_switch_item_height"
+ android:orientation="horizontal"
+ android:background="?android:attr/selectableItemBackground" >
+
+ <TextView
+ android:id="@+id/number"
+ android:layout_width="64dp"
+ android:layout_height="match_parent"
+ android:layout_marginStart="20dp"
+ android:gravity="start|center_vertical"
+ android:fontFamily="@string/condensed_font"
+ android:textSize="18sp"
+ android:textColor="@color/keypad_channel_switch_text_color"
+ android:singleLine="true" />
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:layout_marginEnd="16dp"
+ android:gravity="start|center_vertical"
+ android:fontFamily="@string/condensed_font"
+ android:textSize="18sp"
+ android:textColor="@color/keypad_channel_switch_text_color"
+ android:singleLine="true"
+ android:ellipsize="end" />
+
+</LinearLayout>
diff --git a/res/layout/keypad_view.xml b/res/layout/keypad_view.xml
deleted file mode 100644
index 96705acf..00000000
--- a/res/layout/keypad_view.xml
+++ /dev/null
@@ -1,216 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android" >
- <ImageView
- android:id="@id/key_red"
- android:layout_marginLeft="-2.5dp"
- android:layout_marginRight="-2.5dp"
- android:nextFocusForward="@id/key_green"
- android:nextFocusUp="@id/key_red"
- android:nextFocusLeft="@id/key_red"
- android:nextFocusDown="@id/key_1"
- android:nextFocusRight="@id/key_green"
- android:contentDescription="@string/key_red"
- android:src="@drawable/key_red"
- style="@style/keypad_key_style" />
- <ImageView
- android:id="@id/key_green"
- android:layout_marginLeft="-2.5dp"
- android:layout_marginRight="-2.5dp"
- android:layout_alignTop="@id/key_red"
- android:layout_toRightOf="@id/key_red"
- android:nextFocusForward="@id/key_yellow"
- android:nextFocusUp="@id/key_green"
- android:nextFocusLeft="@id/key_red"
- android:nextFocusDown="@id/key_2"
- android:nextFocusRight="@id/key_yellow"
- android:contentDescription="@string/key_green"
- android:src="@drawable/key_green"
- style="@style/keypad_key_style" />
- <ImageView
- android:id="@id/key_yellow"
- android:layout_marginLeft="-2.5dp"
- android:layout_marginRight="-2.5dp"
- android:layout_alignTop="@id/key_red"
- android:layout_toRightOf="@id/key_green"
- android:nextFocusForward="@id/key_blue"
- android:nextFocusUp="@id/key_yellow"
- android:nextFocusLeft="@id/key_green"
- android:nextFocusDown="@id/key_2"
- android:nextFocusRight="@id/key_blue"
- android:contentDescription="@string/key_yellow"
- android:src="@drawable/key_yellow"
- style="@style/keypad_key_style" />
- <ImageView
- android:id="@id/key_blue"
- android:layout_marginLeft="-2.5dp"
- android:layout_marginRight="-2.5dp"
- android:layout_alignTop="@id/key_red"
- android:layout_toRightOf="@id/key_yellow"
- android:nextFocusForward="@id/key_1"
- android:nextFocusUp="@id/key_blue"
- android:nextFocusLeft="@id/key_yellow"
- android:nextFocusDown="@id/key_3"
- android:nextFocusRight="@id/key_blue"
- android:contentDescription="@string/key_blue"
- android:src="@drawable/key_blue"
- style="@style/keypad_key_style" />
-
- <TextView
- android:id="@id/key_1"
- android:layout_marginTop="4dp"
- android:layout_below="@id/key_red"
- android:nextFocusForward="@id/key_2"
- android:nextFocusUp="@id/key_red"
- android:nextFocusLeft="@id/key_1"
- android:nextFocusDown="@id/key_4"
- android:nextFocusRight="@id/key_2"
- android:text="@string/key_1"
- style="@style/keypad_key_style" />
- <TextView
- android:id="@id/key_2"
- android:layout_marginLeft="8dp"
- android:layout_alignTop="@id/key_1"
- android:layout_toRightOf="@id/key_1"
- android:nextFocusForward="@id/key_3"
- android:nextFocusUp="@id/key_green"
- android:nextFocusLeft="@id/key_1"
- android:nextFocusDown="@id/key_5"
- android:nextFocusRight="@id/key_3"
- android:text="@string/key_2"
- style="@style/keypad_key_style" />
- <TextView
- android:id="@id/key_3"
- android:layout_marginLeft="8dp"
- android:layout_alignTop="@id/key_1"
- android:layout_toRightOf="@id/key_2"
- android:nextFocusForward="@id/key_4"
- android:nextFocusUp="@id/key_blue"
- android:nextFocusLeft="@id/key_2"
- android:nextFocusDown="@id/key_6"
- android:nextFocusRight="@id/key_3"
- android:text="@string/key_3"
- style="@style/keypad_key_style" />
-
- <TextView
- android:id="@id/key_4"
- android:layout_marginTop="4dp"
- android:layout_below="@id/key_1"
- android:nextFocusForward="@id/key_5"
- android:nextFocusUp="@id/key_1"
- android:nextFocusLeft="@id/key_4"
- android:nextFocusDown="@id/key_7"
- android:nextFocusRight="@id/key_5"
- android:text="@string/key_4"
- style="@style/keypad_key_style" />
- <TextView
- android:id="@id/key_5"
- android:layout_marginLeft="8dp"
- android:layout_alignTop="@id/key_4"
- android:layout_toRightOf="@id/key_4"
- android:nextFocusForward="@id/key_6"
- android:nextFocusUp="@id/key_2"
- android:nextFocusLeft="@id/key_4"
- android:nextFocusDown="@id/key_8"
- android:nextFocusRight="@id/key_6"
- android:text="@string/key_5"
- style="@style/keypad_key_style" />
- <TextView
- android:id="@id/key_6"
- android:layout_marginLeft="8dp"
- android:layout_alignTop="@id/key_4"
- android:layout_toRightOf="@id/key_5"
- android:nextFocusForward="@id/key_7"
- android:nextFocusUp="@id/key_3"
- android:nextFocusLeft="@id/key_5"
- android:nextFocusDown="@id/key_9"
- android:nextFocusRight="@id/key_6"
- android:text="@string/key_6"
- style="@style/keypad_key_style" />
-
- <TextView
- android:id="@id/key_7"
- android:layout_marginTop="4dp"
- android:layout_below="@id/key_4"
- android:nextFocusForward="@id/key_8"
- android:nextFocusUp="@id/key_4"
- android:nextFocusLeft="@id/key_7"
- android:nextFocusDown="@id/key_l"
- android:nextFocusRight="@id/key_8"
- android:text="@string/key_7"
- style="@style/keypad_key_style" />
- <TextView
- android:id="@id/key_8"
- android:layout_marginLeft="8dp"
- android:layout_alignTop="@id/key_7"
- android:layout_toRightOf="@id/key_7"
- android:nextFocusForward="@id/key_9"
- android:nextFocusUp="@id/key_5"
- android:nextFocusLeft="@id/key_7"
- android:nextFocusDown="@id/key_0"
- android:nextFocusRight="@id/key_9"
- android:text="@string/key_8"
- style="@style/keypad_key_style" />
- <TextView
- android:id="@id/key_9"
- android:layout_marginLeft="8dp"
- android:layout_alignTop="@id/key_7"
- android:layout_toRightOf="@id/key_8"
- android:nextFocusForward="@id/key_l"
- android:nextFocusUp="@id/key_6"
- android:nextFocusLeft="@id/key_8"
- android:nextFocusDown="@id/key_r"
- android:nextFocusRight="@id/key_9"
- android:text="@string/key_9"
- style="@style/keypad_key_style" />
-
- <TextView
- android:id="@id/key_l"
- android:layout_marginTop="4dp"
- android:layout_below="@id/key_7"
- android:nextFocusForward="@id/key_0"
- android:nextFocusUp="@id/key_7"
- android:nextFocusLeft="@id/key_l"
- android:nextFocusDown="@id/key_l"
- android:nextFocusRight="@id/key_0"
- android:text="@string/key_l"
- style="@style/keypad_key_style" />
- <TextView
- android:id="@id/key_0"
- android:layout_marginLeft="8dp"
- android:layout_alignTop="@id/key_l"
- android:layout_toRightOf="@id/key_l"
- android:nextFocusForward="@id/key_r"
- android:nextFocusUp="@id/key_8"
- android:nextFocusLeft="@id/key_l"
- android:nextFocusDown="@id/key_0"
- android:nextFocusRight="@id/key_r"
- android:text="@string/key_0"
- style="@style/keypad_key_style" />
- <TextView
- android:id="@id/key_r"
- android:layout_marginLeft="8dp"
- android:layout_alignTop="@id/key_l"
- android:layout_toRightOf="@id/key_0"
- android:nextFocusForward="@id/key_red"
- android:nextFocusUp="@id/key_9"
- android:nextFocusLeft="@id/key_0"
- android:nextFocusDown="@id/key_r"
- android:nextFocusRight="@id/key_r"
- android:text="@string/key_r"
- style="@style/keypad_key_style" />
-</merge> \ No newline at end of file
diff --git a/res/layout/list_item_watched_program.xml b/res/layout/list_item_watched_program.xml
index 73d155ad..72955bc4 100644
--- a/res/layout/list_item_watched_program.xml
+++ b/res/layout/list_item_watched_program.xml
@@ -1,18 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
@@ -24,24 +25,35 @@
android:id="@+id/watched_program_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="2dp" />
+ android:paddingStart="2dp"
+ android:paddingEnd="0dp" />
<TextView
android:id="@+id/watched_program_channel_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="4dp" />
+ android:paddingStart="4dp"
+ android:paddingEnd="0dp" />
+
+ <TextView
+ android:id="@+id/watched_program_channel_display_number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingStart="4dp"
+ android:paddingEnd="0dp" />
<TextView
android:id="@+id/watched_program_watch_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="4dp" />
+ android:paddingStart="4dp"
+ android:paddingEnd="0dp" />
<TextView
android:id="@+id/watched_program_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="4dp" />
+ android:paddingStart="4dp"
+ android:paddingEnd="0dp" />
</LinearLayout>
diff --git a/res/layout/main_menu.xml b/res/layout/main_menu.xml
deleted file mode 100644
index 799bd595..00000000
--- a/res/layout/main_menu.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<com.android.tv.ui.MainMenuView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:lb="http://schemas.android.com/apk/res-auto"
- android:id="@+id/main_menu"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:background="@drawable/bg_protection">
-
- <android.support.v17.leanback.widget.VerticalGridView
- android:id="@+id/menu_list"
- android:layout_width="match_parent"
- android:layout_height="318dp"
- android:layout_gravity="bottom"
- android:paddingLeft="56dp"
- android:paddingRight="56dp"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:focusable="true" />
-
-</com.android.tv.ui.MainMenuView>
diff --git a/res/layout/menu.xml b/res/layout/menu.xml
new file mode 100644
index 00000000..8882f9de
--- /dev/null
+++ b/res/layout/menu.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- marginTop is given to ignore the translation of the dim background for
+ menu_enter animation.xml. If the margin is not given, y-translation of dim's boundary is
+ too clearly shown. -->
+<com.android.tv.menu.MenuView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/menu"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/menu_enter_offset_y_negative"
+ android:paddingBottom="@dimen/menu_padding_bottom"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:background="@drawable/menu_background"
+ android:visibility="gone">
+
+ <android.support.v17.leanback.widget.VerticalGridView
+ android:id="@+id/menu_list"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/menu_height"
+ android:layout_gravity="bottom"
+ android:paddingStart="@dimen/menu_padding_start"
+ android:paddingEnd="@dimen/menu_padding_end"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:focusable="true" />
+
+</com.android.tv.menu.MenuView>
diff --git a/res/layout/menu_card_action.xml b/res/layout/menu_card_action.xml
new file mode 100644
index 00000000..f17c5b6a
--- /dev/null
+++ b/res/layout/menu_card_action.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.tv.menu.ActionCardView
+ 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">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="@dimen/action_card_padding"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layout_gravity="top">
+
+ <TextView
+ android:id="@+id/action_card_label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-6dp"
+ android:singleLine="true"
+ android:fontFamily="@string/condensed_font"
+ android:textColor="@color/action_card_label_color"
+ android:textSize="@dimen/action_card_label_font_size" />
+
+ <TextView
+ android:id="@+id/action_card_state"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="4dp"
+ android:singleLine="true"
+ android:fontFamily="@string/condensed_font"
+ android:textColor="@color/action_card_state_color"
+ android:textSize="@dimen/action_card_state_font_size" />
+
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/action_card_icon"
+ android:layout_width="@dimen/action_card_icon_width"
+ android:layout_height="@dimen/action_card_icon_height"
+ android:layout_margin="@dimen/action_card_icon_padding"
+ android:layout_gravity="end|bottom"
+ android:contentDescription="@null"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_tvoption_aspect"
+ android:duplicateParentState="true" />
+
+</com.android.tv.menu.ActionCardView>
diff --git a/res/layout/menu_card_app_link.xml b/res/layout/menu_card_app_link.xml
new file mode 100644
index 00000000..631b5fbb
--- /dev/null
+++ b/res/layout/menu_card_app_link.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.tv.menu.AppLinkCardView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/card_layout_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:focusable="true"
+ android:clickable="true">
+
+ <FrameLayout
+ android:layout_width="@dimen/card_image_layout_width"
+ android:layout_height="@dimen/card_image_layout_height">
+
+ <ImageView android:id="@+id/image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="top"
+ android:scaleType="centerCrop"/>
+
+ <View android:id="@+id/image_gradient"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal"
+ android:background="@drawable/card_image_gradient"
+ android:visibility="gone" />
+
+ <TextView android:id="@+id/app_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|bottom"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginBottom="5dp"
+ android:fontFamily="@string/condensed_font"
+ android:textColor="@color/card_meta_text_color_enabled"
+ android:textSize="12sp" />
+
+ </FrameLayout>
+
+ <FrameLayout android:id="@+id/app_link_text_holder"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView android:id="@+id/app_link_text_focused"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/card_meta_layout_height"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:paddingTop="@dimen/card_meta_padding_top"
+ android:ellipsize="end"
+ android:fontFamily="@string/condensed_font"
+ android:maxLines="2"
+ android:textColor="@color/card_meta_text_color"
+ android:textSize="12sp" />
+
+ <TextView android:id="@+id/app_link_text_unfocused"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/card_meta_layout_height"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:paddingTop="@dimen/card_meta_padding_top"
+ android:ellipsize="end"
+ android:fontFamily="@string/condensed_font"
+ android:singleLine="true"
+ android:textColor="@color/card_meta_text_color"
+ android:textSize="12sp" />
+
+ </FrameLayout>
+
+</com.android.tv.menu.AppLinkCardView>
diff --git a/res/layout/menu_card_channel.xml b/res/layout/menu_card_channel.xml
new file mode 100644
index 00000000..68d2850c
--- /dev/null
+++ b/res/layout/menu_card_channel.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.tv.menu.ChannelCardView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/card_layout_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:focusable="true"
+ android:clickable="true">
+
+ <FrameLayout
+ android:layout_width="@dimen/card_image_layout_width"
+ android:layout_height="@dimen/card_image_layout_height">
+
+ <ImageView android:id="@+id/image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="top"
+ android:scaleType="centerCrop"/>
+
+ <View android:id="@+id/image_gradient"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal"
+ android:background="@drawable/card_image_gradient"
+ android:visibility="gone" />
+
+ <!-- The bottom margin specified in the redline is 8dp, but the redline doesn't consider
+ the descenders. So actully, if the bottom margin is set to 8dp, the bottom line of
+ the text lies 11dp above the bottom of the poster art due to the descenders.
+ Therefore the bottom margin needs to be set to 5dp(=8dp-3dp) here.
+ If the text size or font is changed, the bottom margin needs to be changed. -->
+ <TextView android:id="@+id/channel_number_and_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|bottom"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginBottom="5dp"
+ android:fontFamily="@string/condensed_font"
+ android:textColor="@color/card_meta_text_color_enabled"
+ android:textSize="12sp" />
+
+ <ProgressBar android:id="@+id/progress"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/channel_card_progress_bar_height"
+ android:layout_gravity="bottom"
+ android:mirrorForRtl="false"
+ android:progressDrawable="@drawable/channel_card_progress_bar"
+ android:indeterminate="false"
+ android:max="100"
+ android:visibility="gone" />
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/channel_card_meta_background">
+
+ <TextView android:id="@+id/channel_title_focused"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/card_meta_layout_height"
+ android:paddingStart="@dimen/card_meta_padding_start"
+ android:paddingEnd="@dimen/card_meta_padding_end"
+ android:paddingTop="@dimen/card_meta_padding_top"
+ android:ellipsize="end"
+ android:fontFamily="@string/condensed_font"
+ android:maxLines="2"
+ android:textColor="@color/card_meta_text_color"
+ android:textSize="12sp" />
+
+ <TextView android:id="@+id/channel_title_unfocused"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/card_meta_layout_height"
+ android:paddingStart="@dimen/card_meta_padding_start"
+ android:paddingEnd="@dimen/card_meta_padding_end"
+ android:paddingTop="@dimen/card_meta_padding_top"
+ android:ellipsize="end"
+ android:fontFamily="@string/condensed_font"
+ android:singleLine="true"
+ android:textColor="@color/card_meta_text_color"
+ android:textSize="12sp" />
+
+ </FrameLayout>
+
+</com.android.tv.menu.ChannelCardView>
diff --git a/res/layout/menu_card_guide.xml b/res/layout/menu_card_guide.xml
new file mode 100644
index 00000000..dcc80931
--- /dev/null
+++ b/res/layout/menu_card_guide.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.tv.menu.GuideCardView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/card_layout_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:elevation="@dimen/card_elevation_normal"
+ android:focusable="true"
+ android:clickable="true">
+
+ <FrameLayout
+ android:layout_width="@dimen/card_image_layout_width"
+ android:layout_height="@dimen/card_image_layout_height"
+ android:background="@color/channel_card_guide">
+ <ImageView
+ android:layout_width="82dp"
+ android:layout_height="48dp"
+ android:layout_marginTop="16dp"
+ android:src="@drawable/ic_channel_guide"
+ android:layout_gravity="center_horizontal" />
+ </FrameLayout>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/card_meta_layout_height"
+ android:paddingStart="@dimen/card_meta_padding_start"
+ android:paddingEnd="@dimen/card_meta_padding_end"
+ android:paddingTop="@dimen/card_meta_padding_top"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:fontFamily="@string/condensed_font"
+ android:textColor="@color/card_meta_text_color"
+ android:background="@color/guide_card_meta_background"
+ android:text="@string/channels_item_program_guide"
+ android:textSize="12sp" />
+
+</com.android.tv.menu.GuideCardView>
diff --git a/res/layout/menu_card_setup.xml b/res/layout/menu_card_setup.xml
new file mode 100644
index 00000000..b495e7ea
--- /dev/null
+++ b/res/layout/menu_card_setup.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.tv.menu.SetupCardView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/card_layout_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:focusable="true"
+ android:clickable="true">
+
+ <FrameLayout
+ android:layout_width="@dimen/card_image_layout_width"
+ android:layout_height="@dimen/card_image_layout_height"
+ android:background="@color/channel_card_guide">
+ <ImageView
+ android:layout_width="82dp"
+ android:layout_height="48dp"
+ android:layout_marginTop="16dp"
+ android:src="@drawable/ic_setup_channels"
+ android:layout_gravity="center_horizontal" />
+ </FrameLayout>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/card_meta_layout_height"
+ android:paddingStart="@dimen/card_meta_padding_start"
+ android:paddingEnd="@dimen/card_meta_padding_end"
+ android:paddingTop="@dimen/card_meta_padding_top"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:fontFamily="@string/condensed_font"
+ android:textColor="@color/card_meta_text_color"
+ android:background="@color/guide_card_meta_background"
+ android:text="@string/channels_item_setup"
+ android:textSize="12sp" />
+
+</com.android.tv.menu.SetupCardView>
diff --git a/res/layout/option_container.xml b/res/layout/option_container.xml
new file mode 100644
index 00000000..f053e660
--- /dev/null
+++ b/res/layout/option_container.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/side_panel"
+ android:layout_width="@dimen/side_panel_width"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:elevation="@dimen/side_panel_elevation"
+ android:background="@color/side_panel_background"
+ android:visibility="gone">
+
+ <!-- Background for header -->
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/side_panel_header_height"
+ android:elevation="@dimen/side_panel_header_elevation"
+ android:background="@color/side_panel_header_background" />
+
+ <!-- This fragment container needs to have at least the same elevation as the above FrameLayout
+ for the header background. Otherwise, header text in the fragment won't be drawn, as it's
+ under the above FrameLayout. This container won't draw any shadow though, as it have
+ transparent background. -->
+ <FrameLayout
+ android:id="@+id/side_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:elevation="@dimen/side_panel_header_elevation" />
+
+</FrameLayout>
diff --git a/res/layout/option_fragment.xml b/res/layout/option_fragment.xml
index 00d3f4db..39f67497 100644
--- a/res/layout/option_fragment.xml
+++ b/res/layout/option_fragment.xml
@@ -1,67 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+ ~ 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.
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:lb="http://schemas.android.com/apk/res-auto"
- android:layout_width="@dimen/side_panel_shadow_width"
+ android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_gravity="right" >
+ android:orientation="vertical">
- <FrameLayout
- android:id="@+id/side_panel_shadow"
+ <TextView
+ android:id="@+id/side_panel_title"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginRight="@dimen/side_panel_shadow_margin_right"
- android:background="@drawable/bg_protection_right" />
+ android:layout_height="@dimen/side_panel_header_height"
+ android:paddingTop="@dimen/side_panel_header_padding_top"
+ android:paddingStart="@dimen/side_panel_padding_start"
+ android:paddingEnd="@dimen/side_panel_padding_end"
+ android:gravity="center_vertical"
+ android:fontFamily="@string/condensed_font"
+ android:textSize="@dimen/side_panel_header_text_size"
+ android:textColor="@color/option_item_text_color"
+ android:singleLine="true" />
- <LinearLayout
- android:layout_gravity="right"
- android:layout_width="@dimen/side_panel_width"
+ <android.support.v17.leanback.widget.VerticalGridView
+ android:id="@+id/side_panel_list"
+ android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@color/side_panel_background" >
- <LinearLayout
- android:layout_gravity="right"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="@dimen/side_panel_header_padding_top"
- android:paddingLeft="@dimen/side_panel_padding_left"
- android:paddingRight="@dimen/side_panel_padding_right"
- android:orientation="vertical"
- android:background="@color/side_panel_header_background" >
- <TextView
- android:id="@+id/side_panel_title"
- android:layout_width="match_parent"
- android:layout_height="@dimen/side_panel_header_text_height"
- android:gravity="center_vertical"
- android:textSize="@dimen/side_panel_header_text_size"
- android:textColor="@color/menu_default_text_color"
- android:singleLine="true"
- android:fontFamily="@string/condensed_font" />
- </LinearLayout>
+ 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" />
- <android.support.v17.leanback.widget.VerticalGridView
- android:id="@+id/side_panel_list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:focusable="true"
- android:paddingTop="48dp"
- android:paddingBottom="48dp"
- lb:verticalMargin="0dp" />
- </LinearLayout>
-</FrameLayout>
+</LinearLayout>
diff --git a/res/layout/option_item.xml b/res/layout/option_item.xml
deleted file mode 100644
index 2298bd16..00000000
--- a/res/layout/option_item.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/option_item_height"
- android:paddingRight="@dimen/side_panel_padding_right"
- android:paddingLeft="@dimen/side_panel_padding_left"
- android:focusable="true"
- android:orientation="horizontal" >
-
- <RadioButton
- android:id="@+id/option_item"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:gravity="left|center_vertical"
- android:paddingLeft="@dimen/option_item_padding_left"
- android:textSize="@dimen/option_item_text_size"
- android:textColor="@color/side_panel_item_text"
- android:singleLine="true"
- android:focusable="false"
- android:fontFamily="@string/font" />
-</LinearLayout>
diff --git a/res/layout/option_item_action.xml b/res/layout/option_item_action.xml
index d2345df7..a7a02b2d 100644
--- a/res/layout/option_item_action.xml
+++ b/res/layout/option_item_action.xml
@@ -1,36 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
- android:layout_height="@dimen/option_item_height"
- android:paddingLeft="@dimen/side_panel_padding_left"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/side_panel_padding_start"
+ android:paddingEnd="@dimen/side_panel_padding_end"
android:focusable="true"
- android:focusableInTouchMode="true"
- android:background="@drawable/debug_option_background" >
+ android:background="?android:attr/selectableItemBackground" >
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="36dp"
+ android:layout_height="36dp"
+ android:layout_weight="0"
+ android:layout_marginEnd="20dp"
+ android:layout_marginTop="14dp"
+ android:layout_marginBottom="14dp"
+ android:contentDescription="@null" />
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
+ <include layout="@layout/option_item_common"
+ android:layout_width="0dp"
android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:clickable="false"
- android:fontFamily="@string/font"
- android:textSize="@dimen/option_item_text_size"
- android:textColor="@color/side_panel_item_text" />
+ android:layout_weight="1" />
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/res/layout/option_item_attribution.xml b/res/layout/option_item_attribution.xml
new file mode 100644
index 00000000..3d277268
--- /dev/null
+++ b/res/layout/option_item_attribution.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/option_item_height"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground" >
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:paddingStart="@dimen/side_panel_padding_start"
+ android:paddingEnd="@dimen/side_panel_padding_end"
+ android:fontFamily="@string/option_item_text_font"
+ android:textSize="@dimen/option_item_secondary_text_size"
+ android:textColor="@color/option_item_secondary_text_color_enabled"
+ android:lineSpacingExtra="@dimen/option_item_secondary_text_spacing"
+ android:singleLine="true"
+ android:text="@string/option_attribution" />
+
+</RelativeLayout>
diff --git a/res/layout/option_item_channel_check.xml b/res/layout/option_item_channel_check.xml
new file mode 100644
index 00000000..2817d2b9
--- /dev/null
+++ b/res/layout/option_item_channel_check.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/side_panel_padding_start"
+ android:paddingEnd="@dimen/side_panel_padding_end"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground" >
+
+ <CheckBox
+ 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" />
+
+ <include layout="@layout/option_item_channel_common"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+
+</LinearLayout>
diff --git a/res/layout/option_item_channel_common.xml b/res/layout/option_item_channel_common.xml
new file mode 100644
index 00000000..bf5d554c
--- /dev/null
+++ b/res/layout/option_item_channel_common.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:minHeight="@dimen/option_item_height"
+ android:paddingTop="@dimen/option_item_common_padding_top"
+ android:paddingBottom="@dimen/option_item_common_padding_bottom"
+ android:gravity="center_vertical"
+ android:focusable="false" >
+
+ <TextView
+ android:id="@+id/channel_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fontFamily="@string/option_item_text_font"
+ android:textSize="@dimen/option_item_text_size"
+ android:textColor="@color/option_item_text_color"
+ android:singleLine="true"
+ android:focusable="false" />
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/option_item_text_margin_top"
+ android:focusable="false" >
+
+ <TextView
+ android:id="@+id/channel_number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@string/option_item_secondary_text_font"
+ android:textSize="@dimen/option_item_secondary_text_size"
+ android:textColor="@color/option_item_text_color"
+ android:singleLine="true"
+ android:focusable="false" />
+
+ <TextView
+ android:id="@+id/program_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/option_item_channel_check_spacing"
+ 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:singleLine="true"
+ android:focusable="false" />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/option_item_channel_lock.xml b/res/layout/option_item_channel_lock.xml
new file mode 100644
index 00000000..f4f65f99
--- /dev/null
+++ b/res/layout/option_item_channel_lock.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/side_panel_padding_start"
+ android:paddingEnd="@dimen/side_panel_padding_end"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground" >
+
+ <CheckBox
+ 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" />
+
+ <include layout="@layout/option_item_channel_common"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+
+</LinearLayout>
diff --git a/res/layout/option_item_check_box.xml b/res/layout/option_item_check_box.xml
index 4d04279f..1ab2ba45 100644
--- a/res/layout/option_item_check_box.xml
+++ b/res/layout/option_item_check_box.xml
@@ -1,36 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
- android:layout_height="@dimen/option_item_height"
- android:paddingLeft="@dimen/side_panel_padding_left"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/side_panel_padding_start"
+ android:paddingEnd="@dimen/side_panel_padding_end"
android:focusable="true"
- android:focusableInTouchMode="true"
- android:background="@drawable/debug_option_background" >
+ android:background="?android:attr/selectableItemBackground" >
<CheckBox
android:id="@+id/check_box"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingLeft="@dimen/option_item_padding_left"
- android:clickable="false"
- android:fontFamily="@string/font"
- android:textSize="@dimen/option_item_text_size"
- android:textColor="@color/side_panel_item_text" />
+ 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" />
+
+ <include layout="@layout/option_item_common"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/res/layout/option_item_common.xml b/res/layout/option_item_common.xml
new file mode 100644
index 00000000..ca0013c6
--- /dev/null
+++ b/res/layout/option_item_common.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/option_item_height"
+ android:paddingTop="@dimen/option_item_common_padding_top"
+ android:paddingBottom="@dimen/option_item_common_padding_bottom"
+ android:gravity="center_vertical"
+ android:focusable="false" >
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fontFamily="@string/option_item_text_font"
+ android:textSize="@dimen/option_item_text_size"
+ android:textColor="@color/option_item_text_color"
+ android:singleLine="true"
+ android:focusable="false" />
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/option_item_text_margin_top"
+ 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:ellipsize="end"
+ android:focusable="false" />
+
+</LinearLayout>
diff --git a/res/layout/option_item_divider.xml b/res/layout/option_item_divider.xml
index 32177f8d..a8463cb5 100644
--- a/res/layout/option_item_divider.xml
+++ b/res/layout/option_item_divider.xml
@@ -1,22 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+ ~ 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.
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:focusable="false"
+ android:background="?android:attr/selectableItemBackground" >
- http://www.apache.org/licenses/LICENSE-2.0
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/option_item_divider_height"
+ android:layout_marginTop="@dimen/option_item_divider_margin_top"
+ android:layout_marginBottom="@dimen/option_item_divider_margin_bottom"
+ android:layout_alignParentTop="true"
+ android:background="@color/option_item_divider_color" />
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/option_item_divider_text_margin_top"
+ android:layout_alignParentStart="true"
+ android:paddingStart="@dimen/side_panel_padding_start"
+ android:paddingEnd="@dimen/side_panel_padding_end"
+ android:fontFamily="@string/option_item_divider_font"
+ android:textSize="@dimen/option_item_divider_text_size"
+ android:textColor="@color/option_item_divider_text_color" />
-<View xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="1.5dp"
- android:focusable="false"
- android:focusableInTouchMode="false"
- android:background="@color/side_panel_divider" /> \ No newline at end of file
+</RelativeLayout>
diff --git a/res/layout/option_item_radio_button.xml b/res/layout/option_item_radio_button.xml
index 6e6a05a4..2e4fa11a 100644
--- a/res/layout/option_item_radio_button.xml
+++ b/res/layout/option_item_radio_button.xml
@@ -1,36 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
- android:layout_height="@dimen/option_item_height"
- android:paddingLeft="@dimen/side_panel_padding_left"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/side_panel_padding_start"
+ android:paddingEnd="@dimen/side_panel_padding_end"
android:focusable="true"
- android:focusableInTouchMode="true"
- android:background="@drawable/debug_option_background" >
+ android:background="?android:attr/selectableItemBackground" >
<RadioButton
android:id="@+id/radio_button"
- android:layout_width="match_parent"
+ android:layout_width="@dimen/option_item_compound_button_width"
+ android:layout_height="match_parent"
+ android:layout_weight="0"
+ android:background="@null"
+ android:clickable="false" />
+
+ <include layout="@layout/option_item_common"
+ android:layout_width="0dp"
android:layout_height="match_parent"
- android:paddingLeft="@dimen/option_item_padding_left"
- android:clickable="false"
- android:fontFamily="@string/font"
- android:textSize="@dimen/option_item_text_size"
- android:textColor="@color/side_panel_item_text" />
+ android:layout_weight="1" />
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/res/layout/option_item_rating.xml b/res/layout/option_item_rating.xml
new file mode 100644
index 00000000..156abdf6
--- /dev/null
+++ b/res/layout/option_item_rating.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/side_panel_padding_start"
+ android:paddingEnd="@dimen/side_panel_padding_end"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground" >
+
+ <CheckBox
+ 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_marginTop="@dimen/option_item_compound_button_padding_top"
+ android:button="@drawable/btn_lock_material_anim"
+ android:background="@null"
+ android:clickable="false" />
+
+ <RelativeLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:minHeight="@dimen/option_item_height"
+ android:paddingTop="@dimen/option_item_common_padding_top"
+ android:paddingBottom="@dimen/option_item_common_padding_bottom"
+ android:gravity="center_vertical"
+ android:focusable="false" >
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="16dp"
+ android:layout_height="16dp"
+ android:layout_marginTop="3.5dp"
+ android:layout_marginEnd="7dp"
+ android:contentDescription="@null"
+ android:visibility="gone" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@+id/icon"
+ android:fontFamily="@string/option_item_text_font"
+ android:textSize="@dimen/option_item_text_size"
+ android:textColor="@color/option_item_text_color"
+ android:singleLine="true"
+ android:focusable="false" />
+
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/option_item_text_margin_top"
+ android:layout_below="@+id/title"
+ android:fontFamily="@string/option_item_secondary_text_font"
+ android:textSize="@dimen/option_item_secondary_text_size"
+ android:lineSpacingExtra="@dimen/option_item_secondary_text_spacing"
+ android:textColor="@color/option_item_secondary_text_color"
+ android:maxLines="3"
+ android:focusable="false" />
+
+ </RelativeLayout>
+</LinearLayout>
diff --git a/res/layout/option_item_simple.xml b/res/layout/option_item_simple.xml
new file mode 100644
index 00000000..5dc6b8cf
--- /dev/null
+++ b/res/layout/option_item_simple.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- Non focusable item -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/side_panel_padding_start"
+ android:paddingEnd="@dimen/side_panel_padding_end"
+ android:focusable="false"
+ android:background="?android:attr/selectableItemBackground" >
+
+ <include layout="@layout/option_item_common"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+
+</LinearLayout>
diff --git a/res/layout/option_item_sub_menu.xml b/res/layout/option_item_sub_menu.xml
deleted file mode 100644
index d2345df7..00000000
--- a/res/layout/option_item_sub_menu.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="@dimen/option_item_height"
- android:paddingLeft="@dimen/side_panel_padding_left"
- android:focusable="true"
- android:focusableInTouchMode="true"
- android:background="@drawable/debug_option_background" >
-
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:clickable="false"
- android:fontFamily="@string/font"
- android:textSize="@dimen/option_item_text_size"
- android:textColor="@color/side_panel_item_text" />
-
-</LinearLayout> \ No newline at end of file
diff --git a/res/layout/option_item_switch.xml b/res/layout/option_item_switch.xml
new file mode 100644
index 00000000..96f890c4
--- /dev/null
+++ b/res/layout/option_item_switch.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/side_panel_padding_start"
+ android:paddingEnd="@dimen/side_panel_padding_end"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground" >
+
+ <include layout="@layout/option_item_common"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+
+ <Switch
+ 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" />
+
+</LinearLayout>
diff --git a/res/layout/overlay_root_view.xml b/res/layout/overlay_root_view.xml
new file mode 100644
index 00000000..1048e267
--- /dev/null
+++ b/res/layout/overlay_root_view.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.tv.ui.OverlayRootView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <com.android.tv.ui.TunableTvView android:id="@+id/pip_tunable_tv_view"
+ android:visibility="gone"
+ android:layout_marginLeft="@dimen/pipview_margin_horizontal"
+ android:layout_marginRight="@dimen/pipview_margin_horizontal"
+ android:layout_marginTop="@dimen/pipview_margin_top"
+ android:layout_marginBottom="@dimen/pipview_margin_bottom"
+ android:layout_width="@dimen/pipview_small_size_width"
+ android:layout_height="@dimen/pipview_small_size_height" />
+
+ <FrameLayout
+ android:id="@+id/scene_container"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent" />
+
+ <include layout="@layout/menu" />
+ <include layout="@layout/option_container" />
+ <include layout="@layout/program_guide" />
+ <FrameLayout android:id="@+id/search"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"/>
+
+</com.android.tv.ui.OverlayRootView>
diff --git a/res/layout/pin_dialog.xml b/res/layout/pin_dialog.xml
new file mode 100644
index 00000000..5071717d
--- /dev/null
+++ b/res/layout/pin_dialog.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/pin_dialog_width"
+ android:layout_height="wrap_content"
+ android:paddingTop="19dp"
+ android:paddingBottom="24dp"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
+ android:elevation="8dp"
+ android:background="@drawable/pin_dialog_background">
+
+ <TextView
+ android:id="@+id/wrong_pin"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:lineSpacingExtra="@dimen/pin_dialog_text_line_spacing"
+ android:textSize="@dimen/pin_dialog_text_size"
+ android:textColor="@color/pin_dialog_text_color"
+ android:fontFamily="@string/font"
+ android:visibility="invisible"
+ android:singleLine="false"/>
+
+ <LinearLayout
+ android:id="@+id/enter_pin"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="@dimen/pin_dialog_title_width"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="7dp"
+ android:lineSpacingExtra="@dimen/pin_dialog_text_line_spacing"
+ android:textSize="@dimen/pin_dialog_text_size"
+ android:textColor="@color/pin_dialog_text_color"
+ android:fontFamily="@string/font"
+ android:singleLine="false" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="144dp"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <view class="com.android.tv.dialog.PinDialogFragment$PinNumberPicker"
+ android:id="@+id/first"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <view class="com.android.tv.dialog.PinDialogFragment$PinNumberPicker"
+ android:id="@+id/second"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp" />
+
+ <view class="com.android.tv.dialog.PinDialogFragment$PinNumberPicker"
+ android:id="@+id/third"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp" />
+
+ <view class="com.android.tv.dialog.PinDialogFragment$PinNumberPicker"
+ android:id="@+id/fourth"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp" />
+ </LinearLayout>
+ </LinearLayout>
+</FrameLayout>
diff --git a/res/layout/pin_number_picker.xml b/res/layout/pin_number_picker.xml
new file mode 100644
index 00000000..8e8de9f5
--- /dev/null
+++ b/res/layout/pin_number_picker.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="48dp"
+ android:layout_height="144dp">
+
+ <TextView android:id="@+id/focused_background"
+ android:layout_width="@dimen/pin_number_picker_text_view_width"
+ android:layout_height="@dimen/pin_number_picker_text_view_height"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:textSize="@dimen/pin_number_picker_text_size"
+ android:textColor="@color/pin_number_picker_text_color"
+ android:fontFamily="@string/light_font"
+ android:background="@drawable/pin_number_picker_focused_background" />
+
+ <LinearLayout
+ android:id="@+id/number_view_holder"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:focusable="true"
+ android:orientation="vertical">
+
+ <TextView android:id="@+id/previous2_number"
+ style="@style/pin_number_view"/>
+ <TextView android:id="@+id/previous_number"
+ style="@style/pin_number_view"/>
+ <TextView android:id="@+id/current_number"
+ style="@style/pin_number_view"/>
+ <TextView android:id="@+id/next_number"
+ style="@style/pin_number_view"/>
+ <TextView android:id="@+id/next2_number"
+ style="@style/pin_number_view"/>
+ </LinearLayout>
+
+</FrameLayout>
diff --git a/res/layout/pip_location_item.xml b/res/layout/pip_location_item.xml
deleted file mode 100644
index 37fe80e2..00000000
--- a/res/layout/pip_location_item.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/option_item_height"
- android:paddingRight="@dimen/side_panel_padding_right"
- android:paddingLeft="@dimen/side_panel_padding_left"
- android:focusable="true"
- android:orientation="horizontal" >
-
- <RadioButton
- android:id="@+id/option_item"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:gravity="left|center_vertical"
- android:paddingLeft="@dimen/option_item_padding_left"
- android:textSize="@dimen/option_item_text_size"
- android:textColor="@color/side_panel_item_text"
- android:singleLine="true"
- android:focusable="false"
- android:fontFamily="@string/font" />
-
- <ImageView
- android:id="@+id/pip_location"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content" />
-</LinearLayout>
diff --git a/res/layout/play_controls.xml b/res/layout/play_controls.xml
new file mode 100644
index 00000000..2354ad5e
--- /dev/null
+++ b/res/layout/play_controls.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.tv.menu.PlayControlsRowView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:descendantFocusability="afterDescendants">
+
+ <!-- Although the text leading can be changed accroding to the text size,
+ we decided to set fixed layout_height since it doesn't make a big difference,
+ and it makes code more simpler. -->
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="@dimen/menu_row_title_width"
+ android:layout_height="@dimen/menu_row_title_height"
+ android:paddingBottom="@dimen/menu_row_title_padding_bottom"
+ android:fontFamily="@string/condensed_font"
+ android:textAlignment="viewStart"
+ android:gravity="start|bottom"
+ android:textColor="@color/menu_row_title"
+ android:textSize="@dimen/menu_row_title_text_size_deselected"
+ android:transformPivotX="@dimen/menu_row_title_transform_pivot_x"
+ android:transformPivotY="@dimen/menu_row_title_transform_pivot_y"
+ android:text="@string/menu_title_play_controls" />
+
+ <!-- The animation of this view includes an alpha animation, while one of its child is scaled
+ to out of bounds of this view. This results in visual artifacts while animating, because
+ alpha animation only occurs within the bounds of the view.
+
+ To work around, this view takes up extra space up to its neighbors using negative margins,
+ and compensate the same space with padding.
+ -->
+ <include layout="@layout/play_controls_contents"/>
+
+</com.android.tv.menu.PlayControlsRowView>
diff --git a/res/layout/play_controls_button.xml b/res/layout/play_controls_button.xml
new file mode 100644
index 00000000..f532a45b
--- /dev/null
+++ b/res/layout/play_controls_button.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Need to use additional ImageView to set the scaleType to "centerCrop"
+ If we don't use this ImageView and set the drawable to the icon,
+ the background will be an ellipse, not a circle -->
+ <ImageView android:id="@+id/button"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:scaleType="centerCrop"
+ android:src="@drawable/play_controls_button_background" />
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center" />
+ <TextView
+ android:id="@+id/label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:visibility="gone"
+ style="@style/TextAppearance.Leanback.PlaybackControlLabel" />
+</merge>
diff --git a/res/layout/play_controls_contents.xml b/res/layout/play_controls_contents.xml
new file mode 100644
index 00000000..5b9b333d
--- /dev/null
+++ b/res/layout/play_controls_contents.xml
@@ -0,0 +1,179 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/play_controls"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/menu_list_margin_top"
+ android:layout_marginBottom="@dimen/menu_list_margin_bottom"
+ android:layout_marginStart="@dimen/menu_list_margin_start"
+ android:layout_marginEnd="@dimen/menu_list_margin_end"
+ android:layoutDirection="ltr"
+ android:paddingTop="@dimen/menu_list_padding_top"
+ android:paddingBottom="@dimen/menu_list_padding_bottom"
+ android:paddingStart="@dimen/menu_list_padding_start"
+ android:paddingEnd="@dimen/menu_list_padding_end"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/time_text"
+ android:layout_width="@dimen/play_controls_time_width"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignStart="@+id/body"
+ android:layout_marginBottom="@dimen/play_controls_time_bottom_margin"
+ android:gravity="center"
+ android:singleLine="true"
+ android:textColor="@color/play_controls_time_text_color"
+ android:textSize="@dimen/play_controls_time_text_size"
+ android:fontFamily="@string/font" />
+
+ <LinearLayout
+ android:id="@id/body"
+ android:orientation="vertical"
+ android:layout_width="@dimen/play_controls_width"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/time_text"
+ android:layout_centerHorizontal="true"
+ android:background="@drawable/play_controls_body_shape">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/play_controls_progress_height">
+
+ <ImageView
+ android:id="@+id/timeline_bg_start"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentStart="true"
+ android:src="@color/play_controls_progress_bar_background" />
+ <ImageView
+ android:id="@+id/watched"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_toEndOf="@id/timeline_bg_start"
+ android:src="@color/play_controls_progress_bar_watched" />
+ <ImageView
+ android:id="@+id/buffered"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_toEndOf="@id/watched"
+ android:src="@color/play_controls_progress_bar_buffered" />
+ <ImageView
+ android:id="@+id/timeline_bg_end"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentEnd="true"
+ android:layout_toEndOf="@id/buffered"
+ android:src="@color/play_controls_progress_bar_background" />
+
+ </RelativeLayout>
+
+ <FrameLayout
+ android:id="@+id/background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/play_controls_body_background">
+
+ <TextView
+ android:id="@+id/program_start_time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/play_controls_program_time_margin_start"
+ android:layout_marginTop="@dimen/play_controls_program_time_margin_top"
+ android:singleLine="true"
+ android:textColor="@color/play_controls_rec_time_text_color"
+ android:textSize="@dimen/play_controls_rec_time_text_size"
+ android:fontFamily="@string/font" />
+ <TextView
+ android:id="@+id/program_end_time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/play_controls_program_time_margin_end"
+ android:layout_marginTop="@dimen/play_controls_program_time_margin_top"
+ android:layout_gravity="end"
+ android:singleLine="true"
+ android:textColor="@color/play_controls_rec_time_text_color"
+ android:textSize="@dimen/play_controls_rec_time_text_size"
+ android:fontFamily="@string/font" />
+
+ <LinearLayout
+ android:id="@+id/play_control_bar"
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/play_controls_button_height"
+ android:layout_gravity="center">
+
+ <com.android.tv.menu.PlayControlsButton
+ android:id="@+id/jump_previous"
+ android:layout_width="@dimen/play_controls_button_width"
+ android:layout_height="match_parent" />
+ <com.android.tv.menu.PlayControlsButton
+ android:id="@+id/rewind"
+ android:layout_width="@dimen/play_controls_button_width"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/play_controls_button_start_margin" />
+ <com.android.tv.menu.PlayControlsButton
+ android:id="@+id/play_pause"
+ android:layout_width="@dimen/play_controls_button_width"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/play_controls_button_start_margin" />
+ <com.android.tv.menu.PlayControlsButton
+ android:id="@+id/fast_forward"
+ android:layout_width="@dimen/play_controls_button_width"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/play_controls_button_start_margin" />
+ <com.android.tv.menu.PlayControlsButton
+ android:id="@+id/jump_next"
+ android:layout_width="@dimen/play_controls_button_width"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/play_controls_button_start_margin" />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/unavailable_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:singleLine="true"
+ android:textColor="@color/play_controls_unavailable_message_text_color"
+ android:textSize="@dimen/play_controls_unavailable_message_text_size"
+ android:fontFamily="@string/font"
+ android:visibility="gone"
+ android:text="@string/play_controls_unavailable" />
+ </FrameLayout>
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/time_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/play_controls_time_indicator_top_margin"
+ android:layout_alignTop="@id/body"
+ android:layout_alignStart="@id/body"
+ android:background="@drawable/play_controls_time_indicator"
+ android:visibility="invisible"
+ android:elevation="1dp" />
+ </RelativeLayout>
+</FrameLayout>
diff --git a/res/layout/program_guide.xml b/res/layout/program_guide.xml
new file mode 100644
index 00000000..3ad12afb
--- /dev/null
+++ b/res/layout/program_guide.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/program_guide"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/program_guide_scrim"
+ android:visibility="invisible">
+
+ <include layout="@layout/program_guide_side_panel" />
+ <include layout="@layout/program_guide_table" />
+
+</FrameLayout>
diff --git a/res/layout/program_guide_side_panel.xml b/res/layout/program_guide_side_panel.xml
new file mode 100644
index 00000000..466b7fa8
--- /dev/null
+++ b/res/layout/program_guide_side_panel.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- layout_marginStart is set for the case of full mode. (i.e. side panel is hidden)
+ This FrameLayout is used to show the background of the side panel when the VerticalGridView
+ becomes invisible which happens when the side panel is hidden.
+ translationX is given to set them for initial values of enter animation. -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/program_guide_side_panel"
+ android:layout_width="@dimen/program_guide_side_panel_width"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/program_guide_side_panel_margin_start"
+ android:paddingStart="@dimen/program_guide_side_panel_padding_start"
+ android:paddingEnd="@dimen/program_guide_side_panel_padding_end"
+ android:clipToPadding="false"
+ android:translationX="@dimen/program_guide_side_panel_exit_x"
+ android:elevation="@dimen/program_guide_side_panel_elevation"
+ android:background="@color/program_guide_side_panel_background">
+
+ <android.support.v17.leanback.widget.SearchOrbView
+ android:id="@+id/program_guide_side_panel_search_orb"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|start"
+ android:layout_marginTop="@dimen/lb_browse_padding_top"
+ android:nextFocusDown="@+id/program_guide_side_panel_grid_view"
+ android:visibility="gone" />
+
+ <android.support.v17.leanback.widget.VerticalGridView
+ android:id="@id/program_guide_side_panel_grid_view"
+ android:layout_width="@dimen/program_guide_side_panel_item_width"
+ android:layout_height="match_parent"
+ android:layout_gravity="top|start"
+ android:alpha="0.0"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:transitionGroup="true"
+ lb:focusOutFront="true" />
+
+</FrameLayout>
diff --git a/res/layout/program_guide_side_panel_row.xml b/res/layout/program_guide_side_panel_row.xml
new file mode 100644
index 00000000..1dfb37c4
--- /dev/null
+++ b/res/layout/program_guide_side_panel_row.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/program_guide_side_panel_item_width"
+ android:layout_height="@dimen/program_guide_side_panel_row_height"
+ android:fontFamily="@string/condensed_font"
+ android:textSize="@dimen/program_guide_side_panel_text_size"
+ android:textColor="@color/program_guide_side_panel_text_color"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:gravity="start|bottom"
+ android:textAlignment="viewStart"
+ android:singleLine="true"
+ android:ellipsize="none"
+ android:stateListAnimator="@animator/program_guide_side_panel_text_state_animator"
+ android:transformPivotX="@dimen/program_guide_side_panel_row_pivot_x"
+ android:transformPivotY="@dimen/program_guide_side_panel_row_pivot_y"
+ android:requiresFadingEdge="horizontal"
+ android:fadingEdgeLength="@dimen/program_guide_side_panel_fading_edge" />
diff --git a/res/layout/program_guide_table.xml b/res/layout/program_guide_table.xml
new file mode 100644
index 00000000..cb9e47c7
--- /dev/null
+++ b/res/layout/program_guide_table.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- paddingStart is set for the case of full mode. (i.e. table is fully visible)
+ Set android:clipToPadding="false" in order not to clip current_time_indicator.
+ alpha and translationX are given to set them for initial values of enter animation. -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/program_guide_table"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingTop="@dimen/program_guide_table_margin_top"
+ android:paddingBottom="@dimen/program_guide_table_margin_bottom"
+ android:paddingStart="@dimen/program_guide_table_margin_start"
+ android:paddingEnd="0dp"
+ android:translationX="@dimen/program_guide_table_exit_x"
+ android:alpha="0"
+ android:elevation="@dimen/program_guide_table_elevation"
+ android:outlineProvider="paddedBounds"
+ android:clipToPadding="false" >
+
+ <!-- Provides the background for time_row. This is needed in order to place the current time
+ indicator in between the background and the time label. -->
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/program_guide_table_header_row_height"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentEnd="true"
+ android:background="@drawable/program_guide_table_header_row_background" />
+
+ <com.android.tv.ui.LocaleAwareTextClock
+ android:id="@+id/clock"
+ android:layout_width="@dimen/program_guide_table_header_column_width"
+ android:layout_height="@dimen/program_guide_table_header_row_height"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentStart="true"
+ android:paddingStart="@dimen/program_guide_table_header_column_padding_start"
+ android:paddingEnd="@dimen/program_guide_table_header_column_padding_end"
+ android:gravity="start|center_vertical"
+ android:textAlignment="viewStart"
+ android:fontFamily="@string/condensed_font"
+ android:textSize="@dimen/program_guide_table_header_row_font_size"
+ android:textColor="@color/program_guide_table_header_row_current_time_text_color" />
+
+ <ImageView
+ android:id="@+id/current_time_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@id/clock"
+ android:layout_marginTop="-4dp"
+ android:contentDescription="@null"
+ android:src="@drawable/program_guide_time_indicator" />
+
+ <com.android.tv.guide.TimelineRow
+ android:id="@+id/time_row"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/program_guide_table_header_row_height"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true"
+ android:layout_toEndOf="@id/clock"
+ android:layout_marginStart="@dimen/program_guide_table_header_row_overlap"
+ android:fadingEdgeLength="@dimen/program_guide_table_header_row_fade_length"
+ android:requiresFadingEdge="horizontal"
+ android:background="#00000000" />
+
+ <com.android.tv.guide.ProgramGrid
+ android:id="@+id/grid"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/time_row" />
+
+</RelativeLayout>
diff --git a/res/layout/program_guide_table_header_column_item.xml b/res/layout/program_guide_table_header_column_item.xml
new file mode 100644
index 00000000..ab66b7de
--- /dev/null
+++ b/res/layout/program_guide_table_header_column_item.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- The background of header_column will be programmatically changed
+ in order to support rounded corners. -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/header_column"
+ android:layout_width="@dimen/program_guide_table_header_column_width"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/program_guide_table_header_column_padding_start"
+ android:paddingEnd="@dimen/program_guide_table_header_column_padding_end">
+
+ <TextView
+ android:id="@+id/channel_number"
+ android:layout_width="@dimen/program_guide_table_header_column_channel_number_width"
+ android:layout_height="@dimen/program_guide_table_item_row_height"
+ android:gravity="start|center_vertical"
+ android:fontFamily="@string/light_font"
+ android:textSize="@dimen/program_guide_table_header_column_channel_number_large_font_size"
+ android:textColor="@color/program_guide_table_header_column_channel_number_text_color" />
+
+ <TextView
+ android:id="@+id/channel_name"
+ android:layout_width="@dimen/program_guide_table_header_column_channel_name_width"
+ android:layout_height="@dimen/program_guide_table_item_row_height"
+ android:layout_marginStart="@dimen/program_guide_table_header_column_channel_number_width"
+ android:gravity="start|center_vertical"
+ android:fontFamily="@string/condensed_font"
+ android:textSize="@dimen/program_guide_table_header_column_channel_name_font_size"
+ android:textColor="@color/program_guide_table_header_column_channel_name_text_color"
+ android:lineSpacingExtra="3.5sp" />
+
+ <ImageView
+ android:id="@+id/channel_logo"
+ android:layout_width="@dimen/program_guide_table_header_column_channel_logo_width"
+ android:layout_height="@dimen/program_guide_table_header_column_channel_logo_height"
+ android:layout_marginTop="@dimen/program_guide_table_header_column_channel_logo_margin_top"
+ android:layout_marginStart="@dimen/program_guide_table_header_column_channel_number_width"
+ android:scaleType="fitStart"
+ android:visibility="gone" />
+
+ <ImageView
+ android:id="@+id/channel_block"
+ android:layout_width="@dimen/program_guide_table_header_column_channel_block_width"
+ android:layout_height="@dimen/program_guide_table_header_column_channel_block_height"
+ android:layout_marginTop="@dimen/program_guide_table_header_column_channel_block_margin_top"
+ android:layout_marginStart="@dimen/program_guide_table_header_column_channel_number_width"
+ android:scaleType="center"
+ android:src="@drawable/ic_guide_lock"
+ android:alpha="@dimen/program_guide_table_header_column_channel_block_opacity"
+ android:visibility="visible" />
+
+</FrameLayout>
diff --git a/res/layout/program_guide_table_header_row_item.xml b/res/layout/program_guide_table_header_row_item.xml
new file mode 100644
index 00000000..6e3de2f5
--- /dev/null
+++ b/res/layout/program_guide_table_header_row_item.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/time"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/program_guide_table_header_row_height"
+ android:gravity="center"
+ android:fontFamily="@string/condensed_font"
+ android:textSize="@dimen/program_guide_table_header_row_font_size"
+ android:textColor="@color/program_guide_table_header_row_time_text_color" />
diff --git a/res/layout/program_guide_table_item.xml b/res/layout/program_guide_table_item.xml
new file mode 100644
index 00000000..11b6c52e
--- /dev/null
+++ b/res/layout/program_guide_table_item.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.tv.guide.ProgramItemView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/program_guide_table_item_row_height"
+ android:gravity="start|center_vertical"
+ android:background="@drawable/program_guide_table_item_background"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:paddingStart="@dimen/program_guide_table_item_padding"
+ android:paddingEnd="@dimen/program_guide_table_item_padding"
+ android:fontFamily="@string/condensed_font"
+ android:textAlignment="viewStart"
+ android:textSize="@dimen/program_guide_table_item_program_title_font_size"
+ android:textColor="@color/program_guide_table_item_program_title_text_color"
+ android:lineSpacingExtra="1sp"
+ android:maxLines="2"
+ android:ellipsize="end" />
diff --git a/res/layout/program_guide_table_row.xml b/res/layout/program_guide_table_row.xml
new file mode 100644
index 00000000..c6c46e64
--- /dev/null
+++ b/res/layout/program_guide_table_row.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <include layout="@layout/program_guide_table_header_column_item" />
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <com.android.tv.guide.ProgramRow
+ android:id="@+id/row"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/program_guide_table_item_row_height" />
+
+ <include layout="@layout/program_guide_table_row_detail" />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/program_guide_table_row_detail.xml b/res/layout/program_guide_table_row_detail.xml
new file mode 100644
index 00000000..fff3afae
--- /dev/null
+++ b/res/layout/program_guide_table_row_detail.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/detail"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/program_guide_table_detail_height"
+ android:background="@color/program_guide_table_grid_background" >
+
+ <!-- The following element is needed to not get a gap in the background during transition. -->
+ <FrameLayout
+ android:id="@+id/detail_content_full"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/program_guide_table_detail_padding_negative"
+ android:layout_marginStart="@dimen/program_guide_table_detail_padding_negative"
+ android:layout_marginBottom="@dimen/program_guide_table_detail_padding_negative"
+ android:layout_marginEnd="@dimen/program_guide_table_detail_padding_negative"
+ android:padding="@dimen/program_guide_table_detail_padding"
+ android:background="@color/program_guide_table_detail_background" >
+
+ <!-- The following element is needed to fade content separate from the background. -->
+ <RelativeLayout
+ android:id="@+id/detail_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/program_guide_table_detail_padding"
+ android:paddingTop="@dimen/program_guide_table_detail_padding"
+ android:paddingBottom="@dimen/program_guide_table_detail_padding"
+ android:paddingEnd="@dimen/program_guide_table_detail_padding_end" >
+
+ <ImageView
+ android:id="@+id/image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:adjustViewBounds="true"
+ android:maxWidth="@dimen/program_guide_table_detail_image_width"
+ android:maxHeight="@dimen/program_guide_table_detail_image_height"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true"
+ android:layout_marginEnd="@dimen/program_guide_table_detail_image_margin_end" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignWithParentIfMissing="true"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@id/image"
+ android:layout_marginTop="@dimen/program_guide_table_detail_title_margin_top"
+ android:fontFamily="@string/condensed_font"
+ android:textSize="@dimen/program_guide_table_detail_title_text_size"
+ android:textColor="@color/program_guide_table_detail_title_text_color"
+ android:singleLine="true"
+ android:ellipsize="end" />
+
+ <TextView
+ android:id="@+id/time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignStart="@id/title"
+ android:layout_marginTop="@dimen/program_guide_table_detail_time_margin_top"
+ android:fontFamily="@string/condensed_font"
+ android:textSize="@dimen/program_guide_table_detail_time_text_size"
+ android:textColor="@color/program_guide_table_detail_time_text_color" />
+
+ <include layout="@layout/program_track_meta"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignBottom="@id/time"
+ android:layout_toEndOf="@id/time"
+ android:layout_marginStart="@dimen/program_guide_table_detail_meta_margin_start"
+ android:layout_marginBottom="@dimen/program_guide_table_detail_meta_margin_bottom" />
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/program_guide_table_detail_desc_margin_top"
+ android:layout_alignParentTop="true"
+ android:layout_alignStart="@id/title" >
+
+ <ImageView
+ android:id="@+id/block"
+ android:layout_width="@dimen/program_guide_table_detail_block_width"
+ android:layout_height="@dimen/program_guide_table_detail_block_height"
+ android:layout_marginEnd="@dimen/program_guide_table_detail_block_margin_end"
+ android:src="@drawable/ic_guide_lock"
+ android:alpha="@dimen/program_guide_table_detail_block_opacity" />
+
+ <TextView
+ android:id="@+id/desc"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fontFamily="@string/font"
+ android:textSize="@dimen/program_guide_table_detail_desc_text_size"
+ android:textColor="@color/program_guide_table_detail_desc_text_color"
+ android:lineSpacingExtra="4sp"
+ android:maxLines="2"
+ android:ellipsize="end" />
+
+ </LinearLayout>
+
+ </RelativeLayout>
+
+ </FrameLayout>
+
+</FrameLayout>
diff --git a/res/layout/program_track_meta.xml b/res/layout/program_track_meta.xml
new file mode 100644
index 00000000..a5517f0a
--- /dev/null
+++ b/res/layout/program_track_meta.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <TextView android:id="@+id/closed_caption"
+ style="@style/channel_banner_track_meta_text"
+ android:visibility="gone" />
+
+ <TextView android:id="@+id/aspect_ratio"
+ style="@style/channel_banner_track_meta_text"
+ android:visibility="gone" />
+
+ <TextView android:id="@+id/resolution"
+ style="@style/channel_banner_track_meta_text"
+ android:visibility="gone" />
+
+ <TextView android:id="@+id/audio_channel"
+ style="@style/channel_banner_track_meta_text"
+ android:visibility="gone" />
+
+</LinearLayout>
diff --git a/res/layout/select_input.xml b/res/layout/select_input.xml
new file mode 100644
index 00000000..1380be60
--- /dev/null
+++ b/res/layout/select_input.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- All info banners have the same id for use by TvTransitionManager. -->
+<com.android.tv.ui.SelectInputView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/scene_transition_common"
+ android:layout_width="260dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|top"
+ android:layout_marginStart="56dp"
+ android:layout_marginTop="27dp"
+ android:elevation="8dp"
+ android:background="@drawable/info_banner_background" >
+
+</com.android.tv.ui.SelectInputView>
diff --git a/res/layout/select_input_item.xml b/res/layout/select_input_item.xml
new file mode 100644
index 00000000..1ff6df29
--- /dev/null
+++ b/res/layout/select_input_item.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/input_banner_item_height"
+ android:orientation="horizontal"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:gravity="start|center"
+ android:background="?android:attr/selectableItemBackground"
+ android:focusable="true" >
+
+ <TextView android:id="@+id/input_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@string/condensed_font"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textSize="18sp" />
+
+ <TextView android:id="@+id/secondary_input_label"
+ android:layout_marginStart="16dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@string/condensed_font"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textSize="18sp" />
+</LinearLayout>
diff --git a/res/layout/setup_dialog.xml b/res/layout/setup_dialog.xml
new file mode 100644
index 00000000..c3f8205a
--- /dev/null
+++ b/res/layout/setup_dialog.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.tv.ui.SetupView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/setup_background">
+ <LinearLayout
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal" >
+ <LinearLayout
+ android:layout_width="592dp"
+ android:layout_height="match_parent"
+ android:paddingStart="56dp"
+ android:paddingEnd="32dp"
+ android:paddingTop="183dp"
+ android:orientation="vertical" >
+ <TextView
+ android:id="@+id/setup_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fontFamily="@string/light_font"
+ android:textColor="@color/setup_title"
+ android:textSize="34sp" />
+ <TextView
+ android:id="@+id/setup_description"
+ android:layout_marginTop="8dp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fontFamily="@string/font"
+ android:textColor="@color/setup_description"
+ android:textSize="14sp"/>
+ </LinearLayout>
+ <android.support.v17.leanback.widget.VerticalGridView
+ android:id="@+id/input_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingStart="24dp"
+ android:paddingEnd="40dp"
+ android:paddingTop="183dp"
+ android:paddingBottom="48dp"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:focusableInTouchMode="true" />
+ </LinearLayout>
+</com.android.tv.ui.SetupView>
+
diff --git a/res/layout/setup_item_action.xml b/res/layout/setup_item_action.xml
new file mode 100644
index 00000000..c39e4960
--- /dev/null
+++ b/res/layout/setup_item_action.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- TODO: define its own resources instead of using option_item_xxx, once UI spec is defined. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/option_item_height"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:paddingTop="@dimen/option_item_common_padding_top"
+ android:paddingBottom="@dimen/option_item_common_padding_bottom"
+ android:gravity="center_vertical"
+ android:background="@drawable/setup_item_background"
+ android:focusable="true" >
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fontFamily="@string/option_item_text_font"
+ android:textSize="@dimen/option_item_text_size"
+ android:textColor="@color/option_item_text_color"
+ android:singleLine="true"
+ android:focusable="false" />
+
+</LinearLayout>
diff --git a/res/layout/setup_item_divider.xml b/res/layout/setup_item_divider.xml
new file mode 100644
index 00000000..65430f49
--- /dev/null
+++ b/res/layout/setup_item_divider.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/setup_divider" />
+</FrameLayout>
diff --git a/res/layout/setup_item_input.xml b/res/layout/setup_item_input.xml
new file mode 100644
index 00000000..1bc7ccd4
--- /dev/null
+++ b/res/layout/setup_item_input.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- TODO: define its own resources instead of using option_item_xxx, once UI spec is defined. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/option_item_height"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:paddingTop="@dimen/option_item_common_padding_top"
+ android:paddingBottom="@dimen/option_item_common_padding_bottom"
+ android:gravity="center_vertical"
+ android:background="@drawable/setup_item_background"
+ android:focusable="true" >
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fontFamily="@string/option_item_text_font"
+ android:textSize="@dimen/option_item_text_size"
+ android:textColor="@color/option_item_text_color"
+ android:singleLine="true"
+ android:focusable="false" />
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/option_item_text_margin_top"
+ 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:focusable="false" />
+
+</LinearLayout>
diff --git a/res/layout/shadow_focused.xml b/res/layout/shadow_focused.xml
deleted file mode 100644
index 33bec869..00000000
--- a/res/layout/shadow_focused.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/card_shadow_focused"
- android:alpha="0" />
diff --git a/res/layout/shadow_normal.xml b/res/layout/shadow_normal.xml
deleted file mode 100644
index d8c2332d..00000000
--- a/res/layout/shadow_normal.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/card_shadow_normal" />
diff --git a/res/layout/show_only_item.xml b/res/layout/show_only_item.xml
deleted file mode 100644
index 5d3ca50c..00000000
--- a/res/layout/show_only_item.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/simple_guide_item_height"
- android:focusable="true"
- android:orientation="horizontal" >
-
- <RadioButton
- android:id="@+id/show_only_item"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:gravity="left|center_vertical"
- android:paddingLeft="@dimen/show_only_item_padding_left"
- android:layout_marginLeft="@dimen/show_only_item_margin_left"
- android:textSize="@dimen/show_only_item_text_size"
- android:textColor="@color/side_panel_item_text"
- android:focusable="false"
- android:fontFamily="@string/font" />
-</LinearLayout>
diff --git a/res/layout/simple_guide_item.xml b/res/layout/simple_guide_item.xml
deleted file mode 100644
index e58b4cb4..00000000
--- a/res/layout/simple_guide_item.xml
+++ /dev/null
@@ -1,137 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/simple_guide_item_height"
- android:focusable="true"
- android:orientation="horizontal" >
-
- <LinearLayout
- android:id="@+id/channel"
- android:layout_width="@dimen/simple_guide_channel_width"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:layout_marginLeft="@dimen/simple_guide_margin_left" >
-
- <ImageView android:id="@+id/channel_logo"
- android:layout_width="36dp"
- android:layout_height="22dp"
- android:gravity="center_horizontal"
- android:layout_marginTop="@dimen/simple_guide_channel_margin_top" />
-
- <TextView android:id="@+id/channel_number"
- android:layout_width="36dp"
- android:layout_height="18dp"
- android:gravity="center_horizontal|bottom"
- android:textColor="@color/menu_default_text_color"
- android:textSize="@dimen/simple_guide_item_small_text_size"
- android:fontFamily="@string/condensed_font" />
-
- <TextView android:id="@+id/channel_number_alone"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:textColor="@color/menu_default_text_color"
- android:textSize="@dimen/simple_guide_item_small_text_size"
- android:fontFamily="@string/condensed_font" />
- </LinearLayout>
-
- <LinearLayout
- android:id="@+id/program"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:paddingLeft="@dimen/simple_guide_item_padding_left" >
- <TextView
- android:id="@+id/program_title"
- android:layout_height="@dimen/simple_guide_item_program_title_height"
- android:layout_width="match_parent"
- android:layout_marginRight="56dp"
- android:gravity="left|bottom"
- android:singleLine="true"
- android:textSize="@dimen/simple_guide_item_large_text_size"
- android:textColor="@color/menu_default_text_color"
- android:fontFamily="@string/condensed_font" />
-
- <LinearLayout
- android:id="@+id/detail"
- android:layout_width="match_parent"
- android:layout_height="@dimen/simple_guide_item_program_detail_height"
- android:gravity="bottom"
- android:orientation="horizontal"
- android:paddingRight="56dp" >
-
- <TextView android:id="@+id/resolution_on_left_side"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginRight="8dp"
- android:layout_marginTop="3.5dp"
- android:layout_marginBottom="3dp"
- android:gravity="bottom"
- android:paddingLeft="4dp"
- android:paddingRight="4dp"
- android:singleLine="true"
- android:ellipsize="end"
- android:textColor="@color/side_panel_background"
- android:textSize="8sp"
- android:textStyle="bold"
- android:fontFamily="@string/font"
- android:background="@drawable/track_meta_border"
- android:visibility="gone" />
-
- <ProgressBar
- android:id="@+id/remaining_time"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="@dimen/simple_guide_progress_width"
- android:layout_height="@dimen/simple_guide_progress_height"
- android:layout_marginRight="8dp"
- android:layout_marginBottom="3.5dp"
- android:progressDrawable="@drawable/progress_horizontal"
- android:indeterminate="false"
- android:max="100" />
-
- <TextView android:id="@+id/channel_name"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginRight="8dp"
- android:gravity="bottom"
- android:singleLine="true"
- android:ellipsize="end"
- android:textColor="@color/menu_default_text_color"
- android:textSize="@dimen/simple_guide_item_small_text_size"
- android:fontFamily="@string/condensed_font" />
-
- <TextView android:id="@+id/resolution"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginTop="3.5dp"
- android:layout_marginBottom="3dp"
- android:gravity="bottom"
- android:paddingLeft="4dp"
- android:paddingRight="4dp"
- android:singleLine="true"
- android:ellipsize="end"
- android:textColor="@color/side_panel_background"
- android:textSize="8sp"
- android:textStyle="bold"
- android:fontFamily="@string/font"
- android:background="@drawable/track_meta_border"
- android:visibility="gone" />
- </LinearLayout>
- </LinearLayout>
-</LinearLayout>
diff --git a/res/layout/tunable_tv_view.xml b/res/layout/tunable_tv_view.xml
index 8ffa99a2..15fa68cd 100644
--- a/res/layout/tunable_tv_view.xml
+++ b/res/layout/tunable_tv_view.xml
@@ -1,53 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
- <android.media.tv.TvView
- android:id="@+id/tv_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
<View
- android:id="@+id/block_reason_unknown"
+ android:id="@+id/hide_screen"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black" />
+ <ProgressBar
+ android:id="@+id/buffering_spinner"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@android:color/transparent"
+ android:layout_gravity="center" />
+
<View
- android:id="@+id/block_reason_tune"
+ android:id="@+id/block_screen_for_tune"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black" />
+ <include layout="@layout/block_screen" />
+
<View
- android:id="@+id/block_reason_weak_signal"
+ android:id="@+id/dim"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black" />
-
- <ProgressBar
- android:id="@+id/block_reason_buffering"
- android:visibility="gone"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="@android:color/transparent"
- android:layout_gravity="center" />
-
-</merge> \ No newline at end of file
+</merge>
diff --git a/res/transition/transition_between_scenes.xml b/res/transition/transition_between_scenes.xml
new file mode 100644
index 00000000..8ce4e4f4
--- /dev/null
+++ b/res/transition/transition_between_scenes.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+ android:transitionOrdering="sequential">
+ <fade android:fadingMode="fade_out"
+ android:duration="100"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+ <transitionSet>
+ <changeBounds
+ android:duration="300"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+ <fade android:fadingMode="fade_in"
+ android:startDelay="150"
+ android:duration="150"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+ </transitionSet>
+</transitionSet>
diff --git a/res/values-af/arrays.xml b/res/values-af/arrays.xml
new file mode 100644
index 00000000..4a78c005
--- /dev/null
+++ b/res/values-af/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normaal"</item>
+ <item msgid="2533030282864800794">"Vol"</item>
+ <item msgid="8568284598210500589">"Zoem"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Alle kanale"</item>
+ <item msgid="928298872841713530">"Gesin/kinders"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Inkopies"</item>
+ <item msgid="167201149441442173">"Flieks"</item>
+ <item msgid="525966731464264290">"Komedie"</item>
+ <item msgid="6096710741527327836">"Reis"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Opvoeding"</item>
+ <item msgid="7221999662426308394">"Diere/natuur"</item>
+ <item msgid="375300513250925001">"Nuus"</item>
+ <item msgid="7746320336582330410">"Speletjies"</item>
+ <item msgid="1255741860568329178">"Kunste"</item>
+ <item msgid="7603949681065702867">"Vermaak"</item>
+ <item msgid="4453821994746804366">"Leefstyl"</item>
+ <item msgid="3488534597567932843">"Musiek"</item>
+ <item msgid="7452153120614274095">"Beste"</item>
+ <item msgid="8215762047341133299">"Tegnologie/wetenskap"</item>
+ </string-array>
+</resources>
diff --git a/res/values-af/rating_system_strings.xml b/res/values-af/rating_system_strings.xml
new file mode 100644
index 00000000..2b950758
--- /dev/null
+++ b/res/values-af/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Dwelms"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Seks"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Geweld"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Dwelms"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Seks"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Geweld"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Frans"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Suggestiewe dialoog"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Vuil taal"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Seksuele inhoud"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Geweld"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantasiegeweld"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Fliekgraderings"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
new file mode 100644
index 00000000..4089b4c0
--- /dev/null
+++ b/res/values-af/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Regstreekse kanale"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Speelkontroles"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanale"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Onlangse kanale"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV-opsies"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP-opsies"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Speelkontroles onbeskikbaar vir hierdie kanaal"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Speel of laat wag"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Vinnig vorentoe"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Spoel terug"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Volgende"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Vorige"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programgids"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Nuwe kanale beskikbaar"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Geen skakel beskikbaar"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Maak <xliff:g id="APP_NAME">%1$s</xliff:g> oop"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Onderskrifte"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Vertoonmodus"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Aan"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Af"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-oudio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Kanaalopstelling"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Ouerkontroles"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Meer oor"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Bron"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Ruil"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Aan"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Af"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Klank"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Hoof"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP-venster"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Uitleg"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Regs onder"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Regs bo"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Links bo"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Links onder"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Langs mekaar"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Grootte"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Groot"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Klein"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Invoerbron"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antenna/kabel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Geen programinligting nie"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Geen inligting nie"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Geblokkeerde kanaal"</string>
+ <string name="default_language" msgid="4122326459624337928">"Onbekende taal"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Onderskrifte"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Af"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Pasmaak formatering"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Stel stelselvoorkeure vir onderskrifte"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Vertoonmodus"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-oudio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5,1-omring"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7,1-omring"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d kanale"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Pasmaak kanaallys"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Kies groep"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Ontkies groep"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Groepeer volgens"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Kanaalbron"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Groepeer volgens"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Kanaalopstelling"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Hierdie program word geblokkeer"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Hierdie program is <xliff:g id="RATING">%1$s</xliff:g> gegradeer"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Kanaalbronne"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Nuwe kanale beskikbaar"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Pasmaak kanaallys"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Kies kanale vir jou programgids"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Nie opgestel nie"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Die invoer steun nie outoskandering nie"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Kan nie outoskandeer vir \'<xliff:g id="TV_INPUT">%s</xliff:g>\' begin nie"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Kon nie die stelselwye voorkeure vir onderskrifte begin nie."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d kanaal bygevoeg"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d kanale bygevoeg"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Geen kanale bygevoeg nie"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Ontvanger"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Ouerkontrole"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Aan"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Af"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Kanale geblokkeer"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Blokkeer almal"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Deblokkeer almal"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Versteekte kanale"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Programbeperkings"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Verander PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Graderingstelsels"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Graderings"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Alle gradeerstelsels"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Ander lande"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Geen"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Geen"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Geen"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Hoë beperkings"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Mediumbeperkings"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Lae beperkings"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Gepasmaak"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Inhoud geskik vir kinders"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Inhoud is geskik vir ouer kinders"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Inhoud geskik vir tieners"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Handmatige beperkings"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s en subgradering"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Subgraderings"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Voer jou PIN in om hierdie kanaal te kyk"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Voer jou PIN in om hierdie program te kyk"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Voer jou PIN in"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Skep \'n PIN om ouerkontroles op te stel"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Voer nuwe PIN in"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Bevestig jou PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Voer jou huidige PIN in"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Jy het 5 keer die verkeerde PIN ingevoer.\nProbeer weer oor <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekonde."</item>
+ <item quantity="other" msgid="8829550842387756054">"Jy het 5 keer die verkeerde PIN ingevoer.\nProbeer weer oor <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekondes."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Die PIN is verkeerd. Probeer weer."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Probeer weer. PIN stem nie ooreen nie"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Meer oor"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Oopbronlisensies"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Oopbronlisensies"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Weergawe"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Druk Regs en voer jou PIN in om hierdie kanaal te kyk"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Druk Regs en voer jou PIN in om hierdie program te kyk"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Hierdie program is <xliff:g id="RATING">%1$s</xliff:g> gegradeer.\nDruk Regs en voer jou PIN in om hierdie program te kyk."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Program is geblokkeer"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Hierdie program is <xliff:g id="RATING">%1$s</xliff:g> gegradeer"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Titelloos"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanaal is geblokkeer"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Kanaalbronne"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Stel regstreekse kanale op uit die beskikbare bronne. Dit kan verskeie minute duur, afhangend van die kanaalbron."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Klaar"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d kanaal is beskikbaar"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d kanale is beskikbaar"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Geen kanale is beskikbaar nie"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nuut"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Druk KIES"</b>" om na die TV-kieslys te gaan."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Geen TV-invoer gevind nie"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Kan nie die TV-invoer vind nie"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP word nie gesteun nie"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Daar is geen beskikbare invoer om met PIP te wys nie"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Instemmertipe is nie geskik nie. Begin asseblief die Live Channels-program vir instemmertipe-TV-invoer."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Kon nie instel nie"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Geen program is gevind om hierdie handeling te behartig nie."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Alle bronkanale is versteek.\nKies minstens een kanaal om te kyk."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Onbeskikbaar weens swak videosein"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Die video is onverwags onbeskikbaar"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"TERUG-sleutel is vir gekoppelde toestelle. Druk TUIS-knoppie om uit te gaan."</string>
+</resources>
diff --git a/res/values-am/arrays.xml b/res/values-am/arrays.xml
new file mode 100644
index 00000000..7764b210
--- /dev/null
+++ b/res/values-am/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"መደበኛ"</item>
+ <item msgid="2533030282864800794">"ሙሉ"</item>
+ <item msgid="8568284598210500589">"አጉላ"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"ሁሉም ሰርጦች"</item>
+ <item msgid="928298872841713530">"ቤተሰብ/ህጻናት"</item>
+ <item msgid="2751606947569857164">"ስፖርት"</item>
+ <item msgid="7345749789651321496">"ግዢ"</item>
+ <item msgid="167201149441442173">"ፊልሞች"</item>
+ <item msgid="525966731464264290">"አስቂኝ"</item>
+ <item msgid="6096710741527327836">"ጉዞ"</item>
+ <item msgid="2851882187117833883">"ድራማ"</item>
+ <item msgid="78492781188719038">"ትምህርት"</item>
+ <item msgid="7221999662426308394">"እንስሳት/የዱር ህይወት"</item>
+ <item msgid="375300513250925001">"ዜና"</item>
+ <item msgid="7746320336582330410">"ጨዋታ"</item>
+ <item msgid="1255741860568329178">"ጥበባት"</item>
+ <item msgid="7603949681065702867">"መዝናኛ"</item>
+ <item msgid="4453821994746804366">"የአኗኗር ዘይቤ"</item>
+ <item msgid="3488534597567932843">"ሙዚቃ"</item>
+ <item msgid="7452153120614274095">"ፕሪሚየር"</item>
+ <item msgid="8215762047341133299">"ቴክኖሎጂ/ሳይንስ"</item>
+ </string-array>
+</resources>
diff --git a/res/values-am/rating_system_strings.xml b/res/values-am/rating_system_strings.xml
new file mode 100644
index 00000000..77c1b659
--- /dev/null
+++ b/res/values-am/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"አደገኛ መድሃኒቶች"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"ወሲብ"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"ጥቃት"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"አደገኛ መድሃኒቶች"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"ወሲብ"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"ጥቃት"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"ፈረንሳይኛ"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"ስሜት ቀስቃሽ ንግግር"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"ከባድ ቋንቋ"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"ወሲባዊ ይዘት"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"ጥቃት"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"ምናባዊ ጥቃት"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"የፊልም ደረጃዎች"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
new file mode 100644
index 00000000..1248ab54
--- /dev/null
+++ b/res/values-am/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"ሞኖ"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"ስቲሪዮ"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"የማጫወቻ መቆጣጠሪያዎች"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"ሰርጦች"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"የቅርብ ጊዜ ሰርጦች"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"የቴሌቪዥን አማራጮች"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"የፒአይፒ አማራጮች"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"ለዚህ ሰርጥ የማጫወቻ መቆጣጠሪያዎች አይገኝም"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"አጫውት ወይም ለአፍታ አቁም"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"በፍጥነት አሳልፍ"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"ወደኋላ አጠንጥን"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"ቀጣይ"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"ቀዳሚ"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"የፕሮግራም መመሪያ"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"አዲስ ሰርጦች ይገኛሉ"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"ምንም አገናኝ የለም"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g>ን ክፈት"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"የተዘጉ የስዕል መግለጫዎች"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"የማሳያ ሁኔታ"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"ፒአይፒ"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"በርቷል"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"ጠፍቷል"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"ባለብዙ ተሰሚ"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"የሰርጥ ቅንብር"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"የወላጅ መቆጣጠሪያዎች"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"ስለ"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"ምንጭ"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"ማገላበጥ"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"በርቷል"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"ጠፍቷል"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"ድምፅ"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"ዋና"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"የፒአይፒ መስኮት"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"አቀማመጥ"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"ከታች በስተቀኝ"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"ከላይ በስተቀኝ"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"ከላይ በስተግራ"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"ከታች በስተግራ"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"ጎን ለጎን"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"መጠን"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"ትልቅ"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"ትንሽ"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"የግብዓት ምንጭ"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"ቴሌቪዥን (አንቴና/ገመድ)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"ምንም የፕሮግራም መረጃ የለም"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"ምንም መረጃ የለም"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"የታገደ ሰርጥ"</string>
+ <string name="default_language" msgid="4122326459624337928">"ያልታወቀ ቋንቋ"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"የተዘጉ መግለጫ ጽሑፎች"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"ጠፍቷል"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"ቅርጸትን አብጅ"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"ስርዓት-ተኮር የተዘጉ መግለጫ ጽሑፎች ምርጫዎችን ያዋቅሩ"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"የማሳያ ሁኔታ"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"ባለብዙ ተሰሚ"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"ሞኖ"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"ስቲሪዮ"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 Surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 Surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d ሰርጦች"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"የሰርጥ ዝርዝር አብጅ"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"ስብስብ ምረጥ"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"ስብስብ አትምረጥ"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"ሰብስብ በ"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"የሰርጥ ምንጭ"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"ኤችዲ/ኤስዲ"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"ኤችዲ"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"ኤስዲ"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"ሰብስብ በ"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"የሰርጥ ቅንብር"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"ይህ ፕሮግራም ታግዷል"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"ይህ ፕሮግራም <xliff:g id="RATING">%1$s</xliff:g> ደረጃ ነው የተሰጠው"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"የሰርጥ ምንጮች"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"አዲስ ሰርጦች ይገኛሉ"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"የሰርጥ ዝርዝር አብጅ"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"ለፕሮግራሙ መመሪያዎ ሰርጦችን ይምረጡ"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"አልተዋቀረም"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"ግብዓቱ ራስ-ቃኝን አይደግፍም።"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"ለ«<xliff:g id="TV_INPUT">%s</xliff:g>» ራስ-ቃኝን መጀመር አልተቻለም"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"ለተዘጉ መግለጫ ጽሑፎች ስርዓት-ተኮር ምርጫዎችን ማስጀመር አልተቻለም።"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d ሰርጥ ታክሏል"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d ሰርጦች ታክለዋል"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"ምንም ሰርጦች አልታከሉም"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"ማስተካከያ"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"የወላጅ ቁጥጥሮች"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"በርቷል"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"ጠፍቷል"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"ሰርጦች ታግደዋል"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"ሁሉንም አግድ"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"ሁሉንም የታገዱትን አጥፋ"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"የተደበቁ ሰርጦች"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"የፕሮግራም ገደቦች"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"ፒን ቀይር"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"የደረጃ አሰጣጥ ስርዓቶች"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"የተሰጡ ደረጃዎች"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"ሁሉንም የደረጃ አሰጣት ስርዓቶችን ይመልከቱ"</string>
+ <string name="other_countries" msgid="8342216398676184749">"ሌሎች አገሮች"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"ምንም"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"ምንም"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"ምንም"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"ከፍተኛ ገደቦች"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"መካከለኛ ገደቦች"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"ዝቅተኛ ገደቦች"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"ብጁ"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"ይዘቱ ለህጻናት የሚመች ነው"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"ይዘት ዕድሜያቸው ተለቅ ላሉ ልጆች ይሆናል"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"ይዘቱ ለታዳጊ ወጣቶች የሚመች ነው"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"በሰው የተበጁ ገደቦች"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s እና ንዑስ ደረጃዎች"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"ንዑስ ደረጃዎች"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"ይህን ሰርጥ ለመመልከት የእርስዎን ፒን ያስገቡ"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"ይህን ፕሮግራም ለመመልከት የእርስዎን ፒን ያስገቡ"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"የእርስዎን ፒን ያስገቡ"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"የወላጅ መቆጣጠሪያዎችን ለማዋቀር ፒን ይፍጠሩ።"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"አዲስ ፒን ያስገቡ"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"የእርስዎን ፒን ያረጋግጡ"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"የአሁኑ ፒንዎን ያስገቡ"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"የተሳሳተ ፒን 5 ጊዜ ገብቷል።\nበ<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> ሰከንድ ውስጥ እንደገና ይሞክሩ።"</item>
+ <item quantity="other" msgid="8829550842387756054">"የተሳሳተ ፒን 5 ጊዜ ገብቷል።\nበ<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> ሰከንዶች ውስጥ እንደገና ይሞክሩ።"</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"ይህ ፒን የተሳሳተ ነበር። እንደገና ይሞክሩ።"</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"እንደገና ይሞክሩ፣ ፒኑ አይዛመድም"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"ስለ"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"የክፍት ምንጭ ፍቃዶች"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"የክፍት ምንጭ ፍቃዶች"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"ስሪት"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"ይህን ሰርጥ ለመመልከት ቀኝን ይጫኑ እና የእርስዎን ፒን ያስገቡ"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"ይህን ፕሮግራም ለመመልከት ቀኝን ይጫኑ እና የእርስዎን ፒን ያስገቡ"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"ይህ ፕሮግራም <xliff:g id="RATING">%1$s</xliff:g> ደረጃ ነው የተሰጠው።\nይህን ፕሮግራም ለመመልከት ቀኝን ይጫኑ እና የእርስዎን ፒን ያስገቡ።"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"ፕሮግራም ታግዷል"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"ይህ ፕሮግራም <xliff:g id="RATING">%1$s</xliff:g> ደረጃ ነው የተሰጠው"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"ርእስ የለውም"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"ሰርጥ ታግዷል"</string>
+ <string name="episode_format" msgid="4881195874563241096">"ምዕ <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>፦ ክፍል <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"የሰርጥ ምንጮች"</string>
+ <string name="setup_description" msgid="8728423605912915099">"ከሚገኙ ምንጮች የቀጥታ ሰርጦችን ያዘጋጁ። እንደ የሰርጡ ምንጭ የሚወሰን ሆኖ የተወሰኑ ደቂቃዎች ሊወስድ ይችላል።"</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"ተከናውኗል"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d ሰርጥ ይገኛል"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d ሰርጦች ይገኛል"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"ምንም ሰርጦች አይገኙም"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"አዲስ"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"የቴሌቪዥን ምናሌውን ለመድረስ "<b>"ምረጥን ይጫኑ"</b>"።"</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"ምንም የቴሌቪዥን ግብዓት አልተገኘም"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"የቴሌቪዥን ግብዓቱን ማግኘት አልተቻለም"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"ፒአይፒ አይደገፍም"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"ከፒአይፒው ጋር አብሮ ሊታይ የሚችል ግቤት አይገኝም"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"የቃኚ አይነት ተገቢ አይደለም። እባክዎ የቃኚ አይነት ቴሌቪዥን ግብዓት ለማግኘት የቀጥተኛ ሰርጦች መተግበሪያውን ያስጀምሩት።"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"መቃኘት አልተሳካም"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"ይህን እርምጃ የሚያከናውን ምንም መተግበሪያ አልተገኘም።"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"ሁሉም የምንጭ ሰርጦች ተደብቀዋል።\nቢያንስ አንድ የሚመለከቱት ሰርጥ ይምረጡ።"</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"በደካማ የቪዲዮ ምልክት ምክንያት የማይገኝ"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"ቪዲዮው በማይጠበቅ ሁኔታ የማይገኝ ሆኗል"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"BACK ቁልፍ ለተገናኙ መሳሪያዎች። ለመውጣት HOME አዝራርን ይጫኑ።"</string>
+</resources>
diff --git a/res/values-ar/arrays.xml b/res/values-ar/arrays.xml
new file mode 100644
index 00000000..6b956da4
--- /dev/null
+++ b/res/values-ar/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"عادي"</item>
+ <item msgid="2533030282864800794">"كامل"</item>
+ <item msgid="8568284598210500589">"تكبير/تصغير"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"كل القنوات"</item>
+ <item msgid="928298872841713530">"الأسرة/الأطفال"</item>
+ <item msgid="2751606947569857164">"رياضة"</item>
+ <item msgid="7345749789651321496">"تسوّق"</item>
+ <item msgid="167201149441442173">"أفلام"</item>
+ <item msgid="525966731464264290">"كوميديا"</item>
+ <item msgid="6096710741527327836">"سفر"</item>
+ <item msgid="2851882187117833883">"دراما"</item>
+ <item msgid="78492781188719038">"تعليم"</item>
+ <item msgid="7221999662426308394">"حيوانات/حياة برية"</item>
+ <item msgid="375300513250925001">"أخبار"</item>
+ <item msgid="7746320336582330410">"ألعاب"</item>
+ <item msgid="1255741860568329178">"الفنون"</item>
+ <item msgid="7603949681065702867">"ترفيه"</item>
+ <item msgid="4453821994746804366">"نمط حياة"</item>
+ <item msgid="3488534597567932843">"موسيقي"</item>
+ <item msgid="7452153120614274095">"العرض الأول"</item>
+ <item msgid="8215762047341133299">"تكنولوجيا/علوم"</item>
+ </string-array>
+</resources>
diff --git a/res/values-ar/rating_system_strings.xml b/res/values-ar/rating_system_strings.xml
new file mode 100644
index 00000000..7f348034
--- /dev/null
+++ b/res/values-ar/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"مخدرات"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"جنس"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"عنف"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"مخدرات"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"جنس"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"عنف"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"الفرنسية"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"حوار موحي جنسيًا"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"لغة غليظة"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"محتوى جنسي"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"عنف"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"عنف خيالي"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"تقييمات الأفلام"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
new file mode 100644
index 00000000..0432ccb0
--- /dev/null
+++ b/res/values-ar/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"القنوات المباشرة"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"أحادية"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"ستيريو"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"عناصر التحكم في التشغيل"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"القنوات"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"أحدث القنوات"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"خيارات التلفزيون"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"‏خيارات PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"عناصر تحكم التشغيل غير متاحة لهذه القناة"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"تشغيل الفيديو أو إيقافه مؤقتًا"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"تقديم سريع"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"إرجاع"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"التالي"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"السابق"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"دليل البرامج"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"تتوفر قنوات جديدة"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"لا يتوفر أي رابط"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"فتح <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"التسميات التوضيحية المغلقة"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"وضع العرض"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"تشغيل"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"إيقاف"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"إعدادات صوتية متعددة"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"إعداد القناة"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"عناصر التحكم الأبوي"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"حول"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"المصدر"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"تبديل"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"تشغيل"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"إيقاف"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"الصوت"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"الرئيسي"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"‏نافذة PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"التنسيق"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"أسفل اليمين"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"أعلى اليمين"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"أعلى اليسار"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"أسفل اليسار"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"جنبًا إلى جنب"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"الحجم"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"كبيرة"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"صغيرة"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"مصدر الإدخال"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"تلفزيون (هوائي/كابل)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"لا تتوفّر معلومات عن البرنامج"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"لا توجد معلومات"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"القناة المحظورة"</string>
+ <string name="default_language" msgid="4122326459624337928">"لغة غير معروفة"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"التسميات التوضيحية المغلقة"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"إيقاف"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"تخصيص التنسيق"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"تعيين التفضيلات عبر النظام للتسميات التوضيحية المغلقة"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"وضع العرض"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"إعدادات صوتية متعددة"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"أحادية"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"ستيريو"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"صوت مجسم 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"صوت مجسم 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"‏%d من القنوات"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"تخصيص قائمة قنوات"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"تحديد مجموعة"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"إلغاء تحديد المجموعة"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"تجميع بحسب"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"مصدر القناة"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"جودة عالية / جودة قياسية"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"دقة عالية"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"دقة قياسية"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"تجميع بحسب"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"إعداد القناة"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"تم حظر هذا البرنامج"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"تم تصنيف هذا البرنامج على أنه <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"مصادر القنوات"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"تتوفر قنوات جديدة"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"تخصيص قائمة قنوات"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"اختر القنوات الخاصة بدليل البرامج"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"لم يتم الإعداد"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"الإدخال ليس متوافقًا مع الفحص التلقائي"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"تعذر بدء البحث التلقائي في \"<xliff:g id="TV_INPUT">%s</xliff:g>\""</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"تعذر بدء التفضيلات على مستوى النظام للترجمات المصاحبة."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"‏تمت إضافة %1$d من القنوات"</item>
+ <item quantity="other" msgid="1078861616751739285">"‏تمت إضافة %1$d من القنوات"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"لم تتم إضافة قنوات"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"موالف"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"عناصر التحكم الأبوي"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"تشغيل"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"إيقاف"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"القنوات المحظورة"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"حظر الكل"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"إلغاء حظر الكل"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"القنوات المخفية"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"قيود البرنامج"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"تغيير رقم التعريف الشخصي"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"نظام التقييم"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"التقييمات"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"الاطلاع على جميع أنظمة التقييم"</string>
+ <string name="other_countries" msgid="8342216398676184749">"بلدان أخرى"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"لا شيء"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"لا شيء"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"لا شيء"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"قيود مرتفعة"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"قيود متوسّطة"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"قيود منخفضة"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"مخصص"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"محتوى مناسب للأطفال"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"محتوى مناسب للأطفال في مراحل عمرية متقدمة"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"المحتوى مناسب للمراهقين"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"قيود يدوية"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"‏%1$s وتقييمات فرعية"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"تقييمات فرعية"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"أدخل رقم التعريف الشخصي لمشاهدة هذه القناة"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"أدخل رقم التعريف الشخصي لمشاهدة هذا البرنامج"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"أدخل رقم التعريف الشخصي"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"لتعيين عناصر التحكم الأبوي، عليك إنشاء رقم تعريف شخصي"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"إدخال رقم تعريف شخصي جديد"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"تأكيد رقم التعريف الشخصي"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"أدخل رقم التعريف الشخصي الحالي"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"لقد أدخلت رقم تعريف شخصي خاطئًا 5 مرات.\nأعد المحاولة بعد <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> ثانية."</item>
+ <item quantity="other" msgid="8829550842387756054">"لقد أدخلت رقم تعريف شخصي خاطئًا 5 مرات.\nأعد المحاولة بعد <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> من الثواني."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"رقم التعريف الشخصي هذا خاطئ. أعد المحاولة."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"أعد المحاولة، رقم التعريف الشخصي غير مطابق"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"حول"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"تراخيص البرامج المفتوحة المصدر"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"تراخيص البرامج المفتوحة المصدر"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"الإصدار"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"لمشاهدة هذه القناة، اضغط على اليمين وأدخل رقم التعريف الشخصي"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"لمشاهدة هذا البرنامج، اضغط على اليمين وأدخل رقم التعريف الشخصي"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"تم تصنيف هذا البرنامج على أنه <xliff:g id="RATING">%1$s</xliff:g>.\nلمشاهدة هذا البرنامج، اضغط على اليمين وأدخل رقم التعريف الشخصي."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"البرنامج محظور"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"تم تصنيف هذا البرنامج على أنه <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"بلا عنوان"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"تم حظر القناة"</string>
+ <string name="episode_format" msgid="4881195874563241096">"الموسم <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: الحلقة <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"مصادر القنوات"</string>
+ <string name="setup_description" msgid="8728423605912915099">"يمكنك إعداد قنوات مباشرة من المصادر المتاحة. وقد يستغرق هذا بضع دقائق حسب مصدر القنوات."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"تمّ"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"‏عدد القنوات المتوفرة: %1$d"</item>
+ <item quantity="other" msgid="2386588423841837714">"‏عدد القنوات المتوفرة: %1$d"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"لا تتوفر أية قناة"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"جديد"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"‏"<b>"اضغط على \"تحديد\""</b>" للوصول إلى قائمة TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"لم يتم العثور على إدخال تلفزيون"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"لا يمكن العثور على إدخال تلفزيون"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"‏خدمة PIP ليست متوافقة"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"‏ليس هناك إدخال متاح يمكن عرضه باستخدام PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"نوع الموالف غير مناسب، لذلك يُرجى تشغيل تطبيق القنوات المباشرة لنوع الموالف إدخال التلفزيون."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"أخفق التوليف"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"لم يتم العثور على تطبيق يمكنه مباشرة هذا الإجراء."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"كل قنوات المصدر مخفية.\nحدد على الأقل قناة واحدة لمشاهدتها."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"غير متوفر بسبب ضعف في إشارة الفيديو"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"الفيديو غير متوفر بشكل مفاجئ"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"زر الرجوع مخصص للجهاز المتصل. يمكنك الضغط على زر الصفحة الرئيسية للخروج."</string>
+</resources>
diff --git a/res/values-bg/arrays.xml b/res/values-bg/arrays.xml
new file mode 100644
index 00000000..7fef2d45
--- /dev/null
+++ b/res/values-bg/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Нормално (4:3)"</item>
+ <item msgid="2533030282864800794">"Пълно (16:9)"</item>
+ <item msgid="8568284598210500589">"Променен мащаб"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Всички канали"</item>
+ <item msgid="928298872841713530">"Семейство/деца"</item>
+ <item msgid="2751606947569857164">"Спорт"</item>
+ <item msgid="7345749789651321496">"Пазаруване"</item>
+ <item msgid="167201149441442173">"Филми"</item>
+ <item msgid="525966731464264290">"Комедия"</item>
+ <item msgid="6096710741527327836">"Пътувания"</item>
+ <item msgid="2851882187117833883">"Драма"</item>
+ <item msgid="78492781188719038">"Образование"</item>
+ <item msgid="7221999662426308394">"Животни/дива природа"</item>
+ <item msgid="375300513250925001">"Новини"</item>
+ <item msgid="7746320336582330410">"Игри"</item>
+ <item msgid="1255741860568329178">"Изкуства"</item>
+ <item msgid="7603949681065702867">"Развлечения"</item>
+ <item msgid="4453821994746804366">"Начин на живот"</item>
+ <item msgid="3488534597567932843">"Музика"</item>
+ <item msgid="7452153120614274095">"Премиери"</item>
+ <item msgid="8215762047341133299">"Наука и технологии"</item>
+ </string-array>
+</resources>
diff --git a/res/values-bg/rating_system_strings.xml b/res/values-bg/rating_system_strings.xml
new file mode 100644
index 00000000..55e9292d
--- /dev/null
+++ b/res/values-bg/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Наркотици"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Секс"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Насилие"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Наркотици"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Секс"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Насилие"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"френски"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Диалог, предизвикващ неприлични асоциации"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Груб език"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Сексуално съдържание"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Насилие"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Въображаемо насилие"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Класификации на филми"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
new file mode 100644
index 00000000..d999a6cf
--- /dev/null
+++ b/res/values-bg/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"моно"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"стерео"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Контроли за пускане"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Канали"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Скорошни канали"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Опции за TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Опции за PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"За този канал няма налични контроли за възпроизвеждане"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Пускане или поставяне на пауза"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Превъртане напред"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Превъртане назад"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Напред"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Назад"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Програми: Справочник"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Налице са нови канали"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Няма налична връзка"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Отваряне на <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Надписи"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Показв.: Режим"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Вкл."</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Изкл."</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Много записи"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Канали: Настр."</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Родит. контроли"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Информация"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Източник"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Размяна"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Вкл."</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Изкл."</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Звук"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Основен"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Прозорец на PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Оформление"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Долу вдясно"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Горе вдясно"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Горе вляво"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Долу вляво"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Редом"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Размер"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Голям"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Малък"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Вход"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Телевизор (антена/кабел)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Няма информация за програмите"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Няма информация"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Блокиран канал"</string>
+ <string name="default_language" msgid="4122326459624337928">"Неизвестен език"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Надписи"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Изключване"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Форматиране: Персон."</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Задав. на предпоч. за надписи за цялата система"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Показв.: Режим"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Много записи"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"моно"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"стерео"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Обемен звук 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Обемен звук 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d канала"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Сп. с канали: Персон."</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Избиране на група"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Отмяна на избора на група"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Групиране по"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Източник на канала"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"Висока/стандартна детайлност"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"Висока детайлност"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"Стандартна детайлност"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Групиране по"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Канали: Настройка"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Тази програма е блокирана"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Класификацията на тази програма е „<xliff:g id="RATING">%1$s</xliff:g>“"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Канали: Източници"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Налице са нови канали"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Сп. с канали: Персон."</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Изберете канали за справ. си с програми"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Не е настроено"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Входът не поддържа автоматично сканиране"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Автоматичното сканиране за „<xliff:g id="TV_INPUT">%s</xliff:g>“ не може да се стартира"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Системните предпочитания за субтитрите не могат да се стартират."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"Добавен е %1$d канал"</item>
+ <item quantity="other" msgid="1078861616751739285">"Добавени са %1$d канала"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Няма добавени канали"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Тунер"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Родителски контрол"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Вкл."</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Изкл."</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Блокирани канали"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Блокиране на всички"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Отблокир. на всички"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Скрити канали"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Прогр. ограничения"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Промяна на ПИН кода"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Класифик.: Системи"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Класификации"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Вижте вс. системи за класифик."</string>
+ <string name="other_countries" msgid="8342216398676184749">"Други държави"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Няма"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Няма"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Няма"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Високи ограничения"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Средни ограничения"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Ниски ограничения"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Персонализирано"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Съдържание, подходящо за деца"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Съдържание, подходящо за по-големи деца"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Съдържание, подходящо за тийнейджъри"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Ръчно зададени ограничения"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s и подкласифик."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Подкласификации"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Въведете ПИН кода си, за да гледате този канал"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Въведете ПИН кода си, за да гледате тази програма"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Въведете ПИН кода си"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"За да зададете родителски контроли, създайте ПИН код"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Въведете новия ПИН код"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Потвърдете ПИН кода си"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Въведете текущия си ПИН код"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Въведохте грешен ПИН код 5 пъти.\nОпитайте отново след <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> секунда."</item>
+ <item quantity="other" msgid="8829550842387756054">"Въведохте грешен ПИН код 5 пъти.\nОпитайте отново след <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> секунди."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Този ПИН код бе грешен. Опитайте отново."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Опитайте отново, ПИН кодът не е идентичен"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Информация"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Лицензи за отворен код"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Лицензи за отворен код"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Версия"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"За да гледате този канал, натиснете стрелката за надясно и въведете ПИН кода си"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"За да гледате тази програма, натиснете стрелката за надясно и въведете ПИН кода си"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Класификацията на тази програма е „<xliff:g id="RATING">%1$s</xliff:g>“.\nЗа да я гледате, натиснете стрелката за надясно и въведете ПИН кода си."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Програмата е блокирана"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Класификацията на тази програма е „<xliff:g id="RATING">%1$s</xliff:g>“"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Без заглавие"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Каналът е блокиран"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Сезон <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Еп. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> ˜– „<xliff:g id="EPISODE_TITLE">%3$s</xliff:g>“"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Източници на канали"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Настройване на канали на живо от наличните източници. Това може да отнеме няколко минути в зависимост от източника."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Готово"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Налице е %1$d канал"</item>
+ <item quantity="other" msgid="2386588423841837714">"Налице са %1$d канала"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Няма налични канали"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Нови"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Натиснете „ИЗБИРАНЕ“"</b>" за достъп до менюто на телевизора."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Няма намерен вход на телевизора"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Входът на телевизора не може да бъде намерен"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Функцията „Картина в картината“ не се поддържа"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Няма наличен вход, който да може да се показва с PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Типът тунер не е подходящ. Моля, стартирайте приложението Live Channels за телевизионен вход от типа „тунер“."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Настройването не бе успешно"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Не бе намерено приложение за извършване на това действие."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Всички входящи канали са скрити.\nИзберете поне един, който да гледате."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Не е налице поради слаб видеосигнал"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Видеоклипът неочаквано не е налице"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Клавишът за връщане назад е за свързаното устройство. За изход натиснете бутона „Начало“."</string>
+</resources>
diff --git a/res/values-bn-rBD/arrays.xml b/res/values-bn-rBD/arrays.xml
new file mode 100644
index 00000000..c2e47923
--- /dev/null
+++ b/res/values-bn-rBD/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"স্বাভাবিক"</item>
+ <item msgid="2533030282864800794">"সম্পূর্ণ"</item>
+ <item msgid="8568284598210500589">"জুম"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"সমস্ত চ্যানেল"</item>
+ <item msgid="928298872841713530">"পরিবার/শিশু"</item>
+ <item msgid="2751606947569857164">"খেলাধুলা"</item>
+ <item msgid="7345749789651321496">"কেনাকাটা"</item>
+ <item msgid="167201149441442173">"চলচ্চিত্র"</item>
+ <item msgid="525966731464264290">"কমেডি"</item>
+ <item msgid="6096710741527327836">"ভ্রমণ"</item>
+ <item msgid="2851882187117833883">"নাটক"</item>
+ <item msgid="78492781188719038">"শিক্ষা"</item>
+ <item msgid="7221999662426308394">"প্রাণী/বন্যজীবন"</item>
+ <item msgid="375300513250925001">"সংবাদ"</item>
+ <item msgid="7746320336582330410">"গেমিং"</item>
+ <item msgid="1255741860568329178">"কলা"</item>
+ <item msgid="7603949681065702867">"বিনোদন"</item>
+ <item msgid="4453821994746804366">"জীবনশৈলী"</item>
+ <item msgid="3488534597567932843">"সঙ্গীত"</item>
+ <item msgid="7452153120614274095">"প্রিমিয়ার"</item>
+ <item msgid="8215762047341133299">"প্রযুক্তি/বিজ্ঞান"</item>
+ </string-array>
+</resources>
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..39cc07be
--- /dev/null
+++ b/res/values-bn-rBD/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"ড্রাগস"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"লিঙ্গ"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"হিংস্রতা"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"ড্রাগস"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"লিঙ্গ"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"হিংস্রতা"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"ফরাসি"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"প্রস্তাবিত সংলাপ"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"অমার্জিত ভাষা"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"যৌন সামগ্রী"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"হিংস্রতা"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"কল্পনাপ্রসূত হিংস্রতা"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"ফিল্ম রেটিং"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
new file mode 100644
index 00000000..d386fa84
--- /dev/null
+++ b/res/values-bn-rBD/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"লাইভ চ্যানেলগুলি"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"মোনো"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"স্টিরিও"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"খেলার নিয়ন্ত্রণগুলি"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"চ্যানেলগুলি"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"সাম্প্রতিক চ্যানেলগুলি"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"টিভি বিকল্পগুলি"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP বিকল্পগুলি"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"এই চ্যানেলটির জন্য প্লে নিয়ন্ত্রণগুলি অনুপলব্ধ"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"প্লে করুন বা বিরাম দিন"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"দ্রুত ফরওয়ার্ড"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"পেছনের দিকে যান"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"পরবর্তী"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"পূর্ববর্তী"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"প্রোগ্রাম গাইড"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"নতুন চ্যানেলগুলি উপলব্ধ"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"কোনো লিঙ্ক উপলব্ধ নেই"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> খুলুন"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"সাবটাইটেলগুলি"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"প্রদর্শন মোড"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"চালু"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"বন্ধ"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"একাধিক-অডিও"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"চ্যানেল সেটআপ"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"অভিভাবকীয় নিয়ন্ত্রণগুলি"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"সম্পর্কে"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"উৎস"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"সোয়াইপ করুন"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"চালু"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"বন্ধ"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"আওয়াজ"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"প্রধান"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP উইন্ডো"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"লেআউট"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"ডানদিকে নীচে"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"ডানদিকে শীর্ষে"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"বামদিকে শীর্ষে"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"বামদিকে নীচে"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"পাশাপাশি"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"আকার"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"বড়"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"ক্ষুদ্র"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"ইনপুট উৎস"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (অ্যান্টেনা/কেবল)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"কোনো প্রোগ্রাম তথ্য নেই"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"কোনো তথ্য নেই"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"অবরুদ্ধ চ্যানেল"</string>
+ <string name="default_language" msgid="4122326459624337928">"অজানা ভাষা"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"সাবটাইটেলগুলি"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"বন্ধ করুন"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"ফর্ম্যাটিং কাস্টমাইজ করুন"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"সাবটাইটেলগুলির জন্য সিস্টেম-ব্যাপী পছন্দগুলি সেট করুন"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"প্রদর্শন মোড"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"একাধিক-অডিও"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"মনো"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"স্টিরিও"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"৫.১ সারাউন্ড"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"৭.১ সারাউন্ড"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d চ্যানেলগুলি"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"চ্যানেল তালিকা কাস্টমাইজ করুন"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"গোষ্ঠী নির্বাচন করুন"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"গোষ্ঠী নির্বাচন মুক্ত করুন"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"এর ভিত্তিতে গোষ্ঠীভুক্ত করুন"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"চ্যানেল উৎস"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"এর ভিত্তিতে গোষ্ঠীভুক্ত করুন"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"চ্যানেল সেটআপ"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"এই প্রোগ্রামটি অবরুদ্ধ করা হয়েছে"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"এই প্রোগ্রামটি <xliff:g id="RATING">%1$s</xliff:g> রেট প্রাপ্ত৷"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"চ্যানেলের উৎসগুলি"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"নতুন চ্যানেলগুলি উপলব্ধ"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"চ্যানেল তালিকা কাস্টমাইজ করুন"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"আপনার প্রোগ্রাম গাইডের জন্য চ্যানেলগুলি নির্বাচন করুন"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"সেট আপ করা নেই"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"ইনপুটটি অটো-স্ক্যান সমর্থন করে না"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\'<xliff:g id="TV_INPUT">%s</xliff:g>\' এর জন্য স্বয়ংক্রিয়-স্ক্যান শুরু করা যায়নি"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"সাবটাইটেলগুলির জন্য সিস্টেম-ব্যাপী পছন্দগুলি শুরু করা যায়নি৷"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$dটি চ্যানেল যোগ করা হয়েছে"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$dটি চ্যানেল যোগ করা হয়েছে"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"কোনো চ্যানেল যোগ করা হয়নি"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"ট্যিউনার"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"অভিভাবকীয় নিয়ন্ত্রণগুলি"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"চালু করুন"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"বন্ধ করুন"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"অবরুদ্ধ চ্যানেলগুলি"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"সবগুলি অবরুদ্ধ করুন"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"সবগুলিকে অবরোধ মুক্ত করুন"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"লুকানো চ্যানেলগুলি"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"প্রোগ্রামের বিধিনিষেধগুলি"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"পিন পরিবর্তন করুন"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"রেটিং পদ্ধতি"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"রেটিংগুলি"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"রেটিংয়ের সমস্ত পদ্ধতি দেখুন"</string>
+ <string name="other_countries" msgid="8342216398676184749">"অন্যান্য দেশ"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"একটিও নেই"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"কোনো কিছুই নেই"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"কোনো কিছুই নেই"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"উচ্চ মাত্রার বিধিনিষেধগুলি"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"মাঝারি মাত্রার বিধিনিষেধগুলি"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"কম মাত্রার বিধিনিষেধগুলি"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"কাস্টম"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"শিশুদের জন্য উপযুক্ত সামগ্রী"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"বয়স্ক শিশুদের জন্য উপযুক্ত সামগ্রী"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"তেরো থেকে উনিশ বছর বয়সীদের জন্য উপযুক্ত সামগ্রী"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"নিজে করা সম্পর্কিত বিধিনিষেধগুলি"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s এবং উপ-রেটিংগুলি"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"উপ-রেটিংগুলি"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"এই চ্যানেলটি দেখতে আপনার পিন লিখুন"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"এই প্রোগ্রামটি দেখতে আপনার পিন লিখুন"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"আপনার পিন লিখুন"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"অভিভাবকীয় নিয়ন্ত্রণগুলি সেট করতে, একটি পিন তৈরি করুন"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"নতুন পিন লিখুন"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"আপনার পিন নিশ্চিত করুন"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"আপনার বর্তমান পিন লিখুন"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"আপনি ৫ বার ভুল পিন লিখেছেন।\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> সেকেন্ডের মধ্যে আবার চেষ্টা করুন।"</item>
+ <item quantity="other" msgid="8829550842387756054">"আপনি ৫ বার ভুল পিন লিখেছেন।\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> সেকেন্ডের মধ্যে আবার চেষ্টা করুন।"</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"এই PINটি ভুল ছিল৷ আবার চেষ্টা করুন৷"</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"আবার চেষ্টা করুন, পিন মেলেনি"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"সম্পর্কে"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"মুক্ত উৎস লাইসেন্সগুলি"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"মুক্ত উৎস লাইসেন্সগুলি"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"সংস্করণ"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"এই চ্যানেলটিকে দেখতে, ডানদিকে চাপুন এবং আপনার পিন লিখুন"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"এই প্রোগ্রামটি দেখতে, ডানদিকে চাপুন এবং আপনার পিন লিখুন"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"এই প্রোগ্রামটি <xliff:g id="RATING">%1$s</xliff:g> রেট প্রাপ্ত৷\nএই প্রোগ্রামটি দেখতে, ডানদিকে চাপুন এবং আপনার পিন লিখুন"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"প্রোগ্রামটি অবরুদ্ধ"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"এই প্রোগ্রামটি <xliff:g id="RATING">%1$s</xliff:g> রেট প্রাপ্ত৷"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"কোনো শিরোনাম নেই"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"চ্যানেল অবরুদ্ধ করা হয়েছে"</string>
+ <string name="episode_format" msgid="4881195874563241096">"সে<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: এপিঃ <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"চ্যানেলের উৎসগুলি"</string>
+ <string name="setup_description" msgid="8728423605912915099">"উপলব্ধ উৎসগুলি থেকে লাইভ চ্যানেলগুলি সেট আপ করুন। আপনার চ্যানেলের উৎসগুলির উপর নির্ভর করে এতে কয়েক মিনিট সময় লাগতে পারে৷"</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"সম্পন্ন"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$dটি চ্যানেল উপলব্ধ"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$dটি চ্যানেল উপলব্ধ"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"কোনো চ্যানেল উপলব্ধ নেই"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"নতুন"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"টিভি মেনু অ্যাক্সেস করতে "<b>"নির্বাচন করুন টিপুন"</b>"৷"</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"কোনো TV ইনপুট খুঁজে পাওয়া যায়নি"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"TV ইনপুট খুঁজে পাওয়া যায়নি"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP সমর্থিত নয়"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP এর সাথে দেখানো যেতে পারে এমন কোনো উপলব্ধ ইনপুট নেই"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"টিউনারের প্রকারটি উপযুক্ত নয়; টিউনার প্রকারের টিভি ইনপুটের জন্য দয়া করে লইভ চ্যানেলগুলি অ্যাপ্লিকেশানটি লঞ্চ করুন৷"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"টিউন করা ব্যর্থ হয়েছে"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"এই ক্রিয়াটিকে চালনা করার জন্য কোনো অ্যাপ্লিকেশান পাওয়া যায়নি৷"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"সমস্ত উৎস চ্যানেল লুকানো আছে৷\nদেখার জন্য কমপক্ষে একটি চ্যানেল নির্বাচন করুন৷"</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"দুর্বল ভিডিও সংকেতের কারণে অনুপলব্ধ"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"ভিডিওটি অপ্রত্যাশিতভাবে অনুপলব্ধ"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"\'ব্যাক\' কীটি সংযুক্ত ডিভাইসের ক্ষেত্রে ব্যবহারের জন্য৷ প্রস্থান করতে হোম বোতামটি টিপুন৷"</string>
+</resources>
diff --git a/res/values-ca/arrays.xml b/res/values-ca/arrays.xml
new file mode 100644
index 00000000..9b4c3e0e
--- /dev/null
+++ b/res/values-ca/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Complet"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Tots els canals"</item>
+ <item msgid="928298872841713530">"Familiar/infantil"</item>
+ <item msgid="2751606947569857164">"Esports"</item>
+ <item msgid="7345749789651321496">"Compres"</item>
+ <item msgid="167201149441442173">"Pel·lícules"</item>
+ <item msgid="525966731464264290">"Comèdia"</item>
+ <item msgid="6096710741527327836">"Viatges"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Educació"</item>
+ <item msgid="7221999662426308394">"Animals/fauna"</item>
+ <item msgid="375300513250925001">"Notícies"</item>
+ <item msgid="7746320336582330410">"Jocs"</item>
+ <item msgid="1255741860568329178">"Art"</item>
+ <item msgid="7603949681065702867">"Entreteniment"</item>
+ <item msgid="4453821994746804366">"Estil de vida"</item>
+ <item msgid="3488534597567932843">"Música"</item>
+ <item msgid="7452153120614274095">"Principal"</item>
+ <item msgid="8215762047341133299">"Ciència i tecnologia"</item>
+ </string-array>
+</resources>
diff --git a/res/values-ca/rating_system_strings.xml b/res/values-ca/rating_system_strings.xml
new file mode 100644
index 00000000..5f0e0cd0
--- /dev/null
+++ b/res/values-ca/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drogues"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sexe"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Violència"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drogues"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sexe"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Violència"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Francès"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Diàlegs suggerents"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Llenguatge vulgar"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Contingut sexual"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Violència"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Violència fantàstica"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Classificacions de pel·lícules"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
new file mode 100644
index 00000000..4e0499c3
--- /dev/null
+++ b/res/values-ca/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Canals en directe"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"estèreo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Controls de reproducció"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Canals"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Canals recents"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Opcions de TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Opcions de PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Els controls de reproducció no estan disponibles en aquest canal"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Reprodueix o atura"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Avança ràpidament"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Rebobina"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Següent"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Anterior"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Guia de programes"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Nous canals disponibles"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Sense enllaç disponible"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Obre <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Subtítols"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Mode visualitz."</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Activada"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Desactivada"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multiàudio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Configur. canal"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Control dels pares"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Informació"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Font"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Canvia"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Activada"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Desactivada"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"So"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Principal"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Finestra PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Disseny"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"A baix a dreta"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"A dalt a dreta"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"A dalt a esq."</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"A baix a esq."</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"En paral·lel"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Mida"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Gran"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Petita"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Font d\'entrada"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Televisió (antena/cable)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"No hi ha informació del programa"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"No hi ha informació"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Canal bloquejat"</string>
+ <string name="default_language" msgid="4122326459624337928">"Idioma desconegut"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Subtítols ocults"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Desactivat"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Personalitza format"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Estableix preferències de subtítols al sistema."</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Mode de visualitz."</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multiàudio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"estèreo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"So envoltant 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"So envoltant 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d canals"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Personalitza la llista"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Selecciona un grup"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Anul·la la selecció"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Agrupa per"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Font del canal"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"Alta definició/definició estàndard"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"Alta definició"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"Definició estàndard"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Agrupa per"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Configurac. canals"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Aquest programa està bloquejat"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Aquest programa està classificat com a <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Fonts de canals"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Nous canals disponibles"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Personalitza la llista"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Tria canals per a la guia de programes."</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"No s\'ha configurat."</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"L\'entrada no és compatible amb l\'exploració automàtica."</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"No es pot iniciar l\'exploració automàtica de: <xliff:g id="TV_INPUT">%s</xliff:g>."</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"No es poden iniciar les preferències de subtítols del sistema."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"S\'ha afegit %1$d canal."</item>
+ <item quantity="other" msgid="1078861616751739285">"S\'han afegit %1$d canals."</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"No s\'ha afegit cap canal."</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Sintonitzador"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Controls dels pares"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Activats"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Desactivats"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Canals bloquejats"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Bloqueja-ho tot"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Desbloqueja\'ls tots"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Canals amagats"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Restriccions prog."</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Canvia el PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Sistemes classif."</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Puntuacions"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Mostra tots sistemes"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Altres països"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Cap"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Cap"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Cap"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Restriccions altes"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Restricc. mitjanes"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Restriccions baixes"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Personalitzat"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Contingut apte per a infants"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Contingut apte per a nens més grans"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Contingut apte per a adolescents"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Restriccions manuals"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s i subclassif."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Subclassificacions"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Introducció del PIN per mirar aquest canal"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Introducció del PIN per mirar aquest programa"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Introducció del PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Per definir els controls dels pares, crea un PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Introdueix el PIN nou"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Confirmació del PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Introducció del PIN actual"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Has introduït un PIN incorrecte 5 vegades.\nTorna-ho a provar d\'aquí a <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segon."</item>
+ <item quantity="other" msgid="8829550842387756054">"Has introduït un PIN incorrecte 5 vegades.\nTorna-ho a provar d\'aquí a <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segons."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"El PIN era incorrecte. Torna-ho a provar."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Torna-ho a provar. El PIN no coincideix."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Informació"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Llicències de programari lliure"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Llicències de programari lliure"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versió"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Per veure aquest canal, prem el botó dret i introdueix el PIN."</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Per veure aquest programa, prem el botó dret i introdueix el PIN."</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Aquest programa està classificat com a <xliff:g id="RATING">%1$s</xliff:g>.\nPer veure\'l, prem el botó dret i introdueix el PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"El programa està bloquejat."</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Aquest programa està classificat com a <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Sense títol"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Canal bloquejat"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Temporada <xliff:g id="SEASON_NUMBER">%1$d</xliff:g> Capítol <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>: <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Fonts de canals"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Configura canals en directe a partir de les fonts disponibles. Això pot tardar uns quants minuts en funció de la font del canal."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Fet"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Hi ha %1$d canal disponible"</item>
+ <item quantity="other" msgid="2386588423841837714">"Hi ha %1$d canals disponibles"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"No hi ha cap canal disponible"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nou"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Prem SELECCIONA"</b>" per accedir al menú de TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"No s\'ha trobat cap entrada de televisió"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"No es troba l\'entrada de televisió"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"No s\'admet la funció PIP"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"No hi ha cap entrada disponible que es pugui mostrar amb PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"El sintonitzador no és apte; inicia l\'aplicació Canals en directe per accedir a l\'entrada del sintonitzador."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"No s\'ha pogut sintonitzar"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"No s\'ha trobat cap aplicació per processar aquesta acció."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Tots els canals d\'origen estan amagats.\nSelecciona com a mínim un canal per veure\'l."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"El senyal de vídeo és feble i, per tant, no està disponible"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"El vídeo ha deixat d\'estar disponible de manera inesperada"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"La tecla ENRERE és per als dispositius connectats. Prem el botó INICI per sortir."</string>
+</resources>
diff --git a/res/values-cs/arrays.xml b/res/values-cs/arrays.xml
new file mode 100644
index 00000000..6ac1a427
--- /dev/null
+++ b/res/values-cs/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normální"</item>
+ <item msgid="2533030282864800794">"Úplné"</item>
+ <item msgid="8568284598210500589">"Přiblížení"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Všechny kanály"</item>
+ <item msgid="928298872841713530">"Rodina/děti"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Nakupování"</item>
+ <item msgid="167201149441442173">"Filmy"</item>
+ <item msgid="525966731464264290">"Komedie"</item>
+ <item msgid="6096710741527327836">"Cestování"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Vzdělávání"</item>
+ <item msgid="7221999662426308394">"Zvířata/příroda"</item>
+ <item msgid="375300513250925001">"Zpravodajství"</item>
+ <item msgid="7746320336582330410">"Hraní her"</item>
+ <item msgid="1255741860568329178">"Umění"</item>
+ <item msgid="7603949681065702867">"Zábava"</item>
+ <item msgid="4453821994746804366">"Životní styl"</item>
+ <item msgid="3488534597567932843">"Hudba"</item>
+ <item msgid="7452153120614274095">"Premier"</item>
+ <item msgid="8215762047341133299">"Věda a technika"</item>
+ </string-array>
+</resources>
diff --git a/res/values-cs/rating_system_strings.xml b/res/values-cs/rating_system_strings.xml
new file mode 100644
index 00000000..f3383515
--- /dev/null
+++ b/res/values-cs/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drogy"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sex"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Násilí"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drogy"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sex"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Násilí"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"francouzština"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Obscénní dialogy"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Hrubé výrazy"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Sexuální obsah"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Násilí"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fiktivní násilí"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Hodnocení filmů"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644
index 00000000..5babda7c
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Aktivní kanály"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Ovládání přehrávání"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanály"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Poslední kanály"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Možnosti TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Možnosti PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Ovládací prvky přehrávání pro tento kanál nejsou k dispozici"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Přehrát nebo pozastavit"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Přetočit vpřed"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Přetočit zpět"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Další"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Předchozí"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programový průvodce"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Jsou dostupné nové kanály"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Odkaz není k dispozici"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Spustit aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Skryté titulky"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Režim zobrazení"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Zapnuto"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Vypnuto"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Vícekanál. zvuk"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Nastavení kanálu"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Rodičovská ochrana"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Informace"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Zdroj"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Zaměnit"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Zapnuto"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Vypnuto"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Zvuk"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Hlavní"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Okno PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Rozvržení"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Vpravo dole"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Vpravo nahoře"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Vlevo nahoře"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Vlevo dole"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Vedle sebe"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Rozměry"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Velké"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Malé"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Zdroj vstupu"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (anténa/kabel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Žádné informace o programu"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Žádné informace"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Blokovaný kanál"</string>
+ <string name="default_language" msgid="4122326459624337928">"Neznámý jazyk"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Skryté titulky"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Vypnuto"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Nastavit formátování"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Zadání sys. předvoleb pro skryté titulky"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Režim zobrazení"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Vícekanál. zvuk"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Prostorový zvuk 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Prostorový zvuk 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"Kanály: %d"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Upravit seznam kanálů"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Vybrat skupinu"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Zrušit výběr skupiny"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Seskupit podle"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Zdroj kanálu"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Seskupit podle"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Nastavení kanálu"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Tento program je blokován."</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Tento program má hodnocení <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Zdroje kanálů"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Jsou k dispozici nové kanály"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Upravit seznam kanálů"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Vyberte kanály pro programového průvodce"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Není nastaveno"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Vstup nepodporuje automatické vyhledávání"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Nelze spustit automatické vyhledávání vstupu <xliff:g id="TV_INPUT">%s</xliff:g>"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Celosystémové nastavení titulků nelze spustit."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"Byl přidán %1$d kanál"</item>
+ <item quantity="other" msgid="1078861616751739285">"Přidané kanály: %1$d"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Bylo přidáno 0 kanálů"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Rodičovská ochrana"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Zapnuto"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Vypnuto"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Blokované kanály"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Blokovat vše"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Odblokovat vše"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Skryté kanály"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Omezení programu"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Změna kódu PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Systémy hodnocení"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Hodnocení"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Všechny syst. hodnocení"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Ostatní země"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Žádný"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Žádný"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Žádné"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Vysoké omezení"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Střední omezení"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Nízké omezení"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Vlastní"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Obsah vhodný pro děti"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Obsah vhodný pro starší děti"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Obsah vhodný pro mladistvé"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Ruční omezení"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s a dílčí hod."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Dílčí hodnocení"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Chcete-li sledovat tento kanál, zadejte kód PIN."</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Chcete-li sledovat tento program, zadejte kód PIN."</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Zadání kódu PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Chcete-li nastavit rodičovskou kontrolu, vytvořte kód PIN."</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Zadejte nový PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Potvzení kódu PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Zadání aktuálního kódu PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Zadali jste nesprávný kód PIN pětkrát.\nZkuste to znovu za <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekundu."</item>
+ <item quantity="other" msgid="8829550842387756054">"Zadali jste nesprávný kód PIN pětkrát.\nZkuste to znovu za <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> s."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Kód PIN byl zadán chybně. Zkuste to znovu."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Kód PIN nesouhlasí. Zkuste to znovu."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Informace"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Licence open source"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Licence open source"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Verze"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Chcete-li tento kanál sledovat, stiskněte šipku vpravo a zadejte kód PIN."</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Chcete-li tento program sledovat, stiskněte šipku vpravo a zadejte kód PIN."</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Tento program má hodnocení <xliff:g id="RATING">%1$s</xliff:g>.\nChcete-li tento program sledovat, stiskněte šipku vpravo a zadejte kód PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Program je blokován"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Tento program má hodnocení <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Bez názvu"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanál byl zablokován"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Zdroje kanálů"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Nastavte aktivní kanály z dostupných zdrojů. V závislosti na zdroji kanálu to může trvat několik minut."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Hotovo"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Dostupné kanály: %1$d"</item>
+ <item quantity="other" msgid="2386588423841837714">"Dostupné kanály: %1$d"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Nejsou k dispozici žádné kanály"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nový"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"Chcete-li získat přístup k nabídce TV, "<b>"stiskněte SELECT"</b>"."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Nebyl nalezen žádný televizní vstup."</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Televizní vstup nebyl nalezen."</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Funkce PIP není podporována."</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Není k dispozici vstup, který lze zobrazovat pomocí PIP."</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Typ tuneru není vhodný. Pro TV vstup typu tuneru spusťte aplikaci Aktivní kanály."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Ladění se nezdařilo."</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Aplikace potřebná k provedení této akce nebyla nalezena."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Všechny zdrojové kanály jsou skryty.\nVyberte alespoň jeden kanál, který chcete sledovat."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Nedostupné z důvodu slabého signálu videa."</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Video nečekaně přestalo být dostupné."</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Tlačítko Zpět je určeno pro připojené zařízení. Ukončení provedete pomocí tlačítka Domů."</string>
+</resources>
diff --git a/res/values-da/arrays.xml b/res/values-da/arrays.xml
new file mode 100644
index 00000000..cd5bf655
--- /dev/null
+++ b/res/values-da/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Fuld"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Alle kanaler"</item>
+ <item msgid="928298872841713530">"Familie/børn"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Shopping"</item>
+ <item msgid="167201149441442173">"Film"</item>
+ <item msgid="525966731464264290">"Komedie"</item>
+ <item msgid="6096710741527327836">"Rejser"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Uddannelse"</item>
+ <item msgid="7221999662426308394">"Dyr/naturens verden"</item>
+ <item msgid="375300513250925001">"Nyheder"</item>
+ <item msgid="7746320336582330410">"Spil"</item>
+ <item msgid="1255741860568329178">"Kunst"</item>
+ <item msgid="7603949681065702867">"Underholdning"</item>
+ <item msgid="4453821994746804366">"Livsstil"</item>
+ <item msgid="3488534597567932843">"Musik"</item>
+ <item msgid="7452153120614274095">"Premiere"</item>
+ <item msgid="8215762047341133299">"Teknik/videnskab"</item>
+ </string-array>
+</resources>
diff --git a/res/values-da/rating_system_strings.xml b/res/values-da/rating_system_strings.xml
new file mode 100644
index 00000000..ec17ff29
--- /dev/null
+++ b/res/values-da/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Stoffer"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sex"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Vold"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Stoffer"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sex"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Vold"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Fransk"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Dialog med seksuelt orienteret indhold"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Groft sprogbrug"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Seksuelt indhold"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Vold"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantasivold"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Filmklassificering"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
new file mode 100644
index 00000000..72e44596
--- /dev/null
+++ b/res/values-da/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Tv-kanaler"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Afspilningsknapper"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanaler"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Seneste kanaler"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Tv-indstillinger"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP-muligheder"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Afspilningsstyring er ikke tilgængeligt på denne kanal"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Afspil, eller sæt på pause"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Spol frem"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Spol tilbage"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Næste"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Forrige"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programguide"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Der er nye tilgængelige kanaler"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Intet tilgængeligt link"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Åbn <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Undertekster"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Format"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Til"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Fra"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Flere lydspor"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Kanalkonfig."</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Forældrekontrol"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Om"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Kilde"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Skift"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Til"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Fra"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Lyd"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Primær"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP-vindue"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Layout"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Nederst til højre"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Øverst til højre"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Øverst til venstre"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Nederst til venstre"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Side om side"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Størrelse"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Stor"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Lille"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Inputkilde"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Tv (antenne/kabel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Ingen programoplysninger"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Ingen oplysninger"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Blokeret kanal"</string>
+ <string name="default_language" msgid="4122326459624337928">"Ukendt sprog"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Undertekster"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Fra"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Tilpas formatering"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Angiv dine indstillinger for undertekster"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Format"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Flere lydspor"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d kanaler"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Tilpas kanallisten"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Markér gruppe"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Fravælg gruppe"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Gruppér efter"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Kanalkilde"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Gruppér efter"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Kanalkonfiguration"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Dette program er blokeret"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Dette program er klassificereret som <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Kanalkilder"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Der er nye tilgængelige kanaler"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Tilpas kanallisten"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Vælg kanaler på din programoversigt"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Ikke konfigureret"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Dette input understøtter ikke automatisk scanning"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Automatisk scanning for \"<xliff:g id="TV_INPUT">%s</xliff:g>\" kunne ikke startes"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Præferencer for undertekster gældende for hele systemet kunne ikke startes."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d kanal blev tilføjet"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d kanaler tilføjet"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Ingen kanaler tilføjet"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Forældrekontrol"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Til"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Fra"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Blokerede kanaler"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Bloker alle"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Fjern alle blokeringer"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Skjulte kanaler"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Programbegrænsninger"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Skift pinkode"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Klassificeringssystemer"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Klassificeringer"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Se alle klassificeringssystemer"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Andre lande"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Ingen"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Ingen"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Ingen"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Høje begrænsninger"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Medium begrænsninger"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Lave begrænsninger"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Tilpasset"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Indholdet er egnet for børn"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Indholdet er passende for ældre børn"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Indholdet er egnet til teenagere"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Manuelle begrænsninger"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s og underklassificering"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Underklassificering"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Angiv din pinkode for at se denne kanal"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Angiv din pinkode for at se dette program"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Angiv din pinkode"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Angiv en pinkode for at aktivere forældrekontrol"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Angiv ny pinkode"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Bekræft din pinkode"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Angiv din aktuelle pinkode"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Du har indtastet den forkerte pinkode 5 gange.\nPrøv igen om <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekund."</item>
+ <item quantity="other" msgid="8829550842387756054">"Du har indtastet den forkerte pinkode 5 gange.\nPrøv igen om <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekunder."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Pinkoden var forkert. Prøv igen."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Prøv igen. Pinkoden var forkert"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Om"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Open source-licenser"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Open source-licenser"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Version"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Se denne kanal ved at trykke på højreknappen og angive din pinkode"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Se dette program ved at trykke på højreknappen og angive din pinkode"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Dette program er klassificereret som <xliff:g id="RATING">%1$s</xliff:g>.\nSe dette program ved at trykke på højreknappen og angive din pinkode"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Programmet er blokeret"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Dette program er klassificereret som <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Ingen titel"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanalen er blokeret"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Afsnit <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g><xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Kanalkilder"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Konfigurer tv-kanaler fra de tilgængelige kilder. Dette kan tage flere minutter afhængigt af kanalkilden."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Udfør"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d tilgængelig kanal"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d tilgængelige kanaler"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Der er ingen tilgængelige kanaler"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nye"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Tryk på VÆLG"</b>" for at få adgang til TV-menuen."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Der blev ikke fundet noget tv-input"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Tv-inputtet blev ikke fundet"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP understøttes ikke"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Der er intet tilgængeligt input, som kan vises med PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tunertypen er uegnet. Åbn appen Tv-kanaler for at få tv-input fra tunertypen."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Tuningen mislykkedes"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Der blev ikke fundet nogen app, der kan håndtere denne handling."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Alle kildekanaler er skjult.\nVælg mindst én kanal, som du vil se."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Utilgængelig på grund af svagt videosignal"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Videoen er mod forventning ikke tilgængelig"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Knappen Tilbage er til den tilsluttede enhed. Tryk på knappen Hjem for at afslutte."</string>
+</resources>
diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml
new file mode 100644
index 00000000..a8483d38
--- /dev/null
+++ b/res/values-de/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Voll"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Alle Kanäle"</item>
+ <item msgid="928298872841713530">"Familie/Kinder"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Shopping"</item>
+ <item msgid="167201149441442173">"Filme"</item>
+ <item msgid="525966731464264290">"Komödien"</item>
+ <item msgid="6096710741527327836">"Reisen"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Bildung"</item>
+ <item msgid="7221999662426308394">"Tiere/Wildtiere"</item>
+ <item msgid="375300513250925001">"Nachrichten"</item>
+ <item msgid="7746320336582330410">"Spiele"</item>
+ <item msgid="1255741860568329178">"Kunst"</item>
+ <item msgid="7603949681065702867">"Unterhaltung"</item>
+ <item msgid="4453821994746804366">"Lifestyle"</item>
+ <item msgid="3488534597567932843">"Musik"</item>
+ <item msgid="7452153120614274095">"Premiere"</item>
+ <item msgid="8215762047341133299">"Technik/Wissenschaft"</item>
+ </string-array>
+</resources>
diff --git a/res/values-de/rating_system_strings.xml b/res/values-de/rating_system_strings.xml
new file mode 100644
index 00000000..78050e5c
--- /dev/null
+++ b/res/values-de/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drogen"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sex"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Gewalt"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drogen"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sex"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Gewalt"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Französisch"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Anzüglicher Dialog"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Derbe Sprache"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Pornografische Inhalte"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Gewalt"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantasiegewalt"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Altersfreigaben für Filme"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 00000000..bb3b61e2
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"Mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"Stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Wiedergabesteuerung"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanäle"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Letzte Kanäle"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV-Optionen"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP-Optionen"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Wiedergabesteuerung für diesen Kanal nicht verfügbar"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Abspielen oder pausieren"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Vorspulen"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Zurückspulen"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Weiter"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Zurück"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programmübersicht"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Neue Kanäle verfügbar"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Kein Link verfügbar"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> öffnen"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Untertitel"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Anzeigemodus"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"An"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Aus"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-Audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Kanaleinrichtung"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Jugendschutz"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Info"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Quelle"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Wechseln"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"An"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Aus"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Ton"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Hauptoption"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP-Fenster"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Layout"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Unten rechts"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Oben rechts"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Oben links"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Unten links"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Nebeneinander"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Größe"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Groß"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Klein"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Eingangsquelle"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (Antenne/Kabel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Keine Programminformationen"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Keine Informationen"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Blockierter Kanal"</string>
+ <string name="default_language" msgid="4122326459624337928">"Unbekannte Sprache"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Untertitel"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Aus"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Untertitel anpassen"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Systemübergr. Einst. für Untertitel festlegen"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Anzeigemodus"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-Audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"Mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"Stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 Surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 Surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d Kanäle"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Kanalliste anpassen"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Gruppe auswählen"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Gruppenauswahl aufheben"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Gruppieren nach"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Kanalquelle"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Gruppieren nach"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Kanaleinrichtung"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Diese Sendung ist blockiert."</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Diese Sendung wurde als \"<xliff:g id="RATING">%1$s</xliff:g>\" eingestuft."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Kanalquellen"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Neue Kanäle verfügbar"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Kanalliste anpassen"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Kanäle für Programmübersicht auswählen"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Nicht eingerichtet"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Der Eingang unterstützt keine automatische Suche."</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Automatische Suche für \"<xliff:g id="TV_INPUT">%s</xliff:g>\" kann nicht gestartet werden."</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Systemweite Einstellungen für Untertitel können nicht aufgerufen werden."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d Kanal hinzugefügt"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d Kanäle hinzugefügt"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Keine Kanäle hinzugefügt"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Jugendschutz"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"An"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Aus"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Kanäle blockiert"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Alle blockieren"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Alle Block. aufheben"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Ausgeblendete Kanäle"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Einschränkungen"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN ändern"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Bewertungssysteme"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Bewertungen"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Alle Systeme ansehen"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Andere Länder"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Keiner"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Ohne"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Ohne"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Hohe Einschränkung"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Mäßige Einschränkung"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Wenig Einschränkung"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Personalisieren"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Inhalt für Kinder geeignet"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Inhalt für ältere Kinder geeignet"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Inhalt für Jugendliche geeignet"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Manuelle Beschränkungen"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s + Subkategorien"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Subkategorien"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Zum Ansehen dieses Kanals PIN eingeben"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Zum Ansehen dieses Programms PIN eingeben"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"PIN eingeben"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Erstellen Sie eine PIN, um den Jugendschutz einzurichten."</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Neue PIN eingeben"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"PIN bestätigen"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Aktuelle PIN eingeben"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Sie haben 5 Mal die falsche PIN eingegeben.\nVersuchen Sie es in <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> Sekunde erneut."</item>
+ <item quantity="other" msgid="8829550842387756054">"Sie haben 5 Mal die falsche PIN eingegeben.\nVersuchen Sie es in <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> Sekunden erneut."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Diese PIN war falsch. Versuchen Sie es erneut."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Die PIN stimmt nicht. Bitte versuchen Sie es erneut."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Info"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Open-Source-Lizenzen"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Open-Source-Lizenzen"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Version"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Um sich diesen Kanal anzusehen, drücken Sie rechts und geben Sie die PIN ein."</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Um sich dieses Programm anzusehen, drücken Sie rechts und geben Sie die PIN ein."</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Diese Sendung wurde als \"<xliff:g id="RATING">%1$s</xliff:g>\" eingestuft.\nUm sich diese Sendung anzusehen, drücken Sie rechts und geben Sie Ihre PIN ein."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Das Programm wurde blockiert."</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Diese Sendung wurde als \"<xliff:g id="RATING">%1$s</xliff:g>\" eingestuft."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Kein Titel"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanal blockiert"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Staffel <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Folge <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Kanalquellen"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Richten Sie Livekanäle aus den verfügbaren Quellen ein. Dies kann je nach Kanalquelle einige Minuten dauern."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Fertig"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d Kanal verfügbar"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d Kanäle verfügbar"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Keine Kanäle verfügbar"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Neu"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Drücken Sie die Auswahltaste"</b>", um auf das TV-Menü zuzugreifen."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Kein TV-Eingang gefunden"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"TV-Eingang konnte nicht gefunden werden."</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP wird nicht unterstützt."</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Es ist kein Eingang für die PIP-Anzeige verfügbar."</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tunertyp ungeeignet, bitte Live Channels App für TV-Eingang des Tunertyps starten"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Fehler beim Einstellen"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Für diese Aktion wurde keine App gefunden."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Alle Quellkanäle sind ausgeblendet.\nWählen Sie mindestens einen Kanal aus, den Sie anschauen möchten."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Aufgrund von schwachem Videosignal nicht verfügbar"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Das Video ist unerwarteterweise nicht verfügbar."</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Die Taste \"Zurück\" gilt für das verbundene Gerät. Zum Beenden drücken Sie auf \"Startseite\"."</string>
+</resources>
diff --git a/res/values-el/arrays.xml b/res/values-el/arrays.xml
new file mode 100644
index 00000000..cc8cc4ec
--- /dev/null
+++ b/res/values-el/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Κανονική"</item>
+ <item msgid="2533030282864800794">"Πλήρης"</item>
+ <item msgid="8568284598210500589">"Ζουμ"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Όλα τα κανάλια"</item>
+ <item msgid="928298872841713530">"Οικογενεικά/παιδικά"</item>
+ <item msgid="2751606947569857164">"Αθλητικά"</item>
+ <item msgid="7345749789651321496">"Αγορές"</item>
+ <item msgid="167201149441442173">"Ταινίες"</item>
+ <item msgid="525966731464264290">"Κωμωδία"</item>
+ <item msgid="6096710741527327836">"Ταξίδια"</item>
+ <item msgid="2851882187117833883">"Δραματική"</item>
+ <item msgid="78492781188719038">"Εκπαίδευση"</item>
+ <item msgid="7221999662426308394">"Ζώα/άγρια φύση"</item>
+ <item msgid="375300513250925001">"Ειδήσεις"</item>
+ <item msgid="7746320336582330410">"Παιχνίδια"</item>
+ <item msgid="1255741860568329178">"Τέχνες"</item>
+ <item msgid="7603949681065702867">"Ψυχαγωγία"</item>
+ <item msgid="4453821994746804366">"Lifestyle"</item>
+ <item msgid="3488534597567932843">"Μουσική"</item>
+ <item msgid="7452153120614274095">"Πρεμιέρες"</item>
+ <item msgid="8215762047341133299">"Τεχνολογία/Επιστήμη"</item>
+ </string-array>
+</resources>
diff --git a/res/values-el/rating_system_strings.xml b/res/values-el/rating_system_strings.xml
new file mode 100644
index 00000000..dedd57ed
--- /dev/null
+++ b/res/values-el/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Ναρκωτικές ουσίες"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Σεξ"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Βία"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Ναρκωτικές ουσίες"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Σεξ"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Βία"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Γαλλικά"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Προκλητικοί διάλογοι"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Χυδαία γλώσσα"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Σεξουαλικό περιεχόμενο"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Βία"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Φανταστική βία"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Αξιολογήσεις ταινιών"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
new file mode 100644
index 00000000..e4d2d8a1
--- /dev/null
+++ b/res/values-el/strings.xml
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Κανάλια ζωντανά"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"μονοφων."</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"στερεοφ."</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Στοιχ. ελέγ. αναπαραγωής"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Κανάλια"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Πρόσφατα κανάλια"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Επιλογές TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Επιλογές PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Τα στοιχεία ελέγχου αναπαραγωγής δεν είναι διαθέσιμα γι\' αυτό το κανάλι"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Αναπαραγωγή ή παύση"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Γρήγορη προώθηση"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Επαναφορά"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Επόμενο"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Προηγούμενο"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Οδηγός προγράμματος"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Νέα κανάλια διαθέσιμα"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Δεν υπάρχει σύνδεσμος"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Ανοίξτε το <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Υπότιτλοι"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Τρόπος εμφάν."</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Ενεργό"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Ανενεργό"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Πολλαπλός ήχος"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Ρύθμ. καναλιού"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Γονικός έλεγχος"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Πληροφορίες"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Πηγή"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Ανταλλαγή"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Ενεργό"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Ανενεργό"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Ήχος"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Κύριο"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Παράθυρο PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Διάταξη"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Κάτω δεξιά"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Επάνω δεξιά"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Επάνω αριστερά"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Κάτω αριστερά"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Δίπλα"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Μέγεθος"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Μεγάλο"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Μικρό"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Πηγή εισόδου"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Τηλεόραση (κεραία/καλωδιακή)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Δεν υπάρχουν πληροφορίες προγράμματος"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Δεν υπάρχουν πληροφορίες"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Αποκλεισμένο κανάλι"</string>
+ <string name="default_language" msgid="4122326459624337928">"Άγνωστη γλώσσα"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Υπότιτλοι"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Ανενεργό"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Προσ. μορφοποίησης"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Ορίστε προτ. συστήματος για υπότιτλους"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Τρόπος εμφάν."</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Πολλαπλός ήχος"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"μονοφωνικός ήχος"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"στερεοφωνικός ήχος"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Περιφερειακός ήχος 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Περιφερειακός ήχος 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d κανάλια"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Προσ. λίστ. καναλ."</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Επιλογή ομάδας"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Κατ. επιλογής ομάδας"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Ομαδοποίηση κατά"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Πηγή καναλιού"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Ομαδοποίηση κατά"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Ρύθμιση καναλιού"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Αυτό το πρόγραμμα αποκλείστηκε"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Αυτό το πρόγραμμα αξιολογήθηκε ως <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Πηγές καναλιών"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Νέα κανάλια διαθέσιμα"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Προσ. λίστ. καναλ."</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Επιλέξτε κανάλια για οδηγό προγράμματος"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Δεν ρυθμίστηκε"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Η είσοδος δεν υποστηρίζει την αυτόματη σάρωση"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Αδύνατη η εκκίνηση της αυτόματης σάρωσης για \"<xliff:g id="TV_INPUT">%s</xliff:g>\""</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Δεν είναι δυνατή η έναρξη προτιμήσεων σε όλο το σύστημα για τους υπότιτλους."</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for msg_channel_added:one (6074059986849579215) -->
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Δεν προστέθηκαν κανάλια"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Δέκτης"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Γονικός έλεγχος"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Ενεργό"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Ανενεργό"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Αποκλ. καναλιών"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Αποκλεισμός όλων"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Κατ.αποκλ.όλων"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Κρυφά κανάλια"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Περιορισμοί προγ."</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Αλλαγή PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Συστήματα αξιολ."</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Αξιολογήσεις"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Προβ.συστ.αξιολ."</string>
+ <string name="other_countries" msgid="8342216398676184749">"Άλλες χώρες"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Κανένα"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Κανένα"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Κανένα"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Υψηλοί περιορισμοί"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Μεσαίοι περιορισμοί"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Χαμηλοί περιορισμοί"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Προσαρμοσμένο"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Περιεχόμενο κατάλληλο για παιδιά"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Περιεχόμενο κατάλληλο για μεγάλα παιδιά"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Το περιεχόμενο είναι κατάλληλο για εφήβους"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Μη αυτόματοι περιορισμοί"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s &amp; δευτ.αξιολ."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Δευτ. αξιολογήσεις"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Εισαγάγετε PIN για να δείτε αυτό το κανάλι"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Εισαγάγετε PIN για να δείτε αυτό το πρόγραμμα"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Εισαγάγετε το PIN σας"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Για να ορίσετε γονικούς ελέγχους, δημιουργήστε ένα PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Εισαγωγή νέου κωδικού PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Επιβεβαιώστε το PIN σας"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Εισαγάγετε το τρέχον PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Έχετε εισαγάγει λανθασμένα το PIN σας 5 φορές.\nΔοκιμάστε ξανά σε <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> δευτερόλεπτο."</item>
+ <item quantity="other" msgid="8829550842387756054">"Έχετε εισαγάγει λανθασμένα τον κωδικό PIN 5 φορές. \n Δοκιμάστε ξανά σε <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> δευτερόλεπτα."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Λάθος PIN. Δοκιμάστε ξανά."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Δοκιμάστε ξανά, δεν υπάρχει αντιστοιχία PIN"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Πληροφορίες"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Άδειες λογισμικού ανοικτού κώδικα"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Άδειες λογισμικού ανοικτού κώδικα"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Έκδοση"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Για να παρακολουθήσετε αυτό το κανάλι, πατήστε το δεξιά και εισαγάγετε το PIN σας"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Για να παρακολουθήσετε αυτό το πρόγραμμα, πατήστε δεξιά και εισαγάγετε το PIN σας"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Αυτό το πρόγραμμα αξιολογήθηκε ως <xliff:g id="RATING">%1$s</xliff:g>.\nΓια να παρακολουθήσετε αυτό το πρόγραμμα, πατήστε δεξιά και εισαγάγετε το PIN σας"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Το πρόγραμμα αποκλείστηκε"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Αυτό το πρόγραμμα αξιολογήθηκε ως <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Χωρίς τίτλο"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Το κανάλι αποκλείστηκε"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Σεζ. <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Επ. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Πηγές καναλιών"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Ρυθμίστε ζωντανά κανάλια από τις διαθέσιμες πηγές. Αυτό μπορεί να διαρκέσει αρκετά λεπτά, ανάλογα με την πηγή καναλιού"</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Τέλος"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d διαθέσιμο κανάλι"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d διαθέσιμα κανάλια"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Δεν υπάρχουν διαθέσιμα κανάλια"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Νέα"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Πατήστε το πλήκτρο SELECT"</b>" για να μεταβείτε στο μενού της τηλεόρασης."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Δεν βρέθηκε είσοδος τηλεόρασης"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Δεν είναι δυνατή η εύρεση εισόδου τηλεόρασης"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Το PIP δεν υποστηρίζεται"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Δεν υπάρχει διαθέσιμη είσοδος για εμφάνιση με PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Ακατάλληλος τύπος δέκτη. Εκκινήστε την εφαρμογή Κανάλια ζωντανά για είσοδο τύπου δέκτη τηλεόρασης."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Αποτυχία συντονισμού"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Δεν βρέθηκε εφαρμογή για τη διαχείριση αυτής της ενέργειας."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Όλα τα κανάλια προέλευσης είναι κρυφά.\nΕπιλέξτε τουλάχιστον ένα κανάλι για να παρακολουθήσετε."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Μη διαθέσιμο λόγω αδύναμου σήματος βίντεο"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Το βίντεο δεν είναι διαθέσιμο απροσδόκητα"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Το πλήκτρο BACK αφορά τη συνδεδεμένη συσκευή. Πατήστε το πλήκτρο HOME για έξοδο."</string>
+</resources>
diff --git a/res/values-en-rAU/arrays.xml b/res/values-en-rAU/arrays.xml
new file mode 100644
index 00000000..03b775c6
--- /dev/null
+++ b/res/values-en-rAU/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Full"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"All channels"</item>
+ <item msgid="928298872841713530">"Family/Kids"</item>
+ <item msgid="2751606947569857164">"Sports"</item>
+ <item msgid="7345749789651321496">"Shopping"</item>
+ <item msgid="167201149441442173">"Films"</item>
+ <item msgid="525966731464264290">"Comedy"</item>
+ <item msgid="6096710741527327836">"Travel"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Education"</item>
+ <item msgid="7221999662426308394">"Animal/Wildlife"</item>
+ <item msgid="375300513250925001">"News"</item>
+ <item msgid="7746320336582330410">"Gaming"</item>
+ <item msgid="1255741860568329178">"Arts"</item>
+ <item msgid="7603949681065702867">"Entertainment"</item>
+ <item msgid="4453821994746804366">"Lifestyle"</item>
+ <item msgid="3488534597567932843">"Music"</item>
+ <item msgid="7452153120614274095">"Premier"</item>
+ <item msgid="8215762047341133299">"Tech/Science"</item>
+ </string-array>
+</resources>
diff --git a/res/values-en-rAU/rating_system_strings.xml b/res/values-en-rAU/rating_system_strings.xml
new file mode 100644
index 00000000..d1e836b3
--- /dev/null
+++ b/res/values-en-rAU/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drugs"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sex"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Violence"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drugs"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sex"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Violence"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"French"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Suggestive dialogue"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Coarse language"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Sexual content"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Violence"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantasy violence"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Film ratings"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
new file mode 100644
index 00000000..8f9469db
--- /dev/null
+++ b/res/values-en-rAU/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Play controls"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Channels"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Recent channels"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV options"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP options"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Play controls unavailable for this channel"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Play or pause"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Fast-forward"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Rewind"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Next"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Previous"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programme guide"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"New channels available"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"No link available"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Open <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Closed captions"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Display mode"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"On"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Off"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Channel setup"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Parental controls"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"About"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Source"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Swap"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"On"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Off"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Sound"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Main"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP window"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Layout"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Bottom right"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Top right"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Top left"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Bottom left"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Side by side"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Size"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Big"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Small"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Input source"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (aerial/cable)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"No programme information"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"No information"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Blocked channel"</string>
+ <string name="default_language" msgid="4122326459624337928">"Unknown language"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Closed captions"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Off"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Customise formatting"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Set system-wide preferences for closed captions"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Display mode"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d channels"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Customise channel list"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Select group"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Deselect group"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Group by"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Channel source"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Group by"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Channel setup"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"This programme is blocked"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"This programme is rated <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Channel sources"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"New channels available"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Customise channel list"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Choose channels for your programme guide"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Not set up"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"The input doesn\'t support auto-scan"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Unable to start auto-scan for \'<xliff:g id="TV_INPUT">%s</xliff:g>\'"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Unable to start the system-wide preferences for closed captions."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d channel added"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d channels added"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"No channels added"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Parental controls"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"On"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Off"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Channels blocked"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Block all"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Unblock all"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Hidden channels"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Programme restrictions"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Change PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Rating systems"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Ratings"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"See all rating systems"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Other countries"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"None"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"None"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"None"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"High restrictions"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Medium restrictions"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Low restrictions"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Customised"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Content suitable for children"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Content suitable for older children"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Content suitable for teens"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Manual restrictions"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s and sub-ratings"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Sub-ratings"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Enter your PIN to watch this channel"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Enter your PIN to watch this programme"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Enter your PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"To set parental controls, create a PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Enter new PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Confirm your PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Enter your current PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"You entered the wrong PIN 5 times.\nTry again in <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> second."</item>
+ <item quantity="other" msgid="8829550842387756054">"You entered the wrong PIN 5 times.\nTry again in <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> seconds."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"That PIN was wrong. Try again."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Try again, PIN doesn\'t match"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"About"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Open source licences"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Open source licences"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Version"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"To watch this channel, press Right and enter your PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"To watch this program, press Right and enter your PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"This programme is rated <xliff:g id="RATING">%1$s</xliff:g>.\nTo watch this programme, press Right and enter your PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Programme is blocked"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"This programme is rated <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"No title"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Channel blocked"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Channel sources"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Set up live channels from the available sources. This may take several minutes depending on the channel source."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Finished"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d channel available"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d channels available"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"No channels available"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"New"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Press SELECT"</b>" to access the TV menu."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"No TV input found"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Cannot find the TV input"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP is not supported"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"There is no available input which can be shown with PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tuner type not suitable. Please launch Live Channels app for tuner type TV input."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Tune failed"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"No app was found to handle this action."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"All source channels are hidden.\nSelect at least one channel to watch."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Unavailable due to weak video signal"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"The video is unexpectedly unavailable"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"BACK key is for connected device. Press HOME button to exit."</string>
+</resources>
diff --git a/res/values-en-rGB/arrays.xml b/res/values-en-rGB/arrays.xml
new file mode 100644
index 00000000..03b775c6
--- /dev/null
+++ b/res/values-en-rGB/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Full"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"All channels"</item>
+ <item msgid="928298872841713530">"Family/Kids"</item>
+ <item msgid="2751606947569857164">"Sports"</item>
+ <item msgid="7345749789651321496">"Shopping"</item>
+ <item msgid="167201149441442173">"Films"</item>
+ <item msgid="525966731464264290">"Comedy"</item>
+ <item msgid="6096710741527327836">"Travel"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Education"</item>
+ <item msgid="7221999662426308394">"Animal/Wildlife"</item>
+ <item msgid="375300513250925001">"News"</item>
+ <item msgid="7746320336582330410">"Gaming"</item>
+ <item msgid="1255741860568329178">"Arts"</item>
+ <item msgid="7603949681065702867">"Entertainment"</item>
+ <item msgid="4453821994746804366">"Lifestyle"</item>
+ <item msgid="3488534597567932843">"Music"</item>
+ <item msgid="7452153120614274095">"Premier"</item>
+ <item msgid="8215762047341133299">"Tech/Science"</item>
+ </string-array>
+</resources>
diff --git a/res/values-en-rGB/rating_system_strings.xml b/res/values-en-rGB/rating_system_strings.xml
new file mode 100644
index 00000000..d1e836b3
--- /dev/null
+++ b/res/values-en-rGB/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drugs"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sex"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Violence"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drugs"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sex"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Violence"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"French"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Suggestive dialogue"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Coarse language"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Sexual content"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Violence"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantasy violence"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Film ratings"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
new file mode 100644
index 00000000..8f9469db
--- /dev/null
+++ b/res/values-en-rGB/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Play controls"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Channels"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Recent channels"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV options"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP options"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Play controls unavailable for this channel"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Play or pause"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Fast-forward"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Rewind"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Next"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Previous"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programme guide"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"New channels available"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"No link available"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Open <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Closed captions"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Display mode"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"On"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Off"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Channel setup"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Parental controls"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"About"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Source"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Swap"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"On"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Off"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Sound"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Main"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP window"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Layout"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Bottom right"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Top right"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Top left"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Bottom left"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Side by side"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Size"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Big"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Small"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Input source"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (aerial/cable)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"No programme information"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"No information"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Blocked channel"</string>
+ <string name="default_language" msgid="4122326459624337928">"Unknown language"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Closed captions"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Off"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Customise formatting"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Set system-wide preferences for closed captions"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Display mode"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d channels"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Customise channel list"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Select group"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Deselect group"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Group by"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Channel source"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Group by"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Channel setup"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"This programme is blocked"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"This programme is rated <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Channel sources"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"New channels available"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Customise channel list"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Choose channels for your programme guide"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Not set up"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"The input doesn\'t support auto-scan"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Unable to start auto-scan for \'<xliff:g id="TV_INPUT">%s</xliff:g>\'"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Unable to start the system-wide preferences for closed captions."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d channel added"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d channels added"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"No channels added"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Parental controls"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"On"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Off"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Channels blocked"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Block all"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Unblock all"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Hidden channels"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Programme restrictions"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Change PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Rating systems"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Ratings"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"See all rating systems"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Other countries"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"None"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"None"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"None"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"High restrictions"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Medium restrictions"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Low restrictions"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Customised"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Content suitable for children"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Content suitable for older children"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Content suitable for teens"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Manual restrictions"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s and sub-ratings"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Sub-ratings"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Enter your PIN to watch this channel"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Enter your PIN to watch this programme"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Enter your PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"To set parental controls, create a PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Enter new PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Confirm your PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Enter your current PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"You entered the wrong PIN 5 times.\nTry again in <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> second."</item>
+ <item quantity="other" msgid="8829550842387756054">"You entered the wrong PIN 5 times.\nTry again in <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> seconds."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"That PIN was wrong. Try again."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Try again, PIN doesn\'t match"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"About"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Open source licences"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Open source licences"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Version"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"To watch this channel, press Right and enter your PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"To watch this program, press Right and enter your PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"This programme is rated <xliff:g id="RATING">%1$s</xliff:g>.\nTo watch this programme, press Right and enter your PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Programme is blocked"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"This programme is rated <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"No title"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Channel blocked"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Channel sources"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Set up live channels from the available sources. This may take several minutes depending on the channel source."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Finished"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d channel available"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d channels available"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"No channels available"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"New"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Press SELECT"</b>" to access the TV menu."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"No TV input found"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Cannot find the TV input"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP is not supported"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"There is no available input which can be shown with PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tuner type not suitable. Please launch Live Channels app for tuner type TV input."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Tune failed"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"No app was found to handle this action."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"All source channels are hidden.\nSelect at least one channel to watch."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Unavailable due to weak video signal"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"The video is unexpectedly unavailable"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"BACK key is for connected device. Press HOME button to exit."</string>
+</resources>
diff --git a/res/values-en-rIN/arrays.xml b/res/values-en-rIN/arrays.xml
new file mode 100644
index 00000000..03b775c6
--- /dev/null
+++ b/res/values-en-rIN/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Full"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"All channels"</item>
+ <item msgid="928298872841713530">"Family/Kids"</item>
+ <item msgid="2751606947569857164">"Sports"</item>
+ <item msgid="7345749789651321496">"Shopping"</item>
+ <item msgid="167201149441442173">"Films"</item>
+ <item msgid="525966731464264290">"Comedy"</item>
+ <item msgid="6096710741527327836">"Travel"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Education"</item>
+ <item msgid="7221999662426308394">"Animal/Wildlife"</item>
+ <item msgid="375300513250925001">"News"</item>
+ <item msgid="7746320336582330410">"Gaming"</item>
+ <item msgid="1255741860568329178">"Arts"</item>
+ <item msgid="7603949681065702867">"Entertainment"</item>
+ <item msgid="4453821994746804366">"Lifestyle"</item>
+ <item msgid="3488534597567932843">"Music"</item>
+ <item msgid="7452153120614274095">"Premier"</item>
+ <item msgid="8215762047341133299">"Tech/Science"</item>
+ </string-array>
+</resources>
diff --git a/res/values-en-rIN/rating_system_strings.xml b/res/values-en-rIN/rating_system_strings.xml
new file mode 100644
index 00000000..d1e836b3
--- /dev/null
+++ b/res/values-en-rIN/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drugs"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sex"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Violence"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drugs"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sex"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Violence"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"French"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Suggestive dialogue"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Coarse language"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Sexual content"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Violence"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantasy violence"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Film ratings"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
new file mode 100644
index 00000000..8f9469db
--- /dev/null
+++ b/res/values-en-rIN/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Play controls"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Channels"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Recent channels"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV options"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP options"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Play controls unavailable for this channel"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Play or pause"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Fast-forward"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Rewind"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Next"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Previous"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programme guide"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"New channels available"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"No link available"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Open <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Closed captions"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Display mode"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"On"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Off"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Channel setup"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Parental controls"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"About"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Source"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Swap"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"On"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Off"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Sound"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Main"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP window"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Layout"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Bottom right"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Top right"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Top left"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Bottom left"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Side by side"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Size"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Big"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Small"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Input source"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (aerial/cable)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"No programme information"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"No information"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Blocked channel"</string>
+ <string name="default_language" msgid="4122326459624337928">"Unknown language"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Closed captions"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Off"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Customise formatting"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Set system-wide preferences for closed captions"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Display mode"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d channels"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Customise channel list"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Select group"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Deselect group"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Group by"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Channel source"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Group by"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Channel setup"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"This programme is blocked"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"This programme is rated <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Channel sources"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"New channels available"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Customise channel list"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Choose channels for your programme guide"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Not set up"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"The input doesn\'t support auto-scan"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Unable to start auto-scan for \'<xliff:g id="TV_INPUT">%s</xliff:g>\'"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Unable to start the system-wide preferences for closed captions."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d channel added"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d channels added"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"No channels added"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Parental controls"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"On"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Off"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Channels blocked"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Block all"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Unblock all"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Hidden channels"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Programme restrictions"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Change PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Rating systems"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Ratings"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"See all rating systems"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Other countries"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"None"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"None"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"None"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"High restrictions"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Medium restrictions"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Low restrictions"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Customised"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Content suitable for children"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Content suitable for older children"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Content suitable for teens"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Manual restrictions"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s and sub-ratings"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Sub-ratings"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Enter your PIN to watch this channel"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Enter your PIN to watch this programme"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Enter your PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"To set parental controls, create a PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Enter new PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Confirm your PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Enter your current PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"You entered the wrong PIN 5 times.\nTry again in <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> second."</item>
+ <item quantity="other" msgid="8829550842387756054">"You entered the wrong PIN 5 times.\nTry again in <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> seconds."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"That PIN was wrong. Try again."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Try again, PIN doesn\'t match"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"About"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Open source licences"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Open source licences"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Version"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"To watch this channel, press Right and enter your PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"To watch this program, press Right and enter your PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"This programme is rated <xliff:g id="RATING">%1$s</xliff:g>.\nTo watch this programme, press Right and enter your PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Programme is blocked"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"This programme is rated <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"No title"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Channel blocked"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Channel sources"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Set up live channels from the available sources. This may take several minutes depending on the channel source."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Finished"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d channel available"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d channels available"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"No channels available"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"New"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Press SELECT"</b>" to access the TV menu."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"No TV input found"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Cannot find the TV input"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP is not supported"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"There is no available input which can be shown with PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tuner type not suitable. Please launch Live Channels app for tuner type TV input."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Tune failed"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"No app was found to handle this action."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"All source channels are hidden.\nSelect at least one channel to watch."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Unavailable due to weak video signal"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"The video is unexpectedly unavailable"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"BACK key is for connected device. Press HOME button to exit."</string>
+</resources>
diff --git a/res/values-es-rUS/arrays.xml b/res/values-es-rUS/arrays.xml
new file mode 100644
index 00000000..8873f092
--- /dev/null
+++ b/res/values-es-rUS/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Completa"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Todos los canales"</item>
+ <item msgid="928298872841713530">"Familiar/Infantil"</item>
+ <item msgid="2751606947569857164">"Deportes"</item>
+ <item msgid="7345749789651321496">"Compras"</item>
+ <item msgid="167201149441442173">"Películas"</item>
+ <item msgid="525966731464264290">"Comedia"</item>
+ <item msgid="6096710741527327836">"Viajes"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Educación"</item>
+ <item msgid="7221999662426308394">"Animal/Vida salvaje"</item>
+ <item msgid="375300513250925001">"Noticias"</item>
+ <item msgid="7746320336582330410">"Juegos"</item>
+ <item msgid="1255741860568329178">"Arte"</item>
+ <item msgid="7603949681065702867">"Entretenimiento"</item>
+ <item msgid="4453821994746804366">"Estilo de vida"</item>
+ <item msgid="3488534597567932843">"Música"</item>
+ <item msgid="7452153120614274095">"Estrenos"</item>
+ <item msgid="8215762047341133299">"Ciencia y tecnología"</item>
+ </string-array>
+</resources>
diff --git a/res/values-es-rUS/rating_system_strings.xml b/res/values-es-rUS/rating_system_strings.xml
new file mode 100644
index 00000000..18a0fd69
--- /dev/null
+++ b/res/values-es-rUS/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drogas"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sexo"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Violencia"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drogas"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sexo"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Violencia"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Francés"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Diálogo con contenido provocativo"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Lenguaje vulgar"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Contenido sexual"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Violencia"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Violencia de fantasía"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Clasificaciones de películas"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
new file mode 100644
index 00000000..62a28953
--- /dev/null
+++ b/res/values-es-rUS/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Canales en vivo"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"estéreo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Controles de reproducción"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Canales"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Canales recientes"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Opciones de TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Opciones de PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Los controles de reproducción no están disponibles en este canal."</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Reproducir o pausar"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Avanzar"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Retroceder"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Siguiente"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Anterior"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Guía de programas"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Nuevos canales disp."</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Sin vínculo disponible"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Abrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Subtítulos"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Modo pantalla"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Activada"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Desactivada"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Varios audios"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Configuración"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Cont parentales"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Información"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Fuente"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Cambiar"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Activada"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Desactivada"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Sonido"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Principal"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Ventana PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Diseño"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Abajo derecha"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Arriba derecha"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Arriba izqda."</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Abajo izquierda"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Lado a lado"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Tamaño"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Grande"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Pequeña"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Fuente de entrada"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Televisión (antena/cable)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"No hay información del programa"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Sin información"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Canal bloqueado"</string>
+ <string name="default_language" msgid="4122326459624337928">"Idioma desconocido"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Subtítulos opcionales"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Desactivado"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Personalizar formato"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Preferencias sistema para los subtítulos"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Modo de pantalla"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Varios audios"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"estéreo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Sonido envolvente 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Sonido envolvente 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d canales"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Personalizar lista"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Seleccionar grupo"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Anular selección"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Agrupar por"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Fuente del canal"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Agrupar por"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Configuración"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Este programa está bloqueado."</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Calificación de este programa: <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Fuentes de canales"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Nuevos canales disponibles"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Personalizar lista"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Seleccionar canales de guía de programas"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Sin configurar"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"La entrada no admite la búsqueda automática."</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"No se puede iniciar la búsqueda automática de \"<xliff:g id="TV_INPUT">%s</xliff:g>\"."</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Error al iniciar las preferencias de los subtítulos del sistema"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d canal agregado"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d canales agregados"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"No se agregaron canales."</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Sintonizador"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Control. parentales"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Activado"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Desactivado"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Canales bloqueados"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Bloquear todos"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Desbloquear todos"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Canales ocultos"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Restr. programas"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Cambiar PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Sist. calificación"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Clasificaciones"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Todos los sistemas"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Otros países"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Ninguno"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Ninguno"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Ninguno"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Restricciones altas"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Restricciones medias"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Restricciones bajas"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Personalizado"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Contenido apto para niños"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Contenido apto para niños mayores"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Contenido apto para adolescentes"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Restricciones manuales"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s y subclasific."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Subclasificaciones"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Ingresa tu PIN para ver este canal"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Ingresa tu PIN para ver este programa"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Ingresa tu PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Para configurar controles parentales, debes crear un PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Ingresar nuevo PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Confirmar el PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Ingresa tu PIN actual"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Ingresaste un PIN incorrecto 5 veces.\nReintenta en <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segundo."</item>
+ <item quantity="other" msgid="8829550842387756054">"Ingresaste un PIN incorrecto 5 veces.\nReintenta en <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segundos."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"El PIN es incorrecto. Vuelve a intentarlo."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Inténtalo de nuevo, el PIN no coincide."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Información"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Licencias de código abierto"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Licencias de código abierto"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versión"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Para mirar este canal, presiona la tecla hacia la derecha e ingresa el PIN."</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Para mirar este programa, presiona la tecla hacia la derecha e ingresa el PIN."</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Este programa se calificó como <xliff:g id="RATING">%1$s</xliff:g>.\nPara mirarlo, presiona la tecla hacia la derecha e ingresa el PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"El programa está bloqueado."</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Calificación de este programa: <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Sin título"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Canal bloqueado"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Temporada <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>. Episodio <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>: <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>."</string>
+ <string name="setup_title" msgid="7268875010986705651">"Fuentes de canales"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Configura canales en vivo desde las fuentes disponibles. Este proceso puede demorar unos minutos, según la fuente del canal."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Listo"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d canal disponible"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d canales disponibles"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"No hay canales disponibles."</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nueva"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Presiona SELECCIONAR"</b>" para acceder al menú de la televisión."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"No se encontró ninguna entrada de TV."</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"No se puede encontrar la entrada de TV."</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"No se admite PIP."</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"No hay entradas disponibles que se puedan mostrar con PIP."</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tipo de sintonizador no admitido. Abre la aplicación Canales en vivo para el tipo de sintonizador de entrada de TV."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Error al sintonizar"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"No se encontró ninguna aplicación que pueda realizar esta acción."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Todos los canales de salida están ocultos.\nSelecciona al menos un canal para mirar."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"No está disponible porque la señal de video es débil."</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"El video no está disponible por motivos inesperados."</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"La tecla ATRÁS es para el dispositivo conectado. Presiona el botón INICIO para salir."</string>
+</resources>
diff --git a/res/values-es/arrays.xml b/res/values-es/arrays.xml
new file mode 100644
index 00000000..bd1b9512
--- /dev/null
+++ b/res/values-es/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Completa"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Todos los canales"</item>
+ <item msgid="928298872841713530">"Familiar/Infantil"</item>
+ <item msgid="2751606947569857164">"Deportes"</item>
+ <item msgid="7345749789651321496">"Compras"</item>
+ <item msgid="167201149441442173">"Películas"</item>
+ <item msgid="525966731464264290">"Comedia"</item>
+ <item msgid="6096710741527327836">"Viajes"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Educación"</item>
+ <item msgid="7221999662426308394">"Animales/Vida salvaje"</item>
+ <item msgid="375300513250925001">"Noticias"</item>
+ <item msgid="7746320336582330410">"Juegos"</item>
+ <item msgid="1255741860568329178">"Arte"</item>
+ <item msgid="7603949681065702867">"Ocio"</item>
+ <item msgid="4453821994746804366">"Estilo de vida"</item>
+ <item msgid="3488534597567932843">"Música"</item>
+ <item msgid="7452153120614274095">"De primera línea"</item>
+ <item msgid="8215762047341133299">"Ciencia y tecnología"</item>
+ </string-array>
+</resources>
diff --git a/res/values-es/rating_system_strings.xml b/res/values-es/rating_system_strings.xml
new file mode 100644
index 00000000..553a2d7d
--- /dev/null
+++ b/res/values-es/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drogas"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sexo"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Violencia"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drogas"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sexo"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Violencia"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Francés"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Diálogo sugerente"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Lenguaje grosero"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Contenido sexual"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Violencia"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Violencia de fantasía"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Clasificaciones de películas"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
new file mode 100644
index 00000000..b3085831
--- /dev/null
+++ b/res/values-es/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Canales en directo"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"estéreo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Controles de reproducción"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Canales"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Canales recientes"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Opciones de TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Opciones de PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Controles de reproducción no disponibles en este canal"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Reproducir o pausar"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Avance rápido"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Rebobinar"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Siguiente"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Anterior"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Guía de programas"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Nuevos canales disponibles"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Ningún enlace disponible"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Abrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Subtítulos"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Modo de pantalla"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Activado"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Desactivado"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Varios audios"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Config. canal"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Cont. parentales"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Información"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Fuente"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Cambiar"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Activado"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Desactivado"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Sonido"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Principal"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Ventana PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Diseño"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Abajo derecha"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Arriba derecha"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Arriba izquierda"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Abajo izquierda"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"En paralelo"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Dimensiones"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Grande"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Pequeño"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Fuente de entrada"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antena/cable)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"No hay información del programa"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Sin información"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Canal bloqueado"</string>
+ <string name="default_language" msgid="4122326459624337928">"Idioma desconocido"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Subtítulos"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Desactivados"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Personalizar formato"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Configura las preferencias del sistema para los subtítulos"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Modo de pantalla"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Varios audios"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"estéreo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"envolvente 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"envolvente 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d canales"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Personalizar lista"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Seleccionar grupo"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Anular selección"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Agrupar por"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Fuente de canal"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Agrupar por"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Config. canal"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Este programa está bloqueado"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Este programa se ha clasificado como <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Fuentes de canales"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Nuevos canales disponibles"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Personalizar lista"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Seleccionar canales de la guía de programas"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Sin configurar"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"La entrada no admite la búsqueda automática"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"No se puede iniciar la búsqueda automática de <xliff:g id="TV_INPUT">%s</xliff:g>"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Error al iniciar las preferencias de los ajustes de subtítulos del sistema."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d canal añadido"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d canales añadidos"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"No se han añadido canales"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Sintonizador"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Controles parentales"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Activados"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Desactivados"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Canales bloqueados"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Bloquear todo"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Desbloquear todo"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Canales ocultos"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Restricción de programas"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Cambiar PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Sistemas de clasificación"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Clasificaciones"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Todos los sistemas"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Otros países"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Ninguno"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Ninguna"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Ninguna"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Restricciones altas"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Restricciones medias"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Restricciones bajas"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Personalizado"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Contenido adecuado para niños"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Contenido adecuado para niños mayores"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Contenido adecuado para adolescentes"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Restricciones manuales"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s y subclasificac"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Subclasificaciones"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Introduce el PIN para ver este canal"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Introduce el PIN para ver este programa"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Introduce el número PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Para establecer controles parentales, debes crear un PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Introduce el nuevo PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Confirma el número PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Introduce el número PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Has introducido el PIN incorrectamente 5 veces.\nVuelve a intentarlo en <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segundo."</item>
+ <item quantity="other" msgid="8829550842387756054">"Has introducido el PIN incorrectamente 5 veces.\nVuelve a intentarlo en <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segundos."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Ese PIN era incorrecto. Vuelve a intentarlo."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Vuelve a intentarlo, el PIN no coincide"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Información"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Licencias software libre"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Licencias software libre"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versión"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Para ver este canal, pulsa la tecla hacia la derecha e introduce el número PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Para ver este programa, pulsa la tecla hacia la derecha e introduce el número PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Este programa se ha clasificado como <xliff:g id="RATING">%1$s</xliff:g>.\nPara verlo, pulsa la tecla hacia la derecha e introduce el número PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"El programa está bloqueado"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Este programa se ha clasificado como <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Sin título"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Canal bloqueado"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Temporada <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: episodio <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> (<xliff:g id="EPISODE_TITLE">%3$s</xliff:g>)"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Fuentes de canales"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Configura canales en directo a partir de las fuentes disponibles. Este proceso puede tardar unos minutos en función de la fuente del canal."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Listo"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d canal disponible"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d canales disponibles"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"No hay canales disponibles"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nuevas"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Pulsa SELECCIONAR"</b>" para acceder al menú de la TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"No se han encontrado entradas de TV"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"No se puede encontrar la entrada de TV"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP no admitido"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"No hay entradas disponibles que se puedan mostrar con PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tipo de sintonizador inadecuado. Abre Canales en directo para seleccionar la entrada de TV del tipo de sintonizador."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Error al sintonizar"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"No se ha encontrado ninguna aplicación que pueda realizar esta acción."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Todos los canales de origen están ocultos.\nSelecciona al menos un canal para verlo."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"No está disponible porque la señal de vídeo es débil"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"El vídeo no está disponible por motivos desconocidos"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"La tecla VOLVER es para dispositivos conectados. Pulsa el botón de inicio para salir."</string>
+</resources>
diff --git a/res/values-et-rEE/arrays.xml b/res/values-et-rEE/arrays.xml
new file mode 100644
index 00000000..eb64ab43
--- /dev/null
+++ b/res/values-et-rEE/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Tavaline"</item>
+ <item msgid="2533030282864800794">"Täis"</item>
+ <item msgid="8568284598210500589">"Suumimine"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Kõik kanalid"</item>
+ <item msgid="928298872841713530">"Pere/lapsed"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Ostud"</item>
+ <item msgid="167201149441442173">"Filmid"</item>
+ <item msgid="525966731464264290">"Komöödia"</item>
+ <item msgid="6096710741527327836">"Reisimine"</item>
+ <item msgid="2851882187117833883">"Draama"</item>
+ <item msgid="78492781188719038">"Haridus"</item>
+ <item msgid="7221999662426308394">"Loomad/loodus"</item>
+ <item msgid="375300513250925001">"Uudised"</item>
+ <item msgid="7746320336582330410">"Mängud"</item>
+ <item msgid="1255741860568329178">"Kunstid"</item>
+ <item msgid="7603949681065702867">"Meelelahutus"</item>
+ <item msgid="4453821994746804366">"Elustiil"</item>
+ <item msgid="3488534597567932843">"Muusika"</item>
+ <item msgid="7452153120614274095">"Esilinastus"</item>
+ <item msgid="8215762047341133299">"Tehnika/teadus"</item>
+ </string-array>
+</resources>
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..f66f68d0
--- /dev/null
+++ b/res/values-et-rEE/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Narkootikumid"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Seks"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Vägivald"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Narkootikumid"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Seks"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Vägivald"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Prantsuse"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Kahemõttelise sisuga dialoog"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Vulgaarne tekst"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Seksuaalne sisu"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Vägivald"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Väljamõeldud vägivald"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Filmi hinnangud"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
new file mode 100644
index 00000000..14a21adc
--- /dev/null
+++ b/res/values-et-rEE/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Esituse juhtnupud"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanalid"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Viimased kanalid"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV-valikud"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP-valikud"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Esituse juhtelemendid ei ole selle kanali puhul saadaval"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Esitamine või peatamine"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Edasikerimine"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Tagasikerimine"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Järgmine"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Eelmine"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Saatekava"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Saadaval on uued kanalid"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Ühtegi linki pole saadaval"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Ava <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Subtiitrid"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Kuvarežiim"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Sees"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Väljas"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multiaudio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Kanali seadist."</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Järelevalve"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Teave"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Allikas"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Vaheta"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Sees"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Väljas"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Heli"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Peamine"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP-aken"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Paigutus"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"All paremal"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Ülal paremal"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Ülal vasakul"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"All vasakul"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Kõrvuti"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Suurus"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Suur"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Väike"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Sisendallikas"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antenn/kaabel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Programmiteavet pole"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Teave puudub"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Blokeeritud kanal"</string>
+ <string name="default_language" msgid="4122326459624337928">"Tundmatu keel"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Subtiitrid"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Väljas"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Vormingu kohandamine"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Määrake subtiitrite süsteemiül. eelist."</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Kuvarežiim"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multiaudio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Ruumiline heli 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Ruumiline heli 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d kanalit"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Kanaliloendi kohandam."</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Rühma valimine"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Rühma valiku tühist."</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Rühmitamisalus:"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Kanali allikas"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Rühmitamisalus:"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Kanali seadistus"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"See programm on blokeeritud"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Selle saate reiting on <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Kanali allikad"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Saadaval on uued kanalid"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Kanaliloendi kohandam."</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Kanalite valimine saatekava jaoks"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Pole seadistatud"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Sisend ei toeta automaatset skannimist"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Sisendi „<xliff:g id="TV_INPUT">%s</xliff:g>” automaatset skannimist ei saa alustada"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Subtiitrite süsteemiüleseid eelistusi ei saa käivitada."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"Lisatud on %1$d kanal"</item>
+ <item quantity="other" msgid="1078861616751739285">"Lisatud on %1$d kanalit"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Ühtegi kanalit ei lisatud"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Van. järelevalve"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Sees"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Väljas"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Blokeeritud kanalid"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Blokeeri kõik"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Deblokeeri kõik"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Peidetud kanalid"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Programmipiirangud"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Muuda PIN-koodi"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Hinnangusüsteemid"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Hinnangud"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Kuva hinnangusüsteemid"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Muud riigid"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Puudub"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Puudub"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Puudub"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Suured piirangud"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Keskmised piirangud"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Väikesed piirangud"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Kohandatud"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Sisu sobib lastele"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Sisu sobib vanematele lastele"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Sisu sobib teismelistele"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Käsitsi määratud piirangud"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s &amp; alamreitingud"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Alamreitingud"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Kanali vaatamiseks sisestage PIN-kood"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Saate vaatamiseks sisestage PIN-kood"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Sisestage PIN-kood"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Vanemliku järelevalve määramiseks looge PIN-kood"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Sisestage uus PIN-kood"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Kinnitage PIN-kood"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Sisestage praegune PIN-kood"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Olete 5 korda sisestanud vale PIN-koodi.\nProovige uuesti <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekundi pärast."</item>
+ <item quantity="other" msgid="8829550842387756054">"Olete 5 korda sisestanud vale PIN-koodi.\nProovige uuesti <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekundi pärast."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"See PIN-kood oli vale. Proovige uuesti."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Proovige uuesti, PIN-kood pole õige"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Teave"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Avatud lähtekoodi litsentsid"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Avatud lähtekoodi litsentsid"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versioon"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Kanali vaatamiseks vajutage paremale ja sisestage PIN-kood"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Saate vaatamiseks vajutage paremale ja sisestage PIN-kood"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Saate reiting on <xliff:g id="RATING">%1$s</xliff:g>\nSaate vaatamiseks vajutage valikut Paremale ja sisestage PIN-kood."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Saade on blokeeritud"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Selle saate reiting on <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Pealkiri puudub"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanal on blokeeritud"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Hooaeg <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: jagu <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>, <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Kanali allikad"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Seadistage saadaolevatest allikatest otseülekandes kanalid. Selleks võib olenevalt kanali allikast kuluda mitu minutit."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Valmis"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Sisendi %1$d kanal on saadaval"</item>
+ <item quantity="other" msgid="2386588423841837714">"Sisendi %1$d kanalid on saadaval"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Ühtegi kanalit pole saadaval"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Uus"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"Teleri menüüle juurdepääsemiseks "<b>"vajutage nuppu SELECT"</b>"."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"TV-sisendit ei leitud"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"TV-sisendit ei õnnestu leida"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP-d ei toetata"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Pole ühtegi sisendit, mida saab näidata koos PIP-ga"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Sobimatu tuuneri tüüp. Käivitage tuuneri tüübi TV-sisendi jaoks rakendus Otseülekande kanalid."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Häälestamine ebaõnnestus"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Selle toimingu käsitlemiseks ei leitud ühtegi rakendust."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Kõik allikakanalid on peidetud.\nValige vaatamiseks vähemalt üks kanal."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Pole saadaval nõrga videosignaali tõttu"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Video pole ootamatult saadaval"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Klahv TAGASI on mõeldud ühendatud seadme jaoks. Väljumiseks vajutage nuppu AVAEKRAAN."</string>
+</resources>
diff --git a/res/values-eu-rES/arrays.xml b/res/values-eu-rES/arrays.xml
new file mode 100644
index 00000000..e25f86bc
--- /dev/null
+++ b/res/values-eu-rES/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normala"</item>
+ <item msgid="2533030282864800794">"Osoa"</item>
+ <item msgid="8568284598210500589">"Zooma"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Kanal guztiak"</item>
+ <item msgid="928298872841713530">"Familia/Umeak"</item>
+ <item msgid="2751606947569857164">"Kirolak"</item>
+ <item msgid="7345749789651321496">"Erosketak"</item>
+ <item msgid="167201149441442173">"Filmak"</item>
+ <item msgid="525966731464264290">"Komedia"</item>
+ <item msgid="6096710741527327836">"Bidaiak"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Hezkuntza"</item>
+ <item msgid="7221999662426308394">"Animaliak/Basabizitza"</item>
+ <item msgid="375300513250925001">"Albisteak"</item>
+ <item msgid="7746320336582330410">"Jokoak"</item>
+ <item msgid="1255741860568329178">"Artea"</item>
+ <item msgid="7603949681065702867">"Ikuskizunak"</item>
+ <item msgid="4453821994746804366">"Bizimodua"</item>
+ <item msgid="3488534597567932843">"Musika"</item>
+ <item msgid="7452153120614274095">"Onenak"</item>
+ <item msgid="8215762047341133299">"Zientzia/Teknologia"</item>
+ </string-array>
+</resources>
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..b8d9d675
--- /dev/null
+++ b/res/values-eu-rES/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drogak"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sexua"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Indarkeria"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drogak"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sexua"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Indarkeria"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"frantsesa"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Elkarrizketa iradokitzailea"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Hizkera zakarra"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Eduki sexuala"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Indarkeria"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantasiazko indarkeria"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Filmen balorazioak"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
new file mode 100644
index 00000000..4be1c413
--- /dev/null
+++ b/res/values-eu-rES/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Zuzeneko kanalak"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"estereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Erreprodukzio-kontrolak"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanalak"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Azken kanalak"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Telebistaren aukerak"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Pantaila txikia"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Erreprodukzioa kontrolatzeko aukerak ez daude erabilgarri kanal honetan"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Erreproduzitu edo pausatu"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Aurreratu"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Atzeratu"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Hurrengoa"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Aurrekoa"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Telesaioen gida"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Kanal berriak daude"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Ez dago estekarik"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Ireki <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Azpitituluak"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Bistaratzeko modua"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"Pantaila txikia"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Aktibatuta"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Desaktibatuta"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Audio anitza"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Konfigurazioa"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Gurasoen ezarpenak"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Honi buruz"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Iturburua"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Aldatu"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Aktibatuta"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Desaktibatuta"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Soinua"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Nagusia"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Pantaila txikia"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Diseinua"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Behean eskuinean"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Goian eskuinean"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Goian ezkerrean"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Behean ezkerrean"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Alboz albo"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Tamaina"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Handia"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Txikia"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Sarrera-iturburua"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Telebista (antena/digitala)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Ez dago telesaioei buruzko informaziorik"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Ez dago informaziorik"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Blokeatutako kanala"</string>
+ <string name="default_language" msgid="4122326459624337928">"Hizkuntza ezezaguna"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Azpitituluak"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Desaktibatuta"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Pertsonalizatu formatua"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Zehaztu azpitituluen hobespen orokorrak"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Bistaratzeko modua"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Audio anitza"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"monoa"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"estereoa"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 inguratzailea"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 inguratzailea"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d kanal"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Pertsonalizatu zerrenda"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Hautatu taldea"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Desautatu taldea"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Taldekatzeko irizpidea"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Kanalen iturburua"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"Bereizmen handia eta estandarra"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"Bereizmen handia"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"Bereizmen estandarra"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Taldekatzeko irizpidea"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Konfigurazioa"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Telesaio hau blokeatuta dago"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Telesaioa \"<xliff:g id="RATING">%1$s</xliff:g>\" gisa sailkatu da"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Kanalen iturburuak"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Kanal berriak daude"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Pertsonalizatu zerrenda"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Aukeratu telesaioen gidako kanalak"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Konfiguratu gabe"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Sarrerak ez du automatikoki sintonizatzeko aukera onartzen"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Ezin da hasi \"<xliff:g id="TV_INPUT">%s</xliff:g>\" automatikoki sintonizatzen"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Ezin dira ireki sistemaren azpitituluen hobespenak."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d kanal gehitu da"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d kanal gehitu dira"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Ez da gehitu kanalik"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Sintonizadorea"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Gurasoen ezarpenak"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Aktibatuta"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Desaktibatuta"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Kanalak blokeatuta"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Blokeatu guztiak"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Desblokeatu guztiak"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Ezkutuko kanalak"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Telesaioen murrizk."</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Aldatu PIN kodea"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Sailkapen-sistemak"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Balorazioak"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Sailkapen-sistema guztiak"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Beste herrialdeak"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Bat ere ez"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Bat ere ez"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Bat ere ez"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Murrizketa asko"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Erdi-mailako murriztapenak"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Murriztapen gutxi"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Pertsonalizatua"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Edukia egokia da umeentzat"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Edukia egokia da beste ume batzuentzat"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Edukia egokia da nerabeentzat"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Eskuzko murrizketak"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s eta azpisailkapenak"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Azpisailkapenak"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Kanal hau ikusi nahi baduzu, idatzi PIN kodea."</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Telesaio hau ikusi nahi baduzu, idatzi PIN kodea."</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Idatzi PIN kodea"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Gurasoen kontrol-aukerak zehazteko, sortu PIN kode bat"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Idatzi PIN kode berria"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Berretsi PIN kodea"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Idatzi uneko PIN kodea"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"PIN kodea oker idatzi duzu bost aldiz.\nSaiatu berriro <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segundo barru."</item>
+ <item quantity="other" msgid="8829550842387756054">"PIN kodea oker idatzi duzu bost aldiz.\nSaiatu berriro <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segundo barru."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"PIN kodea ez da zuzena. Saiatu berriro."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"PIN kodeak ez datoz bat. Saiatu berriro."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Honi buruz"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Kode irekiko lizentziak"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Kode irekiko lizentziak"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Bertsioa"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Kanal hau ikusteko, sakatu Eskuinera tekla eta idatzi PIN kodea."</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Telesaio hau ikusteko, sakatu Eskuinera tekla eta idatzi PIN kodea."</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Telesaioa \"<xliff:g id="RATING">%1$s</xliff:g>\" gisa sailkatu da.\nIkusi ahal izateko, sakatu Eskuinera tekla eta idatzi PIN kodea."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Telesaioa blokeatuta dago"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Telesaioa \"<xliff:g id="RATING">%1$s</xliff:g>\" gisa sailkatu da"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Izenik ez"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanala blokeatuta dago"</string>
+ <string name="episode_format" msgid="4881195874563241096">"<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>. denboraldia, <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>. atala (<xliff:g id="EPISODE_TITLE">%3$s</xliff:g>)"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Kanalen iturburuak"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Konfiguratu zuzenean ikus daitezkeen kanalak, erabilgarri dauden iturburuak erabilita. Hainbat minutu behar izan daitezke, kanalaren iturburuaren arabera."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Eginda"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d kanal dago erabilgarri"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d kanal daude erabilgarri"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Ez dago kanalik erabilgarri"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Berriak"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Telebistaren menua atzitzeko, sakatu HAUTATU"</b></string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Ez da telebista-sarrerarik aurkitu"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Ezin da aurkitu telebista-sarrera"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Ez da pantaila txikia erabiltzea onartzen"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Ez dago pantaila txikian erakuts daitekeen sarrerarik"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Sintonizagailua ez da egokia. Abiarazi zuzeneko kanalen aplikazioa sintonizagailua telebistaren sarrera gisa erabiltzeko."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Ezin izan da sintonizatu"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Ez da aurkitu ekintza gauza dezakeen aplikaziorik."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Iturburuko kanal guztiak ezkutuan daude.\nHautatu gutxienez kanal bat ikusteko."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Ez dago erabilgarri, seinalea ahulegia delako"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Bideoa ez dago erabilgarri"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Atzera tekla konektatutako gailuari dagokio. Irteteko, sakatu Hasiera botoia."</string>
+</resources>
diff --git a/res/values-fa/arrays.xml b/res/values-fa/arrays.xml
new file mode 100644
index 00000000..b60cbf07
--- /dev/null
+++ b/res/values-fa/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"عادی"</item>
+ <item msgid="2533030282864800794">"کامل"</item>
+ <item msgid="8568284598210500589">"بزرگ‌نمایی"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"همه کانال‌ها"</item>
+ <item msgid="928298872841713530">"خانواده/کودکان"</item>
+ <item msgid="2751606947569857164">"ورزش"</item>
+ <item msgid="7345749789651321496">"خرید"</item>
+ <item msgid="167201149441442173">"فیلم"</item>
+ <item msgid="525966731464264290">"کمدی"</item>
+ <item msgid="6096710741527327836">"سفر"</item>
+ <item msgid="2851882187117833883">"درام"</item>
+ <item msgid="78492781188719038">"آموزش"</item>
+ <item msgid="7221999662426308394">"حیوانات/حیات وحش"</item>
+ <item msgid="375300513250925001">"اخبار"</item>
+ <item msgid="7746320336582330410">"بازی"</item>
+ <item msgid="1255741860568329178">"هنر"</item>
+ <item msgid="7603949681065702867">"سرگرمی"</item>
+ <item msgid="4453821994746804366">"روش زندگی"</item>
+ <item msgid="3488534597567932843">"موسیقی"</item>
+ <item msgid="7452153120614274095">"برتر"</item>
+ <item msgid="8215762047341133299">"فناوری/علم"</item>
+ </string-array>
+</resources>
diff --git a/res/values-fa/rating_system_strings.xml b/res/values-fa/rating_system_strings.xml
new file mode 100644
index 00000000..c38f4fe7
--- /dev/null
+++ b/res/values-fa/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"مواد مخدر"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"روابط جنسی"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"خشونت"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"مواد مخدر"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"روابط جنسی"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"خشونت"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"فرانسوی"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"گفتگوی وسوسه برانگیز"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"دشنام‌گویی"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"محتوای جنسی"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"خشونت"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"خشونت فانتزی"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"رتبه‌بندی‌های‌ فیلم"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
new file mode 100644
index 00000000..4ff7f719
--- /dev/null
+++ b/res/values-fa/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"کانال‌های فعال"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"مونو"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"استریو"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"کنترل‌های پخش"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"کانال‌ها"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"کانال‌های اخیر"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"گزینه‌‌ تلویزیون"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"‏گزینه‌های PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"دسترسی به کنترل‌های پخش برای این کانال امکان‌پذیر نیست"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"پخش یا توقف موقت"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"جلو بردن سریع"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"عقب بردن"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"بعدی"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"قبلی"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"راهنمای برنامه"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"کانال‌های جدید در دسترس است"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"پیوندی موجود نیست"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"باز کردن <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"زیرنویس‌ها"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"حالت نمایش"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"تصویر در تصویر"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"روشن"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"خاموش"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"چند صدایی"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"تنظیم کانال"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"کنترل‌های والدین"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"درباره"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"منبع"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"تعویض"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"روشن"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"خاموش"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"صدا"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"اصلی"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"پنجره تصویردرتصویر"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"طرح‌بندی"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"پایین سمت راست"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"بالا سمت راست"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"بالا سمت چپ"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"پایین سمت چپ"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"پهلو به پهلو"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"اندازه"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"بزرگ"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"کوچک"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"منبع ورودی"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"تلویزیون (آنتنی/کابلی)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"هیچ اطلاعات برنامه‌ای وجود ندارد"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"هیچ اطلاعاتی موجود نیست"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"کانال مسدود شده"</string>
+ <string name="default_language" msgid="4122326459624337928">"زبان نامشخص"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"زیرنویس"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"خاموش"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"سفارشی کردن قالب‌بندی"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"تنظیمات ترجیحی زیرنویس‌ها در کل سیستم"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"حالت نمایش"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"چند صدایی"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"مونو"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"استریو"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"فراگیر ۵٫۱"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"فراگیر ۷٫۱"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"‏%d کانال"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"سفارشی کردن فهرست کانال‌ها"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"انتخاب گروه"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"لغو انتخاب گروه"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"گروه‌بندی براساس"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"منبع کانال"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"وضوح استاندارد/وضوح بالا"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"وضوح بالا"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"وضوح استاندارد"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"گروه‌بندی براساس"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"تنظیم کانال"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"این برنامه مسدود است"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"رتبه‌بندی این برنامه <xliff:g id="RATING">%1$s</xliff:g> است."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"منابع کانال"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"کانال‌های جدید در دسترس است"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"سفارشی کردن فهرست کانال‌ها"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"کانال‌ها را جهت راهنمای برنامه‌تان انتخاب کنید"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"تنظیم نشده است"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"ورودی از اسکن خودکار پشتیبانی نمی‌کند"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"اسکن خودکار «<xliff:g id="TV_INPUT">%s</xliff:g>» شروع نمی‌شود"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"شروع تنظیمات ترجیحی سراسر سیستم برای زیرنویس‌ها ممکن نیست."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"‏%1$d کانال اضافه شد"</item>
+ <item quantity="other" msgid="1078861616751739285">"‏%1$d کانال اضافه شدند"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"هیچ کانالی اضافه نشد"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"تیونر"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"کنترل‌های والدین"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"روشن"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"خاموش"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"کانال‌ها مسدود شده"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"انسداد همه"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"رفع انسداد همه"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"کانال‌های پنهان"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"محدودیت‌های برنامه"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"تغییر پین"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"سیستم‌‌ رتبه‌بندی"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"رتبه‌بندی‌ها"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"مشاهده سیستم‌‌های رتبه‌بندی"</string>
+ <string name="other_countries" msgid="8342216398676184749">"کشورهای دیگر"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"هیچ‌‌کدام"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"هیچ‌‌کدام"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"هیچ‌‌کدام"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"محدودیت‌های زیاد"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"محدودیت‌های متوسط"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"محدودیت‌های کم"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"سفارشی"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"محتوای مناسب برای کودکان"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"محتوای مناسب برای بچه‌های بزرگ‌تر"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"محتوای مناسب برای نوجوانان"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"محدودیت‌های دستی"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"‏%1$s و رتبه‌بندی‌ فرعی"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"رتبه‌بندی‌های فرعی"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"برای تماشای این کانال، پین خودتان را وارد کنید"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"برای تماشای این برنامه، پین خودتان را وارد کنید"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"پین خودتان را وارد کنید"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"برای تنظیم کنترل‌های والدین، پین ایجاد کنید"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"پین جدید را وارد کنید"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"پین خودتان را تأیید کنید"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"پین کنونی‌ خودتان را وارد کنید"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"پین اشتباه را ۵ بار وارد کردید.\nبعد از <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> ثانیه، دوباره امتحان کنید."</item>
+ <item quantity="other" msgid="8829550842387756054">"پین اشتباه را ۵ بار وارد کردید.\n بعد از <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> ثانیه، دوباره امتحان کنید."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"این پین اشتباه بود. دوباره امتحان کنید."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"دوباره امتحان کنید، پین منطبق نیست"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"درباره"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"مجوزهای منبع آزاد"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"مجوزهای منبع آزاد"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"نسخه"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"برای مشاهده این کانال، راست را فشار دهید و پین خودتان را وارد کنید"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"برای مشاهده این برنامه، راست را فشار دهید و پین خودتان را وارد کنید"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"‏رتبه‌بندی این برنامه <xliff:g id="RATING">%1$s</xliff:g> است.\nبرای تماشای این برنامه، Right (راست) را فشار دهید و پین خودتان را وارد کنید."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"برنامه مسدود شده است"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"رتبه‌بندی این برنامه <xliff:g id="RATING">%1$s</xliff:g> است."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"بدون عنوان"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"کانال مسدود شد"</string>
+ <string name="episode_format" msgid="4881195874563241096">"ف <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: ق <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> ‏<xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"منابع کانال"</string>
+ <string name="setup_description" msgid="8728423605912915099">"تنظیم کانال‌های مستقیم از منابع در دسترس. بسته به منبع کانال، ممکن است چند دقیقه طول بکشد."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"انجام شد"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"‏%1$d کانال موجود"</item>
+ <item quantity="other" msgid="2386588423841837714">"‏%1$d کانال موجود"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"هیچ کانالی موجود نیست"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"جدید"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"‏برای دسترسی به منوی تلویزیون، "<b>"SELECT (انتخاب) را فشار دهید"</b>"."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"هیچ ورودی تلویزیون یافت نشد"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"نمی‌توان ورودی تلویزیون پیدا کرد"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"تصویر در تصویر پشتیبانی نمی‌شود."</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"ورودی قابل نمایش با تصویر در تصویر، در دسترس نیست."</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"نوع تیونر مناسب نیست. لطفاً برنامه‌ «کانال‌های مستقیم» را برای ورودی تلویزیون نوع تیونر، راه‌اندازی کنید."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"تنظیم نشد"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"برنامه‌ای برای انجام این اقدام پیدا نشد."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"همه کانال‌های منبع پنهان هستند.\nحداقل یک کانال را برای تماشا انتخاب کنید."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"به علت سیگنال ضعیف ویدیو، در دسترس نیست"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"این ویدیو به‌طور غیر منتظره‌ای در دسترس نیست."</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"کلید بازگشت برای دستگاه‌ متصل است. برای خروج دکمه صفحه اصلی را فشار دهید."</string>
+</resources>
diff --git a/res/values-fi/arrays.xml b/res/values-fi/arrays.xml
new file mode 100644
index 00000000..055465ed
--- /dev/null
+++ b/res/values-fi/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normaali"</item>
+ <item msgid="2533030282864800794">"Täysi"</item>
+ <item msgid="8568284598210500589">"Zoomattu"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Kaikki kanavat"</item>
+ <item msgid="928298872841713530">"Perhe/lapset"</item>
+ <item msgid="2751606947569857164">"Urheilu"</item>
+ <item msgid="7345749789651321496">"Ostokset"</item>
+ <item msgid="167201149441442173">"Elokuvat"</item>
+ <item msgid="525966731464264290">"Komedia"</item>
+ <item msgid="6096710741527327836">"Matkailu"</item>
+ <item msgid="2851882187117833883">"Draama"</item>
+ <item msgid="78492781188719038">"Opetus"</item>
+ <item msgid="7221999662426308394">"Eläimet/luonto"</item>
+ <item msgid="375300513250925001">"Uutiset"</item>
+ <item msgid="7746320336582330410">"Pelit"</item>
+ <item msgid="1255741860568329178">"Taiteet"</item>
+ <item msgid="7603949681065702867">"Viihde"</item>
+ <item msgid="4453821994746804366">"Elämäntapa"</item>
+ <item msgid="3488534597567932843">"Musiikki"</item>
+ <item msgid="7452153120614274095">"Ensiesitys"</item>
+ <item msgid="8215762047341133299">"Tekniikka/tiede"</item>
+ </string-array>
+</resources>
diff --git a/res/values-fi/rating_system_strings.xml b/res/values-fi/rating_system_strings.xml
new file mode 100644
index 00000000..b5209dde
--- /dev/null
+++ b/res/values-fi/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Huumeet"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Seksi"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Väkivalta"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Huumeet"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Seksi"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Väkivalta"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"ranska"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Sanallisia viittauksia seksiin"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Karkea kielenkäyttö"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Seksuaalinen sisältö"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Väkivalta"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantasiaväkivalta"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Elokuvien luokitukset"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
new file mode 100644
index 00000000..813640a2
--- /dev/null
+++ b/res/values-fi/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live-kanavat"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Toistosäätimet"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanavat"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Viimeaikaiset kanavat"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV-asetukset"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP-asetukset"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Toistosäätimet eivät ole käytettävissä tällä kanavalla."</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Toisto tai keskeytys"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Kelaa eteenpäin"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Kelaa taaksepäin"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Seuraava"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Edellinen"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Ohjelmaopas"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Uusia kanavia"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Ei linkkiä saatavilla"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Avaa <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Tekstitykset"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Näyttötila"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Käytössä"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Ei käytössä"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Moniääni"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Kanavien asetus"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Lapsilukko"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Tietoja"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Lähde"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Vaihda"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Käytössä"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Ei käytössä"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Ääni"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Ensisijainen"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP-ikkuna"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Asettelu"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Alaoikealla"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Yläoikealla"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Ylävasemmalla"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Alavasemmalla"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Vierekkäin"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Mitat"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Suuri"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Pieni"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Syötteen lähde"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antenni/kaapeli)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Ei ohjelmatietoja"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Ei tietoja"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Estetty kanava"</string>
+ <string name="default_language" msgid="4122326459624337928">"Tuntematon kieli"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Tekstitykset"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Ei käytössä"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Muokkaa muotoilua"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Määritä koko järjest. tekstitysasetukset"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Näyttötila"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Moniääni"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 Surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 Surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d kanavaa"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Oma kanavaluettelo"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Valitse ryhmä"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Peru ryhmävalinta"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Ryhmittely:"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Kanavalähde"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Ryhmittely:"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Kanavien asetus"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Ohjelma on estetty."</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Tämän ohjelman luokitus on <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Kanavalähteet"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Uusia kanavia saatavilla."</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Oma kanavaluettelo"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Valitse kanavat ohjelmaoppaaseesi"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Ei määritetty"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Tulo ei tue automaattista skannausta."</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Tulon <xliff:g id="TV_INPUT">%s</xliff:g> automaattista skannausta ei voi käynnistää"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Tekstitysten koko järjestelmää koskevien asetusten määritystä ei voi aloittaa."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d kanava lisätty"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d kanavaa lisätty"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Kanavia ei lisätty"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Viritin"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Lapsilukko"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Käytössä"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Ei käytössä"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Estetyt kanavat"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Estä kaikki"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Poista kaikkien esto"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Piilotetut kanavat"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Ohjelmarajoitukset"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Vaihda PIN-koodi"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Luokitusjärjestelmät"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Arviot"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Kaikki luokitusjärjestelmät"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Muut maat"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Ei mitään"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Ei mitään"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Ei mitään"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Suuret rajoitukset"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Keskim. rajoitukset"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Pienet rajoitukset"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Oma"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Sisältö sopii lapsille"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Sisältö sopii isommille lapsille"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Sisältö sopii teini-ikäisille"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Manuaaliset rajoitukset"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s ja aliluokituk."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Aliluokitukset"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Anna PIN-koodi, jotta voit katsella tätä kanavaa"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Anna PIN-koodi, jotta voit katsella tätä ohjelmaa"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Anna PIN-koodi"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Aseta lapsilukko luomalla PIN-koodi"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Anna uusi PIN-koodi"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Vahvista PIN-koodi"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Anna nykyinen PIN-koodi"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Annoit väärän PIN-koodin 5 kertaa.\nYritä uudelleen <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekunnin kuluttua."</item>
+ <item quantity="other" msgid="8829550842387756054">"Annoit väärän PIN-koodin 5 kertaa.\nYritä uudelleen <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekunnin kuluttua."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Väärä PIN-koodi. Yritä uudelleen."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"PIN-koodit ovat erilaiset, yritä uudelleen"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Tietoja"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Avoimen lähdekoodin käyttöluvat"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Avoimen lähdekoodin käyttöluvat"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versio"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Katsele tätä kanavaa painamalla näppäimellä oikealle ja antamalla PIN-koodi"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Katsele tämä ohjelma painamalla näppäimellä oikealle ja antamalla PIN-koodi"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Tämän ohjelman luokitus on <xliff:g id="RATING">%1$s</xliff:g>.\nKatso ohjelma painamalla näppäimellä oikealle ja antamalla PIN-koodi."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Ohjelma on estetty"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Tämän ohjelman luokitus on <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Ei nimeä"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanava estetty"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Kausi <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: jakso <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>, <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Kanavalähteet"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Hae live-kanavia saatavilla olevista lähteistä. Kanavalähteestä riippuen tässä voi kestää useita minuutteja."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Valmis"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d kanavaa käytettävissä"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d kanavaa käytettävissä"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Ei käytettävissä olevia kanavia"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Uusi"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"Avaa TV-valikko "<b>"painamalla VALITSE"</b>"."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"TV-tuloa ei löytynyt."</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"TV-tuloa ei löydy."</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP:tä ei tueta."</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Käytettävissä ei ole syötettä, joka voitaisiin näyttää PIP:ssä."</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Viritintä ei tueta. Voit käyttää viritintä käynnistämällä Live-kanavat-sovelluksen."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Viritys epäonnistui."</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Tätä toimintoa käsittelevää sovellusta ei löydy."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Kaikki lähdekanavat on piilotettu.\nValitse vähintään yksi katseltava kanava."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Ei käytettävissä heikon videosignaalin takia."</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Video ei yllättäen ole käytettävissä."</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"EDELLINEN-painike koskee liitettyä laitetta. Poistu painamalla ETUSIVU-painiketta."</string>
+</resources>
diff --git a/res/values-fr-rCA/arrays.xml b/res/values-fr-rCA/arrays.xml
new file mode 100644
index 00000000..75c1e38c
--- /dev/null
+++ b/res/values-fr-rCA/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Plein écran"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Toutes les chaînes"</item>
+ <item msgid="928298872841713530">"Famille/Enfants"</item>
+ <item msgid="2751606947569857164">"Sports"</item>
+ <item msgid="7345749789651321496">"Produits"</item>
+ <item msgid="167201149441442173">"Films"</item>
+ <item msgid="525966731464264290">"Humour"</item>
+ <item msgid="6096710741527327836">"Voyages et déplacements"</item>
+ <item msgid="2851882187117833883">"Drame"</item>
+ <item msgid="78492781188719038">"Éducation"</item>
+ <item msgid="7221999662426308394">"Animaux/Faune sauvage"</item>
+ <item msgid="375300513250925001">"Actualités"</item>
+ <item msgid="7746320336582330410">"Jeux"</item>
+ <item msgid="1255741860568329178">"Arts"</item>
+ <item msgid="7603949681065702867">"Divertissement"</item>
+ <item msgid="4453821994746804366">"Style de vie"</item>
+ <item msgid="3488534597567932843">"Musique"</item>
+ <item msgid="7452153120614274095">"Premier"</item>
+ <item msgid="8215762047341133299">"Technologies et sciences"</item>
+ </string-array>
+</resources>
diff --git a/res/values-fr-rCA/rating_system_strings.xml b/res/values-fr-rCA/rating_system_strings.xml
new file mode 100644
index 00000000..42c1bc76
--- /dev/null
+++ b/res/values-fr-rCA/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drogues"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sexe"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Violence"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drogues"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sexe"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Violence"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"français"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Dialogue suggestif"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Langage grossier"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Contenu à caractère sexuel"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Violence"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Violence dans des scènes fantastiques"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Classements des films"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
new file mode 100644
index 00000000..c95cec32
--- /dev/null
+++ b/res/values-fr-rCA/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Chaînes en direct"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stéréo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Commandes de lecture"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Chaînes"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Chaînes récentes"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Options télé"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Options IDI"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Les commandes de lecture ne sont pas disponibles pour cette chaîne"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Lire ou mettre en pause"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Avance rapide"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Reculer"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Suivant"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Précédent"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Guide des programmes"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Nouvelles chaînes"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Aucun lien disponible"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Ouvrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Sous-titres"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Mode d\'affichage"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"IDI"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Activé"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Désactivé"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Config. chaînes"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Contr. parental"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"À propos"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Source"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Basculer"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Activé"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Désactivé"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Son"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Principale"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Fenêtre IDI"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Disposition"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"En bas à droite"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"En haut à droite"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"En haut à gauche"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"En bas à gauche"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Côte à côte"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Taille"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Grande"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Petite"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Source de l\'entrée"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Télévision (antenne/câble)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Aucune information sur le programme"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Pas d\'information"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Chaîne bloquée"</string>
+ <string name="default_language" msgid="4122326459624337928">"Langue : indéterminée"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Sous-titres"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Désactiver"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Personnaliser format"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Déf. param. s-titres, pr tout le système"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Mode d\'affichage"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stéréo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Ambiophonique 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Ambiophonique 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d chaînes"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Person. liste chaîn."</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Sélect. le groupe"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Désélect. le groupe"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Grouper par"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Source de la chaîne"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD et SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Grouper par"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Config. chaînes"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Ce programme est bloqué"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Ce programme est classé « <xliff:g id="RATING">%1$s</xliff:g> »"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Sources des chaînes"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Nouvelles chaînes"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Person. liste chaîn."</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Choisir des chaînes pour le guide des programmes"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Non configuré"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"L\'entrée ne prend pas en charge la recherche automatique"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Impossible de lancer la recherche automatique pour « <xliff:g id="TV_INPUT">%s</xliff:g> »."</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Impossible de démarrer les préférences de sous-titrage à l\'échelle du système."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d chaîne ajoutée"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d chaînes ajoutées"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Aucune chaîne ajoutée"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Syntoniseur"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Contrôle parental"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Activer"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Désactiver"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Chaînes bloquées"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Tout bloquer"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Tout débloquer"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Chaînes masquées"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Restrictions prog."</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Modifier le NIP"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Syst. classif."</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Avis"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Voir tous syst. clas."</string>
+ <string name="other_countries" msgid="8342216398676184749">"Autres pays"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Aucune"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Aucun"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Aucune"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Restrictions élevées"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Restrictions moy."</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Restrictions faibles"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Personnalisé"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Contenu adapté aux enfants"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Contenu adapté aux enfants plus âgés"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Contenu adapté aux adolescents"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Restrictions manuelles"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s et sous-catég."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Sous-catégories"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Entrez votre NIP pour regarder cette chaîne"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Entrez votre NIP pour regarder ce programme"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Entrez votre NIP"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Créez un NIP pour régler les contrôles parentaux"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Entrez le nouveau NIP"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Confirmez votre NIP"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Entrez votre NIP actuel"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Vous avez saisi un NIP incorrect à cinq reprises.\nVeuillez réessayer dans <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> seconde."</item>
+ <item quantity="other" msgid="8829550842387756054">"Vous avez entré un NIP erroné cinq fois. \nRéessayez dans <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> secondes."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Ce NIP est incorrect. Veuillez réessayer."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Le NIP est incorrect. Veuillez réessayer."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"À propos"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Licences de logiciels libres"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Licences de logiciels libres"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Version"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Pour regarder cette chaîne, touchez la droite, puis entrez votre NIP."</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Pour regarder ce programme, touchez la droite, puis entrez votre NIP."</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Ce programme est classé « <xliff:g id="RATING">%1$s</xliff:g> ». \nPour le regarder, touchez la droite, puis entrez votre NIP."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Programme bloqué"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Ce programme est classé « <xliff:g id="RATING">%1$s</xliff:g> »"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Sans titre"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Chaîne bloquée"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Saison <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>, épisode <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>, « <xliff:g id="EPISODE_TITLE">%3$s</xliff:g> »"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Sources des chaînes"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Configurer des chaînes en direct à partir des sources disponibles. Cela peut prendre plusieurs minutes, en fonction de la source de la chaîne."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Terminé"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d chaîne disponible"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d chaînes disponibles"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Aucune chaîne disponible"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nouveau"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"Appuyez sur la touche "<b>"Sélectionner"</b>" pour accéder au menu Télévision."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Aucune entrée trouvée"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Entrée introuvable"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Le mode Image dans image n\'est pas pris en charge"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Aucune entrée ne pouvant être affichée en mode IDI"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Le type d\'entrée ne convient pas. Veuillez lancer l\'application Chaînes en direct pour une entrée de type syntoniseur."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Échec des réglages"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Aucune application pouvant gérer cette action n\'a été trouvée."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Toutes les chaînes source sont masquées.\nSélectionnez au moins une chaîne à regarder."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Indisponible en raison d\'un signal vidéo faible"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Indisponibilité inattendue de la vidéo"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"La touche RETOUR concerne l\'appareil connecté. Appuyez sur le bouton ACCUEIL pour quitter."</string>
+</resources>
diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml
new file mode 100644
index 00000000..46b9f692
--- /dev/null
+++ b/res/values-fr/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Plein écran"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Toutes les chaînes"</item>
+ <item msgid="928298872841713530">"Famille/Enfants"</item>
+ <item msgid="2751606947569857164">"Sports"</item>
+ <item msgid="7345749789651321496">"Shopping"</item>
+ <item msgid="167201149441442173">"Films"</item>
+ <item msgid="525966731464264290">"Humour"</item>
+ <item msgid="6096710741527327836">"Voyage"</item>
+ <item msgid="2851882187117833883">"Drame"</item>
+ <item msgid="78492781188719038">"Enseignement"</item>
+ <item msgid="7221999662426308394">"Animaux/Faune sauvage"</item>
+ <item msgid="375300513250925001">"Actualités"</item>
+ <item msgid="7746320336582330410">"Jeux"</item>
+ <item msgid="1255741860568329178">"Arts"</item>
+ <item msgid="7603949681065702867">"Divertissement"</item>
+ <item msgid="4453821994746804366">"Style de vie"</item>
+ <item msgid="3488534597567932843">"Musique"</item>
+ <item msgid="7452153120614274095">"Haut de gamme"</item>
+ <item msgid="8215762047341133299">"Technologie/Science"</item>
+ </string-array>
+</resources>
diff --git a/res/values-fr/rating_system_strings.xml b/res/values-fr/rating_system_strings.xml
new file mode 100644
index 00000000..e719b4f6
--- /dev/null
+++ b/res/values-fr/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drogues"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sexe"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Violence"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drogues"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sexe"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Violence"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"français"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Dialogue suggestif"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Propos grossiers"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Contenu à caractère sexuel"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Violence"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Violence dans des scènes fantastiques"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Classification des films"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
new file mode 100644
index 00000000..4cb01be0
--- /dev/null
+++ b/res/values-fr/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Chaînes en direct"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stéréo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Commandes de lecture"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Chaînes"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Chaînes récentes"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Options TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Options PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Commandes de lecture indisponibles pour cette chaîne."</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Lire ou mettre en pause"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Avance rapide"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Retour arrière"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Suivant"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Précédent"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Guide des programmes"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Nouvelles chaînes disponibles"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Aucun lien disponible"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Ouvrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Sous-titres"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Mode d\'affichage"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"Mode PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Activé"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Désactivé"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Configuration"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Contrôle parental"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"À propos"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Source"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Permuter"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Activé"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Désactivé"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Son"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Principale"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Fenêtre mode PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Mise en page"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"En bas à droite"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"En haut à droite"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"En haut à gauche"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"En bas à gauche"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Côte à côte"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Taille"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Grande"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Petite"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Source d\'entrée"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Téléviseur (antenne/câble)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Aucune information sur le programme."</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Aucune information"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Chaîne bloquée"</string>
+ <string name="default_language" msgid="4122326459624337928">"Langue inconnue"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Sous-titres"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Désactivé"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Personnaliser le formatage"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Configurer les paramètres du système pour les sous-titres"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Mode d\'affichage"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stéréo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 Surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 Surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d chaînes"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Person. liste chaînes"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Sélectionner groupe"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Désélectionner groupe"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Grouper par :"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Source de la chaîne"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Grouper par"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Configuration chaîne"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Ce programme est bloqué."</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Classification de ce programme : <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Sources chaînes"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Nouvelles chaînes disponibles"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Personnaliser la liste des chaînes"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Choisir des chaînes pour le guide des programmes"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Non configuré"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"L\'entrée n\'est pas compatible avec la recherche automatique."</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Impossible de lancer la recherche automatique pour \"<xliff:g id="TV_INPUT">%s</xliff:g>\"."</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Impossible de lancer les préférences de sous-titrage pour l\'ensemble du système."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d chaîne ajoutée."</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d chaînes ajoutées."</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Aucune chaîne ajoutée."</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Contrôle parental"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Activé"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Désactivé"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Chaînes bloquées"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Tout bloquer"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Tout débloquer"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Chaînes masquées"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Filtrage des programmes"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Modifier le code PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Système de classification"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Notes"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Voir tous les systèmes de classification"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Autres pays"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Aucune"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Aucun"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Aucun"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Restrictions élevées"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Restrictions moyennes"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Restrictions faibles"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Personnalisé"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Contenu adapté aux enfants"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Contenu adapté aux enfants plus âgés"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Contenu adapté aux adolescents"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Restrictions manuelles"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s et sous-catég."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Sous-catégories"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Saisir votre code d\'accès pour regarder cette chaîne"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Saisir votre code d\'accès pour regarder ce programme"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Saisir votre code d\'accès"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Créer un code d\'accès pour paramétrer le contrôle parental"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Saisir le nouveau code d\'accès"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Confirmer votre code d\'accès"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Saisir votre code d\'accès actuel"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Vous avez saisi un code d\'accès incorrect à cinq reprises.\nVeuillez réessayer dans <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> seconde."</item>
+ <item quantity="other" msgid="8829550842387756054">"Vous avez saisi un code d\'accès incorrect à cinq reprises.\nVeuillez réessayer dans <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> secondes."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Ce code d\'accès est incorrect. Veuillez réessayer."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Le code PIN est incorrect. Veuillez réessayer."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"À propos"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Licences Open Source"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Licences Open Source"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Version"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Pour regarder cette chaîne, appuyez sur le bouton droit, puis saisissez votre code d\'accès."</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Pour regarder ce programme, appuyez sur la droite, puis saisissez votre code d\'accès."</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Classification de ce programme : <xliff:g id="RATING">%1$s</xliff:g>.\nPour le regarder, appuyez sur la touche droite, puis saisissez votre code d\'accès."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Programme bloqué"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Classification de ce programme : <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Sans titre"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Chaîne bloquée"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>, ép. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> : <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Sources des chaînes"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Configurez des chaînes en direct à partir des sources disponibles. Cette opération peut prendre plusieurs minutes en fonction de la source de la chaîne."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"OK"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d chaîne disponible"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d chaînes disponibles"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Aucune chaîne disponible"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nouveau"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Appuyez sur SÉLECTIONNER"</b>" pour accéder au menu de la télévision."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Aucune entrée TV trouvée."</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Entrée TV introuvable."</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Le mode PIP n\'est pas compatible."</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Aucune entrée disponible ne peut être affichée en mode PIP."</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Type de tuner non adapté. Veuillez lancer l\'application Chaînes en direct pour l\'entrée TV de type tuner."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Échec des réglages."</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Aucune application trouvée pour gérer cette action."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Toutes les chaînes source sont masquées.\nSélectionnez au moins une chaîne à regarder."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Indisponible en raison d\'un signal vidéo faible"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Indisponibilité inattendue de la vidéo"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"La touche RETOUR concerne l\'appareil connecté. Appuyez sur le bouton ACCUEIL pour quitter."</string>
+</resources>
diff --git a/res/values-gl-rES/arrays.xml b/res/values-gl-rES/arrays.xml
new file mode 100644
index 00000000..cd8c31f3
--- /dev/null
+++ b/res/values-gl-rES/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Completa"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Todas as canles"</item>
+ <item msgid="928298872841713530">"Familiar/infantil"</item>
+ <item msgid="2751606947569857164">"Deportes"</item>
+ <item msgid="7345749789651321496">"Compras"</item>
+ <item msgid="167201149441442173">"Películas"</item>
+ <item msgid="525966731464264290">"Comedia"</item>
+ <item msgid="6096710741527327836">"Viaxes"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Educación"</item>
+ <item msgid="7221999662426308394">"Animais/vida salvaxe"</item>
+ <item msgid="375300513250925001">"Noticias"</item>
+ <item msgid="7746320336582330410">"Xogos"</item>
+ <item msgid="1255741860568329178">"Arte"</item>
+ <item msgid="7603949681065702867">"Entretemento"</item>
+ <item msgid="4453821994746804366">"Estilo de vida"</item>
+ <item msgid="3488534597567932843">"Música"</item>
+ <item msgid="7452153120614274095">"Estrea"</item>
+ <item msgid="8215762047341133299">"Ciencia e tecnoloxía"</item>
+ </string-array>
+</resources>
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..4afd59cc
--- /dev/null
+++ b/res/values-gl-rES/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drogas"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sexo"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Violencia"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drogas"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sexo"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Violencia"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Francés"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Diálogo insinuante"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Linguaxe vulgar"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Contido sexual"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Violencia"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Violencia ficticia"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Clasificacións de filmes"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
new file mode 100644
index 00000000..6a4dfe08
--- /dev/null
+++ b/res/values-gl-rES/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Canles en directo"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"estéreo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Controis de reprodución"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Canles"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Canles recentes"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Opcións de TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Opcións de PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Os controis de reprodución non están dispoñibles nesta canle"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Reprodución ou pausa"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Avance rápido"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Rebobinado"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Seguinte"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Anterior"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Guía de programas"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Novas canles dispoñib."</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Sen ligazón dispoñible"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Abrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Subtítulos"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Modo visualiz."</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Activado"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Desactivado"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multiaudio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Config. canle"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Controis pais"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Acerca de"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Fonte"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Cambiar"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Activado"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Desactivado"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Son"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Principal"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Ventá de PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Deseño"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Abaixo dereita"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Arriba dereita"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Arriba esquerda"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Abaixo esquerda"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"En paralelo"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Tamaño"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Grande"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Pequeno"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Fonte de entrada"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antena/cable)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Non hai información do programa"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Non hai información"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Canle bloqueada"</string>
+ <string name="default_language" msgid="4122326459624337928">"Idioma descoñecido"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Subtítulos"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Desactivado"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Personalizar formato"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Definir pref. do sistema para subtítulos"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Modo visualiz."</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multiaudio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"estéreo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Surround 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Surround 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d canles"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Personalizar lista"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Seleccionar grupo"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Anular a selección"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Agrupar por"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Fonte de canles"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"Alta definición/definición estándar"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"Alta definición"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"Definición estándar"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Agrupar por"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Configuración canle"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Este programa está bloqueado"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Este programa está clasificado como <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Fontes de canles"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Novas canles dispoñibles"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Personalizar lista"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Selecciona canles para a túa guía"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Sen configurar"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"A entrada non admite a busca automática"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Non é posible iniciar a busca automática para \"<xliff:g id="TV_INPUT">%s</xliff:g>\""</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Non se poden iniciar as preferencias de todo o sistema para os subtítulos."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"Engadiuse %1$d canle"</item>
+ <item quantity="other" msgid="1078861616751739285">"Engadíronse %1$d canles"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Non se engadiron canles"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Sintonizador"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Controis parentais"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Activados"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Desactivados"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Canles bloqueadas"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Bloquear todo"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Desbloquear todo"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Canles ocultas"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Restricións prog."</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Cambiar PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Sistemas clasific."</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Valoracións"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Ver sistemas clasif."</string>
+ <string name="other_countries" msgid="8342216398676184749">"Outros países"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Ningunha"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Ningunha"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Ningún"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Restricións altas"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Restricións medias"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Restricións baixas"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Personalizada"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Contido adecuado para nenos"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Contido adecuado para nenos máis vellos"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Contido adecuado para adolescentes"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Restricións manuais"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s e subclasific."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Subclasificacións"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Introduce o teu PIN para ver esta canle"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Introduce o teu PIN para ver este programa"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Introduce o teu PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Para definir controis parentais, crea un PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Introduce o novo PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Confirma o teu PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Introduce o teu PIN actual"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Introduciches o PIN incorrecto 5 veces.\nTéntao de novo en <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segundo."</item>
+ <item quantity="other" msgid="8829550842387756054">"Introduciches o PIN incorrecto 5 veces.\nTéntao de novo en <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segundos."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"О PIN era incorrecto. Téntao de novo."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Téntao de novo. O PIN non coincide."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Acerca de"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Licenzas de código aberto"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Licenzas de código aberto"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versión"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Para ver esta canle, preme na tecla cara á dereita e introduce o PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Para ver esta programa, preme na tecla cara á dereita e introduce o PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Este programa está clasificado como <xliff:g id="RATING">%1$s</xliff:g>.\nPara ver esta programa, preme na tecla cara á dereita e introduce o PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"O programa está bloqueado"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Este programa está clasificado como <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Sen título"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Canle bloqueada"</string>
+ <string name="episode_format" msgid="4881195874563241096">"T<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Fontes de canles"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Configura as canles en directo a partir das fontes dispoñibles. Esta acción pode tardar varios minutos dependendo da fonte da canle."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Feito"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d canle dispoñible"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d canles dispoñibles"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Non hai canles dispoñibles"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Novo"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Preme SELECCIONAR"</b>" para acceder ao menú da televisión."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Non se atopou ningunha entrada de televisión"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Non se pode atopar a entrada de televisión"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP non é compatible"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Non hai ningunha entrada que se poida mostrar con PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tipo de sintonizador non adecuado. Inicia a aplicación Canles en directo para a entrada de TV do tipo de sintonizador."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Erro de sintonización"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Non se encontrou ningunha aplicación para procesar esta acción."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Todas as canles de orixe están ocultas.\nSelecciona polo menos unha canle para ver."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Non dispoñible debido a un sinal de vídeo feble"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"O vídeo deixou de estar dispoñible de forma inesperada"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"A tecla Atrás aplícase ao dispositivo conectado. Preme o botón de inicio para saír."</string>
+</resources>
diff --git a/res/values-hi/arrays.xml b/res/values-hi/arrays.xml
new file mode 100644
index 00000000..f9eea000
--- /dev/null
+++ b/res/values-hi/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"सामान्य"</item>
+ <item msgid="2533030282864800794">"पूर्ण"</item>
+ <item msgid="8568284598210500589">"ज़ूम करें"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"सभी चैनल"</item>
+ <item msgid="928298872841713530">"परिवार/बच्चे"</item>
+ <item msgid="2751606947569857164">"खेलकूद"</item>
+ <item msgid="7345749789651321496">"खरीदारी"</item>
+ <item msgid="167201149441442173">"फ़िल्में"</item>
+ <item msgid="525966731464264290">"हास्य"</item>
+ <item msgid="6096710741527327836">"यात्रा"</item>
+ <item msgid="2851882187117833883">"नाटक"</item>
+ <item msgid="78492781188719038">"शिक्षा"</item>
+ <item msgid="7221999662426308394">"पशु/वन्य जीवन"</item>
+ <item msgid="375300513250925001">"समाचार"</item>
+ <item msgid="7746320336582330410">"गेमिंग"</item>
+ <item msgid="1255741860568329178">"कला"</item>
+ <item msgid="7603949681065702867">"मनोरंजन"</item>
+ <item msgid="4453821994746804366">"जीवनशैली"</item>
+ <item msgid="3488534597567932843">"संगीत"</item>
+ <item msgid="7452153120614274095">"प्रीमियर"</item>
+ <item msgid="8215762047341133299">"तकनीक/विज्ञान"</item>
+ </string-array>
+</resources>
diff --git a/res/values-hi/rating_system_strings.xml b/res/values-hi/rating_system_strings.xml
new file mode 100644
index 00000000..184b84a9
--- /dev/null
+++ b/res/values-hi/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"ड्रग"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"सेक्स"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"हिंसा"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"ड्रग"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"सेक्स"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"हिंसा"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"फ़्रेंच"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"अश्लील संवाद"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"अशिष्ट भाषा"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"यौन सामग्री"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"हिंसा"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"काल्पनिक हिंसा"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"फ़िल्म रेटिंग"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
new file mode 100644
index 00000000..7429ccd4
--- /dev/null
+++ b/res/values-hi/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"मोनो"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"स्टीरियो"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"चलाने के नियंत्रण"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"चैनल"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"हाल ही के चैनल"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"टीवी विकल्प"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP विकल्‍प"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"इस चैनल के लिए चलाने के नियंत्रण अनुपलब्‍ध हैं"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"चलाएं या रोकें"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"फ़ास्ट फ़ॉरवर्ड करें"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"रिवाइंड करें"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"अगला"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"पिछला"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"कार्यक्रम गाइड"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"नए चैनल उपलब्‍ध हैं"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"कोई लिंक उपलब्ध नहीं है"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> खोलें"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"उपशीर्षक"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"प्रदर्शन मोड"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"चालू"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"बंद"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"एकाधिक-ऑडियो"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"चैनल सेटअप"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"अभिभावकीय नियंत्रण"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"संक्षिप्त विवरण"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"स्रोत"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"स्वैप करें"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"चालू"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"बंद"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"ध्वनि"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"मुख्‍य"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP विंडो"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"लेआउट"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"नीचे दाएं"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"ऊपर दाएं"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"ऊपर बाएं"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"नीचे बाएं"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"साथ-साथ"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"आकार"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"बड़ा"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"छोटा"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"इनपुट स्रोत"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"टीवी (एंटिना/केबल)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"कोई कार्यक्रम जानकारी नहीं"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"कोई सूचना नहीं"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"अवरोधित चैनल"</string>
+ <string name="default_language" msgid="4122326459624337928">"अज्ञात भाषा"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"उपशीर्षक"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"बंद"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"प्रारूपण कस्टमाइज़ करें"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"उपशीर्षकों के लिए सिस्टम-व्यापी प्राथमिकताएं सेट करें"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"प्रदर्शन मोड"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"एकाधिक-ऑडियो"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"मोनो"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"स्टीरियो"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 सराउंड"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 सराउंड"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d चैनल"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"चैनल सूची कस्टमाइज़ करें"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"समूह चुनें"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"समूह का चयन निकालें"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"इसके द्वारा समूहीकृत"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"चैनल का स्रोत"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"इसके द्वारा समूहीकृत"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"चैनल सेटअप"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"यह कार्यक्रम अवरोधित है"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"इस कार्यक्रम को <xliff:g id="RATING">%1$s</xliff:g> रेट किया गया है"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"चैनल के स्रोत"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"नए चैनल उपलब्‍ध हैं"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"चैनल सूची कस्टमाइज़ करें"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"कार्यक्रम मार्गदर्शिका के लिए चैनल चुनें"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"सेट नहीं है"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"इनपुट ऑटो-स्कैन का समर्थन नहीं करता है"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\'<xliff:g id="TV_INPUT">%s</xliff:g>\' के लिए स्वतः स्कैन प्रारंभ करने में असमर्थ"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"उपशीर्षक के लिए सिस्टम व्यापी प्राथमिकताएं प्रारंभ करने में अक्षम."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d चैनल जोड़ा गया"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d चैनल जोड़े गए"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"कोई चैनल नहीं जोड़ा गया"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"ट्यूनर"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"अभिभावकीय नियंत्रण"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"चालू"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"बंद"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"चैनल अवरोधित"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"सभी अवरुद्ध करें"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"सभी को अनवरोधित करें"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"छिपे हुए चैनल"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"कार्यक्रम प्रतिबंध"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"पिन बदलें"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"रेटिंग सिस्टम"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"रेटिंग"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"सभी रेटिंग सिस्टम देखें"</string>
+ <string name="other_countries" msgid="8342216398676184749">"अन्य देश"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"कोई नहीं"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"कोई नहीं"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"कोई नहीं"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"उच्च प्रतिबंध"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"मध्यम प्रतिबंध"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"कम प्रतिबंध"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"कस्टम"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"सामग्री बच्चों के लिए उपयुक्त है"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"सामग्री बड़े बच्चों के लिए उपयुक्त है"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"सामग्री किशोरों के लिए उपयुक्त है"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"मैन्युअल प्रतिबंध"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s और उप-रेटिंग"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"उप-रेटिंग"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"इस चैनल को देखने के लिए अपना पिन डालें"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"इस कार्यक्रम को देखने के लिए अपना पिन डालें"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"अपना पिन डालें"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"अभिभावकीय नियंत्रण सेट करने के लिए, पिन बनाएं"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"नया पिन डालें"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"अपने पिन की पुष्टि करें"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"अपना वर्तमान पिन डालें"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"आपने 5 बार गलत पिन डाला है.\n <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> सेकंड में पुनः प्रयास करें."</item>
+ <item quantity="other" msgid="8829550842387756054">"आपने 5 बार गलत पिन डाला है.\n <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> सेकंड में पुनः प्रयास करें."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"वह पिन गलत था. पुन: प्रयास करें."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"फिर से प्रयास करें, पिन का मिलान नहीं हुआ"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"संक्षिप्त विवरण"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"ओपन सोर्स लाइसेंस"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"ओपन सोर्स लाइसेंस"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"वर्शन"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"इस चैनल को देखने के लिए, दाईं ओर दबाएं और अपना पिन डालें"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"इस कार्यक्रम को देखने के लिए, दाईं ओर दबाएं और अपना पिन डालें"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"इस कार्यक्रम को <xliff:g id="RATING">%1$s</xliff:g> रेट किया गया है.\nइस कार्यक्रम को देखने के लिए, Right दबाएं और अपना पिन डालें."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"यह कार्यक्रम बंद कर दिया गया है"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"इस कार्यक्रम को <xliff:g id="RATING">%1$s</xliff:g> रेट किया गया है"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"कोई शीर्षक नहीं"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"चैनल अवरोधित"</string>
+ <string name="episode_format" msgid="4881195874563241096">"क्रमांक<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: एपिसोड <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"चैनल के स्रोत"</string>
+ <string name="setup_description" msgid="8728423605912915099">"उपलब्ध स्रोतों से लाइव चैनल सेट अप करें. चैनल स्रोत के आधार पर इसमें कुछ मिनट लग सकते हैं."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"हो गया"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d चैनल उपलब्‍ध है"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d चैनल उपलब्‍ध हैं"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"कोई चैनल उपलब्‍ध नहीं है"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"नया"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"टीवी मेनू ऐक्‍सेस करने के लिए "<b>"चुनें"</b>" दबाएं."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"कोई टीवी इनपुट नहीं मिला"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"टीवी इनपुट नहीं मिल पा रहा है"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP समर्थित नहीं है"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"कोई उपलब्ध इनपुट नहीं है जिसे PIP के साथ दिखाया जा सके"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"ट्यूनर प्रकार उपयुक्‍त नहीं है. कृपया ट्यूनर प्रकार टीवी इनपुट के लिए Live Channels ऐप लॉन्‍च करें."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"ट्यून विफल रहा"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"यह कार्रवाई प्रबंधित करने के लिए कोई ऐप नहीं मिला."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"सभी स्रोत चैनल छिपे हुए हैं.\nदेखने के लिए कम से कम कोई एक चैनल चुनें."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"कमज़ोर वीडियो सिग्नल के कारण अनुपलब्ध है"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"वीडियो अनपेक्षित रूप से अनुपलब्ध है"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"BACK कुंजी कनेक्ट किए गए डिवाइस के लिए है. बाहर निकलने के लिए HOME बटन दबाएं."</string>
+</resources>
diff --git a/res/values-hr/arrays.xml b/res/values-hr/arrays.xml
new file mode 100644
index 00000000..93253b96
--- /dev/null
+++ b/res/values-hr/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Uobičajeni"</item>
+ <item msgid="2533030282864800794">"Cijeli"</item>
+ <item msgid="8568284598210500589">"Zumiranje"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Svi kanali"</item>
+ <item msgid="928298872841713530">"Obitelj/djeca"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Kupnja"</item>
+ <item msgid="167201149441442173">"Filmovi"</item>
+ <item msgid="525966731464264290">"Komedija"</item>
+ <item msgid="6096710741527327836">"Putovanja"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Obrazovanje"</item>
+ <item msgid="7221999662426308394">"Životinjski svijet"</item>
+ <item msgid="375300513250925001">"Vijesti"</item>
+ <item msgid="7746320336582330410">"Igre"</item>
+ <item msgid="1255741860568329178">"Umjetnost"</item>
+ <item msgid="7603949681065702867">"Zabava"</item>
+ <item msgid="4453821994746804366">"Životni stil"</item>
+ <item msgid="3488534597567932843">"Glazba"</item>
+ <item msgid="7452153120614274095">"Premijere"</item>
+ <item msgid="8215762047341133299">"Tehnologija/znanost"</item>
+ </string-array>
+</resources>
diff --git a/res/values-hr/rating_system_strings.xml b/res/values-hr/rating_system_strings.xml
new file mode 100644
index 00000000..a343b713
--- /dev/null
+++ b/res/values-hr/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Droge"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Seks"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Nasilje"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Droge"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Seks"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Nasilje"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"francuski"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Sugestivan dijalog"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Vulgarni izrazi"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Seksualni sadržaj"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Nasilje"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Nerealno nasilje"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Ocjene filmova"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
new file mode 100644
index 00000000..c2ba6a58
--- /dev/null
+++ b/res/values-hr/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Kanali uživo"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Kontrole reprodukcije"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanali"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Nedavni kanali"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV opcije"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Opcije PIP-a"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Kontrole reprodukcije nisu dostupne za ovaj kanal"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Reprodukcija ili pauziranje"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Brzo unaprijed"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Unatrag"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Dalje"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Prethodno"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programski vodič"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Dostupni su novi kanali"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Nema dostupnih veza"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Otvori <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Titlovi"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Način prikaza"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Uključeno"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Isključeno"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multiaudio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Postavljanje kanala"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Nadzor djece"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Informacije"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Izvor"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Zamijeni"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Uključeno"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Isključeno"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Zvuk"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Glavni"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Prozor PIP-a"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Raspored"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Pri dnu desno"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Pri vrhu desno"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Pri vrhu lijevo"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Pri dnu lijevo"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Jedno uz drugo"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Veličina"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Velik"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Malen"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Izvor ulaza"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antena/kabel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Nema podataka o programu"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Nema informacija"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Blokirani kanal"</string>
+ <string name="default_language" msgid="4122326459624337928">"Nepoznati jezik"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Titlovi"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Isključeno"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Prilagodi format"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Postavljanje postavki sustava za titlove"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Način prikaza"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multiaudio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 Surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 Surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d kanala"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Prilagodi popis"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Odaberi grupu"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Poništi odabir grupe"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Grupiraj po"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Izvor kanala"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Grupiraj po"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Postavljanje kanala"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Program je blokiran"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Program ima ocjenu <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Izvori kanala"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Dostupni su novi kanali"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Prilagodi popis"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Odaberite kanale za vodič kroz programe"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Nije postavljen"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Ulaz ne podržava automatsko pretraživanje"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Nije moguće pokretanje automatskog pretraživanja za \"<xliff:g id="TV_INPUT">%s</xliff:g>\""</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Postavke za titlove na razini sustava nisu pokrenute."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"Dodan je %1$d kanal"</item>
+ <item quantity="other" msgid="1078861616751739285">"Dodano je %1$d kanala"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Kanali nisu dodani"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tražilo"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Roditeljski nadzor"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Uključeno"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Isključeno"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Kanali blokirani"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Blokiraj sve"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Deblokiraj sve"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Skriveni kanali"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Progr. ograničenja"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Promjena PIN-a"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Sustavi ocjena"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Ocjene"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Svi sustavi ocjena"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Ostale zemlje"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Ništa"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Ništa"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Ništa"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Visoka ograničenja"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Srednja ograničenja"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Niska ograničenja"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Prilagođeno"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Sadržaj prikladan za djecu"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Sadržaj prikladan za stariju djecu"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Sadržaj prikladan za tinejdžere"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Ručna ograničenja"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s i podocjene"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Podocjene"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Unesite PIN da biste gledali ovaj kanal"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Unesite PIN da biste gledali ovaj program"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Unesite PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Da biste postavili roditeljski nadzor, stvorite PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Unesite novi PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Potvrdite PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Unesite trenutačni PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Unijeli ste pogrešan PIN 5 puta.\nPokušajte ponovo za <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekundu."</item>
+ <item quantity="other" msgid="8829550842387756054">"Unijeli ste pogrešan PIN 5 puta.\nPokušajte ponovo za <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> s."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"PIN je pogrešan. Pokušajte ponovno."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Pokušaj ponovo, PIN se ne podudara"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Informacije"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Licence otvorenog izvornog koda"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Licence otvorenog izvornog koda"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Verzija"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Da biste gledali ovaj kanal, pritisnite desno pa unesite PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Da biste gledali ovaj program, pritisnite desno pa unesite PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Ovaj program ima ocjenu <xliff:g id="RATING">%1$s</xliff:g>.\nDa biste ga gledali, pritisnite desni gumb pa unesite PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Program je blokiran"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Program ima ocjenu <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Bez naslova"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanal blokiran"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S. <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Izvori kanala"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Postavlja kanale uživo iz dostupnih izvora. To može potrajati nekoliko minuta ovisno o izvoru kanala."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Gotovo"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Dostupan je %1$d kanal"</item>
+ <item quantity="other" msgid="2386588423841837714">"Broj dostupnih kanala: %1$d"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Nema dostupnih kanala"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Novi"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Pritisnite ODABERITE"</b>" da biste pristupili izborniku TV-a."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"TV ulaz nije pronađen"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"TV ulaz nije pronađen"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP nije podržan"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Nema dostupnog ulaza koji se može prikazati putem PIP-a"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Vrsta prijemnika nije prikladna. Pokrenite aplikaciju Kanali uživo ako želite koristiti prijemnik kao TV ulaz."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Traženje kanala nije uspjelo"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Nije pronađena nijedna aplikacija koja može provesti tu radnju."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Skriveni su kanali svih izvora.\nOdaberite barem jedan kanal za gledanje."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Nije dostupno zbog slabog videosignala"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Videozapis neočekivano nije dostupan"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Tipka NATRAG namijenjena je za povezani uređaj. Pritisnite tipku POČETNA za izlaz."</string>
+</resources>
diff --git a/res/values-hu/arrays.xml b/res/values-hu/arrays.xml
new file mode 100644
index 00000000..cbc69895
--- /dev/null
+++ b/res/values-hu/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normál"</item>
+ <item msgid="2533030282864800794">"Teljes"</item>
+ <item msgid="8568284598210500589">"Nagyítás/kicsinyítés"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Összes csatorna"</item>
+ <item msgid="928298872841713530">"Család/gyermekek"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Vásárlás"</item>
+ <item msgid="167201149441442173">"Filmek"</item>
+ <item msgid="525966731464264290">"Vígjáték"</item>
+ <item msgid="6096710741527327836">"Utazás"</item>
+ <item msgid="2851882187117833883">"Dráma"</item>
+ <item msgid="78492781188719038">"Oktatás"</item>
+ <item msgid="7221999662426308394">"Állatok/vadvilág"</item>
+ <item msgid="375300513250925001">"Hírműsor"</item>
+ <item msgid="7746320336582330410">"Játék"</item>
+ <item msgid="1255741860568329178">"Művészetek"</item>
+ <item msgid="7603949681065702867">"Szórakozás"</item>
+ <item msgid="4453821994746804366">"Életstílus"</item>
+ <item msgid="3488534597567932843">"Zene"</item>
+ <item msgid="7452153120614274095">"Premier"</item>
+ <item msgid="8215762047341133299">"Technika/tudomány"</item>
+ </string-array>
+</resources>
diff --git a/res/values-hu/rating_system_strings.xml b/res/values-hu/rating_system_strings.xml
new file mode 100644
index 00000000..54d23b03
--- /dev/null
+++ b/res/values-hu/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Kábítószerek"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Szex"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Erőszak"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Kábítószerek"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Szex"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Erőszak"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"francia"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Burkoltan szexuális jellegű párbeszéd"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Durva nyelvezet"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Szexuális tartalom"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Erőszak"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Kitalált erőszak-ábrázolás"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Filmes besorolások"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
new file mode 100644
index 00000000..d8838937
--- /dev/null
+++ b/res/values-hu/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Élő csatornák"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"monó"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"sztereó"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Lejátszásvezérlők"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Csatornák"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Legutóbbi csatornák"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Tv beállításai"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP-beállítások"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"A lejátszási vezérlők nem érhetők el ennél a csatornánál"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Lejátszás vagy szüneteltetés"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Előretekerés"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Visszatekerés"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Tovább"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Vissza"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Műsorfüzet"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Új csatornák"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Nincs elérhető link"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> megnyitása"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Feliratok"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Megjelenítés"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Bekapcsolva"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Kikapcsolva"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Több hangsáv"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Csatornabeállítás"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Felügyelet"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Névjegy"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Forrás"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Csere"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Bekapcsolva"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Kikapcsolva"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Hang"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Elsődleges"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP-ablak"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Elrendezés"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Lent jobbra"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Fent jobbra"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Fent balra"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Lent balra"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Egymás mellett"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Méret"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Nagy"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Kicsi"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Bemeneti forrás"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antenna/kábel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Nincsenek műsorinformációk"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Nincs információ."</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Letiltott csatorna"</string>
+ <string name="default_language" msgid="4122326459624337928">"Ismeretlen nyelv"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Feliratok"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Kikapcsolva"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Személyre szabás"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Rendszerszintű beállítások feliratokhoz"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Megjelenítési mód"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Több hangsáv"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"monó"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"sztereó"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1-es térhatású hangzás"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1-es térhatású hangzás"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d csatorna"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Listatestreszabás"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Csoport kiválasztása"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Visszavonás"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Csoportosítás"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Csatornaforrás"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Csoportosítás"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Csatornabeállítás"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"A műsor le van tiltva."</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"A műsor besorolása: <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Csatornaforrások"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Új rendelkezésre álló csatornák"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Listatestreszabás"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Válasszon csatornákat a műsorfüzetbe"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Nincs beállítva"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"A bemenet nem támogatja az automatikus keresést"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Nem lehet elindítani a(z) „<xliff:g id="TV_INPUT">%s</xliff:g>” automatikus keresését"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Nem lehet elindítani a feliratok rendszerszintű beállításait."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d csatorna hozzáadva"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d csatorna hozzáadva"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Nincs csatorna hozzáadva"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Szülői felügyelet"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Bekapcsolva"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Kikapcsolva"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Tiltott csatornák"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Összes letiltása"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Tiltások feloldása"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Rejtett csatornák"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Programkorlátozás"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN kód módosítása"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Besorolásfajták"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Minősítések"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Összes megjelenítése"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Más országok"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Nincs"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Nincs"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Nincs"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Magas korlátozások"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Közepes korlátozások"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Alacsony korlátozás"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Egyedi"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"A tartalom megfelelő gyermekek számára"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Nagyobb gyermekeknek megfelelő tartalom"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"A tartalom megfelelő tizenévesek számára"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Manuális korlátozások"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s + alértékelések"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Alértékelések"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"A csatorna megtekintéséhez PIN kód szükséges"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"A műsor megtekintéséhez PIN kód szükséges"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"PIN kód megadása"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Szülői felügyelet beállításához hozzon létre egy PIN kódot."</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Új PIN kód megadása"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"PIN kód megerősítése"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Adja meg a jelenlegi PIN kódot"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"5 alkalommal rosszul adta meg a PIN kódot.\nPróbálja újra <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> másodperc múlva."</item>
+ <item quantity="other" msgid="8829550842387756054">"5 alkalommal rosszul adta meg a PIN kódot.\nPróbálja újra <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> másodperc múlva."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"A PIN kód helytelen, próbálja újra."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"A PIN kód nem egyezik, próbálja újra"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Névjegy"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Nyílt forráskódú licencek"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Nyílt forráskódú licencek"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Verzió"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"A csatorna megtekintéséhez nyomja meg a jobbra gombot, majd adja meg a PIN kódot"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"A műsor megtekintéséhez nyomja meg a jobbra gombot, majd adja meg a PIN kódot"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"A műsor besorolása: <xliff:g id="RATING">%1$s</xliff:g>.\nA megtekintéséhez nyomja meg a jobbra gombot, majd adja meg a PIN kódot."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"A műsor le van tiltva"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"A műsor besorolása: <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Nincs cím"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Csatorna letiltva"</string>
+ <string name="episode_format" msgid="4881195874563241096">"<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>. évad, <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>. rész: <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Csatornaforrások"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Élő csatornák beállítása az elérhető források alapján. Ez a csatornaforrástól függően néhány percig is eltarthat."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Kész"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d elérhető csatorna"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d elérhető csatorna"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Nincs elérhető csatorna"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Új"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Nyomja meg a KIVÁLASZTÁS"</b>" gombot a tévé menüjének eléréséhez."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Nem található tévébemenet."</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"A tévébemenet nem található."</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"A kép a képben funkció nem támogatott."</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Nincs elérhető bemeneti adás, amely megjeleníthető lenne a kép a képben (picture in picture, PIP) funkcióval."</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"A tuner típusa nem megfelelő. Indítsa el a tévé bemeneti tunertípusának megfelelő Élő csatornák alkalmazást."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Tunerhiba"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Nincs megfelelő alkalmazás a művelet végrehajtásához."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Minden forráscsatorna rejtve van.\nVálasszon ki legalább egy csatornát, amelyet meg szeretne tekinteni."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"A gyenge videojel miatt nem érhető el."</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"A videó váratlanul nem érhető el."</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"A BACK gomb a csatlakoztatott eszközzel használható. A kilépéshez nyomja meg a HOME gombot."</string>
+</resources>
diff --git a/res/values-hy-rAM/arrays.xml b/res/values-hy-rAM/arrays.xml
new file mode 100644
index 00000000..0c378758
--- /dev/null
+++ b/res/values-hy-rAM/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Սովորական"</item>
+ <item msgid="2533030282864800794">"Լիցքավորված"</item>
+ <item msgid="8568284598210500589">"Խոշորացնել"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Բոլոր ալիքները"</item>
+ <item msgid="928298872841713530">"Ընտանիք/Երեխաներ"</item>
+ <item msgid="2751606947569857164">"Սպորտ"</item>
+ <item msgid="7345749789651321496">"Գնումներ"</item>
+ <item msgid="167201149441442173">"Ֆիլմեր"</item>
+ <item msgid="525966731464264290">"Կատակերգություն"</item>
+ <item msgid="6096710741527327836">"Ճամփորդություն"</item>
+ <item msgid="2851882187117833883">"Դրամա"</item>
+ <item msgid="78492781188719038">"Կրթություն"</item>
+ <item msgid="7221999662426308394">"Կենդ./Վայրի բնություն"</item>
+ <item msgid="375300513250925001">"Նորություններ"</item>
+ <item msgid="7746320336582330410">"Համակարգչային խաղեր"</item>
+ <item msgid="1255741860568329178">"Արվեստ"</item>
+ <item msgid="7603949681065702867">"Ժամանց"</item>
+ <item msgid="4453821994746804366">"Ապրելակերպ"</item>
+ <item msgid="3488534597567932843">"Երաժշտություն"</item>
+ <item msgid="7452153120614274095">"Նորույթներ"</item>
+ <item msgid="8215762047341133299">"Տեխնիկա/Գիտություն"</item>
+ </string-array>
+</resources>
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..a99dca56
--- /dev/null
+++ b/res/values-hy-rAM/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Թմրանյութեր"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Սեռական բնույթի տեսարաններ"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Բռնության տեսարաններ"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Թմրանյութեր"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Սեռական բնույթի տեսարաններ"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Բռնության տեսարաններ"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"ֆրանսերեն"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Անպարկեշտ խոսքեր"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Կոպիտ բառեր"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Սեռական բովանդակություն"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Բռնության տեսարաններ"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Բռնության տեսարաններ հրեշների մասնակցությամբ"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Ֆիլմի վարկանիշները"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
new file mode 100644
index 00000000..d052e662
--- /dev/null
+++ b/res/values-hy-rAM/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Ուղիղ եթեր"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"մոնո"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"ստերեո"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Նվագարկման կառավար"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Հեռուստաալիքներ"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Վերջին վարկածները"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Հեռ. ընտրանքներ"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP ընտրանքներ"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Նվագարկման կառավարներն այս ալիքում անհասանելի են"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Նվագարկել կամ դադարեցնել"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Արագ առաջանցում"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Հետանցում"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Հաջորդը"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Նախորդը"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Ծրագրի ուղեցույց"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Նոր ալիքներ"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Հասանելի հղում չկա"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Բացել <xliff:g id="APP_NAME">%1$s</xliff:g>-ը"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Փակ խորագրեր"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Ցուցադրման ռեժիմ"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Միացված է"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Անջատված է"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Բազմաուդիո"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Կարգավորում"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Ծնողական հսկում"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Ընտրանքի մասին"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Աղբյուր"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Փոխել"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Միացված է"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Անջատված է"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Ձայն"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Հիմնական"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP պատուհան"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Դասավորություն"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Ստորին աջ"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Վերին աջ"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Վերին ձախ"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Ստորին ձախ"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Կողք կողքի"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Չափը"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Մեծ"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Փոքր"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Աղբյուր"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Հեռուստացույց (ալեհավաք/մալուխ)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Ծրագրի մասին տեղեկություններ չկան"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Տեղեկություն չկա"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Ալիքն արգելափակված է:"</string>
+ <string name="default_language" msgid="4122326459624337928">"Անհայտ լեզու"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Փակ ենթագրեր"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Անջատված է"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Հարմարացնել ձևաչափը"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Սահմանել համակարգի խորագրերի պարամետրեր"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Ցուցադրման ռեժիմ"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Բազմաուդիո"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"Մոնո"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"Ստերեո"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 ծավալային ձայն"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 ծավալային ձայն"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d հեռուստաալիք"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Հարմարեցնել ցանկը"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Ընտրել խումբը"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Ապընտրել խումբը"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Խմբավորել"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Ալիք աղբյուրը"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Խմբավորել"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Կարգավորում"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Այս ծրագիրը արգելափակված է"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Այս ծրագրի վարկանիշը <xliff:g id="RATING">%1$s</xliff:g> է"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Ալիքի աղբյուրները"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Նոր ալիքներ"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Հարմարեցնել ցանկը"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Ընտրեք կապուղիներ ձեր ուղեցույցի համար"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Կարգավորված չէ"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Մուտքը ինքնասկանավարում չի աջակցում"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Չի հաջողվում սկսել «<xliff:g id="TV_INPUT">%s</xliff:g>»-ի ավտոմատ սկանավորումը"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Չհաջողվեց բացել խորագրերի համակարգի կարգավորումները:"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d ալիք ավելացվեց"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d ալիք ավելացվեց"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Ալիքներ չեն ավելացվել"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Ծնողական հսկում"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Միացված է"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Անջատված է"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Արգելափակված են"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Արգելափակել բոլորը"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Ապաարգելափակել բոլորը"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Թաքնված ալիքներ"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Ծր. սահմանափակումներ"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Փոխել PIN-ը"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Գն. համակարգեր"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Վարկանիշ"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Բոլոր գն. համակարգերը"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Այլ երկրներ"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Չկա"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Չկա"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Չկա"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Խիստ սահմանափակում"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Միջ. սահմանափակումներ"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Թույլ սահմանափակում"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Հատուկ"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Բովանդակությունը հարմար է երեխաների համար"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Բարձր տարիքային խմբի երեխաների համար"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Բովանդակությունը հարմար է անչափահասների համար"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Ձեռքով կարգավորման սահմանափակումներ"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s և ենթավարկանիշեր"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Ենթավարկանիշեր"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Այս ալիքը դիտելու համար մուտքագրեք PIN-ը"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Այս ծրագիրը դիտելու համա մուտքագրեք PIN-ը"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Մուտքագրեք ձեր PIN-ը"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Ծնողական վերահսկումը կարգավորելու համար PIN կոդ նախադրեք"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Մուտքագրեք նոր PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Հաստատեք ձեր PIN-ը"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Մուտքագրեք ձեր ներկայիս PIN-ը"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"PIN կոդը 5 անգամ սխալ է մուտքագրվել:\nՓորձեք նորից <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> վայրկյանից:"</item>
+ <item quantity="other" msgid="8829550842387756054">"PIN կոդը 5 անգամ սխալ է մուտքագրվել:\nՓորձեք նորից <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> վայրկյանից:"</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"PIN-ը սխալ էր: Կրկին փորձեք:"</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Փորձեք կրկին, PIN-ը չի համապատասխանում"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Ընտրանքի մասին"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Բաց կոդով ծրագրաշարի լիցենզիաներ"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Բաց կոդով ծրագրաշարի լիցենզիաներ"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Տարբերակ"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Այս կապուղին դիտելու համար սեղմեք Աջ և մուտքագրեք ձեր PIN-ը"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Այս ծրագիրը դիտելու համար սեղմեք Աջ և մուտքագրեք ձեր PIN-ը"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Այս ծրագրի վարկանիշը <xliff:g id="RATING">%1$s</xliff:g> է:\nԱյն դիտելու համար սեղմեք Աջ և մուտքագրեք PIN կոդը:"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Ծրագիրն արգելափակված է"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Այս ծրագրի վարկանիշը <xliff:g id="RATING">%1$s</xliff:g> է"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Անվերնագիր"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Ալիքն արգելափակված է"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Ս<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Դրվ.՝ <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Ալիքների աղբյուրներ"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Ուղիղ եթերի ալիքների կարգավորում: Սա կարող է մի քանի րոպե տևել՝ կախված ալիքի աղբյուրից:"</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Պատրաստ է"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d ալիք"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d ալիք"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Ալիքներ չկան"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Նոր"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"Հեռուստացույցի ընտրացանկից օգտվելու համար "<b>"սեղմեք ԸՆՏՐԵԼ"</b>":"</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Հեռուստացույցի մուտք չի գտնվել"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Հեռուստացույցի մուտքը տեղադրված չէ"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP-ը չի աջակցվում"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP-ը ցուցադրելու համար աղբյուր չկա"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Կարգավորիչի մուտքը համապատասխան չէ: Գործարկեք «Ուղիղ եթեր» հավելվածը, եթե օգտագործում եք հեռուստացույցի մուտք:"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Կարգավորումը չհաջողվեց"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Այս գործողությունը կատարելու համար ոչ մի հավելված չի գտնվել"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Բոլոր ալիքները թաքնված են:\nԸնտրեք առնվազն մեկ ալիք:"</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Անհասանելի է թույլ տեսաազդանշանի պատճառով"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Անսպասելի սխալ առաջացավ տեսանյութի ցուցադրման ժամանակ"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"«Հետ» կոճակը միացված սարքի համար է: Դուս գալու համար սեղմեք «Գլխավոր» կոճակը:"</string>
+</resources>
diff --git a/res/values-in/arrays.xml b/res/values-in/arrays.xml
new file mode 100644
index 00000000..50fee494
--- /dev/null
+++ b/res/values-in/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Penuh"</item>
+ <item msgid="8568284598210500589">"Perbesar/Perkecil"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Semua saluran"</item>
+ <item msgid="928298872841713530">"Keluarga/Anak-anak"</item>
+ <item msgid="2751606947569857164">"Olahraga"</item>
+ <item msgid="7345749789651321496">"Belanja"</item>
+ <item msgid="167201149441442173">"Film"</item>
+ <item msgid="525966731464264290">"Komedi"</item>
+ <item msgid="6096710741527327836">"Perjalanan"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Pendidikan"</item>
+ <item msgid="7221999662426308394">"Satwa/Alam Liar"</item>
+ <item msgid="375300513250925001">"Berita"</item>
+ <item msgid="7746320336582330410">"Game"</item>
+ <item msgid="1255741860568329178">"Seni"</item>
+ <item msgid="7603949681065702867">"Hiburan"</item>
+ <item msgid="4453821994746804366">"Gaya Hidup"</item>
+ <item msgid="3488534597567932843">"Musik"</item>
+ <item msgid="7452153120614274095">"Tayang Perdana"</item>
+ <item msgid="8215762047341133299">"Teknologi/Sains"</item>
+ </string-array>
+</resources>
diff --git a/res/values-in/rating_system_strings.xml b/res/values-in/rating_system_strings.xml
new file mode 100644
index 00000000..561a5750
--- /dev/null
+++ b/res/values-in/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Obat-obatan"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Seksual"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Kekerasan"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Obat-obatan"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Seksual"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Kekerasan"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Prancis"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Dialog yang merangsang"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Bahasa yang vulgar"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Konten seksual"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Kekerasan"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Kekerasan fantasi"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Rating film"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
new file mode 100644
index 00000000..8ad7f663
--- /dev/null
+++ b/res/values-in/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Saluran Siaran Langsung"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Kontrol pemutar"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Saluran"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Saluran saat ini"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Opsi TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Opsi PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Kontrol Play tidak tersedia untuk saluran ini"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Putar atau jeda"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Maju cepat"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Putar ulang"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Berikutnya"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Sebelumnya"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Panduan program"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Tersedia saluran baru"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Tidak ada tautan yang tersedia"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Buka <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Teks"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Mode tayang"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Aktif"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Nonaktif"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Siapkan saluran"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Kontrol induk"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Tentang"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Sumber"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Tukar"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Aktif"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Nonaktif"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Suara"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Utama"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Jendela PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Tata letak"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Kanan bawah"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Kanan atas"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Kiri atas"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Kiri bawah"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Berdampingan"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Ukuran"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Besar"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Kecil"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Sumber masukan"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antena/kabel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Tidak ada informasi program"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Tidak ada informasi"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Saluran yang diblokir"</string>
+ <string name="default_language" msgid="4122326459624337928">"Bahasa tidak dikenal"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Teks"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Nonaktif"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Sesuaikan format"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Setel preferensi sistem untuk teks"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Mode tayang"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d saluran"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Sesuaikan daftar saluran"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Pilih grup"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Batal memilih grup"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Kelompokkan menurut"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Sumber saluran"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Kelompokkan menurut"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Penyiapan saluran"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Program ini diblokir"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Program ini diberi rating <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Sumber saluran"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Tersedia saluran baru"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Sesuaikan daftar saluran"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Pilih saluran untuk panduan program"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Belum disiapkan"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Masukan tidak mendukung pemindaian otomatis"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Tidak dapat memulai pemindaian otomatis untuk \'<xliff:g id="TV_INPUT">%s</xliff:g>\'"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Tidak dapat memulai preferensi seluruh sistem untuk teks."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d saluran ditambahkan"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d saluran ditambahkan"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Tidak ditambahkan saluran"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Kontrol induk"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Aktif"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Nonaktif"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Saluran diblokir"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Blokir semua"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Bebaskan semua"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Saluran tersembunyi"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Pembatasan program"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Ubah PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Sistem penilaian"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Rating"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Lihat sistem rating"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Negara lain"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Tidak ada"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Tidak ada"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Tidak ada"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Pembatasan tinggi"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Pembatasan menengah"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Pembatasan rendah"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Khusus"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Konten untuk anak-anak"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Konten yang cocok untuk praremaja"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Konten untuk remaja"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Pembatasan manual"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s &amp; subperingkat"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Subperingkat"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Masukkan PIN untuk menonton saluran ini"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Masukkan PIN untuk menonton program ini"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Masukkan PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Untuk menyetel kontrol induk, buat PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Masukkan PIN baru"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Konfirmasikan PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Masukkan PIN saat ini"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Anda salah memasukkan PIN sebanyak 5 kali.\nCoba lagi dalam <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> detik."</item>
+ <item quantity="other" msgid="8829550842387756054">"Anda salah memasukkan PIN sebanyak 5 kali.\nCoba lagi dalam <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> detik."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"PIN salah. Coba lagi."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Pin tidak cocok, coba lagi"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Tentang"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Lisensi sumber terbuka"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Lisensi sumber terbuka"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versi"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Untuk menonton saluran ini, tekan Kanan dan masukkan PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Untuk menonton program ini, tekan Kanan dan masukkan PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Program ini diberi rating <xliff:g id="RATING">%1$s</xliff:g>.\nUntuk menonton program ini, tekan Panah Kanan dan masukkan PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Program diblokir"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Program ini diberi rating <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Tanpa judul"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Saluran diblokir"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Sumber saluran"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Menyiapkan saluran siaran langsung dari sumber yang tersedia. Proses ini mungkin perlu waktu beberapa menit bergantung pada sumber saluran."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Selesai"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Tersedia %1$d saluran"</item>
+ <item quantity="other" msgid="2386588423841837714">"Tersedia %1$d saluran"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Tidak ada saluran yang tersedia"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Baru"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Tekan PILIH"</b>" untuk mengakses menu TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Tidak ditemukan masukan TV"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Tidak dapat menemukan masukan TV"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP tidak didukung"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Tidak tersedia masukan yang dapat ditampilkan dengan PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Jenis tuner tidak cocok. Luncurkan aplikasi Saluran Siaran Langsung untuk masukan TV yang sesuai dengan jenis tuner"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Penalaan gagal"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Tidak ditemukan aplikasi untuk menangani tindakan ini."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Semua saluran asal disembunyikan.\nPilih setidaknya satu saluran untuk ditonton."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Tidak tersedia karena sinyal video lemah"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Video tiba-tiba tidak tersedia"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Tombol KEMBALI adalah untuk perangkat yang tersambung. Tekan tombol UTAMA untuk keluar."</string>
+</resources>
diff --git a/res/values-is-rIS/arrays.xml b/res/values-is-rIS/arrays.xml
new file mode 100644
index 00000000..3683481b
--- /dev/null
+++ b/res/values-is-rIS/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Venjulegt"</item>
+ <item msgid="2533030282864800794">"Allur skjárinn"</item>
+ <item msgid="8568284598210500589">"Aðdráttur"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Allar rásir"</item>
+ <item msgid="928298872841713530">"Fjölskylduefni/barnaefni"</item>
+ <item msgid="2751606947569857164">"Íþróttir"</item>
+ <item msgid="7345749789651321496">"Verslun"</item>
+ <item msgid="167201149441442173">"Kvikmyndir"</item>
+ <item msgid="525966731464264290">"Grín"</item>
+ <item msgid="6096710741527327836">"Ferðalög"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Fræðsla"</item>
+ <item msgid="7221999662426308394">"Dýr/dýralíf"</item>
+ <item msgid="375300513250925001">"Fréttir"</item>
+ <item msgid="7746320336582330410">"Leikir"</item>
+ <item msgid="1255741860568329178">"List"</item>
+ <item msgid="7603949681065702867">"Afþreying"</item>
+ <item msgid="4453821994746804366">"Lífsstíll"</item>
+ <item msgid="3488534597567932843">"Tónlist"</item>
+ <item msgid="7452153120614274095">"Úrvalsefni"</item>
+ <item msgid="8215762047341133299">"Tækni og vísindi"</item>
+ </string-array>
+</resources>
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..26fff6e6
--- /dev/null
+++ b/res/values-is-rIS/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Eiturlyf"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Kynlíf"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Ofbeldi"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Eiturlyf"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Kynlíf"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Ofbeldi"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Franskt"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Tvíræðar samræður"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Óheflað málfar"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Kynferðislegt efni"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Ofbeldi"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Ofbeldi í ímynduðum heimi"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Kvikmyndaflokkun"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
new file mode 100644
index 00000000..837a98db
--- /dev/null
+++ b/res/values-is-rIS/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Rásir í beinni"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"einóma"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"víðóma"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Spilunarstýringar"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Rásir"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Nýlegar rásir"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Sjónvarpskostir"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Innfelld mynd"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Spilunarstýringar eru ekki í boði fyrir þessa rás"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Spila eða gera hlé"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Spóla áfram"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Spóla til baka"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Áfram"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Til baka"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Dagskrárvísir"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Nýjar rásir í boði"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Enginn tengill í boði"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Opna <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Skjátextar"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Birtingarstill."</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"Innfelld mynd"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Kveikt"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Slökkt"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-Audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Uppsetning rása"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Foreldraeftirlit"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Um"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Inntak"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Víxla"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Kveikt"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Slökkt"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Hljóð"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Aðalgluggi"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Innfelling"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Útlit"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Neðst til hægri"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Efst til hægri"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Efst til vinstri"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Neðst til vinstri"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Hlið við hlið"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Stærð"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Stór"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Lítil"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Inntak"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Sjónvarp (loftnet/kapall)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Engar dagskrárupplýsingar"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Engar upplýsingar"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Læst rás"</string>
+ <string name="default_language" msgid="4122326459624337928">"Óþekkt tungumál"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Skjátextar"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Slökkt"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Sérstilla snið"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Stilla skjátextaval fyrir allt kerfið"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Birtingarstilling"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-Audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"einóma"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"víðóma"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d rásir"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Sérsníða rásalista"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Velja hóp"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Taka val af hóp"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Flokka eftir"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Inntak rása"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"Háskerpa/staðalgæði"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"Háskerpa"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"Staðalgæði"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Flokka eftir"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Uppsetning rása"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Þessi dagskrárliður er læstur"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Þessi dagskrárliður er flokkaður sem <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Inntak rása"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Nýjar rásir í boði"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Sérsníða rásalista"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Veldu rásir fyrir dagskrárvísinn þinn"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Ekki uppsett"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Inntakið styður ekki sjálfvirka skönnun"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Ekki tókst að hefja sjálfvirka skönnun fyrir „<xliff:g id="TV_INPUT">%s</xliff:g>“"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Ekki er hægt að opna skjátextastillingar fyrir kerfið allt."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d rás bætt við"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d rásum bætt við"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Engum rásum bætt við"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Stillir"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Foreldraeftirlit"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Kveikt"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Slökkt"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Útilokaðar rásir"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Útiloka allar"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Opna allar"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Faldar rásir"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Aldurstakmark"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Breyta PIN-númeri"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Flokkunarkerfi"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Flokkun"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Sjá flokkunarkerfi"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Önnur lönd"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Engin"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Ekkert"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Ekkert"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Miklar takmarkanir"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Miðlungs takmarkanir"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Litlar takmarkanir"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Sérsniðið"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Efni sem hentar fyrir börn"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Efni sem hentar eldri börnum"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Efni sem hentar fyrir unglinga"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Handstillt aldurstakmark"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s og undirflokkar"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Undirflokkar"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Sláðu inn PIN-númerið til að horfa á þessa rás"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Sláðu inn PIN-númerið til að horfa á þennan þátt"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Sláðu inn PIN-númerið"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Til að stilla foreldraeftirlit skaltu búa til PIN-númer"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Sláðu inn nýtt PIN-númer"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Staðfestu PIN-númerið"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Sláðu inn núverandi PIN-númerið þitt"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Þú slóst inn rangt PIN-númer fimm sinnum. \nReyndu aftur eftir <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekúndu."</item>
+ <item quantity="other" msgid="8829550842387756054">"Þú slóst inn rangt PIN-númer fimm sinnum.\nReyndu aftur eftir <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekúndur."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Rangt PIN-númer. Reyndu aftur."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Reyndu aftur; PIN-númerin stemma ekki"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Um"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Leyfi opins kóða"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Leyfi opins kóða"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Útgáfa"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Til að horfa á þessa rás skaltu ýta til hægri og slá inn PIN-númerið þitt"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Til að horfa á þennan þátt skaltu ýta til hægri og slá inn PIN-númerið þitt"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Þessi þáttur er flokkaður sem <xliff:g id="RATING">%1$s</xliff:g>.\nTil að horfa á þáttinn skaltu ýta til hægri og slá inn PIN-númerið þitt."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Þátturinn er læstur"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Þessi dagskrárliður er flokkaður sem <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Ekkert heiti"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Rás læst"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Þ. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Inntak rása"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Setja upp rásir í beinni frá tiltæku inntaki. Þetta getur tekið nokkrar mínútur, allt eftir því hvaðan rásin er upprunnin."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Lokið"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d rás í boði"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d rásir í boði"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Engar rásir í boði"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nýtt"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Ýttu á VELJA"</b>" til að fá aðgang að sjónvarpsvalmyndinni."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Ekkert sjónvarpsinntak fannst"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Sjónvarpsinntak finnst ekki"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Ekki er stuðningur við innfellda mynd"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Ekkert inntak er fyrir hendi til að nota í innfelldri mynd"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Gerð móttakara passar ekki. Ræstu forritið Rásir í beinni til að fá sjónvarpsinntak með móttakaragerð."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Stilling mistókst"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Ekkert forrit fannst sem getur framkvæmt þessa aðgerð."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Allar inntaksrásir eru faldar.\nVeldu minnst eina rás til að horfa á."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Ekki tiltækt vegna þess að sendistyrkur myndefnis er lítill"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Myndskeiðið er óvænt ekki tiltækt"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Til baka-lykillinn er fyrir tengd tæki. Ýttu á heimahnappinn til að hætta."</string>
+</resources>
diff --git a/res/values-it/arrays.xml b/res/values-it/arrays.xml
new file mode 100644
index 00000000..21ac42c4
--- /dev/null
+++ b/res/values-it/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normale"</item>
+ <item msgid="2533030282864800794">"Intera"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Tutti i canali"</item>
+ <item msgid="928298872841713530">"Famiglia/bambini"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Shopping"</item>
+ <item msgid="167201149441442173">"Film"</item>
+ <item msgid="525966731464264290">"Commedie"</item>
+ <item msgid="6096710741527327836">"Viaggi"</item>
+ <item msgid="2851882187117833883">"Drammatici"</item>
+ <item msgid="78492781188719038">"Istruzione"</item>
+ <item msgid="7221999662426308394">"Animali/fauna selvatica"</item>
+ <item msgid="375300513250925001">"Notizie"</item>
+ <item msgid="7746320336582330410">"Giochi"</item>
+ <item msgid="1255741860568329178">"Arte"</item>
+ <item msgid="7603949681065702867">"Svago"</item>
+ <item msgid="4453821994746804366">"Stile di vita"</item>
+ <item msgid="3488534597567932843">"Musica"</item>
+ <item msgid="7452153120614274095">"Il meglio"</item>
+ <item msgid="8215762047341133299">"Scienza/tecnica"</item>
+ </string-array>
+</resources>
diff --git a/res/values-it/rating_system_strings.xml b/res/values-it/rating_system_strings.xml
new file mode 100644
index 00000000..e3f9a6a3
--- /dev/null
+++ b/res/values-it/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Droga"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sesso"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Violenza"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Droga"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sesso"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Violenza"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"francese"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Dialoghi allusivi"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Linguaggio volgare"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Contenuti di natura sessuale"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Violenza"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Violenza di fantasia"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Classificazioni dei film"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
new file mode 100644
index 00000000..d283c552
--- /dev/null
+++ b/res/values-it/strings.xml
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Controlli riproduzione"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Canali"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Canali recenti"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Opzioni TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Opzioni PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Controlli di riproduzione non disponibili per questo canale"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Riproduci o metti in pausa"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Avanza velocemente"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Riavvolgi"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Avanti"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Indietro"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Guida ai programmi"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Nuovi canali disponibili"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Nessun link disponibile"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Apri <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Sottotitoli"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Visualizzazione"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Attiva"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Non attiva"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Impostaz. canale"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Filtro famiglia"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Informazioni"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Fonte"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Scambia"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Attiva"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Non attiva"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Suono"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Principale"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Finestra PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Layout"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"In basso a des."</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"In alto a des."</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"In alto a sin."</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"In basso a sin."</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Affiancata"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Dimensioni"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Grande"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Piccola"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Origine ingresso"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antenna/cavo)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Nessuna informazione sul programma"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Nessuna informazione"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Canale bloccato"</string>
+ <string name="default_language" msgid="4122326459624337928">"Lingua sconosciuta"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Sottotitoli"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Non attivi"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Personalizza"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Imposta pref. di sistema per sottotitoli"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Visualizzazione"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Surround 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Surround 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"Canali %d"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Personalizza canali"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Seleziona gruppo"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Deseleziona gruppo"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Raggruppa per"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Fonte canale"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"Alta definizione"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"Bassa definizione"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Raggruppa per"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Impostaz. canale"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Questo programma è bloccato"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Questo programma è classificato come <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Fonti canali"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Nuovi canali disponibili"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Personalizza canali"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Scegli canali per guida ai programmi"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Non configurato"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"L\'ingresso non supporta la ricerca automatica"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Impossibile avviare la ricerca automatica per \"<xliff:g id="TV_INPUT">%s</xliff:g>\""</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Impossibile avviare le preferenze su tutto il sistema per i sottotitoli."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d canale aggiunto"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d canali aggiunti"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Nessun canale aggiunto"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Sintonizzatore"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Filtro famiglia"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Attivo"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Non attivo"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Canali bloccati"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Blocca tutti"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Sblocca tutti"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Canali nascosti"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Limitaz. programmi"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Cambia PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Classificazioni"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Classificazioni"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Tutte classificaz."</string>
+ <string name="other_countries" msgid="8342216398676184749">"Altri Paesi"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Nessuno"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Nessuna"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Nessuna"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Limitazioni alte"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Limitazioni medie"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Limitazioni basse"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Personalizzata"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Contenuti adatti ai bambini"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Contenuto disponibile per ragazzi più grandi"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Contenuti adatti agli adolescenti"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Limitazioni manuali"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <!-- String.format failed for translation -->
+ <!-- no translation found for option_subrating_title (5485055507818077595) -->
+ <skip />
+ <string name="option_subrating_header" msgid="4637961301549615855">"Classificazioni secondarie"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Inserisci il codice PIN per guardare questo canale"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Inserisci il codice PIN per guardare questo programma"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Inserisci il codice PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Per impostare il filtro famiglia, crea un codice PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Inserisci il nuovo PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Conferma il PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Inserisci il PIN attuale"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Hai inserito cinque volte il PIN sbagliato.\nRiprova tra <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> secondo."</item>
+ <item quantity="other" msgid="8829550842387756054">"Hai inserito cinque volte il PIN sbagliato.\nRiprova tra <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> secondi."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Il PIN è errato. Riprova."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Riprova, il PIN non corrisponde"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Informazioni"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Licenze open source"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Licenze open source"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versione"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Per guardare questo canale, premi il pulsante destro e inserisci il PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Per guardare questo programma, premi il pulsante destro e inserisci il PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Questo programma è classificato come <xliff:g id="RATING">%1$s</xliff:g>.\nPer guardarlo, premi il tasto destro e inserisci il PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Il programma è bloccato"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Questo programma è classificato come <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Senza titolo"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Canale bloccato"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Stag. <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Punt. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Fonti canale"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Configura Live Channels dalle fonti disponibili. L\'operazione potrebbe richiedere diversi minuti a seconda della fonte del canale."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Fine"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d canale disponibile"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d canali disponibili"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Nessun canale disponibile"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Elementi nuovi"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Premi SELEZIONA"</b>" per accedere al menu della TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Nessun ingresso TV trovato"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Impossibile trovare l\'ingresso TV"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Funzione PIP non supportata"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Nessun ingresso disponibile per visualizzazione PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tipo di sintonizzatore non adatto. Avvia l\'app Live Channels per l\'ingresso TV di tipo sintonizzatore."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Sintonizzazione non riuscita"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Nessuna app trovata per gestire questa azione."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Tutti i canali di origine sono nascosti.\nSeleziona almeno un canale da guardare."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Non disponibile a causa di segnale video debole"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Il video è improvvisamente non disponibile"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Il tasto INDIETRO è per il dispositivo connesso. Per uscire premi il pulsante HOME."</string>
+</resources>
diff --git a/res/values-iw/arrays.xml b/res/values-iw/arrays.xml
new file mode 100644
index 00000000..ea2ed7b6
--- /dev/null
+++ b/res/values-iw/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"רגילה"</item>
+ <item msgid="2533030282864800794">"מלאה"</item>
+ <item msgid="8568284598210500589">"שנה מרחק מתצוגה"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"כל הערוצים"</item>
+ <item msgid="928298872841713530">"משפחה/ילדים"</item>
+ <item msgid="2751606947569857164">"ספורט"</item>
+ <item msgid="7345749789651321496">"קניות"</item>
+ <item msgid="167201149441442173">"סרטים"</item>
+ <item msgid="525966731464264290">"קומדיה"</item>
+ <item msgid="6096710741527327836">"טיולים"</item>
+ <item msgid="2851882187117833883">"דרמה"</item>
+ <item msgid="78492781188719038">"חינוך"</item>
+ <item msgid="7221999662426308394">"בעלי חיים/טבע"</item>
+ <item msgid="375300513250925001">"חדשות"</item>
+ <item msgid="7746320336582330410">"משחקים"</item>
+ <item msgid="1255741860568329178">"אמנויות"</item>
+ <item msgid="7603949681065702867">"בידור"</item>
+ <item msgid="4453821994746804366">"החיים הטובים"</item>
+ <item msgid="3488534597567932843">"מוזיקה"</item>
+ <item msgid="7452153120614274095">"עילית"</item>
+ <item msgid="8215762047341133299">"טכנולוגיה/מדע"</item>
+ </string-array>
+</resources>
diff --git a/res/values-iw/rating_system_strings.xml b/res/values-iw/rating_system_strings.xml
new file mode 100644
index 00000000..4eb2082d
--- /dev/null
+++ b/res/values-iw/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"סמים"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"מין"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"אלימות"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"סמים"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"מין"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"אלימות"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"צרפתית"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"שיח בעל רמיזות מיניות"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"שפה גסה"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"תוכן מיני"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"אלימות"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"אלימות לא מציאותית"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"סיווגי סרטים"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
new file mode 100644
index 00000000..ee2088e6
--- /dev/null
+++ b/res/values-iw/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"מונו"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"סטריאו"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"פקדי הפעלה"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"ערוצים"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"ערוצים אחרונים"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"‏אפשרויות TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"‏אפשרויות PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"פקדי הפעלה אינם זמינים בשביל הערוץ הזה"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"הפעל או השהה"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"הרץ קדימה"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"הרץ אחורה"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"הבא"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"הקודם"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"מדריך תכניות"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"ערוצים חדשים זמינים"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"אין קישור זמין"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"פתח את <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"כתוביות"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"מצב תצוגה"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"מופעל"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"כבוי"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"מולטי-אודיו"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"הגדרת ערוץ"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"בקרות הוריות"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"מידע כללי"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"מקור"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"החלף"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"מופעל"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"כבוי"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"צליל"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"ראשי"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"‏חלון PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"פריסה"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"שמאל למטה"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"שמאל למעלה"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"ימין למעלה"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"ימין למטה"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"זה לצד זה"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"גודל"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"גדול"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"קטן"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"מקור קלט"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"טלוויזיה (אנטנה/כבלים)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"אין פרטי תכנית"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"אין מידע"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"ערוץ חסום"</string>
+ <string name="default_language" msgid="4122326459624337928">"שפה לא ידועה"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"כתוביות"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"כבוי"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"התאם אישית את הפורמט"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"הגדר העדפות למערכת כולה עבור כתוביות"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"מצב תצוגה"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"מולטי-אודיו"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"מונו"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"סטריאו"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"סראונד 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"סראונד 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"‏%d ערוצים"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"התאם רשימת ערוצים"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"בחר קבוצה"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"בטל את בחירת הקבוצה"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"קבץ לפי"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"מקור ערוץ"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"‏HD/איכות רגילה"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"איכות רגילה"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"קבץ לפי"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"הגדרת ערוץ"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"התכנית הזו חסומה"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"סיווג התכנית הזו הוא <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"מקורות של ערוצים"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"ערוצים חדשים זמינים"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"התאם רשימת ערוצים"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"בחר ערוצים עבור מדריך התכניות שלך"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"לא מוגדר"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"הקלט אינו תומך בסריקה אוטומטית"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"לא ניתן להתחיל בסריקה אוטומטית עבור \'<xliff:g id="TV_INPUT">%s</xliff:g>\'"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"לא ניתן להפעיל את ההעדפות לכלל המערכת עבור כתוביות."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"‏ערוץ %1$d נוסף"</item>
+ <item quantity="other" msgid="1078861616751739285">"‏%1$d ערוצים נוספו"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"לא נוספו ערוצים"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"טיונר"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"בקרות הוריות"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"מופעל"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"כבוי"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"ערוצים חסומים"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"חסום הכל"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"בטל את כל החסימות"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"ערוצים מוסתרים"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"הגבלות תכנית"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"שנה קוד אימות"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"מערכות סיווג"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"דירוגים"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"הצגת כל מער\' הסיווג"</string>
+ <string name="other_countries" msgid="8342216398676184749">"מדינות אחרות"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"ללא"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"ללא"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"ללא"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"הגבלות קפדניות"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"הגבלות בינוניות"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"הגבלות קלות"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"מותאם אישית"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"תוכן מתאים לילדים"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"התוכן מתאים לילדים מבוגרים יותר"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"תוכן מתאים לבני נוער"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"הגבלות ידניות"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"‏%1$s וסיווגי משנה"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"סיווגי משנה"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"הזן את קוד האימות שלך כדי לצפות בערוץ זה"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"הזן את קוד האימות שלך כדי לצפות בתכנית זו"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"הזן את קוד האימות שלך"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"‏להגדרת בקרות הוריות, צור מספר PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"‏הזן את מספר ה-PIN החדש"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"‏אשר את מספר ה-PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"‏הזן את מספר ה-PIN הנוכחי"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"‏הזנת PIN שגוי חמש פעמים.\nנסה שוב בעוד שנייה <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g>."</item>
+ <item quantity="other" msgid="8829550842387756054">"‏הזנת PIN שגוי חמש פעמים.\nנסה שוב בעוד <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> שניות."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"‏מספר ה-PIN היה שגוי. נסה שוב."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"נסה שוב, קוד האימות אינו תואם"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"מידע כללי"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"רישיונות קוד פתוח"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"רישיונות קוד פתוח"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"גרסה"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"‏כדי לצפות בערוץ הזה, לחץ על \'ימין\' והזן את מספר ה-PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"‏כדי לצפות בתכנית הזו, לחץ על \'ימין\' והזן את מספר ה-PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"‏סיווג התכנית הזו הוא <xliff:g id="RATING">%1$s</xliff:g>.\nכדי לצפות בתכנית, לחץ ימינה והזן את ה-PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"התכנית חסומה"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"סיווג התכנית הזו הוא <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"ללא כותרת"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"הערוץ חסום"</string>
+ <string name="episode_format" msgid="4881195874563241096">"עונה <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: פרק <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"מקורות של ערוצים"</string>
+ <string name="setup_description" msgid="8728423605912915099">"הגדרה של ערוצים בשידור חי מהמקורות הזמינים. התהליך עשוי לארוך מספר דקות, בהתאם למקור הערוץ."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"בוצע"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"‏יש ערוץ %1$d זמין"</item>
+ <item quantity="other" msgid="2386588423841837714">"‏יש %1$d ערוצים זמינים"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"אין ערוצים זמינים"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"חדש"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"לחץ על \'בחר\'"</b>" כדי לפתוח את תפריט הטלוויזיה."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"לא נמצא קלט טלוויזיה"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"לא ניתן למצוא את קלט הטלוויזיה"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"‏PIP אינו נתמך"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"‏אין קלט זמין הניתן להצגה באמצעות PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"סוג הטיונר אינו מתאים. הפעל את האפליקציה \'ערוצים בשידור חי\' בשביל טיונר מסוג קלט טלוויזיה."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"הכוונון נכשל"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"לא נמצאה אפליקציה שיכולה לטפל בפעולה הזו."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"כל ערוצי המקור מוסתרים.\nבחר ערוץ אחד לפחות לצפייה."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"לא זמין עקב אות וידאו חלש"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"מסיבה בלתי צפויה, הסרטון אינו זמין"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"מקש \'הקודם\' מיועד למכשירים מחוברים. לחץ על לחצן \'דף הבית\' כדי לצאת."</string>
+</resources>
diff --git a/res/values-ja/arrays.xml b/res/values-ja/arrays.xml
new file mode 100644
index 00000000..6fe04daa
--- /dev/null
+++ b/res/values-ja/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"標準"</item>
+ <item msgid="2533030282864800794">"フル"</item>
+ <item msgid="8568284598210500589">"ズーム"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"すべてのチャンネル"</item>
+ <item msgid="928298872841713530">"ファミリー/子ども"</item>
+ <item msgid="2751606947569857164">"スポーツ"</item>
+ <item msgid="7345749789651321496">"ショッピング"</item>
+ <item msgid="167201149441442173">"映画"</item>
+ <item msgid="525966731464264290">"コメディー"</item>
+ <item msgid="6096710741527327836">"旅行"</item>
+ <item msgid="2851882187117833883">"ドラマ"</item>
+ <item msgid="78492781188719038">"教育"</item>
+ <item msgid="7221999662426308394">"動物/野生生物"</item>
+ <item msgid="375300513250925001">"ニュース"</item>
+ <item msgid="7746320336582330410">"ゲーム"</item>
+ <item msgid="1255741860568329178">"芸術"</item>
+ <item msgid="7603949681065702867">"エンターテインメント"</item>
+ <item msgid="4453821994746804366">"ライフスタイル"</item>
+ <item msgid="3488534597567932843">"音楽"</item>
+ <item msgid="7452153120614274095">"プレミア"</item>
+ <item msgid="8215762047341133299">"科学、技術"</item>
+ </string-array>
+</resources>
diff --git a/res/values-ja/rating_system_strings.xml b/res/values-ja/rating_system_strings.xml
new file mode 100644
index 00000000..78e2794d
--- /dev/null
+++ b/res/values-ja/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"薬物"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"性描写"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"暴力"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"薬物"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"性描写"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"暴力"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"フランス語"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"性的なものを暗示する会話"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"下品な言葉遣い"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"性的な内容"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"暴力"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"暴力的なファンタジー"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"映画のレーティング"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 00000000..a7efaebb
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"ライブチャンネル"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"モノラル"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"ステレオ"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"再生操作"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"チャンネル"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"最近のチャンネル"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"テレビオプション"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIPオプション"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"このチャンネルでは再生操作を使用できません"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"再生または一時停止"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"早送り"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"巻き戻し"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"次へ"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"前へ"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"プログラムガイド"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"利用できる新しいチャンネル"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"リンクはありません"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g>を開く"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"字幕"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"表示モード"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"ON"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"OFF"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"マルチオーディオ"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"チャンネル設定"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"ペアレンタルコントロール"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"概要"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"ソース"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"切り替え"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"ON"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"OFF"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"サウンド"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"メイン"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIPウィンドウ"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"レイアウト"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"右下"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"右上"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"左上"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"左下"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"横並び"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"サイズ"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"大"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"小"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"入力ソース"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"テレビ(アンテナ/ケーブル)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"プログラム情報がありません"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"情報はありません"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"ブロックされているチャンネル"</string>
+ <string name="default_language" msgid="4122326459624337928">"不明な言語"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"字幕"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"OFF"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"書式設定のカスタマイズ"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"字幕を設定(システム全体に適用)"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"表示モード"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"マルチオーディオ"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"モノラル"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"ステレオ"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1サラウンド"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1サラウンド"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%dチャンネル"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"チャンネルリストのカスタマイズ"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"グループを選択"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"グループの選択を解除"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"グループ化"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"チャンネルソース"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"グループ化"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"チャンネル設定"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"このプログラムはブロックされています"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"このプログラムのレーティングは<xliff:g id="RATING">%1$s</xliff:g>です"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"チャンネルソース"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"利用できる新しいチャンネル"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"チャンネルリストをカスタマイズ"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"プログラムガイド用のチャンネルを選択"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"セットアップしていません"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"入力が自動スキャンをサポートしていません"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"「<xliff:g id="TV_INPUT">%s</xliff:g>」の自動スキャンを開始できません"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"字幕に関するシステム全体の設定を開始できません。"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"チャンネル%1$d件が追加済み"</item>
+ <item quantity="other" msgid="1078861616751739285">"チャンネル%1$d件が追加済み"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"追加されたチャンネルなし"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"チューナー"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"ペアレンタルコントロール"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"ON"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"OFF"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"ブロック済み"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"すべてブロック"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"すべてのブロックを解除"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"非表示チャンネル"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"プログラムの制限"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PINを変更"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"レーティングシステム"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"レーティング"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"レーティングシステムをすべて表示"</string>
+ <string name="other_countries" msgid="8342216398676184749">"その他の国"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"なし"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"なし"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"なし"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"制限: 高"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"制限: 中"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"制限: 低"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"カスタム"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"子ども向けのコンテンツ"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"年長の子ども(8~12歳)向けのコンテンツです"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"13歳以上向けのコンテンツ"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"制限: 手動"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$sとサブレーティング"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"サブレーティング"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"PINを入力してこのチャンネルを視聴"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"PINを入力してこのプログラムを視聴"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"PINの入力"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"ペアレンタルコントロールを設定するには、PINを作成してください"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"新しいPINを入力"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"PINの確認"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"現在のPINの入力"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"PINの入力を5回間違えました。\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g>秒後にもう一度お試しください。"</item>
+ <item quantity="other" msgid="8829550842387756054">"PINの入力を5回間違えました。\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g>秒後にもう一度お試しください。"</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"PINが正しくありません。もう一度お試しください。"</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"もう一度お試しください。PINが一致しません。"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"概要"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"オープンソースライセンス"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"オープンソースライセンス"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"バージョン"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"このチャンネルを視聴するには、右のボタンを押してPINを入力してください。"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"このプログラムを視聴するには、右のボタンを押してPINを入力してください。"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"このプログラムのレーティングは<xliff:g id="RATING">%1$s</xliff:g>です。\nこのプログラムを視聴するには、右のボタンを押してPINを入力してください。"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"プログラムはブロックされています"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"このプログラムのレーティングは<xliff:g id="RATING">%1$s</xliff:g>です"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"タイトルなし"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"チャンネルをブロック"</string>
+ <string name="episode_format" msgid="4881195874563241096">"シーズン<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: エピソード<xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>「<xliff:g id="EPISODE_TITLE">%3$s</xliff:g>」"</string>
+ <string name="setup_title" msgid="7268875010986705651">"チャンネルソース"</string>
+ <string name="setup_description" msgid="8728423605912915099">"有効なソースからライブチャンネルを設定します。チャンネルソースによっては数分かかる場合があります。"</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"完了"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d件のチャンネルがあります"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d件のチャンネルがあります"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"チャンネルはありません"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"新規"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"テレビのメニューにアクセスするには"<b>"[SELECT]を押してください"</b>"。"</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"テレビ入力が見つかりませんでした"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"テレビ入力が見つかりません"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIPがサポートされていません"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIPで表示できる有効な入力はありません"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"無効なチューナータイプ。チューナー入力のライブチャンネルアプリを起動してください。"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"同調に失敗しました"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"この操作を行うアプリが見つかりませんでした。"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"ソースチャンネルはすべて非表示になっています。\n視聴するチャンネルを1つ以上選択してください。"</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"動画の信号が弱いため再生できません"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"動画を再生できなくなりました"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"戻るキーは接続されたデバイス用です。終了するにはホームボタンを押してください。"</string>
+</resources>
diff --git a/res/values-ka-rGE/arrays.xml b/res/values-ka-rGE/arrays.xml
new file mode 100644
index 00000000..2e6ba0f8
--- /dev/null
+++ b/res/values-ka-rGE/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"ჩვეულებრივი"</item>
+ <item msgid="2533030282864800794">"სრული"</item>
+ <item msgid="8568284598210500589">"მასშტაბი"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"ყველა არხი"</item>
+ <item msgid="928298872841713530">"ოჯახი/ბავშვები"</item>
+ <item msgid="2751606947569857164">"სპორტი"</item>
+ <item msgid="7345749789651321496">"საყიდლები"</item>
+ <item msgid="167201149441442173">"ფილმები"</item>
+ <item msgid="525966731464264290">"კომედია"</item>
+ <item msgid="6096710741527327836">"მოგზაურობა"</item>
+ <item msgid="2851882187117833883">"დრამატული"</item>
+ <item msgid="78492781188719038">"განათლება"</item>
+ <item msgid="7221999662426308394">"ცხოველები/ველური ბუნება"</item>
+ <item msgid="375300513250925001">"ახალი ამბები"</item>
+ <item msgid="7746320336582330410">"თამაშები"</item>
+ <item msgid="1255741860568329178">"ხელოვნება"</item>
+ <item msgid="7603949681065702867">"გართობა"</item>
+ <item msgid="4453821994746804366">"ცხოვრების სტილი"</item>
+ <item msgid="3488534597567932843">"მუსიკა"</item>
+ <item msgid="7452153120614274095">"პრემიერა"</item>
+ <item msgid="8215762047341133299">"ტექნოლოგიები/მეცნიერება"</item>
+ </string-array>
+</resources>
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..d8191f32
--- /dev/null
+++ b/res/values-ka-rGE/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"ნარკოტიკები"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"სექსი"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"ძალადობა"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"ნარკოტიკები"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"სექსი"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"ძალადობა"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"ფრანგული"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"მაცდუნებელი დიალოგი"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"უხამსი მეტყველება"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"სექსუალური ხასიათის შინაარსი"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"ძალადობა"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"ძალადობის შემცველი ფანტასტიკა"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"ფილმის რეიტინგები"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
new file mode 100644
index 00000000..457fe741
--- /dev/null
+++ b/res/values-ka-rGE/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"ლაივ არხები"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"მონო"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"სტერეო"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"ჩართვის კონტროლი"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"არხები"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"ბოლო არხები"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV პარამეტრები"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP პარამეტრები"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"ამ არხისთვის არ არის ხელმისაწვდომი ჩართვის კონტროლი"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"დაკვრა ან პაუზა"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"წინ გადახვევა"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"გადახვევა"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"შემდეგი"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"წინა"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"პროგ. სახელმძღვანელო"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"წვდომაა ახალ არხებზე"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"ხელმისაწ. ბმული არ არის"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ის გახსნა"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"დახურ.წარწერები"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"ჩვენების რეჟიმი"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"ჩართული"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"გამორთული"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"მულტი-აუდიო"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"არხის დაყენება"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"მშობელთა კონტრ."</string>
+ <string name="options_item_about" msgid="3023532413252052050">"შესახებ"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"წყარო"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"შენაცვლება"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"ჩართული"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"გამორთული"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"ხმა"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"მთავარი"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP ფანჯარა"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"განლაგება"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"ქვედა მარჯვენა"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"ზედა მარჯვენა"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"ზედა მარცხენა"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"ქვედა მარცხენა"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"გვერდი-გვერდ"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"ზომა"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"დიდი"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"პატარა"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"შემავალი წყარო"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"ტელევიზია (ანტენა/კაბელი)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"პროგრამის ინფორმაცია არ არის"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"ინფორმაცია არ არის ხელმისაწვდომი"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"დაბლოკილი არხი"</string>
+ <string name="default_language" msgid="4122326459624337928">"უცნობი ენა"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"დახურული სუბტიტრები"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"გამორთულია"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"ფორმატირების მორგება"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"მთელ სისტ. პარ.დაყ. დახ. წარწერებისთვის"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"ჩვენების რეჟიმი"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"მულტი-აუდიო"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"მონო"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"სტერეო"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d არხი"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"არხ.სიის მორგება"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"ჯგუფის არჩევა"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"ჯგუფის მონიშვ.გაუქმ."</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"დაჯგუფება:"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"არხის წყარო"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"დაჯგუფება:"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"არხის დაყენება"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"ეს პროგრამა დაბლოკილია"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"ამ პროგრამის რეიტინგია <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"არხების წყაროები"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"ხელმისაწვდომია ახალი არხები"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"არხ. სიის მორგება"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"აირჩიეთ არხები პროგრამის გზამკვლევისთვის"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"არ არის დაყენებული"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"ავტო-სკანირება არ არის მხარდაჭერილი შეყვანით."</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"ვერ ხერხდება ავტო-სკანირება „<xliff:g id="TV_INPUT">%s</xliff:g>“-ისთვის"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"დახურული წარწერებისთვის სისტემის მასშტაბით პარამეტრების გაშვება ვერ ხერხდება."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"დაემატა %1$d არხი"</item>
+ <item quantity="other" msgid="1078861616751739285">"დაემატა %1$d არხი"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"არხები არ დამატებულა"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"ტუნერი"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"მშობელთა კონტროლი"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"ჩართულია"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"გამორთულია"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"დაბლოკილი არხები"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"ყველას დაბლოკვა"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"ყველას განბლოკვა"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"დამალული არხები"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"პროგრ. შეზღუდვები"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN-ის შეცვლა"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"რეიტინგ. სისტემები"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"რეიტინგები"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"ყველ.რეიტ.სის. ნახვა"</string>
+ <string name="other_countries" msgid="8342216398676184749">"სხვა ქვეყნები"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"არცერთი"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"არცერთი"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"არცერთი"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"მაღალი შეზღუდვები"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"საშუალო შეზღუდვები"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"დაბალი შეზღუდვები"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"მორგებული"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"კონტენტი მიზანშეწონილია ბავშვებისთვის"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"კონტენ. შეეფერება უფროსი ასაკის ბავშვებს"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"კონტენტი განკუთვნილია მოზარდებისთვის"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"ხელით დაყენების შეზღუდვები"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s და ქვე-რეიტინგ."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"ქვე-რეიტინგები"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"შეიყვანეთ თქვენი PIN ამ არხის საყურებლად"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"შეიყვანეთ თქვენი PIN ამ პროგრამის საყურებლად"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"შეიყვანეთ თქვენი PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"მშობელთა კონტროლის დასაყენებლად შექმენით PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"შეიყვანეთ ახალი PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"კვლავ შეიყვანეთ თქვენი PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"შეიყვანეთ თქვენი მიმდინარე PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"თქვენ შეიყვანეთ არასწორი PIN ხუთჯერ.\nკიდევ სცადეთ <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> წამში."</item>
+ <item quantity="other" msgid="8829550842387756054">"თქვენ შეიყვანეთ არასწორი PIN ხუთჯერ.\nკიდევ სცადეთ <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> წამში."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"ეს PIN არასწორი იყო. ისევ სცადეთ."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"სცადეთ ისევ, PIN არ შეესაბამება"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"შესახებ"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"ღია კოდის ლიცენზიები"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"ღია კოდის ლიცენზიები"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"ვერსია"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"ამ არხის საყურებლად დააჭირეთ „მარჯვენას“ და შეიყვანოთ თქვენი PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"ამ პროგრამის საყურებლად დააჭირეთ „მარჯვენას“ და შეიყვანოთ თქვენი PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"ამ პროგრამის რეიტინგია <xliff:g id="RATING">%1$s</xliff:g>.\nამ პროგრამის საყურებლად დააჭირეთ „მარჯვენას“ და შეიყვანოთ თქვენი PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"პროგრამა დაბლოკილია"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"ამ პროგრამის რეიტინგია <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"უსათაურო"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"არხი დაბლოკილია"</string>
+ <string name="episode_format" msgid="4881195874563241096">"სეზ.<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: ეპ. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"არხების წყაროები"</string>
+ <string name="setup_description" msgid="8728423605912915099">"დააყენეთ პირდაპირი არხები ხელმისაწვდომი წყაროებიდან. ამას შეიძლება რამდენიმე წუთი დასჭირდეს, არხის წყაროს მიხედვით."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"დასრულდა"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"ხელმისაწვდომია %1$d არხი"</item>
+ <item quantity="other" msgid="2386588423841837714">"ხელმისაწვდომია %1$d არხი"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"ხელმისაწვდომი არხები არ არის"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"ახალი"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"დააჭირეთ არჩევას"</b>" სატელევიზიო მენიუზე წვდომისთვის."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"TV შეყვანა ვერ მოიძებნა."</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"TV შეყვანა ვერ იძებნება."</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"არ არის PIP-ის მხარდაჭერა."</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP-ში გამოსაჩენი შემავალი სიგნალი მიუწვდომელია."</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"ტიუნერის ტიპი არ არის შესაფერისი; გთხოვთ გაუშვით პირდაპირი არხების აპი, სატელევიზიო შეტანის ტიუნერის ტიპისათვის."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"არხების დაჭერა ვერ მოხერხდა."</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"ამ მოქმედების შესასრულებლად აპი ვერ მოიძებნა."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"წყაროს ყველა არხი დამალულია.\nსაყურებლად აირჩიეთ მინიმუმ ერთი არხი."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"მიუწვდომელია სუსტი ვიდეო სიგნალის გამო."</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"ვიდეო მიუწვდომელი გახდა მოულოდნელად."</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"ღილაკი „უკან“ დაკავშირებული მოწყობილობისთვისაა. გამოსასვლელად დააჭირეთ ღილაკს „მთავარი“."</string>
+</resources>
diff --git a/res/values-kk-rKZ/arrays.xml b/res/values-kk-rKZ/arrays.xml
new file mode 100644
index 00000000..f1f1a16e
--- /dev/null
+++ b/res/values-kk-rKZ/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Қалыпты"</item>
+ <item msgid="2533030282864800794">"Толық"</item>
+ <item msgid="8568284598210500589">"Масштабтау"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Барлық арналар"</item>
+ <item msgid="928298872841713530">"Отбасы/балалар"</item>
+ <item msgid="2751606947569857164">"Спорт"</item>
+ <item msgid="7345749789651321496">"Сатып алулар"</item>
+ <item msgid="167201149441442173">"Фильмдер"</item>
+ <item msgid="525966731464264290">"Комедия"</item>
+ <item msgid="6096710741527327836">"Саяхат"</item>
+ <item msgid="2851882187117833883">"Драма"</item>
+ <item msgid="78492781188719038">"Білім"</item>
+ <item msgid="7221999662426308394">"Жануарлар/жабайы табиғат"</item>
+ <item msgid="375300513250925001">"Жаңалықтар"</item>
+ <item msgid="7746320336582330410">"Ойындар"</item>
+ <item msgid="1255741860568329178">"Өнер"</item>
+ <item msgid="7603949681065702867">"Ойын-сауық"</item>
+ <item msgid="4453821994746804366">"Өмір салты"</item>
+ <item msgid="3488534597567932843">"Mузыка"</item>
+ <item msgid="7452153120614274095">"Премьера"</item>
+ <item msgid="8215762047341133299">"Технология/ғылым"</item>
+ </string-array>
+</resources>
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..5c47f233
--- /dev/null
+++ b/res/values-kk-rKZ/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Есірткі"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Секс"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Қатыгездік"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Есірткі"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Секс"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Қатыгездік"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Француз"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Ұятсыз әңгіме"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Дөрекі сөздер"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Жыныстық қатынасқа қатысты мазмұн"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Қатыгездік"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Қиял және қатыгездік"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Фильм рейтингтері"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
new file mode 100644
index 00000000..f1c26d76
--- /dev/null
+++ b/res/values-kk-rKZ/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"моно"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"стерео"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Ойын тетіктері"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Арналар"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Соңғы арналар"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"ТД опциялары"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP опциялары"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Бұл арна үшін ойнатуды басқару элементтерін қол жетімсіз"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Ойнату немесе кідірту"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Жылдам алға айналдыру"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Кері айналдыру"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Келесі"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Алдыңғы"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Бағдарламалар нұс-ғы"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Жаңа арналар қол жетім."</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Сілтеме қолжетімді емес"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын ашу"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Жасырын титрлер"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Дисплей режимі"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Қосулы"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Өшірулі"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Мультиаудио"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Арнаны реттеу"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Ата-аналық бақылау"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Ақпарат"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Көз"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Алмастыру"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Қосулы"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Өшірулі"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Дыбыс"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Негізгі"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP терезесі"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Орналасу"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Төменгі оң жақ"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Жоғарғы оң жақ"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Жоғарғы сол жақ"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Төменгі сол жақ"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Қатар"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Өлшем"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Үлкен"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Кішкентай"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Енгізу көзі"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"ТД (антенна/кабель)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Бағдарлама туралы ақпарат жоқ"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Ақпарат жоқ"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Бөгелген арна"</string>
+ <string name="default_language" msgid="4122326459624337928">"Белгісіз тіл"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Жасырын титрлер"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Өшірулі"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Пішімдеуді теңшеу"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Жасырын титрлер үшін жүйелік пар-ді орн."</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Дисплей режимі"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Мультиаудио"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"моно"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"стерео"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 көлемді"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 көлемді"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d арна"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Арналар тіз. тең."</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Топты таңдау"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Топтан таңдауды алу"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Топтау шарты"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Арна көзі"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Топтау шарты"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Арнаны реттеу"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Бұл бағдарламаға тыйым салынған"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Бұл бағдарламаға <xliff:g id="RATING">%1$s</xliff:g> бағасы қойылған"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Арна көздері"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Жаңа арналар қосылды"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Арналар тіз. тең."</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Бағ-лар нұсқаулығы үшін арналарды таңдау"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Реттелмеген"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Кіріс автоматты түрде іздеуді қолдамайды"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"«<xliff:g id="TV_INPUT">%s</xliff:g>» үшін автоматты түрде іздеуді бастау мүмкін болмады"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Жасырын титрлер үшін жүйелік параметрлерді іске қосу мүмкін болмады."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d арна қосылды"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d арна қосылды"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Арналар қосылмаған"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Тюнер"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Ата-аналық бақылау"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Қосулы"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Өшірулі"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Арналар бөгелген"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Барлығын бөгеу"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Бәрін бөгеуден шығ."</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Жасырын арналар"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Бағдарлама шек-рі"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN кодын өзгерту"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Бағалау жүйелері"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Рейтингтер"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Бар. бағ. жүй-н көру"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Басқа елдер"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Ешқандай"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Ешқандай"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Ешқандай"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Жоғары шектеулер"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Орташа шектеулер"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Төмен шектеулер"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Теңшелетін"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Мазмұн балалар үшін жарамды"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Үлкенірек балаларға жарамды мазмұн"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Мазмұн жасөспірімдерге жарамды"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Қолмен реттелетін шектеулер"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s және ішкі бағ-р"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Ішкі бағалар"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Бұл арнаны көру үшін PIN кодын енгізіңіз"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Бұл бағдарламаны көру үшін PIN кодын енгізіңіз"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"PIN кодын енгізіңіз"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Ата-аналық бақылауды орнату үшін PIN кодын жасаңыз"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Жаңа PIN кодын енгізіңіз"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"PIN кодын растау"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Ағымдағы PIN кодын енгізіңіз"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Қате PIN кодын 5 рет енгіздіңіз.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> секундтан кейін әрекетті қайталаңыз."</item>
+ <item quantity="other" msgid="8829550842387756054">"Қате PIN кодын 5 рет енгіздіңіз.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> секундтан кейін әрекетті қайталаңыз."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Бұл PIN қате болды. Әрекетті қайталаңыз."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Әрекетті қайталаңыз, PIN кодтары сәйкес емес"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Ақпарат"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Ашық бастапқы код лицензиялары"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Ашық бастапқы код лицензиялары"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Нұсқа"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Бұл арнаны көру үшін оңға түймесін басып, PIN кодын енгізіңіз"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Бұл бағдарламаны көру үшін оңға түймесін басып, PIN кодын енгізіңіз"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Бұл бағдарламаға <xliff:g id="RATING">%1$s</xliff:g> бағасы қойылған.\nБұл бағдарламаны көру үшін \"Оңға\" түймесін басып, PIN кодын енгізіңіз."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Бағдарлама бөгелген"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Бұл бағдарламаға <xliff:g id="RATING">%1$s</xliff:g> бағасы қойылған"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Тақырыпсыз"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Арна бөгелген"</string>
+ <string name="episode_format" msgid="4881195874563241096">"<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>-маусым: <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>-эпизод <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Арна көздері"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Қолжетімді көздерден тікелей эфирдегі арналарды реттеңіз. Арна көзіне байланысты бұған бірнеше минут кетуі мүмкін."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Орындалды"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d арна қолжетімді"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d арна қолжетімді"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Қолжетімді арна жоқ"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Жаңа"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Теледидар мәзіріне кіру үшін ТАҢДАУ"</b>" түймесін басыңыз."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"ТД кіріс сигналы табылмады"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"ТД кіріс сигналын табу мүмкін емес"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP мүмкіндігіне қолдау көрсетілмейді"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP мүмкіндігімен көрсетуге болатын кіріс сигналы жоқ"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Тюнер түрі жарамсыз. Тюнер түрінің ТД кіріс сигналы үшін Тікелей эфир арналары қолданбасын іске қосыңыз."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Арнаға реттелмеді"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Бұл әрекетті орындайтын қолданба табылмады."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Барлық көз арналар жасырылған.\nКөру үшін кемінде бір арнаны таңдаңыз."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Әлсіз бейне сигналына байланысты қолжетімсіз"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Бейне белгісіз себеппен қолжетімсіз болып тұр"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"КЕРІ пернесі қосылған құрылғыға арналған. Шығу үшін НЕГІЗГІ пернесін басыңыз."</string>
+</resources>
diff --git a/res/values-km-rKH/arrays.xml b/res/values-km-rKH/arrays.xml
new file mode 100644
index 00000000..9b2076e0
--- /dev/null
+++ b/res/values-km-rKH/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"ធម្មតា"</item>
+ <item msgid="2533030282864800794">"ពេញ"</item>
+ <item msgid="8568284598210500589">"ពង្រីក"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"គ្រប់​ឆានែល"</item>
+ <item msgid="928298872841713530">"គ្រួសារ/កុមារ"</item>
+ <item msgid="2751606947569857164">"កីឡា"</item>
+ <item msgid="7345749789651321496">"ទិញ​ទំនិញ"</item>
+ <item msgid="167201149441442173">"ភាពយន្ត"</item>
+ <item msgid="525966731464264290">"រឿង​កំប្លែង"</item>
+ <item msgid="6096710741527327836">"ធ្វើដំណើរ"</item>
+ <item msgid="2851882187117833883">"ល្ខោន"</item>
+ <item msgid="78492781188719038">"ការអប់រំ"</item>
+ <item msgid="7221999662426308394">"សត្វ​ពាហនៈ/សត្វព្រៃ"</item>
+ <item msgid="375300513250925001">"ព័ត៌មាន"</item>
+ <item msgid="7746320336582330410">"លេង​ហ្គេម​"</item>
+ <item msgid="1255741860568329178">"សិល្បៈ"</item>
+ <item msgid="7603949681065702867">"ការ​កម្សាន្ត"</item>
+ <item msgid="4453821994746804366">"របៀបរស់នៅ"</item>
+ <item msgid="3488534597567932843">"តន្ត្រី"</item>
+ <item msgid="7452153120614274095">"បង្ហាញជូនលើដំបូង"</item>
+ <item msgid="8215762047341133299">"បច្ចេក/វិទ្យាសាស្ត្រ"</item>
+ </string-array>
+</resources>
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..5e0518eb
--- /dev/null
+++ b/res/values-km-rKH/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"គ្រឿងញៀន"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"សិច"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"អំពើហិង្សា"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"គ្រឿងញៀន"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"សិច"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"អំពើហិង្សា"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"ភាសាបារាំង"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"ការពិភាក្សាផ្តល់យោបល់"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"ភាសាអសុរោះ"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"មាតិកាផ្លូវភេទ"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"អំពើហិង្សា"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"អំពើហិង្សាបែបស្រមើស្រមៃ"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"ការវាយតម្លៃភាពយន្ត"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
new file mode 100644
index 00000000..8d1db82e
--- /dev/null
+++ b/res/values-km-rKH/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"ប៉ុស្តិ៍ផ្សាយផ្ទាល់"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"ម៉ូណូ"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"ស្តេរ៉េអូ"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"បញ្ជាការចាក់"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"ឆានែល"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"ឆានែលថ្មីៗ"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"ជម្រើសទូរទស្សន៍"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"ជម្រើស PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"ការបញ្ជាលើការចាក់មិនអាចប្រើបានទេសម្រាប់ឆានែលនេះ។"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"ចាក់ ឬផ្អាក"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"ទៅមុខរហ័ស"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"ខាថយក្រោយ"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"បន្ទាប់"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"មុន"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"ការ​ណែនាំ​កម្មវិធី"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"មានប៉ុស្តិ៍ថ្មី"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"មិនមានតំណទេ"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"បើក <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"ចំណងជើង​បិទ"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"របៀប​បង្ហាញ"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"បើក"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"បិទ"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"ពហុ​អូឌីយ៉ូ"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"ការដំឡើងប៉ុស្តិ៍"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"ការពិនិត្យរបស់ឪពុកម្ដាយ"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"អំពី"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"ប្រភព"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"ប្ដូរ"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"បើក"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"បិទ"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"សំឡេង"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"ចម្បង"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"ផ្ទាំងវិនដូ PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"ប្លង់"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"បាតខាងស្តាំ"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"ចុងខាងស្តាំ"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"ចុងខាងឆ្វេង"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"បាតខាងឆ្វេង"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"នៅក្បែរគ្នា"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"ទំហំ"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"ធំ"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"តូច"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"ប្រភព​ចូល"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"ទូរទស្សន៍ (អង់តែន/ខ្សែកាប)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"មិន​មាន​ព័ត៌មាន​កម្មវិធី"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"មិនមានព័ត៌មានទេ"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"បណ្តាញដែលបានរារាំង"</string>
+ <string name="default_language" msgid="4122326459624337928">"ភាសាមិនស្គាល់"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"បានបិទចំណងជើង"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"បិទ"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"ប្ដូររាងតាមត្រូវការ"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"កំណត់ចំណូលចិត្តប្រព័ន្ធទូលាយសម្រាប់ចំណងជើងបិទ"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"របៀប​បង្ហាញ"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"ពហុ​អូឌីយ៉ូ"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"ម៉ូណូ"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"ស្តេរ៉េអូ"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 ជុំវិញ"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 ជុំវិញ"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"ឆានែល %d"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"ប្ដូរបញ្ជីឆានែលតាម​តម្រូវការ"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"ជ្រើស​ក្រុម"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"មិន​ជ្រើស​ក្រុម"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"ដាក់​ជា​ក្រុម​តាម"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"ប្រភព​ឆា​នែ​ល"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"ដាក់​ជា​ក្រុម​តាម"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"ការដំឡើងប៉ុស្តិ៍"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"កម្មវិធីនេះត្រូវបានរារាំង"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"កម្មវិធីនេះត្រូវបានវាយតម្លៃ <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"ប្រភពប៉ុស្តិ៍"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"មានប៉ុស្តិ៍ថ្មី"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"ប្ដូរបញ្ជីឆានែលតាមបំណង"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"ជ្រើស​ឆានែល​សម្រាប់​ការណែនាំ​កម្មវិធី"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"មិន​បាន​កំណត់"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"ការ​បញ្ចូល​មិន​គាំទ្រ​ការ​វិភាគ​រក​ស្វ័យ​ប្រវត្តិ"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"មិន​អាច​ចាប់ផ្ដើម​ការ​វិភាគ​ស្វ័យប្រវត្តិ​សម្រាប់ \'<xliff:g id="TV_INPUT">%s</xliff:g>\'"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"មិន​អាច​ចាប់ផ្ដើម​ចំណូល​ចិត្ត​ប្រព័ន្ធ​ទូលាយ​សម្រាប់​ចំណងជើង​បិទ​ជិត​។"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"បាន​បន្ថែម​ឆានែល %1$d"</item>
+ <item quantity="other" msgid="1078861616751739285">"បាន​បន្ថែម​ឆានែល %1$d"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"គ្មាន​ឆា​នែ​ល​បាន​បន្ថែម"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"ការ​ត្រួតពិនិត្យមេ"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"បើក"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"បិទ"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"បាន​ទប់ស្កាត់ឆានែល"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"ទប់ស្កាត់​​ទាំងអស់"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"មិន​ទប់ស្កាត់ទាំងអស់"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"ឆានែល​​ដែល​បាន​លាក់"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"ការដាក់កម្រិតកម្មវិធី"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"ប្ដូរ​កូដ PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"ប្រព័ន្ធ​វាយ​តម្លៃ"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"វាយតម្លៃ"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"មើលប្រព័ន្ធវាយតម្លៃទាំងអស់"</string>
+ <string name="other_countries" msgid="8342216398676184749">"ប្រទេសផ្សេងទៀត"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"គ្មាន"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"គ្មាន"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"គ្មាន"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"ការ​ដាក់កម្រិត​ខ្ពស់"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"ការដាក់កម្រិតមធ្យម"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"ការ​ដាក់​កម្រិត​ទាប"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"តាម​តម្រូវ​ការ"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"មាតិកា​សមស្រប​សម្រាប់​កុមារ"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"មាតិកា​សមស្រប​សម្រាប់​កុមារ​ធំៗ"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"មាតិកា​សមស្រប​សម្រាប់​យុវវ័យ"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"ការ​ដាក់កម្រិត​ដោយ​ដៃ"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s និងការវាតម្លៃរង"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"ការ​វាយ​តម្លៃ​រង"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"បញ្ចូល​កូដ PIN របស់​អ្នក​ដើម្បី​មើល​​ឆានែល​នេះ"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"បញ្ចូល​កូដ PIN របស់​អ្នក​ដើម្បី​មើល​កម្មវិធី​នេះ"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"បញ្ចូល​កូដ PIN របស់​អ្នក"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"ដើម្បី​កំណត់​ការ​ពិនិត្យ​របស់​ឪពុកម្ដាយ អ្នក​ត្រូវ​បង្កើត​កូដ PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"បញ្ចូល​កូដ PIN ថ្មី"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"បញ្ជាក់​កូដ PIN របស់​អ្នក"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"បញ្ចូល​កូដ PIN បច្ចុប្បន្ន​របស់​អ្នក"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"អ្នកបានបញ្ចូលកូដ PIN ខុស ៥ដង។\nព្យាយាមម្ដងទៀតក្នុងរយៈពេល <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> វិនាទីទៀត។"</item>
+ <item quantity="other" msgid="8829550842387756054">"អ្នកបានបញ្ចូលកូដ PIN ខុស ៥ដង។\nព្យាយាមម្ដងទៀតក្នុងរយៈពេល <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> វិនាទីទៀត។"</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"កូដ PIN មិន​ត្រឹមត្រូវ។ ព្យាយាម​ម្ដងទៀត។"</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"ព្យាយាម​ម្ដង​ទៀត កូដ​ PIN មិន​ដូច​គ្នា"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"អំពី"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"អាជ្ញាប័ណ្ណប្រភពកូដចំហ"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"អាជ្ញាប័ណ្ណប្រភពកូដចំហ"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"កំណែ"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"ដើម្បី​មើល​ឆានែល​នេះ អ្នក​ត្រូវ​ចុច​កណ្ដុរស្ដាំ រួច​បញ្ចូល​កូដ PIN របស់​អ្នក"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"ដើម្បី​មើល​កម្មវិធី​នេះ អ្នក​ត្រូវ​ចុច​កណ្ដុរស្ដាំ រួច​បញ្ចូល​កូដ PIN របស់​អ្នក"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"កម្មវិធីនេះត្រូវបានវាយតម្លៃ <xliff:g id="RATING">%1$s</xliff:g>។\n ដើម្បីមើលកម្មវិធីនេះ សូមចុច ស្តាំ រួចបញ្ចូលកូដ PIN របស់អ្នក។"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"កម្មវិធី​នេះ​ត្រូវ​បាន​ទប់ស្កាត់"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"កម្មវិធីនេះត្រូវបានវាយតម្លៃ <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"គ្មាន​ចំណងជើង"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"បាន​ទប់​ស្កាត់​ឆានែល"</string>
+ <string name="episode_format" msgid="4881195874563241096">"រដូវកាល <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>៖ វគ្គ <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"ប្រភពប៉ុស្តិ៍"</string>
+ <string name="setup_description" msgid="8728423605912915099">"ដំឡើងប៉ុស្តិ៍ផ្សាយផ្ទាល់ចេញពីប្រភពដែលមានផ្តល់ជូន។ វាអាចចំណាយពេលប៉ុន្មាននាទីអាស្រ័យលើប្រភពរបស់ប៉ុស្តិ៍នោះ។"</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"រួចរាល់"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"មានប៉ុស្តិ៍ %1$d"</item>
+ <item quantity="other" msgid="2386588423841837714">"មានប៉ុស្តិ៍ %1$d"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"មិនមានប៉ុស្តិ៍ទេ"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"ថ្មី"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"ចុច ជ្រើសរើស"</b>" ដើម្បីចូលប្រើម៉ឺនុយទូរទស្សន៍។"</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"រកមិនឃើញបញ្ចូលទូរទស្សន៍ទេ"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"រកមិនឃើញបញ្ចូលទូរទស្សន៍ទេ"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"មិនគាំទ្រ PIP ទេ។"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"មិនមានបញ្ចូលដែលអាចបង្ហាញជាមួយ PIP បានទេ។"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"ប្រភេទឧបករណ៍ចាប់ប៉ុស្តិ៍មិនសមស្រប។ សូមចាប់ផ្តើមដំណើរការកម្មវិធី ប៉ុស្តិ៍ផ្សាយផ្ទាល់ សម្រាប់ធាតុបញ្ជូលទូរទស្សន៍ប្រភេទឧបករណ៍ចាប់ប៉ុស្តិ៍។"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"កាកែសម្រួលប៉ុស្តិ៍បានបរាជ័យ"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"រកមិនឃើញកម្មវិធីដើម្បីគ្រប់គ្រងសកម្មភាពនេះទេ។"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"រាល់​​ឆានែល​ប្រភព​គឺ​​លាក់​។ \n ជ្រើស​ឆា​នែ​ល​យ៉ាង​ហោច​ណាស់​មួយ​​ដើម្បី​​មើល​។"</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"មិនអាចចាក់បានដោយសារសេវាវីដេអូខ្សោយ"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"មិនបានរំពឹងថាមិនមានវីដេអូនេះទេ"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"ប៊ូតុង​ថយក្រោយ​សម្រាប់​ឧបករណ៍​ដែល​បាន​ភ្ជាប់។ ចុច​ប៊ូតុង​ដើម ដើម្បី​ចាកចេញ។"</string>
+</resources>
diff --git a/res/values-kn-rIN/arrays.xml b/res/values-kn-rIN/arrays.xml
new file mode 100644
index 00000000..9e3fa48a
--- /dev/null
+++ b/res/values-kn-rIN/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"ಸಾಮಾನ್ಯ"</item>
+ <item msgid="2533030282864800794">"ಸಂಪೂರ್ಣ"</item>
+ <item msgid="8568284598210500589">"ಜೂಮ್"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"ಎಲ್ಲಾ ಚಾನಲ್‌ಗಳು"</item>
+ <item msgid="928298872841713530">"ಕುಟುಂಬ/ಮಕ್ಕಳು"</item>
+ <item msgid="2751606947569857164">"ಕ್ರೀಡೆಗಳು"</item>
+ <item msgid="7345749789651321496">"ಶಾಪಿಂಗ್"</item>
+ <item msgid="167201149441442173">"ಚಲನಚಿತ್ರಗಳು"</item>
+ <item msgid="525966731464264290">"ಹಾಸ್ಯ ಪ್ರಧಾನ"</item>
+ <item msgid="6096710741527327836">"ಪ್ರಯಾಣ"</item>
+ <item msgid="2851882187117833883">"ನಾಟಕ"</item>
+ <item msgid="78492781188719038">"ವಿದ್ಯಾಭ್ಯಾಸ"</item>
+ <item msgid="7221999662426308394">"ಪ್ರಾಣಿ/ವನ್ಯಜೀವಿ"</item>
+ <item msgid="375300513250925001">"ಸುದ್ದಿ"</item>
+ <item msgid="7746320336582330410">"ಗೇಮಿಂಗ್"</item>
+ <item msgid="1255741860568329178">"ಕಲೆ"</item>
+ <item msgid="7603949681065702867">"ಮನರಂಜನೆ"</item>
+ <item msgid="4453821994746804366">"ಲೈಫ್‌ಸ್ಟೈಲ್‌‌"</item>
+ <item msgid="3488534597567932843">"ಸಂಗೀತ"</item>
+ <item msgid="7452153120614274095">"ಪ್ರೀಮಿಯರ್"</item>
+ <item msgid="8215762047341133299">"ತಂತ್ರಜ್ಞಾನ/ವಿಜ್ಞಾನ"</item>
+ </string-array>
+</resources>
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..e8626f7f
--- /dev/null
+++ b/res/values-kn-rIN/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"ಮಾದಕ ದ್ರವ್ಯ"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"ಲೈಂಗಿಕ"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"ಹಿಂಸೆ"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"ಮಾದಕ ದ್ರವ್ಯ"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"ಲೈಂಗಿಕ"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"ಹಿಂಸೆ"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"ಫ್ರೆಂಚ್"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"ಸೂಚಿತ ಸಂಭಾಷಣೆ"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"ಒರಟಾದ ಭಾಷೆ"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"ಲೈಂಗಿಕ ವಿಷಯ"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"ಹಿಂಸೆ"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"ಕಾಲ್ಪನಿಕ ಹಿಂಸೆ"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"ಚಲನಚಿತ್ರ ರೇಟಿಂಗ್‌ಗಳು"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
new file mode 100644
index 00000000..c6b42cfe
--- /dev/null
+++ b/res/values-kn-rIN/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"ಲೈವ್‌ ಚಾನಲ್‌ಗಳು"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"ಮೊನೊ"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"ಸ್ಟೀರಿಯೋ"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Play ನಿಯಂತ್ರಣಗಳು"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"ಚಾನಲ್‌ಗಳು"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"ಇತ್ತೀಚಿನ ಚಾನಲ್‌ಗಳು"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV ಆಯ್ಕೆಗಳು"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP ಆಯ್ಕೆಗಳು"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"ಈ ಚಾನಲ್‌ಗೆ ಪ್ಲೇ ನಿಯಂತ್ರಣಗಳು ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"ಪ್ಲೇ ಮಾಡಿ ಅಥವಾ ವಿರಾಮಗೊಳಿಸಿ"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"ವೇಗವಾಗಿ ಮುಂದಕ್ಕೆ"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"ರೀವೈಂಡ್"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"ಮುಂದೆ"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"ಹಿಂದೆ"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"ಕಾರ್ಯಕ್ರಮ ಮಾರ್ಗಸೂಚಿ"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"ಹೊಸ ಚಾನಲ್‌ಗಳು ಲಭ್ಯವಿವೆ"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"ಯಾವುದೇ ಲಿಂಕ್ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> ತೆರೆಯಿರಿ"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಗಳು"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"ಪ್ರದರ್ಶನ ಮೋಡ್"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"ಆನ್"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"ಆಫ್"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"ಬಹು-ಆಡಿಯೊ"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"ಚಾನಲ್ ಸೆಟಪ್"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"ಪೋಷಕ ನಿಯಂತ್ರಣಗಳು"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"ಕುರಿತು"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"ಮೂಲ"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"ಸ್ವ್ಯಾಪ್‌ ಮಾಡು"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"ಆನ್"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"ಆಫ್"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"ಶಬ್ದ"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"ಪ್ರಮುಖ"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP ವಿಂಡೋ"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"ಲೇಔಟ್"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"ಕೆಳಗಿನ ಬಲಭಾಗ"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"ಮೇಲಿನ ಬಲಭಾಗ"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"ಮೇಲಿನ ಎಡಭಾಗ"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"ಕೆಳಗಿನ ಎಡಭಾಗ"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"ಅಕ್ಕ ಪಕ್ಕ"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"ಗಾತ್ರ"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"ದೊಡ್ಡದು"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"ಸಣ್ಣ"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"ಇನ್‌ಪುಟ್ ಮೂಲ"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (ಆಂಟೆನಾ/ಕೇಬಲ್)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"ಯಾವುದೇ ಕಾರ್ಯಕ್ರಮದ ಮಾಹಿತಿ ಇಲ್ಲ"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"ಯಾವುದೇ ಮಾಹಿತಿ ಇಲ್ಲ"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"ನಿರ್ಬಂಧಿಸಲಾದ ಚಾನಲ್"</string>
+ <string name="default_language" msgid="4122326459624337928">"ಅಪರಿಚಿತ ಭಾಷೆ"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಗಳು"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"ಆಫ್"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"ಫಾರ್ಮ್ಯಾಟ್‌ ಮಾಡುವಿಕೆ ವೈಯಕ್ತೀಕರಿಸಿ"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಗಳಿಗಾಗಿ ಸಿಸ್ಟಂನಾದ್ಯಂತ ಪ್ರಾಶಸ್ತ್ಯಗಳನ್ನು ಹೊಂದಿಸಿ"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"ಪ್ರದರ್ಶನ ಮೋಡ್"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"ಬಹು-ಆಡಿಯೊ"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"ಮೊನೊ"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"ಸ್ಟೀರಿಯೋ"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 ಸರೌಂಡ್"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 ಸರೌಂಡ್"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d ಚಾನಲ್‌ಗಳು"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"ಚಾನಲ್‌ಪಟ್ಟಿ ಕಸ್ಟಮೈಸ್‌"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"ಗುಂಪು ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"ಗುಂಪು ಆಯ್ಕೆರದ್ದು"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"ಈ ಪ್ರಕಾರ ಗುಂಪು ಮಾಡಿ"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"ಚಾನಲ್ ಮೂಲ"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"ಈ ಪ್ರಕಾರ ಗುಂಪು ಮಾಡಿ"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"ಚಾನಲ್ ಸೆಟಪ್"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು <xliff:g id="RATING">%1$s</xliff:g> ಎಂದು ರೇಟ್‌ ಮಾಡಲಾಗಿದೆ."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"ಚಾನಲ್ ಮೂಲಗಳು"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"ಹೊಸ ಚಾನಲ್‌ಗಳು ಲಭ್ಯವಿದೆ"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"ಚಾನಲ್‌ಪಟ್ಟಿ ಕಸ್ಟಮೈಸ್‌"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"ನಿಮ್ಮ ಕಾರ್ಯಕ್ರಮ ಸೂಚಿಗಾಗಿ ಚಾನಲ್‌ಗಳನ್ನು ಆರಿಸಿ"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"ಇನ್ನೂ ಹೊಂದಿಸಿಲ್ಲ"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"ಇನ್‌ಪುಟ್ ಸ್ವಯಂ-ಸ್ಕ್ಯಾನ್ ಅನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\'<xliff:g id="TV_INPUT">%s</xliff:g>\' ಗಾಗಿ ಸ್ವಯಂ-ಸ್ಕ್ಯಾನ್ ಪ್ರಾರಂಭಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಗಳಿಗೆ ಸಿಸ್ಟಂ-ವೈಡ್‌ ಪ್ರಾಶಸ್ತ್ಯಗಳನ್ನು ಪ್ರಾರಂಭಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d ಚಾನಲ್ ಸೇರಿಸಲಾಗಿದೆ"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d ಚಾನಲ್‌ಗಳನ್ನು ಸೇರಿಸಲಾಗಿದೆ"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"ಯಾವುದೇ ಚಾನಲ್‌ಗಳನ್ನು ಸೇರಿಸಲಾಗಿಲ್ಲ"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"ಟ್ಯೂನರ್"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"ಪೋಷಕ ನಿಯಂತ್ರಣಗಳು"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"ಆನ್"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"ಆಫ್"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"ಚಾನಲ್‌ಗಳನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"ಎಲ್ಲಾ ನಿರ್ಬಂಧಿಸಿ"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"ಎಲ್ಲವನ್ನೂ ಅನಿರ್ಬಂಧಿಸಿ"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"ಮರೆಮಾಡಲಾದ ಚಾನಲ್‌ಗಳು"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"ಕಾರ್ಯಕ್ರಮ ನಿರ್ಬಂಧಗಳು"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN ಬದಲಾಯಿಸಿ"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"ಸಿಸ್ಟಂಗಳನ್ನು ರೇಟ್ ಮಾಡುವುದು"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"ರೇಟಿಂಗ್‌ಗಳು"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"ಎಲ್ಲಾ ರೇಟಿಂಗ್ ಸಿಸ್ಟಂಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
+ <string name="other_countries" msgid="8342216398676184749">"ಇತರ ದೇಶಗಳು"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"ಯಾವುದೂ ಇಲ್ಲ"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"ಯಾವುದೂ ಇಲ್ಲ"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"ಯಾವುದೂ ಇಲ್ಲ"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"ಅಧಿಕ ನಿರ್ಬಂಧಗಳು"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"ಮಧ್ಯಮ ರೀತಿಯ ನಿರ್ಬಂಧ"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"ಕಡಿಮೆ ನಿರ್ಬಂಧಗಳು"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"ಕಸ್ಟಮ್"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಾದ ವಿಷಯ"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"ಹಿರಿಯ ಮಕ್ಕಳಿಗೆ ಸೂಕ್ತವಾದ ವಿಷಯ"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"ಹದಿಹರೆಯದವರಿಗೆ ಸೂಕ್ತವಾದ ವಿಷಯ"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"ಹಸ್ತಚಾಲಿತ ನಿರ್ಬಂಧಗಳು"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s ಮತ್ತು ಉಪ-ರೇಟಿಂಗ್‌ಗಳು"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"ಉಪ-ರೇಟಿಂಗ್‌ಗಳು"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"ಈ ಚಾನಲ್‌ ವೀಕ್ಷಿಸಲು ನಿಮ್ಮ PIN ನಮೂದಿಸಿ"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"ಈ ಕಾರ್ಯಕ್ರಮ ವೀಕ್ಷಿಸಲು ನಿಮ್ಮ PIN ನಮೂದಿಸಿ"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"ನಿಮ್ಮ PIN ಅನ್ನು ನಮೂದಿಸಿ"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"ಪೋಷಕ ನಿಯಂತ್ರಣಗಳನ್ನು ಹೊಂದಿಸಲು, PIN ರಚಿಸಿ"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"ಹೊಸ PIN ನಮೂದಿಸಿ"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"ನಿಮ್ಮ PIN ಅನ್ನು ದೃಢೀಕರಿಸಿ"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"ನಿಮ್ಮ ಪ್ರಸ್ತುತ PIN ಅನ್ನು ನಮೂದಿಸಿ"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"ನೀವು ತಪ್ಪಾದ ಪಿನ್ ಅನ್ನು 5 ಬಾರಿ ನಮೂದಿಸಿರುವಿರಿ.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> ಸೆಕೆಂಡಿನಲ್ಲಿ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."</item>
+ <item quantity="other" msgid="8829550842387756054">"ನೀವು ತಪ್ಪಾದ ಪಿನ್ ಅನ್ನು 5 ಬಾರಿ ನಮೂದಿಸಿರುವಿರಿ.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> ಸೆಕೆಂಡುಗಳಲ್ಲಿ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"ಆ PIN ತಪ್ಪಾಗಿದೆ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ, PIN ಹೊಂದಾಣಿಕೆಯಾಗುವುದಿಲ್ಲ"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"ಕುರಿತು"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"ಮುಕ್ತ ಮೂಲ ಪರವಾನಗಿಗಳು"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"ಮುಕ್ತ ಮೂಲ ಪರವಾನಗಿಗಳು"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"ಆವೃತ್ತಿ"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"ಈ ಚಾನಲ್ ಅನ್ನು ವೀಕ್ಷಿಸಲು, ಬಲಕ್ಕೆ ಒತ್ತಿ ಮತ್ತು ನಿಮ್ಮ PIN ಅನ್ನು ನಮೂದಿಸಿ"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು, ಬಲಕ್ಕೆ ಒತ್ತಿ ಮತ್ತು ನಿಮ್ಮ PIN ಅನ್ನು ನಮೂದಿಸಿ"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು <xliff:g id="RATING">%1$s</xliff:g> ರೇಟ್ ಮಾಡಲಾಗಿದೆ.\nಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು, ಬಲಕ್ಕೆ ಒತ್ತಿ ಮತ್ತು ನಿಮ್ಮ ಪಿನ್ ನಮೂದಿಸಿ."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"ಕಾರ್ಯಕ್ರಮವನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು <xliff:g id="RATING">%1$s</xliff:g> ಎಂದು ರೇಟ್‌ ಮಾಡಲಾಗಿದೆ."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"ಯಾವುದೇ ಶೀರ್ಷಿಕೆಯಿಲ್ಲ"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"ಚಾನಲ್ ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: ಸಂ. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"ಚಾನಲ್ ಮೂಲಗಳು"</string>
+ <string name="setup_description" msgid="8728423605912915099">"ಲಭ್ಯವಿರುವ ಮೂಲಗಳಿಂದ ಲೈವ್ ಚಾನಲ್‌ಗಳನ್ನು ಹೊಂದಿಸಿ. ಚಾನಲ್ ಮೂಲಗಳನ್ನು ಅವಲಂಬಿಸಿ ಇದಕ್ಕೆ ಹಲವಾರು ನಿಮಿಷಗಳು ತೆಗೆದುಕೊಳ್ಳಬಹುದು."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"ಮುಗಿದಿದೆ"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d ಚಾನಲ್‌ ಲಭ್ಯವಿದೆ"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d ಚಾನಲ್‌ಗಳು ಲಭ್ಯವಿವೆ"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"ಯಾವುದೇ ಚಾನಲ್‌ಗಳು ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"ಹೊಸತು"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"TV ಮೆನುವನ್ನು ಪ್ರವೇಶಿಸಲು "<b>"ಆಯ್ಕೆ ಮಾಡು ಒತ್ತಿರಿ"</b>"."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"ಯಾವುದೇ TV ಇನ್‌ಪುಟ್ ಕಂಡುಬಂದಿಲ್ಲ"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"TV ಇನ್‌ಪುಟ್ ಹುಡುಕಲಾಗಲಿಲ್ಲ"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP ಬೆಂಬಲಿತವಾಗಿಲ್ಲ"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP ನೊಂದಿಗೆ ತೋರಿಸಬಹುದಾದ ಯಾವುದೇ ಇನ್‌ಪುಟ್ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"ಟ್ಯೂನರ್ ಪ್ರಕಾರವು ಹೊಂದಿಕೆಯಾಗುವುದಿಲ್ಲ. ದಯವಿಟ್ಟು ಟ್ಯೂನರ್ ಪ್ರಕಾರದ TV ಇನ್‌ಪುಟ್‌ಗೆ ಲೈವ್‌ ಚಾನಲ್‌ಗಳ ಅಪ್ಲಿಕೇಶನ್‌ ಪ್ರಾರಂಭಿಸಿ."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"ಟ್ಯೂನ್ ವಿಫಲವಾಗಿದೆ"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"ಈ ಕ್ರಿಯೆಯನ್ನು ನಿರ್ವಹಿಸಲು ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ ಕಂಡುಬಂದಿಲ್ಲ."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"ಎಲ್ಲ ಮೂಲ ಚಾನಲ್‌ಗಳನ್ನು ಮರೆಮಾಡಲಾಗಿದೆ.\nವೀಕ್ಷಿಸಲು ಕನಿಷ್ಠ ಒಂದು ಚಾನಲ್‌‌ ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"ದುರ್ಬಲ ವೀಡಿಯೊ ಸಿಗ್ನಲ್‌‌ನಿಂದಾಗಿ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"ವೀಡಿಯೊ ಅನಿರೀಕ್ಷಿತವಾಗಿ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"ಸಂಪರ್ಕಪಡಿಸಲಾದ ಸಾಧನಕ್ಕಾಗಿ ಹಿಂದೆ ಕೀ. ನಿರ್ಗಮಿಸಲು ಮುಖಪುಟ ಬಟನ್ ಒತ್ತಿರಿ."</string>
+</resources>
diff --git a/res/values-ko/arrays.xml b/res/values-ko/arrays.xml
new file mode 100644
index 00000000..38618382
--- /dev/null
+++ b/res/values-ko/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"표준"</item>
+ <item msgid="2533030282864800794">"전체"</item>
+ <item msgid="8568284598210500589">"확대/축소"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"모든 채널"</item>
+ <item msgid="928298872841713530">"가족/어린이"</item>
+ <item msgid="2751606947569857164">"스포츠"</item>
+ <item msgid="7345749789651321496">"쇼핑"</item>
+ <item msgid="167201149441442173">"영화"</item>
+ <item msgid="525966731464264290">"코미디"</item>
+ <item msgid="6096710741527327836">"여행"</item>
+ <item msgid="2851882187117833883">"드라마"</item>
+ <item msgid="78492781188719038">"교육"</item>
+ <item msgid="7221999662426308394">"동물/야생 생물"</item>
+ <item msgid="375300513250925001">"뉴스"</item>
+ <item msgid="7746320336582330410">"게임"</item>
+ <item msgid="1255741860568329178">"예술"</item>
+ <item msgid="7603949681065702867">"예능"</item>
+ <item msgid="4453821994746804366">"라이프스타일"</item>
+ <item msgid="3488534597567932843">"음악"</item>
+ <item msgid="7452153120614274095">"프리미엄"</item>
+ <item msgid="8215762047341133299">"기술/과학"</item>
+ </string-array>
+</resources>
diff --git a/res/values-ko/rating_system_strings.xml b/res/values-ko/rating_system_strings.xml
new file mode 100644
index 00000000..dad2fab7
--- /dev/null
+++ b/res/values-ko/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"마약"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"성행위"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"폭력"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"마약"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"성행위"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"폭력"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"프랑스어"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"외설적인 대화"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"거친 욕설"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"성적인 콘텐츠"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"폭력"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"판타지 폭력"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"영화 등급"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
new file mode 100644
index 00000000..5e595a15
--- /dev/null
+++ b/res/values-ko/strings.xml
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"실시간 채널"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"모노"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"스테레오"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"재생 컨트롤"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"채널"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"최근 시청한 채널"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV 옵션"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP 옵션"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"이 채널에서 재생 컨트롤을 사용할 수 없습니다."</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"재생 또는 일시중지"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"빨리 감기"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"되감기"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"다음"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"이전"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"프로그램 가이드"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"새 채널 사용 가능"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"사용 가능한 링크 없음"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> 열기"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"자막"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"표시 모드"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"사용"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"사용 안함"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"멀티 오디오"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"채널 설정"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"자녀 보호 기능"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"정보"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"소스"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"전환"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"사용"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"사용 안함"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"소리"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"기본"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP 창"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"레이아웃"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"오른쪽 하단"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"오른쪽 상단"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"왼쪽 상단"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"왼쪽 하단"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"나란히"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"크기"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"크게"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"작게"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"입력 소스"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV(안테나/케이블)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"프로그램 정보가 없습니다."</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"정보 없음"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"차단된 채널"</string>
+ <string name="default_language" msgid="4122326459624337928">"알 수 없는 언어"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"자막"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"사용 안함"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"포맷 맞춤설정"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"자막에 대한 시스템 전체 환경설정 지정"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"표시 모드"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"멀티 오디오"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"모노"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"스테레오"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 서라운드"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 서라운드"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d개 채널"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"채널 목록 맞춤설정"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"그룹 선택"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"그룹 선택 해제"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"그룹 기준"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"채널 소스"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"그룹 기준"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"채널 설정"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"프로그램이 차단되었습니다."</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"이 프로그램의 시청 등급은 <xliff:g id="RATING">%1$s</xliff:g>입니다."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"채널 소스"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"새 채널 사용 가능"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"채널 목록 맞춤설정"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"편성표를 표시할 채널을 선택합니다."</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"설정되지 않음"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"입력이 자동 스캔을 지원하지 않습니다."</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\'<xliff:g id="TV_INPUT">%s</xliff:g>\'의 자동 스캔을 시작할 수 없습니다."</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"자막 관련 시스템 전체 환경설정을 시작할 수 없습니다."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d개 채널 추가됨"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d개 채널 추가됨"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"추가된 채널 없음"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"튜너"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"자녀 보호 기능"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"사용"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"사용 안함"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"차단된 채널"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"모두 차단"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"모두 차단 해제"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"숨겨진 채널"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"프로그램 제한"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN 변경"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"등급 체계"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"등급"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"모든 등급 체계 보기"</string>
+ <string name="other_countries" msgid="8342216398676184749">"기타 국가"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"없음"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"없음"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"없음"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"높은 제한"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"중간 제한"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"낮은 제한"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"맞춤"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"어린이에게 적합한 콘텐츠"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"고학년 어린이에게 적합한 콘텐츠"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"청소년에게 적합한 콘텐츠"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"수동 제한"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <!-- String.format failed for translation -->
+ <!-- no translation found for option_subrating_title (5485055507818077595) -->
+ <skip />
+ <string name="option_subrating_header" msgid="4637961301549615855">"하위 등급"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"PIN을 입력하여 이 채널 시청"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"PIN을 입력하여 이 프로그램 시청"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"PIN 입력"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"PIN을 생성하여 자녀 보호 기능 설정"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"새 PIN 입력"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"PIN 확인"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"현재 PIN 입력"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"PIN 입력이 5회 잘못되었습니다.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g>초 후에 다시 시도하세요."</item>
+ <item quantity="other" msgid="8829550842387756054">"PIN 입력이 5회 잘못되었습니다.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g>초 후에 다시 시도하세요."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"PIN이 잘못되었습니다. 다시 시도해 주세요."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"다시 시도해 주세요. PIN이 일치하지 않습니다."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"정보"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"오픈소스 라이선스"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"오픈소스 라이선스"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"버전"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"이 채널을 보려면 오른쪽을 누르고 PIN을 입력하세요."</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"이 프로그램을 보려면 오른쪽을 누르고 PIN을 입력하세요."</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"이 프로그램의 시청 등급은 <xliff:g id="RATING">%1$s</xliff:g>입니다.\n이 프로그램을 보려면 오른쪽을 누르고 PIN을 입력하세요."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"차단된 프로그램입니다."</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"이 프로그램의 시청 등급은 <xliff:g id="RATING">%1$s</xliff:g>입니다."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"제목 없음"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"차단된 채널"</string>
+ <string name="episode_format" msgid="4881195874563241096">"시즌 <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: 에피소드 <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"채널 소스"</string>
+ <string name="setup_description" msgid="8728423605912915099">"사용할 수 있는 소스로 라이브 채널을 설정하세요. 채널 소스에 따라 몇 분 정도 걸릴 수 있습니다."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"완료"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"채널 %1$d개를 이용할 수 있습니다."</item>
+ <item quantity="other" msgid="2386588423841837714">"채널 %1$d개를 이용할 수 있습니다."</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"이용할 수 있는 채널이 없습니다."</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"새 입력"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"선택을 눌러"</b>" TV 메뉴에 액세스합니다."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"TV 입력이 없습니다."</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"TV 입력을 찾을 수 없습니다."</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP가 지원되지 않습니다."</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP로 표시할 수 있는 입력이 없습니다."</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"튜너 유형이 적합하지 않습니다. 튜너 유형 TV 입력에 실시간 채널 앱을 실행하세요."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"조정에 실패했습니다."</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"이 작업을 처리하는 앱을 찾을 수 없습니다."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"모든 소스 채널이 숨겨져 있습니다.\n시청할 채널을 하나 이상 선택하세요."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"동영상 신호가 약해서 사용할 수 없습니다."</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"예기치 않게 동영상을 사용할 수 없습니다."</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"뒤로 키는 연결된 기기에 사용됩니다. 종료하려면 홈 버튼을 누르세요."</string>
+</resources>
diff --git a/res/values-ky-rKG/arrays.xml b/res/values-ky-rKG/arrays.xml
new file mode 100644
index 00000000..10b48161
--- /dev/null
+++ b/res/values-ky-rKG/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Кадимки"</item>
+ <item msgid="2533030282864800794">"Толук"</item>
+ <item msgid="8568284598210500589">"Ченөлчөм"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Бардык каналдар"</item>
+ <item msgid="928298872841713530">"Үй-бүлө/Балдар"</item>
+ <item msgid="2751606947569857164">"Спорт"</item>
+ <item msgid="7345749789651321496">"Соода-сатык"</item>
+ <item msgid="167201149441442173">"Тасмалар"</item>
+ <item msgid="525966731464264290">"Комедия"</item>
+ <item msgid="6096710741527327836">"Саякат"</item>
+ <item msgid="2851882187117833883">"Драма"</item>
+ <item msgid="78492781188719038">"Билим алуу"</item>
+ <item msgid="7221999662426308394">"Жаныбарлар дүйнөсү"</item>
+ <item msgid="375300513250925001">"Жаңылыктар"</item>
+ <item msgid="7746320336582330410">"Оюндар"</item>
+ <item msgid="1255741860568329178">"Көркөм өнөрчүлүк"</item>
+ <item msgid="7603949681065702867">"Көңүл ачуу"</item>
+ <item msgid="4453821994746804366">"Жашоо мүнөзү"</item>
+ <item msgid="3488534597567932843">"Музыка"</item>
+ <item msgid="7452153120614274095">"Бет ачар"</item>
+ <item msgid="8215762047341133299">"Тех/Илим"</item>
+ </string-array>
+</resources>
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..8527a25c
--- /dev/null
+++ b/res/values-ky-rKG/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Баңгизаттар"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Секс"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Зомбулук"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Баңгизаттар"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Секс"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Зомбулук"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Французча"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Адепсиз диалог"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Сөгүнгөн сөздөр"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Сексуалдык мазмун"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Зомбулук"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Зомбулук көрүнүштөрү чагылдырылган фантастика"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Тасмалар рейтинги"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
new file mode 100644
index 00000000..b8433131
--- /dev/null
+++ b/res/values-ky-rKG/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Жандуу каналдар"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"моно"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"стерео"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Ойнотууну башкаруу"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Каналдар"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Акыркы каналдар"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Сынлг прметрлр"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP параметрлери"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Бул канал үчүн ойнотуу көзөмөлдөрү жеткиликтүү эмес"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Ойнотуу же тындыруу"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Алдыга түрүү"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Артка түрүү"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Кийинки"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Мурунку"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Программа көрсөтмөсү"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Жаңы каналдар бар"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Шилтеме жеткиликсиз"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> ачуу"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Жабык субттрлр"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Көрсөтүү режими"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Күйүк"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Өчүк"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Мульти-аудио"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Каналды жөндөө"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Ата-эне көзөмөл"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Ушул жөнүндө"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Булак"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Алмаштыруу"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Күйүк"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Өчүк"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Үн"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Негизги"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP терезеси"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Үлгү"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Төмөнкү оң"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Жогорку оң"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Жогорку сол"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Төмөнкү сол"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Тушма-туш"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Өлчөмү"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Чоң"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Кичине"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Киргизүү булагы"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Сыналгы (антенна/кабель)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Программанын маалыматы жок"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Маалымат жок"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Бөгөттөлгөн канал"</string>
+ <string name="default_language" msgid="4122326459624337928">"Белгисиз тил"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Коштомо жазуулар"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Өчүк"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Формттоону өзгөчөлшт"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Жабк субтрл үчн жалп верся жөндөөлр коюу"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Көрсөтүү режими"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Мульти-аудио"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"моно"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"стерео"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 айлана"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 айлана"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d канал"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Канл тизмсн өзгөчл"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Топ тандоо"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Топту тандоодн чыгр"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Топ"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Канал булагы"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Топ"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Каналды жөндөө"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Бул программа бөгөттөлгөн."</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Бул программанын рейтинги <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Канал булактары"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Жаңы каналдар бар"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Канл тизмсн өзгөчл"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Программа көрсөтмөңүз үчүн канал тандоо"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Орнотулган жок"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Киргизмеде авто-скан колдоого алынбайт"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\"<xliff:g id="TV_INPUT">%s</xliff:g>\" үчүн авто издөөнү баштай албайт"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Жабык субтитрлер үчүн жалпы версия жөндөөлөрүн иштетүү мүмкүн болбой жатат."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d канал кошулду"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d канал кошулду"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Каналдар кошулган жок"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Жөндөгүч"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Ата-энелик көзөмөл"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Күйүк"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Өчүк"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Каналдар бөгөттлдү"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Баарын бөгөттөө"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Баарн бөгөттн чыгруу"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Жашыруун каналдар"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Программа чектлрү"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN алмаштыруу"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Рейтинг тутумдары"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Баалоолор"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Бард рйтнг тутмд көр"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Башка өлкөлөр"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Эч бири"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Эч бири"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Эч бири"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Катаал чектөөлөр"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Орточо чектөөлөр"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Жумшак чектөөлөр"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Өзгөчөлөштүрүлгөн"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Балдарга ылайыктуу мазмун"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Чоңураак балдарга ылайыктуу мазмун"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Өспүрүмдөргө ылайыктуу мазмун"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Кол менен чектөөлөр"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s жн көмкч рейтнг"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Көмөкчү рейтинг"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Бул каналды көрүү үчүн PIN\'иңизди киргизиңиз"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Бул программаны көрүү үчүн PIN\'иңизди киргизиңиз"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"PIN\'иңизди киргизиңиз"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Ата-энелер көзөмөлүн коюу үчүн, PIN код түзүңүз"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Жаңы PIN киргизиңиз"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"PIN кодуңузду ырастаңыз"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Учурдагы PIN-иңизди киргизиңиз"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"PIN кодду 5 жолу туура эмес киргиздиңиз.\n <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> секунддан кийин кайра аракет кылып көрүңүз."</item>
+ <item quantity="other" msgid="8829550842387756054">"PIN кодду 5 жолу туура эмес киргиздиңиз.\n <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> секунддан кийин кайра аракет кылып көрүңүз."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Ал PIN туура эмес. Дагы бир жолу киргизиңиз."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"PIN дал келбей жатат, дагы бир жолу аракет кылыңыз"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Ушул жөнүндө"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Ачык программа уруксаттамалары"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Ачык программа уруксаттамалары"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Версиясы"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Бул каналды көрүү үчүн, Оңго басып, PIN-иңизди киргизиңиз"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Бул программаны көрүү үчүн, Оңго басып, PIN-иңизди киргизиңиз"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Бул программанын рейтинги <xliff:g id="RATING">%1$s</xliff:g> \n Бул программаны көрүү үчүн, Оң баскычын басып, PIN\'иңизди киргизиңиз"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Бул программа бөгөттөлгөн"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Бул программанын рейтинги – <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Аталышы жок"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Канал бөгөттөлдү"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Мезгил<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Эпизод <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Канал булактары"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Жеткиликтүү булактардан түз обо каналдарын жөндөңүз. Каналдын булагына жараша бир нече мүнөткө созулушу мүмкүн."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Бүттү"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d канал жеткиликтүү"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d канал жеткиликтүү"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Жеткиликтүү каналдар жок"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Жаңы"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"Сыналгынын менюсун ачуу үчүн "<b>"ТАНДОО"</b>" баскычын басыңыз."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Сыналгыга киргизме табылган жок"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Сыналгыга киргизме табылбай жатат"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP колдоого алынбайт"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP аркылуу көрсөтүлө турган эч нерсе киргизилген жок."</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Тюнердин түрү ылайыксыз. Тюнердин түрүндөгү сыналгы киргизмеси үчүн Жандуу каналдар колдонмосун ишке киргизиңиз."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Жөндөлбөй калды"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Бул ишти аткара турган бир дагы колдонмо табылган жок."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Бардык булак каналдары жашырылган.\nКөрүү үчүн кеминде бир канал тандаңыз."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Үзүл-кесил байланыштан улам видео жеткиликсиз болуп калды"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Видео күтүүсүздөн жеткиликсиз болуп калды."</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"АРТКА баскычы – туташкан түзмөккө. Чыгуу үчүн БАШКЫ баскычты басыңыз."</string>
+</resources>
diff --git a/res/values-ldrtl/dimens.xml b/res/values-ldrtl/dimens.xml
new file mode 100644
index 00000000..0b79b144
--- /dev/null
+++ b/res/values-ldrtl/dimens.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="menu_row_title_transform_pivot_x">@dimen/menu_row_title_width</dimen>
+
+ <!-- Keep belows as the same as negative value of LTR case -->
+ <dimen name="side_panel_enter_offset_x">-100dp</dimen>
+ <dimen name="side_panel_exit_offset_x">-100dp</dimen>
+ <dimen name="side_panel_fragment_enter_offset_x">-32dp</dimen>
+ <dimen name="side_panel_fragment_exit_offset_x">32dp</dimen>
+
+ <dimen name="program_guide_table_background_round_radius_left">0dp</dimen>
+ <dimen name="program_guide_table_background_round_radius_right">2dp</dimen>
+
+ <dimen name="program_guide_shift_start_to_end_x">-238dp</dimen>
+ <dimen name="program_guide_shift_end_to_start_x">238dp</dimen>
+ <dimen name="program_guide_side_panel_row_pivot_x">
+ @dimen/program_guide_side_panel_item_width</dimen>
+ <dimen name="program_guide_side_panel_exit_x">32dp</dimen>
+ <dimen name="program_guide_table_exit_x">-368dp</dimen>
+
+</resources>
diff --git a/res/values-ldrtl/integers.xml b/res/values-ldrtl/integers.xml
new file mode 100644
index 00000000..30bb2edd
--- /dev/null
+++ b/res/values-ldrtl/integers.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <integer name="fullscreen_dialog_enter_translation_x">-200</integer>
+ <integer name="fullscreen_dialog_exit_translation_x">200</integer>
+</resources>
diff --git a/res/values-lo-rLA/arrays.xml b/res/values-lo-rLA/arrays.xml
new file mode 100644
index 00000000..7845f241
--- /dev/null
+++ b/res/values-lo-rLA/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"​ປົກ​ກະ​ຕິ"</item>
+ <item msgid="2533030282864800794">"ເຕັມ"</item>
+ <item msgid="8568284598210500589">"ຊູມ"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"​ທຸກ​ຊ່ອງ"</item>
+ <item msgid="928298872841713530">"ຄອບ​ຄົວ/ເດັກ​ນ້ອຍ"</item>
+ <item msgid="2751606947569857164">"ກິລາ"</item>
+ <item msgid="7345749789651321496">"ຊັອບປິງ"</item>
+ <item msgid="167201149441442173">"ຮູບເງົາ"</item>
+ <item msgid="525966731464264290">"ຄອມເມດີ"</item>
+ <item msgid="6096710741527327836">"ການເດີນທາງ"</item>
+ <item msgid="2851882187117833883">"ດຣາມ່າ"</item>
+ <item msgid="78492781188719038">"ການສຶກສາ"</item>
+ <item msgid="7221999662426308394">"ສັດ"</item>
+ <item msgid="375300513250925001">"​ຂ່າວ"</item>
+ <item msgid="7746320336582330410">"ເກມ"</item>
+ <item msgid="1255741860568329178">"ສິ​ລະ​ປະ"</item>
+ <item msgid="7603949681065702867">"ບັນເທີງ"</item>
+ <item msgid="4453821994746804366">"ການດຳລົງຊີວິດ"</item>
+ <item msgid="3488534597567932843">"ເພງ"</item>
+ <item msgid="7452153120614274095">"ການ​ສະ​ແດງ"</item>
+ <item msgid="8215762047341133299">"ເຕັກ​ໂນ​ໂລ​ຊີ/ວິ​ທະ​ຍາ​ສາດ"</item>
+ </string-array>
+</resources>
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..36245d4c
--- /dev/null
+++ b/res/values-lo-rLA/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"ຢາ​"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"ເພດ"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"​ຄວາມ​ຮຸນ​ແຮງ"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"ຢາ​"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"ເພດ"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"​ຄວາມ​ຮຸນ​ແຮງ"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"ພາ​ສາຝຣັ່ງ"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"ການ​ສົນ​ທະ​ນາ​ຊັກ​ຊວນ"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"ພາ​ສາ​ຫຍາບຄາຍ"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"ເນື້ອ​ໃນກ່ຽວ​ກັບ​ເພດ"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"​ຄວາມ​ຮຸນ​ແຮງ"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"ຄວາມ​ຮຸນ​ແຮງ​ເພີ້​ຝັນ"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"ການ​ຈັດ​ອັນ​ດັບຮູບ​ເງົາ"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
new file mode 100644
index 00000000..57b99939
--- /dev/null
+++ b/res/values-lo-rLA/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"ຊ່ອງ​ສົດ"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"​ໂມ​ໂນ"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"ສະ​ເຕ​ຣິ​ໂອ"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"ຄວບ​ຄຸມ​ການ​ຫຼິ້ນ"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"​ຊ່ອງ"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"ຊ່ອງ​ບໍ່ດົນ​ມານີ້"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"ໂຕເລືອກ​ໂທລະພາບ"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"​ຕົວ​ເລືອກ PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"ຫຼິ້ນ​ການ​ຄວບ​ຄຸມ​ບໍ່​ມີ​ໃຫ້​ສຳ​ລັບ​ຊ່ອງ​ນີ້"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"ຫຼິ້ນ​ ຫລື​ຢຸດ​ຊົ່ວ​ຄາວ"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"ເລື່ອນ​ໄປ​ໜ້າ"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"​ຣີ​ວາຍກັບ"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"ຕໍ່​ໄປ"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"ກ່ອນໜ້າ"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"ຄຳ​ແນະນຳ​ລາຍການ"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"ມີ​ຊ່ອງ​ໃໝ່​ໃຫ້​ຢູ່"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"ບໍ່​ມີ​ລິ້ງ​ຢູ່"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"ເປີດ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"ຄຳ​ບັນຍາຍ​ແບບ​ປິດ"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"ໂໝດ​ການ​ສະແດງຜົນ"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"​ເປີດ"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"ປິດ"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"ຫຼາຍສຽງ"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"ຕັ້ງ​ຊ່ອງ"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"ການ​ຄວບຄຸມ​ຂອງ​ຜູ່​ປົກ​ຄອງ"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"ກ່ຽວກັບ"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"ທີ່​ມາ"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"ສະຫຼັບ"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"​ເປີດ"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"ປິດ"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"ສຽງ"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"ຫຼັກ"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"ໜ້າ​ຕ່າງ PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"ຮູບ​ແບບ"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"ເບື້ອງ​ຂວາ​ທາງ​ລຸ່ມ"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"ເບື້ອງ​ຂວາ​ດ້ານ​ເທິງ"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"ເບື້ອງ​ຊ້າຍ​ດ້ານ​ເທິງ"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"ເບື້ອງ​ຊ້າຍທາງ​ລຸ່ມ"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"ເທື່ອ​ລະ​ດ້ານ"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"ຂະໜາດ"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"ໃຫຍ່"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"ນ້ອຍ"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"​ແຫຼ່ງ​ສັນ​ຍານ"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (ເສົາ​ສັນ​ຍານ/ສາຍ​ເຄ​ເບິນ)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"ບໍ່​ມີ​ຂໍ້​ມູນ​ລາຍ​ການ"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"ບໍ່ມີຂໍ້ມູນ"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"ຊ່ອງ​ທີ່​ຖືກບ​ລັອກ"</string>
+ <string name="default_language" msgid="4122326459624337928">"ພາ​ສາ​ບໍ່​ຮູ້​ຈັກ"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"ຄຳ​ບັນຍາຍ​ແບບ​ປິດ"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"ປິດ"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"ປັບແຕ່ງການຈັດຮູບແບບ"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"ກຳນົດ​ການຕັ້ງຄ່າ​ລະບົບ​ສຳລັບ​ຄຳ​ບັນຍາຍ​ແບບ​ປິດ"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"ໂໝດ​ສະແດງ​ຜົນ"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"ຫຼາຍ​ສຽງ"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"​ໂມ​ໂນ"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"ສະເຕຣິໂອ"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"ສຽງ​ຮອບ​ທິດ​ທາງ 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"ສຽງ​ຮອບ​ທິດ​ທາງ 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d ​ຊ່ອງ"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"ປັບແຕ່ງ​ລາຍ​ຊື່​ຊ່ອງ"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"ເລືອກກຸ່ມ"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"ບໍ່​ເລືອກ​ກຸ່ມ"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"ຈັດກຸ່ມຕາມ"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"ທີ່​ມາ​ຂອງ​ຊ່ອງ"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"ຈັດກຸ່ມຕາມ"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"ຕັ້ງ​ຊ່ອງ"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"ໂປ​ຣ​ແກ​ຣມ​ນີ້​ຖືກບ​ລັອກ​ແລ້ວ"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"​ລາຍ​ການ​ນີ້​ຖືກ​ຈັດ​ປະ​ເພດ <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"ທີ່ມາ​ຂອງ​ຊ່ອງ"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"ມີ​ຊ່ອງ​ໃໝ່​ໃຫ້​ຢູ່"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"ປັບແຕ່ງລາຍ​ຊື່​ຊ່ອງ"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"ເລືອກ​ຊ່ອງ​ສຳລັບ​ແນະນຳ​ລາຍການ​ຂອງ​ທ່ານ"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"ບໍ່ໄດ້​ຕິດ​ຕັ້ງ"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"ອິນ​ພຸດ​ນີ້ບໍ່​ຮອງ​ຮັບ​ການ​ສະ​ແກນ​ອັດ​ຕະ​ໂນ​ມັດ"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"ບໍ່​ສາ​ມາດ​ເລີ່ມ​ການ​ສະ​ແກນ​ອັດ​ຕະ​ໂນ​ມັດ​ສຳ​ລັບ \'<xliff:g id="TV_INPUT">%s</xliff:g>\' ໄດ້"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"ບໍ່​ສາ​ມາດ​ເລີ່ມ​ການ​ຕັ້ງ​ຄ່າ​ທົ່ວ​ລະ​ບົບ​ສຳ​ລັບ​ຄຳ​ບັນ​ຍາຍ​ແບບ​ປິດ​ໄດ້."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"ເພີ່ມ​ %1$d ​ຊ່ອງແລ້ວ"</item>
+ <item quantity="other" msgid="1078861616751739285">"​ເພີ່ມ %1$d ຊ່ອງ​ແລ້ວ"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"ບໍ່​ໄດ້​ເພີ່ມ​ຊ່ອງ"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"​ທູນ​ເນີ"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"ການ​ຄວບຄຸມ​ຂອງ​ຜູ່​ປົກ​ຄອງ"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"ເປີດ"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"ປິດ"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"ຊ່ອງ​ຖືກ​ບລັອກ​ແລ້ວ"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"​ບລັອກ​​ທັງ​​ໝົດ"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"ປົດ​ບລັອກ​ທັງ​ໝົດ"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"​ຊ່ອງ​ທີ່​ຖືກ​ເຊື່ອງ"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"ການ​ຈຳກັດ​ລາຍການ"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"​ປ່ຽນ​ PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"​ລະ​ບົບ​ການ​ຈັດ​ປະ​ເພດ"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"​​​ການ​ຈັດ​ປະ​ເພດ"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"ເບິ່ງ​ລະບົບ​ການ​ຈັດ​ປະເພດ​ທັງ​ໝົດ"</string>
+ <string name="other_countries" msgid="8342216398676184749">"ປະ​ເທດ​ອື່ນໆ"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"ບໍ່ມີ"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"ບໍ່ມີ"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"ບໍ່ມີ"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"ຈຳກັດສູງ"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"ຈຳກັດປານກາງ"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"ຈຳກັດຕ່ຳ"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"ກຳນົດເອງ"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"ເນື້ອຫາ​ເໝາະສົມ​ສຳລັບ​ເດັກນ້ອຍ"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"ເນື້ອຫາ​ເໝາະ​ສຳລັບ​ເດັກ​ທີ່​ບໍ່​ນ້ອຍ​ຫຼາຍ"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"​ເນື້ອ​ຫາ​ເໝາະ​ສົມ​ສຳ​ລັບ​ໄວ​ຮຸ່ນ"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"ຈຳ​ກັດແບບ​​ລະ​ບຸ​ເອງ"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s ແລະປະເພດຍ່ອຍ"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"ປະເພດຍ່ອຍ"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"ປ້ອນ​ລະ​ຫັດ​ PIN ຂອງ​ທ່ານ​ເພື່ອ​ເບິ່ງ​ຊ່ອງ​ນີ້"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"​ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ​ເພື່ອ​ເບິ່ງ​ລາຍ​ການ​ນີ້"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"​ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"ເພື່ອ​ຕັ້ງ​ຄ່າ​ຄວບ​ຄຸມ​ຂອງ​ຜູ່​ປົກ​ຄອງ, ກະລຸ​ນາຕັ້ງ​ລະ​ຫັດ PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"ໃສ່ລະຫັດ PIN ໂຕໃຫມ່"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"ຢືນຢັນ​ລະຫັດ PIN ຂອງທ່ານ"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"ປ້ອນ​ລະ​ຫັດ PIN ປັດ​ຈຸ​ບັນ​ຂອງ​ທ່ານ"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"ທ່ານ​ໄດ້​ປ້ອນ​ລະ​ຫັດ PIN ຜິດ 5 ຄັ້ງ.\nລອງ​ໃໝ່​ອີກ​ໃນ <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> ວິ​ນາ​ທີ."</item>
+ <item quantity="other" msgid="8829550842387756054">"ທ່ານ​ໄດ້​ປ້ອນ​ລະ​ຫັດ PIN ຜິດ 5 ຄັ້ງ.\nລອງ​ໃໝ່​ອີກ​ໃນ <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> ວິ​ນາ​ທີ."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"ລະຫັດ PIN ນັ້ນບໍ່​ຖືກ​ຕ້ອງ, ກະ​ລຸ​ນາລອງ​ໃຫມ່​ອີກ​ຄັ້ງ."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"ລະ​ຫັດ PIN ບໍ່​ກົງ​ກັນ, ກະ​ລຸ​ນາ​ລອງ​ໃໝ່."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"ກ່ຽວກັບ"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"​ໃບ​ອະ​ນຸ​ຍາດ​ແຫຼ່ງ​ເປີດ"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"​ໃບ​ອະ​ນຸ​ຍາດ​ແຫຼ່ງ​ເປີດ"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"ເວີຊັນ"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"​ເພື່ອ​ເບິ່ງ​ຊ່ອງ​ນີ້, ກົດ​ປຸ່ມ ຂວາ ແລະ ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"​ເພື່ອ​ເບິ່ງ​ລາຍ​ການ​ນີ້, ໃຫ້ກົດ​ປຸ່ມ ຂວາ ແລະ ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"​ລາຍ​ການ​ນີ້​ຖືກ​ຈັດ​ປະ​ເພດ​ <xliff:g id="RATING">%1$s</xliff:g>.\n​ເພື່ອ​ເບິ່ງ​ລາຍ​ການ​ນີ້, ໃຫ້ກົດ​ປຸ່ມ ຂວາ ແລະ ປ້ອນ​ລະ​ຫັດ PIN ຂອງ​ທ່ານ"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"​ລາຍ​ການ​ຖືກບລັອກ"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"​ລາຍ​ການ​ນີ້​ຖືກ​ຈັດ​ປະ​ເພດ <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"ບໍ່ມີຫົວຂໍ້"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"​ຊ່ອງ​ຖືກບລັອກ"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: ຕອນ <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"ທີ່ມາ​ຂອງ​ຊ່ອງ"</string>
+ <string name="setup_description" msgid="8728423605912915099">"ຕັ້ງ​ຊ່ອງ​ສົດ​ຈາກ​ແຫຼ່ງ​ທີ່​ມີ​ຢູ່. ອັນ​ນີ້​ອາດ​ຈະໃຊ້​ເວ​ລາ​ຫຼາຍ​ນາ​ທີ ຂຶ້ນ​ກັບ​ແຫຼ່ງ​ທີ່​ມາ​ຂອງ​ຊ່ອງ."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"ແລ້ວໆ"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d ຊ່ອງ​ພ້ອມ​ໃຊ້​ງານ​ໄດ້"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d ຊ່ອງ​ພ້ອມ​ໃຊ້​ງານ​ໄດ້"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"ບໍ່​ມີ​ຊ່ອງ​ພ້ອມ​ໃຊ້​ງານ​ໄດ້​ຢູ່"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"ໃໝ່"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"ກົດ SELECT"</b>" ເພື່ອ​ເຂົ້າ​ຫາ​ເມ​ນູ TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"ບໍ່​ພົບການ​ປ້ອນ​ເຂົ້າ TV"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"ບໍ່​ສາ​ມາດ​ຊອກ​ຫາການ​ປ້ອນ​ເຂົ້າ TV ​ໄດ້"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"ບໍ່​ຮອງ​ຮັບ PIP"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"ບໍ່ມີການຮັບສັນຍານທີ່ສາມາດສະແດງໄດ້ດ້ວຍຮູບພາບຊ້ອນຮູບພາບ PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"ປະ​ເພດ​ເຄື່ອງ​ຈູນ​ບໍ່​ເໝາະ​ສົມ; ກະ​ລຸ​ນາ​ເປີດ​ໃຊ້​ແອັບ​ຊ່ອງ​ສົດ​ສຳ​ລັບ​ການ​ປ້ອນ​ເຂົ້າ TV ປະ​ເພດ​ເຄື່ອງ​ຈູນ."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"ການ​ປັບ​ຊ່ອງ​ລົ້ມ​ເຫລວ"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"ບໍ່ພົບແອັບຯທີ່ໃຊ້ເພື່ອດຳເນີນການ."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"ຊ່ອງ​ແຫລ່ງ​ຂໍ້​ມູນ​ທັງ​ໝົດ​ຖືກ​ເຊື່ອງ​ໄວ້​ແລ້ວ.\nກະ​ລຸ​ນາ​ເລືອກ​ຢ່າງ​ໜ້ອຍ​ນຶ່ງ​ຊ່ອງ​ເພື່ອ​ເບິ່ງ."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"ບໍ່​ສາ​ມາດ​ໃຊ້​ໄດ້​ເນື່ອງ​ຈາກ​ສັນ​ຍານ​ວິ​ດີ​ໂອ​ອ່ອນ"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"ວິ​ດີ​ໂອບໍ່​ສາ​ມາດ​ເບິ່ງ​ໄດ້​ໂດຍບໍ່​ຄາດ​ຄິດ"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"ປຸ່ມ Back ແມ່ນ​ສຳ​ລັບ​ອຸ​ປະ​ກອນ​ທີ່​ເຊື່ອມ​ຕໍ່​ແລ້ວ. ໃຫ້​ກົດ​ປຸ່ມ Home ເພື່ອອອກ."</string>
+</resources>
diff --git a/res/values-lt/arrays.xml b/res/values-lt/arrays.xml
new file mode 100644
index 00000000..877c6d6c
--- /dev/null
+++ b/res/values-lt/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Įprastas"</item>
+ <item msgid="2533030282864800794">"Visas"</item>
+ <item msgid="8568284598210500589">"Mastelio keitimas"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Visi kanalai"</item>
+ <item msgid="928298872841713530">"Šeima / vaikai"</item>
+ <item msgid="2751606947569857164">"Sportas"</item>
+ <item msgid="7345749789651321496">"Apsipirkimas"</item>
+ <item msgid="167201149441442173">"Filmai"</item>
+ <item msgid="525966731464264290">"Komedijos"</item>
+ <item msgid="6096710741527327836">"Kelionės"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Švietimas"</item>
+ <item msgid="7221999662426308394">"Gyvūn. / lauk. gamta"</item>
+ <item msgid="375300513250925001">"Naujienos"</item>
+ <item msgid="7746320336582330410">"Žaidimai"</item>
+ <item msgid="1255741860568329178">"Menai"</item>
+ <item msgid="7603949681065702867">"Pramogos"</item>
+ <item msgid="4453821994746804366">"Gyvenimo būdas"</item>
+ <item msgid="3488534597567932843">"Muzika"</item>
+ <item msgid="7452153120614274095">"Premjera"</item>
+ <item msgid="8215762047341133299">"Technol. / mokslas"</item>
+ </string-array>
+</resources>
diff --git a/res/values-lt/rating_system_strings.xml b/res/values-lt/rating_system_strings.xml
new file mode 100644
index 00000000..a9e37c7f
--- /dev/null
+++ b/res/values-lt/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Narkotikai"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Seksas"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Smurtas"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Narkotikai"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Seksas"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Smurtas"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Prancūzų k."</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Dviprasmiškas dialogas"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Nemandagi kalba"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Seksualinis turinys"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Smurtas"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Maginės fantastikos smurtas"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Filmų įvertinimai"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
new file mode 100644
index 00000000..7d03e8bc
--- /dev/null
+++ b/res/values-lt/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Tiesioginiai kanalai"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"monofon."</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereof."</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Leidimo valdikliai"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanalai"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Naujausi kanalai"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV parinktys"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP parinktys"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Leidimo valdikliai negalimi šiame kanale"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Leisti arba pristabdyti"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Sukti pirmyn"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Sukti atgal"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Kitas"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Ankstesnis"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programų vadovas"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Galimi nauji kanalai"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Nėra jokių nuorodų"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Atidaryti „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Subtitrai"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Rodymo režimas"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Įjungta"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Išjungta"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Keli garso įr."</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Kanalų sąranka"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Tėvų valdikliai"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Apie"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Šaltinis"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Sukeisti"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Įjungta"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Išjungta"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Garsas"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Pagrindinis"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP langas"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Išdėstymas"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Apač. dešinėje"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Virš. dešinėje"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Viršuje kairėje"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Apač. kairėje"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Šalia"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Dydis"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Didelis"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Mažas"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Įvesties šaltinis"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antena / kabelis)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Nėra informacijos apie programą"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Informacijos nėra."</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Užblokuotas kanalas"</string>
+ <string name="default_language" msgid="4122326459624337928">"Nežinoma kalba"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Subtitrai"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Išjungti"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Tinkinti formatavimą"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Nustatykite visos sist. subtitrų nuost."</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Rodymo režimas"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Keli garso įrašai"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"monofoninis garsas"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereofoninis garsas"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 erdvinis garsas"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 erdvinis garsas"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d kanalai"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Tinkinti kan. sąr."</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Pasirinkti grupę"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Pan. grupės pasir."</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Grupuoti pagal"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Kanalo šaltinis"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD / SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Grupuoti pagal"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Kanalų sąranka"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Ši programa yra užblokuota"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Ši programa įvertinta kaip <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Kanalų šaltiniai"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Galimi nauji kanalai"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Tinkinti kan. sąr."</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Pasirinkite programų vadovo kanalus"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Nenustatyta"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Įvestis nepalaiko automatinio nuskaitymo"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Nepavyko paleisti „<xliff:g id="TV_INPUT">%s</xliff:g>“ automatinio nuskaitymo"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Nepavyksta paleisti visos sistemos subtitrų nuostatų."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"Pridėta kanalų: %1$d"</item>
+ <item quantity="other" msgid="1078861616751739285">"Pridėta kanalų: %1$d"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Nėra pridėtų kanalų"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Radijo imtuvas"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Tėvų valdikliai"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Įjungti"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Išjungti"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Kanalai užblokuoti"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Blokuoti visus"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Atblokuoti visus"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Paslėpti kanalai"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Progr. apribojimai"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Keisti PIN kodą"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Įvertin. sistemos"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Įvertinimai"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Žr. visas įvert. s."</string>
+ <string name="other_countries" msgid="8342216398676184749">"Kitos šalys"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Nėra"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Nėra"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Nėra"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Dideli apribojimai"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Vid. apribojimai"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Maži apribojimai"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Tinkinta"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Vaikams tinkamas turinys"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Vyresniems vaikams tinkamas turinys"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Paaugliams tinkamas turinys"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Neautomatiniai apribojimai"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s ir antr. įvert."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Antriniai įvert."</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Įveskite PIN kodą, kad galėt. žiūrėti šį kanalą"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Įveskite PIN kodą, kad galėt. žiūrėti šią programą"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Įveskite PIN kodą"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Jei norite nustatyti tėvų valdiklius, sukurkite PIN kodą"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Įveskite naują PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Patvirtinkite PIN kodą"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Įveskite dabartinį PIN kodą"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"5 kartus įvedėte ne tą PIN kodą.\nBandykite dar kartą po <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sek."</item>
+ <item quantity="other" msgid="8829550842387756054">"5 kartus įvedėte ne tą PIN kodą.\nBandykite dar kartą po <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sek."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Tas PIN kodas buvo netinkamas. Bandykite dar kartą."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Bandykite dar kartą, PIN kodas neatitinka"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Apie"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Atvirojo šaltinio licencijos"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Atvirojo šaltinio licencijos"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versija"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Jei norite žiūrėti šį kanalą, paspauskite „Tinkamas“ ir įveskite PIN kodą"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Jei norite žiūrėti šią programą, paspauskite „Tinkama“ ir įveskite PIN kodą"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Ši programa įvertinta kaip <xliff:g id="RATING">%1$s</xliff:g>.\nJei norite žiūrėti šią programą, paspauskite „Tinkama“ ir įveskite PIN kodą"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Programa užblokuota"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Ši programa įvertinta kaip <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Nėra pavadinimo"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanalas užblokuotas"</string>
+ <string name="episode_format" msgid="4881195874563241096">"<xliff:g id="SEASON_NUMBER">%1$d</xliff:g> sezonas: <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> serija „<xliff:g id="EPISODE_TITLE">%3$s</xliff:g>“"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Kanalų šaltiniai"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Nustatykite tiesioginius kanalus iš pasiekiamų šaltinių. Tai gali užtrukti kelias minutes, atsižvelgiant į kanalo šaltinį."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Atlikta"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Pasiekiama kanalų: %1$d"</item>
+ <item quantity="other" msgid="2386588423841837714">"Pasiekiama kanalų: %1$d"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Nėra pasiekiamų kanalų"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Naujos"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Paspauskite PASIRINKTI,"</b>" kad pasiektumėte TV meniu."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Nerasta jokių TV įvesčių"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Nepavyksta rasti TV įvesties"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP nepalaikoma"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Nėra galimos įvesties, kurią galima rodyti kartu su PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Netinkamas derintuvo tipas. Paleiskite derintuvo tipo TV įvestį, skirtą programai „Live Channels“."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Derinimas nepavyko"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Nerasta jokių programų šiam veiksmui apdoroti."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Visi šaltinio kanalai yra paslėpti.\nPasirinkite bent vieną norimą žiūrėti kanalą."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Negalimas dėl silpno vaizdo įrašo signalo"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Vaizdo įrašas netikėtai negalimas"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"GRĮŽIMO klavišas skirtas prijungtam įrenginiui. Paspauskite PAGRINDINIO PUSLAPIO mygtuką, kad išeitumėte."</string>
+</resources>
diff --git a/res/values-lv/arrays.xml b/res/values-lv/arrays.xml
new file mode 100644
index 00000000..56969edd
--- /dev/null
+++ b/res/values-lv/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Parasts"</item>
+ <item msgid="2533030282864800794">"Pilns"</item>
+ <item msgid="8568284598210500589">"Tālummaiņa"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Visi kanāli"</item>
+ <item msgid="928298872841713530">"Ģimenei/bērniem"</item>
+ <item msgid="2751606947569857164">"Sports"</item>
+ <item msgid="7345749789651321496">"Iepirkšanās"</item>
+ <item msgid="167201149441442173">"Filmas"</item>
+ <item msgid="525966731464264290">"Komēdijas"</item>
+ <item msgid="6096710741527327836">"Ceļojumi"</item>
+ <item msgid="2851882187117833883">"Drāmas"</item>
+ <item msgid="78492781188719038">"Izglītība"</item>
+ <item msgid="7221999662426308394">"Dzīvnieki/daba"</item>
+ <item msgid="375300513250925001">"Ziņas"</item>
+ <item msgid="7746320336582330410">"Spēles"</item>
+ <item msgid="1255741860568329178">"Māksla"</item>
+ <item msgid="7603949681065702867">"Izklaide"</item>
+ <item msgid="4453821994746804366">"Dzīvesveids"</item>
+ <item msgid="3488534597567932843">"Mūzika"</item>
+ <item msgid="7452153120614274095">"Svarīgākais"</item>
+ <item msgid="8215762047341133299">"Tehnoloģijas/zinātne"</item>
+ </string-array>
+</resources>
diff --git a/res/values-lv/rating_system_strings.xml b/res/values-lv/rating_system_strings.xml
new file mode 100644
index 00000000..3bd6a1ff
--- /dev/null
+++ b/res/values-lv/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Narkotiskās vielas"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sekss"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Vardarbība"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Narkotiskās vielas"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sekss"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Vardarbība"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"franču"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Divdomīgs dialogs"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Rupja valoda"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Seksuāla rakstura saturs"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Vardarbība"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantāzijas veida vardarbība"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Filmu vērtējumi"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
new file mode 100644
index 00000000..67ff2866
--- /dev/null
+++ b/res/values-lv/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Tiešraides kanāli"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Atskaņošanas vadīklas"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanāli"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Nesenie kanāli"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV iespējas"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP opcijas"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Šim kanālam nav pieejamas atskaņošanas vadīklas."</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Atskaņot vai pauzēt"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Pārtīt uz priekšu"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Attīt atpakaļ"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Tālāk"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Atpakaļ"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programmu ceļvedis"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Pieejami jauni kanāli"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Saite nav pieejama."</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Atvērt lietotni <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Slēgtie paraksti"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Attēla režīms"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Ieslēgts"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Izslēgts"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multiaudio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Kanāla izveide"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Vecāku kontrole"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Par"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Avots"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Mainīt"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Ieslēgts"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Izslēgts"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Skaņa"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Galvenais"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP logs"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Izkārtojums"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Apakšā pa labi"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Augšā pa labi"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Augšā pa kreisi"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Apakšā pa kreisi"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Līdzās"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Izmēri"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Liels"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Mazs"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Ievades avots"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antena/kabelis)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Nav informācijas par programmu"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Nav informācijas"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Bloķēts kanāls"</string>
+ <string name="default_language" msgid="4122326459624337928">"Nezināma valoda"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Slēgtie paraksti"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Izslēgti"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Pielāgot formatēšanu"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Slēgto parakstu sistēmas preferences"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Attēla režīms"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multiaudio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 ieskaujošā skaņa"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 ieskaujošā skaņa"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d kanāli"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Pielāgot kanālu sar."</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Atlasīt grupu"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Noņemt atlasi grupai"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Grupēt pēc:"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Kanāla avots"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Grupēt pēc:"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Kanāla izveide"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Šī programma ir bloķēta."</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Šī programma ir novērtēta kā “<xliff:g id="RATING">%1$s</xliff:g>”."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Kanālu avoti"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Pieejami jauni kanāli"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Pielāgot kanālu sar."</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Izvēlieties kanālus programmu ceļvedim."</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Nav iestatīts"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Ieeja neatbalsta automātisko meklēšanu."</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Nevar sākt automātisko skenēšanu ieejā “<xliff:g id="TV_INPUT">%s</xliff:g>”."</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Nevar palaist slēgto parakstu sistēmas preferences."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"Tika pievienots %1$d kanāls."</item>
+ <item quantity="other" msgid="1078861616751739285">"Tika pievienoti %1$d kanāli."</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Kanāli netika pievienoti."</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Kanālu meklētājs"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Vecāku kontrole"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Ieslēgts"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Izslēgts"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Bloķētie kanāli"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Bloķēt visus"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Atbloķēt visus"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Paslēptie kanāli"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Ierobežojumi"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Mainīt PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Vērtēšanas sistēmas"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Klasifikācija"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Visas sistēmas"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Citas valstis"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Nav"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Nav"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Nav"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Stingri ierobežojumi"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Vidēji ierobežojumi"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Maz ierobežojumu"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Pielāgota"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Bērniem piemērots saturs"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Saturs piemērots vecākiem bērniem"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Pusaudžiem piemērots saturs"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Manuālie ierobežojumi"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s, detalizēti"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Detalizēti vērtējumi"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"PIN ievade, lai skatītos šo kanālu"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"PIN ievade, lai skatītos šo programmu"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"PIN ievade"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Lai iestatītu vecāku kontroli, izveidojiet PIN kodu."</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Ievadiet jauno PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Apstipriniet PIN kodu"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Ievadiet pašreizējo PIN kodu"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Jūs 5 reizes ievadījāt nepareizu PIN.\nMēģiniet vēlreiz pēc <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekundes."</item>
+ <item quantity="other" msgid="8829550842387756054">"Jūs 5 reizes ievadījāt nepareizu PIN.\nMēģiniet vēlreiz pēc <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekundēm."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"PIN kods nav pareizs. Mēģiniet vēlreiz."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Neatbilstošs PIN. Mēģiniet vēlreiz."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Par"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Atklātā pirmkoda licences"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Atklātā pirmkoda licences"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versija"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Lai skatītos šo kanālu, nospiediet pa labi vērsto bultiņu un ievadiet PIN kodu."</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Lai skatītos šo programmu, nospiediet pa labi vērsto bultiņu un ievadiet PIN kodu."</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Šī programma ir novērtēta kā “<xliff:g id="RATING">%1$s</xliff:g>”.\nLai skatītos šo programmu, nospiediet pa labi vērsto bultiņu un ievadiet PIN kodu."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Programma ir bloķēta."</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Šī programma ir novērtēta kā “<xliff:g id="RATING">%1$s</xliff:g>”."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Nav nosaukuma"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanāls bloķēts"</string>
+ <string name="episode_format" msgid="4881195874563241096">"<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>. sezona, <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>. sērija “<xliff:g id="EPISODE_TITLE">%3$s</xliff:g>”"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Kanālu avoti"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Izveidojiet tiešraides kanālus no pieejamajiem avotiem. Atkarībā no kanāla avota tas var ilgt vairākas minūtes."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Gatavs"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Pieejams %1$d kanāls"</item>
+ <item quantity="other" msgid="2386588423841837714">"Pieejami %1$d kanāli"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Nav pieejams neviens kanāls"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Jauna"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Nospiediet ATLASĪT"</b>", lai piekļūtu TV izvēlnei."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Nav atrasta neviena TV ieeja."</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Nevar atrast TV ieeju."</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Funkcija PIP netiek atbalstīta."</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Nav ievades, ko parādīt, izmantojot PIP."</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"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."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Kanālu meklēšana neizdevās."</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Netika atrasta neviena lietotne šīs darbības veikšanai."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Visi avota kanāli ir paslēpti.\nAtlasiet vismaz vienu kanālu, ko skatīties."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Nav pieejams vāja video signāla dēļ."</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Videoklips negaidīti nav pieejams."</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Poga ATPAKAĻ ir paredzēta pievienotajai ierīcei. Lai izietu, nospiediet pogu SĀKUMS."</string>
+</resources>
diff --git a/res/values-mk-rMK/arrays.xml b/res/values-mk-rMK/arrays.xml
new file mode 100644
index 00000000..3a405966
--- /dev/null
+++ b/res/values-mk-rMK/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Нормален"</item>
+ <item msgid="2533030282864800794">"Цел"</item>
+ <item msgid="8568284598210500589">"Зум"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Сите канали"</item>
+ <item msgid="928298872841713530">"Семејство/деца"</item>
+ <item msgid="2751606947569857164">"Спорт"</item>
+ <item msgid="7345749789651321496">"Пазарење"</item>
+ <item msgid="167201149441442173">"Филмови"</item>
+ <item msgid="525966731464264290">"Комедија"</item>
+ <item msgid="6096710741527327836">"Патување"</item>
+ <item msgid="2851882187117833883">"Драма"</item>
+ <item msgid="78492781188719038">"Образование"</item>
+ <item msgid="7221999662426308394">"Животни/див свет"</item>
+ <item msgid="375300513250925001">"Вести"</item>
+ <item msgid="7746320336582330410">"Игри"</item>
+ <item msgid="1255741860568329178">"Уметности"</item>
+ <item msgid="7603949681065702867">"Забава"</item>
+ <item msgid="4453821994746804366">"Стил на живеење"</item>
+ <item msgid="3488534597567932843">"Музика"</item>
+ <item msgid="7452153120614274095">"Главно"</item>
+ <item msgid="8215762047341133299">"Техника/наука"</item>
+ </string-array>
+</resources>
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..4194ec13
--- /dev/null
+++ b/res/values-mk-rMK/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Дрога"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Секс"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Насилство"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Дрога"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Секс"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Насилство"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"француски"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Сугестивен дијалог"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Непристоен говор"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Сексуална содржина"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Насилство"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Митолошко насилство"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Оценки за филм"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
new file mode 100644
index 00000000..74f7cdec
--- /dev/null
+++ b/res/values-mk-rMK/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"моно"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"стерео"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Контроли за игри"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Канали"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Неодамнешни канали"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"ТВ опции"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Опции за ПИП"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Контролите за репродуцирање се недостапни за овој канал"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Пуштање или паузирање"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Брзо премотување напред"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Премотување наназад"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Следно"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Претходно"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Програмски водич"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Достапни се нови канали"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Не е достапна врска"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Отвори <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Затворени титли"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Реж. на прикаж."</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"ПИП"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Вклучено"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Исклучено"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Мултиаудио"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Пост. канали"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Родит. контрола"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"За"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Извор"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Замени"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Вклучено"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Исклучено"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Звук"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Главен"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"ПИП прозорец"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Распоред"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Долу десно"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Горе десно"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Горе лево"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Долу лево"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Едно до друго"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Големина"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Голема"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Мала"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Влезен извор"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"ТВ (антена/кабел)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Нема информации за програмата"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Нема информации"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Блокиран канал"</string>
+ <string name="default_language" msgid="4122326459624337928">"Непознат јазик"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Затворени титли"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Исклучено"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Прилаг. форматирање"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Пост. парам. за затв, титли во системот"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Реж. на прикаж."</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Мултиаудио"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"моно"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"стерео"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 опкружувачки"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 опкружувачки"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d канали"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Присп.листа канали"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Избери група"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Деселектирај група"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Групирај по"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Извор на канал"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Групирај по"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Поставување канали"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Оваа програма е блокиранa"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Оваа програма е оценета <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Извори на канал"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Достапни се нови канали"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Присп.листа канали"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Изберете канали за програмскиот водич"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Не е поставен"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Влезот не поддржува автоматско скенирање"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Не може да се вклучи автоскенирање на „<xliff:g id="TV_INPUT">%s</xliff:g>“"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Не можеше да ги активира претпочитањата на целиот систем за објаснувања."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d канал е додаден"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d канали се додадени"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Не се додадени канали"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Приемник"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Родител. контрола"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Вклучено"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Исклучено"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Блокирани канали"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Блокирај ги сите"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Одблокирај ги сите"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Сокриени канали"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Прог. ограничувања"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Измени ПИН"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Систем за процена"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Проценки"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Види ги сите сист."</string>
+ <string name="other_countries" msgid="8342216398676184749">"Останати земји"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Ништо"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Ништо"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Ништо"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Големи ограничување"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Средно ограничување"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Ниско ограничување"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Приспособено"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Содржина погодна за деца"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Содржина погодна за постари деца"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Содржина погодна за млади"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Рачни ограничувања"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s и под-рејтинг"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Под-рејтинг"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Внесете го вашиот ПИН за да го гледате каналот"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Внесете го вашиот ПИН за да ја гледате програмата"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Внесете го вашиот ПИН"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"За да поставите родителска контрола, креирајте ПИН."</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Внесете го новиот ПИН"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Потврдете го вашиот ПИН"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Внесете го тековниот ПИН"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Внесовте погрешен ПИН пет пати.\nОбидете се повторно за <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> секунда."</item>
+ <item quantity="other" msgid="8829550842387756054">"Внесовте погрешен ПИН пет пати.\nОбидете се повторно за <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> секунди."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"ПИН-кодот е погрешен. Обидете се повторно."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Обидете се повторно, ПИН-кодот не се совпаѓа"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"За"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Лиценци за отворен код"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Лиценци за отворен код"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Верзија"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"За да го гледате овој канал, притиснете Во ред и внесете го вашиот ПИН"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"За да ја гледате оваа програма, притиснете Во ред и внесете го вашиот ПИН"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Оваа програма е оценета <xliff:g id="RATING">%1$s</xliff:g>.\nЗа да ја гледате оваа програма, притиснете Во ред и внесете го ПИН-от."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Програмата е блокирана"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Оваа програма е оценета <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Без наслов"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Каналот е блокиран"</string>
+ <string name="episode_format" msgid="4881195874563241096">"С<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Еп. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Извори на канал"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Поставете канали во живо од достапните извори. Ова може да потрае неколку минути, во зависност од изворот на каналот."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Готово"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d канал е достапен"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d канали се достапни"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Нема достапни канали"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Нов"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Притиснете ИЗБЕРИ"</b>" за да пристапите до ТВ-менито."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Не е пронајден ТВ-влез"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"ТВ-влезот не може да се најде"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"ПИП не е поддржано"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Нема достапен влез што може да се прикаже со ПИП"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Типот приемник не е соодветен. Стартувајте ја апликацијата Live Channels за ТВ-влез од типот приемник."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Бирањето не успеа"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Не е пронајдена апликација што ќе се справи со ова дејство."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Сите изворни канали се сокриени.\nИзберете барем еден канал за да го гледате."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Недостапно поради слаб видеосигнал"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Видеото е неочекувано недостапно"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Копчето НАЗАД е за поврзаниот уред. Притиснете на копчето ПОЧЕТНА СТРАНИЦА за да излезете."</string>
+</resources>
diff --git a/res/values-ml-rIN/arrays.xml b/res/values-ml-rIN/arrays.xml
new file mode 100644
index 00000000..1bf34cee
--- /dev/null
+++ b/res/values-ml-rIN/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"സാധാരണം"</item>
+ <item msgid="2533030282864800794">"നിറഞ്ഞു"</item>
+ <item msgid="8568284598210500589">"സൂം ചെയ്യുക"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"എല്ലാ ചാനലുകളും"</item>
+ <item msgid="928298872841713530">"കുടുംബാംഗങ്ങൾ/കുട്ടികൾ"</item>
+ <item msgid="2751606947569857164">"സ്‌പോർട്സ്"</item>
+ <item msgid="7345749789651321496">"ഷോപ്പിംഗ്"</item>
+ <item msgid="167201149441442173">"സിനിമകൾ"</item>
+ <item msgid="525966731464264290">"ഹാസ്യം"</item>
+ <item msgid="6096710741527327836">"യാത്ര"</item>
+ <item msgid="2851882187117833883">"നാടകം"</item>
+ <item msgid="78492781188719038">"വിദ്യാഭ്യാസം"</item>
+ <item msgid="7221999662426308394">"മൃഗങ്ങൾ/വൈൽഡ്‌‌ലൈഫ്"</item>
+ <item msgid="375300513250925001">"വാർത്ത"</item>
+ <item msgid="7746320336582330410">"ഗെയിമിംഗ്"</item>
+ <item msgid="1255741860568329178">"കലകള്‍"</item>
+ <item msgid="7603949681065702867">"വിനോദം"</item>
+ <item msgid="4453821994746804366">"ജീവിതശൈലി"</item>
+ <item msgid="3488534597567932843">"സംഗീതം"</item>
+ <item msgid="7452153120614274095">"പ്രീമിയർ"</item>
+ <item msgid="8215762047341133299">"സാങ്കേതികവിദ്യ/ശാസ്‌ത്രം"</item>
+ </string-array>
+</resources>
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..34938b64
--- /dev/null
+++ b/res/values-ml-rIN/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"മയക്കുമരുന്നുകൾ"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"ലൈംഗികത"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"അക്രമം"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"മയക്കുമരുന്നുകൾ"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"ലൈംഗികത"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"അക്രമം"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"ഫ്രഞ്ച്"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"ലൈംഗികസ്‌പഷ്‌ടമായ സംഭാഷണം"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"മോശമായ ഭാഷ"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"ലൈംഗിക ഉള്ളടക്കം"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"അക്രമം"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"ഭാവനാരൂപത്തിലുള്ള അക്രമം"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"ഫിലിം റേറ്റിംഗുകൾ"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
new file mode 100644
index 00000000..d722810f
--- /dev/null
+++ b/res/values-ml-rIN/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"തത്സമയ ചാനലുകൾ"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"മോണോ"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"സ്‌റ്റീരിയോ"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"പ്ലേ നിയന്ത്രണങ്ങൾ"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"ചാനലുകള്‍"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"സമീപകാല ചാനലുകൾ"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"ടിവി ഓപ്‌ഷനുകൾ"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP ഓ‌പ്‌ഷനുകൾ"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"ഈ ചാനലിന് പ്ലേ നിയന്ത്രണങ്ങൾ ലഭ്യമല്ല"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"പ്ലേ ചെയ്യുക അല്ലെങ്കിൽ താല്‍‌ക്കാലികമായി നിര്‍‌ത്തുക"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"വേഗത്തിലുള്ള കൈമാറൽ"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"റിവൈൻഡുചെയ്യുക"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"അടുത്തത്"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"മുമ്പത്തെ"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"പ്രോഗ്രാം സഹായി"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"പുതിയ ചാനലുകൾ ലഭ്യമാണ്"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"ലിങ്കുകളൊന്നും ലഭ്യമല്ല"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> തുറക്കുക"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"അടച്ച അടിക്കുറിപ്പുകൾ"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"ഡിസ്‌പ്ലേ മോഡ്"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"ഓണാണ്"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"ഓഫാണ്"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"മൾട്ടി ഓഡിയോ"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"ചാനൽ സെറ്റപ്പ്"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"രക്ഷാകർതൃ നിയന്ത്രണങ്ങൾ"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"വിവരം"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"ഉറവിടം"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"സ്വാപ്പുചെയ്യുക"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"ഓണാണ്"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"ഓഫാണ്"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"ശബ്ദം"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"പ്രധാന വിൻഡോ"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP വിൻഡോ"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"ലേ‌ഔട്ട്"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"ചുവടെ വലതുഭാഗത്ത്"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"മുകളില്‍‌ വലതുഭാഗത്ത്"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"മുകളില്‍‌ ഇടതുഭാഗത്ത്"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"ചുവടെ ഇടതുഭാഗത്ത്"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"വശങ്ങളിലായി"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"വലുപ്പം"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"വലുത്"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"ചെറുത്"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"ഇൻപുട്ട് ഉറവിടം"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"ടിവി (ആന്റിന/കേബിൾ)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"പ്രോഗ്രാം വിവരമൊന്നുമില്ല"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"വിവരമൊന്നുമില്ല"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"തടഞ്ഞ ചാനൽ"</string>
+ <string name="default_language" msgid="4122326459624337928">"അറിയാത്ത ഭാഷ"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"അടച്ച അടിക്കുറിപ്പുകൾ"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"ഓഫ്"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"ഫോർമാറ്റുചെയ്യൽ ഇഷ്‌ടാനുസൃതമാക്കുക"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"അടച്ച അടിക്കുറിപ്പുകൾക്കുള്ള സിസ്‌റ്റം വിസ്‌തൃത മുൻഗണനകൾ സജ്ജമാക്കുക"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"ഡിസ്‌പ്ലേ മോഡ്"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"മൾട്ടി ഓഡിയോ"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"മോണോ"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"സ്‌റ്റീരിയോ"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 സറൗണ്ട്"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 സറൗണ്ട്"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d ചാനലുകൾ"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"ചാനൽ ലിസ്റ്റ് ഇഷ്‌ടാനുസൃതമാക്കുക"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"ഗ്രൂപ്പ് തിരഞ്ഞെടുക്കുക"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"ഗ്രൂപ്പ് തിരഞ്ഞെടുത്തത് മാറ്റുക"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"ഗ്രൂപ്പ് അനുസരിച്ച്"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"ചാനൽ ഉറവിടം"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"ഗ്രൂപ്പ് അനുസരിച്ച്"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"ചാനൽ സെറ്റപ്പ്"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"ഈ പ്രോഗ്രാം തടഞ്ഞിരിക്കുന്നു"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"ഈ പ്രോഗ്രാമിനെ <xliff:g id="RATING">%1$s</xliff:g> എന്ന് റേറ്റുചെയ്‌തു"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"ചാനൽ ഉറവിടങ്ങൾ"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"പുതിയ ചാനലുകൾ ലഭ്യമാണ്"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"ചാനൽ ലിസ്റ്റ് ഇഷ്‌ടാനുസൃതമാക്കുക"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"നിങ്ങളുടെ പ്രോഗ്രാം ഗൈഡിനായി ചാനലുകൾ തിരഞ്ഞെടുക്കുക"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"സജ്ജീകരിച്ചിട്ടില്ല"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"യാന്ത്രികമായി സ്‌കാൻ ചെയ്യുന്നതിനെ ഇൻപുട്ട് പിന്തുണയ്‌ക്കുന്നില്ല"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\'<xliff:g id="TV_INPUT">%s</xliff:g>\' എന്നതിനായി യാന്ത്രിക സ്‌കാൻ ആരംഭിക്കാനായില്ല"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"അടച്ച അടിക്കുറിപ്പുകൾക്കായി സിസ്‌‌റ്റത്തിലെ മുൻഗണനകൾ ആരംഭിക്കാനാകില്ല."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d ചാനൽ ചേർത്തു"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d ചാനലുകൾ ചേർത്തു"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"ചാനലുകളൊന്നും ചേർത്തിട്ടില്ല"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"ട്യൂണർ"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"രക്ഷാകർതൃ നിയന്ത്രണങ്ങൾ"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"ഓൺ"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"ഓഫ്"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"തടഞ്ഞ ചാനലുകൾ"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"എല്ലാം തടയുക"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"തടഞ്ഞതെല്ലാം മാറ്റുക"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"മറച്ച ചാനലുകൾ"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"പ്രോഗ്രാം നിയന്ത്രണങ്ങൾ"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"പിൻ മാറ്റുക"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"റേറ്റിംഗ് സംവിധാനങ്ങൾ"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"റേറ്റിംഗുകള്‍"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"എല്ലാ റേറ്റിംഗ് സംവിധാനങ്ങളും കാണുക"</string>
+ <string name="other_countries" msgid="8342216398676184749">"മറ്റു രാജ്യങ്ങൾ"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"ഒന്നുമില്ല"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"ഒന്നുമില്ല"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"ഒന്നുമില്ല"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"ഉയർന്ന നിയന്ത്രണങ്ങൾ"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"ഇടത്തരം നിയന്ത്രണങ്ങൾ"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"പരിമിതമായ നിയന്ത്രണങ്ങൾ"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"ഇഷ്‌ടാനുസൃതം"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"കുട്ടികൾക്ക് അനുയോജ്യമായ ഉള്ളടക്കം"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"മുതിർന്ന കുട്ടികൾക്ക് അനുയോജ്യമായ ഉള്ളടക്കം"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"കൗമാരക്കാർക്ക് അനുയോജ്യമായ ഉള്ളടക്കം"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"സ്വമേധയായുള്ള നിയന്ത്രണങ്ങൾ"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s എന്നതും ഉപറേറ്റിംഗുകളും"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"ഉപറേറ്റിംഗുകൾ"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"ഈ ചാനൽ കാണാൻ നിങ്ങളുടെ പിൻ നൽകുക"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"ഈ പ്രോഗ്രാം കാണാൻ നിങ്ങളുടെ പിൻ നൽകുക"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"നിങ്ങളുടെ പിൻ നൽകുക"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"രക്ഷാകർതൃ നിയന്ത്രണങ്ങൾ സജ്ജമാക്കാൻ, ഒരു പിൻ സൃഷ്‌ടിക്കുക"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"പുതിയ പിൻ നൽകുക"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"നിങ്ങളുടെ പിൻ സ്ഥിരീകരിക്കുക"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"നിങ്ങളുടെ നിലവിലെ പിൻ നൽകുക"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"നിങ്ങൾ പിൻ അഞ്ചുതവണ തെറ്റായി നൽകി.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> നിമിഷത്തിനുള്ളിൽ വീണ്ടും ശ്രമിക്കുക."</item>
+ <item quantity="other" msgid="8829550842387756054">"നിങ്ങൾ പിൻ അഞ്ചുതവണ തെറ്റായി നൽകി.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> നിമിഷങ്ങൾക്കുള്ളിൽ വീണ്ടും ശ്രമിക്കുക."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"നൽകിയ പിൻ തെറ്റാണ്. വീണ്ടും ശ്രമിക്കുക."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"പിൻ യോജിക്കുന്നില്ല, വീണ്ടും ശ്രമിക്കുക"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"വിവരം"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"ഓപ്പൺ സോഴ്‌സ് ലൈസൻസുകൾ"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"ഓപ്പൺ സോഴ്‌സ് ലൈസൻസുകൾ"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"പതിപ്പ്"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"ഈ ചാനൽ കാണുന്നതിന് വലതുവശത്ത് അമർത്തിക്കൊണ്ട് നിങ്ങളുടെ പിൻ നൽകുക"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"ഈ പ്രോഗ്രാം കാണുന്നതിന് വലതുവശത്ത് അമർത്തിക്കൊണ്ട് നിങ്ങളുടെ പിൻ നൽകുക"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"ഈ പ്രോഗ്രാമിനെ <xliff:g id="RATING">%1$s</xliff:g> എന്ന് റേറ്റുചെയ്‌തു.\nഈ പ്രോഗ്രാം കാണുന്നതിന് വലതുവശത്ത് അമർത്തിക്കൊണ്ട് നിങ്ങളുടെ പിൻ നൽകുക."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"പ്രോഗ്രാം തടഞ്ഞു"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"ഈ പ്രോഗ്രാമിനെ <xliff:g id="RATING">%1$s</xliff:g> എന്ന് റേറ്റുചെയ്‌തു"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"ശീർഷകമൊന്നുമില്ല"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"ചാനൽ തടഞ്ഞിരിക്കുന്നു"</string>
+ <string name="episode_format" msgid="4881195874563241096">"സീസൺ<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: ഭാഗം. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"ചാനൽ ഉറവിടങ്ങൾ"</string>
+ <string name="setup_description" msgid="8728423605912915099">"ലഭ്യമായ ഉറവിടങ്ങളിൽ നിന്ന് തൽസമയ ചാനലുകൾ സജ്ജമാക്കുക. ചാനൽ ഉറവിടത്തെ ആശ്രയിച്ച് ഇതിന് ധാരാളം സമയം എടുത്തേക്കാം."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"പൂർത്തിയായി"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d ചാനൽ ലഭ്യമാണ്"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d ചാനലുകൾ ലഭ്യമാണ്"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"ചാനലുകളൊന്നും ലഭ്യമല്ല"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"പുതിയത്"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"ടിവി മെനു ആക്‌സസ്സ് ചെയ്യാൻ "<b>"\'തിരഞ്ഞെടുക്കുക\' അമർത്തുക"</b>"."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"ടിവി ഇൻപുട്ടൊന്നും കണ്ടെത്തിയില്ല"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"ടിവി ഇൻപുട്ട് കണ്ടെത്താനാകില്ല"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP പിന്തുണയ്‌ക്കുന്നില്ല"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP-യിൽ കാണിക്കാനാകുന്ന ഇൻപുട്ടൊന്നും ലഭ്യമല്ല"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"ട്യൂണർ തരം അനുയോജ്യമല്ല. ട്യൂണർ തരം ടിവി ഇൻപുട്ടിനായി തത്സമയ ചാനലുകളുടെ അപ്ലിക്കേഷൻ സമാരംഭിക്കുക."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"ട്യൂൺ ചെയ്യൽ പരാജയപ്പെട്ടു"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"ഈ പ്രവർത്തനം കൈകാര്യം ചെയ്യാൻ ആപ്പുകളൊന്നും കണ്ടെത്തിയില്ല."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"എല്ലാ ഉറവിട ചാനലുകളും മറച്ചിരിക്കുന്നു.\nകാണാനായി ഒരു ചാനലെങ്കിലും തിരഞ്ഞെടുക്കുക."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"ദുർബലമായ വീഡിയോ സി‌ഗ്‌നൽ കാരണം ലഭ്യമല്ല"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"അപ്രതീക്ഷിതമായി വീഡിയോ ലഭ്യമല്ല"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"കണക്‌റ്റു‌ചെയ്‌തിരിക്കുന്ന ഉപകരണത്തിനുള്ളതാണ് മടങ്ങുക എന്ന കീ. പുറത്തുകടക്കാൻ ഹോം ബട്ടൺ അമർത്തുക."</string>
+</resources>
diff --git a/res/values-mn-rMN/arrays.xml b/res/values-mn-rMN/arrays.xml
new file mode 100644
index 00000000..0985c64b
--- /dev/null
+++ b/res/values-mn-rMN/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Энгийн"</item>
+ <item msgid="2533030282864800794">"Дүүрэн"</item>
+ <item msgid="8568284598210500589">"Томруулах"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Бүх суваг"</item>
+ <item msgid="928298872841713530">"Гэр бүл/Хүүхдүүд"</item>
+ <item msgid="2751606947569857164">"Спорт"</item>
+ <item msgid="7345749789651321496">"Худалдаа"</item>
+ <item msgid="167201149441442173">"Кино"</item>
+ <item msgid="525966731464264290">"Инээдмийн"</item>
+ <item msgid="6096710741527327836">"Аялал"</item>
+ <item msgid="2851882187117833883">"Драма"</item>
+ <item msgid="78492781188719038">"Боловсрол"</item>
+ <item msgid="7221999662426308394">"Амьтад/Зэрлэг байгаль"</item>
+ <item msgid="375300513250925001">"Мэдээ"</item>
+ <item msgid="7746320336582330410">"Тоглоом"</item>
+ <item msgid="1255741860568329178">"Урлаг"</item>
+ <item msgid="7603949681065702867">"Цэнгээн"</item>
+ <item msgid="4453821994746804366">"Амьдралын хэв маяг"</item>
+ <item msgid="3488534597567932843">"Дуу хөгжим"</item>
+ <item msgid="7452153120614274095">"Эхний"</item>
+ <item msgid="8215762047341133299">"Технологи/ШУ"</item>
+ </string-array>
+</resources>
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..aaba1d61
--- /dev/null
+++ b/res/values-mn-rMN/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Хар тамхи"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Бэлгийн харьцаа"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Хүчирхийлэл"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Хар тамхи"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Секс"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Хүчирхийлэл"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Франц"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Уруу татсан яриа"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Бүдүүлэг яриа"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Секс контент"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Хүчирхийлэл"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Уран зөгнөлт хэлбэрээр үзүүлсэн хүчирхийлэл"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Киноны зэрэглэл"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
new file mode 100644
index 00000000..4059945d
--- /dev/null
+++ b/res/values-mn-rMN/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Шууд Сувгууд"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"моно"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"стерео"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Play хяналт"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Сувгууд"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Сүүлийн суваг"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"ТВ-н сонголтууд"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP Сонголтууд"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Энэ сувагт тоглуулах хяналтыг ашиглах боломжгүй байна"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Тоглуулах эсвэл түр зогсоох"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Хурдан урагшлуулах"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Хураах"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Дараах"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Өмнөх"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Хөтөлбөр"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Шинэ суваг нээлттэй байна"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Холбоос байхгүй байна"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г нээх"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Хаалттай капшн"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Дэлгэцийн горим"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Идэвхтэй"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Идэвхгүй"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Мульти-аудио"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Сувгийн тохиргоо"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Эцэг эх хяналт"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Талаар"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Эх сурвалж"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Солих"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Идэвхтэй"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Идэвхгүй"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Дуу"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Үндсэн"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP цонх"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Байрлал"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Баруун доод"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Баруун дээд"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Зүүн дээд"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Зүүн доод"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Зэрэгцсэн"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Хэмжээ"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Том"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Жижиг"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Оролтын эх үүсвэр"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TВ (Антен /Кабел)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Хөтөлбөрийн мэдээлэл байхгүй"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Мэдээлэл байхгүй"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Хаагдсан суваг"</string>
+ <string name="default_language" msgid="4122326459624337928">"Үл мэдэгдэх хэл"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Хаалттай тайлбар"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Идэвхгүй"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Форматыг тааруулах"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Системийн хэмжээнд тохируулга хийх"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Дэлгэцийн горим"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Мульти-аудио"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"моно"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"стерео"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 Surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d суваг"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Сувгийг тохируулах"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Бүлэг сонгох"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Бүлгийн сонголтыг цуцлах"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Бүлэглэх"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Сувгийн эх сурвалж"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Бүлэглэх"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Сувгийн тохиргоо"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Энэ програмыг блоклосон байна."</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Энэ хөтөлбөрийг <xliff:g id="RATING">%1$s</xliff:g>-ээр үнэлэгдсэн байна"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Сувгийн эх сурвалж"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Шинэ суваг нээлттэй байна"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Сувгийн жагсаалтыг тохируулах"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Өөрийн хөтөлбөрт оруулах сувгуудыг сонгоно уу"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Тохируулаагүй"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Оролт нь автомат хайлтыг дэмждэггүй"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\'<xliff:g id="TV_INPUT">%s</xliff:g>\'-н автомат хайлтыг эхлүүлэх боломжгүй"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Текстийн системийн түвшний тохируулгыг эхлүүлэх боломжгүй."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d суваг нэмэгдсэн"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d суваг нэмэгдсэн"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Суваг нэмэгдээгүй"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Тааруулагч"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Эцэг эхийн хяналт"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Идэвхтэй"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Идэвхгүй"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Суваг хориглогдсон"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Бүгдийг хориглох"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Бүх хоригийг авах"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Нууцалсан сувгууд"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Хөтөлбөрийн хязгаар"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN өөрчлөх"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Үнэлгээний систем"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Үнэлгээ"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Үнэлгээний систем"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Бусад улс орон"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Хоосон"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Хоосон"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Хоосон"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Хатуу хязгаарлалт"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Дунд хязгаарлалт"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Сул хязгаарлалт"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Тааруулсан"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Хүүхдэд зориулсан тохиромжтой контент"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Том хүүхдэд тохиромжтой контент"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Өсвөр насныханд тохиромжтой контент"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Гар хязгаарлалт"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s ба дэд-үнэлгээ"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Дэд-үнэлгээ"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Энэ сувгийг үзэхийн тулд PIN оруулна уу"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Энэ хөтөлбөрийг үзэхийн тулд PIN оруулна уу"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Өөрийн PIN оруулна уу"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Эцэг эхийн хяналт тохируулахын тулд PIN үүсгээрэй"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Шинэ PIN оруулна уу"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Өөрийн PIN-г баталгаажуулна уу"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Одоогийн PIN оруулна уу"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Та ПИН-ээ 5 удуу буруу орууллаа.\n <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g>секундын дараа дахин оролдоно уу."</item>
+ <item quantity="other" msgid="8829550842387756054">"Та ПИН-ээ 5 удаа буруу орууллаа.\n <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> секундын дараа дахин оролдоно уу."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Энэ PIN буруу байна. Дахин оролдоно уу."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Дахин оролдоно уу, PIN таарахгүй байна"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Талаар"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Нээлттэй эхийн лиценз"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Нээлттэй эхийн лиценз"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Хувилбар"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Энэ сувгийг үзэхийн тулд Баруун товчийг дараад өөрийн PIN-г оруулна уу"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Энэ хөтөлбөрийг үзэхийн тулд Баруун товчийг дараад өөрийн PIN-г оруулна уу"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Энэ хөтөлбөр <xliff:g id="RATING">%1$s</xliff:g> үнэлгээтэй байна.\nэнэ хөтөлбөрийг үзэхийн тулд Баруун товчийг дараад өөрийн PIN-г оруулна уу."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Хөтөлбөр хориглогдсон"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Энэ хөтөлбөрийг <xliff:g id="RATING">%1$s</xliff:g>-ээр үнэлэгдсэн байна"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Гарчиггүй"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Сувгийг хориглосон"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Бүлэг<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Анги. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Сувгийн эх сурвалж"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Бэлэн эх үүсвэрээс шууд сувгийг тохируулна уу.Сувгийн эх үүсвэрээс шалтгаалан хэдэн минут болж магадгүй."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Үйлдлийг гүйцэтгэсэн байна"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d суваг нээлттэй"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d суваг нээллтэй"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Ямар ч суваг байхгүй байна"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Шинэ"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"ТВ цэс рүү хандахын тулд "<b>"СОНГОХ гэснийг дарна уу"</b></string>
+ <string name="msg_no_input" msgid="3897674146985427865">"ТВ оролт олдсонгүй."</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"ТВ оролтыг олж чадахгүй байна."</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP дэмжигдээгүй байна."</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP-тай харуулж болох бэлэн оролт байхгүй байна."</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Дуу тохируулагчийн төрөл тохирохгүй байна. Зурагтын оролтын дуу тохируулагчийн төрөлд зориулан Шууд Сувгуудын апликейшнийг эхлүүл."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Дуу тааруулж чадсангүй."</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Энэ үйлдлийг гүйцэтгэх апп олдсонгүй."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Бүх эх сурвалж сувгуудыг нууцалсан.\nҮзэхийн тулд дор хаяж нэг суваг сонгоно уу."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Видео сигналь сул байгаа учир үзэх боломжгүй байна."</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Энэ видеог үзэх боломжгүй болсон."</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Буцах товчийг холбогдсон төхөөрөмжүүдэд ашиглана. Гарахын тулд Нүүр товчийг дарна уу."</string>
+</resources>
diff --git a/res/values-mr-rIN/arrays.xml b/res/values-mr-rIN/arrays.xml
new file mode 100644
index 00000000..a1b369a8
--- /dev/null
+++ b/res/values-mr-rIN/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"सामान्य"</item>
+ <item msgid="2533030282864800794">"पूर्ण"</item>
+ <item msgid="8568284598210500589">"झूम करा"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"सर्व चॅनेल"</item>
+ <item msgid="928298872841713530">"कुटुंब/मुले"</item>
+ <item msgid="2751606947569857164">"क्रीडा"</item>
+ <item msgid="7345749789651321496">"खरेदी"</item>
+ <item msgid="167201149441442173">"चित्रपट"</item>
+ <item msgid="525966731464264290">"विनोदी"</item>
+ <item msgid="6096710741527327836">"प्रवास"</item>
+ <item msgid="2851882187117833883">"नाटक"</item>
+ <item msgid="78492781188719038">"शिक्षण"</item>
+ <item msgid="7221999662426308394">"प्राणी/वन्यजीवन"</item>
+ <item msgid="375300513250925001">"बातम्या"</item>
+ <item msgid="7746320336582330410">"गेमिंग"</item>
+ <item msgid="1255741860568329178">"कला"</item>
+ <item msgid="7603949681065702867">"मनोरंजन"</item>
+ <item msgid="4453821994746804366">"जीवनशैली"</item>
+ <item msgid="3488534597567932843">"संगीत"</item>
+ <item msgid="7452153120614274095">"प्रमुख"</item>
+ <item msgid="8215762047341133299">"तंत्रज्ञान/विज्ञान"</item>
+ </string-array>
+</resources>
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..ed39ce79
--- /dev/null
+++ b/res/values-mr-rIN/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"अंमली पदार्थ"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"लिंग"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"हिंसा"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"अंमली पदार्थ"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"लिंग"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"हिंसा"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"फ्रेंच"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"अश्लील संवाद"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"असभ्य भाषा"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"लैंगिक सामग्री"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"हिंसा"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"काल्पनिक हिंसा"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"फिल्म रेटिंग"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
new file mode 100644
index 00000000..4fa1091e
--- /dev/null
+++ b/res/values-mr-rIN/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"थेट चॅनेल"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"एक"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"स्टिरिओ"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"प्ले नियंत्रणे"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"चॅनेल"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"अलीकडील चॅनेल"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"टीव्‍ही पर्याय"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP पर्याय"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"या चॅनेलसाठी अनुपलब्ध असलेली नियंत्रणे प्ले करा"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"प्ले करा किंवा विराम द्या"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"फास्ट फॉरवर्ड करा"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"रिवाईँड करा"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"पुढील"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"मागील"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"कार्यक्रम मार्गदर्शक"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"उपलब्ध नवीन चॅनेल"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"दुवा उपलब्ध नाही"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> उघडा"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"उपशीर्षक"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"प्रदर्शन मोड"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"चालू"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"बंद"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"मल्टी-ऑडिओ"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"चॅनेल सेटअप"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"पालक नियंत्रणे"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"विषयी"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"स्त्रोत"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"अदलाबदल करा"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"चालू"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"बंद"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"ध्वनी"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"मुख्य"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP विंडो"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"लेआउट"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"तळाशी उजवीकडे"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"शीर्षस्थानी उजवीकडे"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"शीर्षस्थानी डावीकडे"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"तळाशी डावीकडे"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"शेजारी शेजारी"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"आकार"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"मोठा"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"लहान"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"इनपुट स्त्रोत"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"टीव्ही (अँटेना/केबल)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"कोणतीही कार्यक्रम माहिती नाही"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"कोणतीही माहिती नाही"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"अवरोधित चॅनेल"</string>
+ <string name="default_language" msgid="4122326459624337928">"अज्ञात भाषा"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"बंद मथळा"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"बंद"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"स्वरुपन सानुकूलित करा"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"उपशीर्षकांसाठी सिस्‍टीम-विस्तृत प्राधान्ये सेट करा"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"प्रदर्शन मोड"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"मल्टी-ऑडिओ"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"एक"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"स्टिरिओ"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 सराउंड"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 सराउंड"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d चॅनेल"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"चॅनेल सूची सानुकूल करा"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"गट निवडा"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"गट निवड रद्द करा"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"नुसार गट करा"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"चॅनेल स्त्रोत"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"नुसार गट करा"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"चॅनेल सेटअप"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"हा कार्यक्रम अवरोधित केला आहे"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"हा कार्यक्रम <xliff:g id="RATING">%1$s</xliff:g> रेट केला आहे"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"चॅनेल स्त्रोत"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"उपलब्ध नवीन चॅनेल"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"चॅनेल सूची सानुकूल करा"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"आपल्या कार्यक्रम मार्गदर्शकासाठी चॅनेल निवडा"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"सेट केलेले नाही"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"इनपुट स्वयं-स्‍कॅनला समर्थन देत नाही"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\'<xliff:g id="TV_INPUT">%s</xliff:g>\' साठी स्वयं-स्कॅन प्रारंभ करण्‍यात अक्षम"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"उपशीर्षकांसाठी सिस्‍टीम-विस्तृत प्राधान्ये प्रारंभ करण्‍यात अक्षम."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d चॅनेल जोडले"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d चॅनेल जोडले"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"कोणतेही चॅनेल जोडले नाही"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"ट्यूनर"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"पालक नियंत्रणे"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"चालू"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"बंद"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"चॅनेल अवरोधित केले"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"सर्व अवरोधित करा"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"सर्व अनावरोधित करा"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"लपविलेले चॅनेल"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"कार्यक्रम निर्बंध"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"पिन बदला"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"रेटिंग सिस्‍टीम"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"रेटिंग"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"सर्व रेटिंग सिस्‍टीम पहा"</string>
+ <string name="other_countries" msgid="8342216398676184749">"इतर देश"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"कोणतेही नाही"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"काहीही नाही"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"काहीही नाही"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"उच्च निर्बंंध"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"मध्यम निर्बंध"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"कमी निर्बंध"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"सानुकूल"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"मुलांसाठी योग्य असलेली सामग्री"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"मोठ्या मुलांसाठी योग्य असलेली सामग्री"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"किशोरवयीन मुलांसाठी योग्य असलेली सामग्री"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"व्यक्तीचलित निर्बंध"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s आणि उप रेटिंग"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"उप रेटिंग"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"हे चॅनेल पाहण्‍यासाठी आपला पिन प्रविष्‍ट करा"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"हा कार्यक्रम पाहण्‍यासाठी आपला पिन प्रविष्‍ट करा"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"आपला पिन प्रविष्‍ट करा"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"पालक नियंत्रणे सेट करण्यासाठी, एक पिन तयार करा"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"नवीन पिन प्रविष्ट करा"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"आपल्या पिन ची पुष्टी करा"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"आपला वर्तमान पिन प्रविष्ट करा"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"आपण चुकीचा पिन 5 वेळा प्रविष्ट केला.\n <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> सेकंदांनी पुन्हा प्रयत्न करा."</item>
+ <item quantity="other" msgid="8829550842387756054">"आपण चुकीचा पिन 5 वेळा प्रविष्ट केला.\n <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> सेकंदांनी पुन्हा प्रयत्न करा."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"तो पिन चुकीचा होता. पुन्हा प्रयत्न करा."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"पुन्हा प्रयत्न करा, पिन जुळत नाही"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"विषयी"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"मुक्त स्त्रोत परवाने"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"मुक्त स्त्रोत परवाने"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"आवृत्ती"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"हे चॅनेल पाहण्यासाठी, उजवे दाबा आणि आपला पिन प्रविष्‍ट करा"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"हा कार्यक्रम पाहण्‍यासाठी, उजवे दाबा आणि आपला पिन प्रविष्‍ट करा"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"हा कार्यक्रम <xliff:g id="RATING">%1$s</xliff:g> रेट केला आहे.\n हा कार्यक्रम पाहण्‍यासाठी, उजवे दाबा आणि आपला पिन प्रविष्‍ट करा."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"कार्यक्रम अवरोधित केला आहे"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"हा कार्यक्रम <xliff:g id="RATING">%1$s</xliff:g> रेट केला आहे"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"शीर्षक नाही"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"चॅनेल अवरोधित केले"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: भाग. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"चॅनेल स्त्रोत"</string>
+ <string name="setup_description" msgid="8728423605912915099">"उपलब्ध स्त्रोतांवरून थेट चॅनेल सेट करा. चॅनेल स्त्रोताच्या आधारावर यास कित्येक मिनिटे लागू शकतात."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"पूर्ण झाले"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d चॅनेल उपलब्ध"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d चॅनेल उपलब्ध"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"कोणतेही चॅनेल उपलब्ध नाही"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"नवीन"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"टीव्ही मेनूवर प्रवेश करण्यासाठी "<b>"निवडा दाबा"</b>"."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"कोणतेही टीव्ही इनपुट आढळले नाही"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"टीव्ही इनपुट शोधू शकत नाही"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP समर्थित नाही"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP सह दर्शविले जाऊ शकते असे कोणतेही उपलब्ध इनपुट नाही"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"ट्यूनर प्रकार अनुकूल नाही. कृपया ट्यूनर प्रकार टीव्ही इनपुटसाठी थेट चॅनेल अॅप लाँच करा."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"ट्यून अयशस्वी झाले"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"ही क्रिया हाताळण्यासाठी कोणताही अ‍ॅप आढळला नाही."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"सर्व स्त्रोत चॅनेल लपविले आहेत.\nपाहण्‍यासाठी कमीतकमी एक चॅनेल निवडा."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"कमकुवत व्हिडिओ सिग्नलमुळे अनुपलब्ध"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"व्‍हिडिओ अनपेक्षितपणे अनुपलब्ध आहे"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"बॅक की कनेक्ट केलेल्या डिव्हाइससाठी आहे. बाहेर पडण्यासाठी मुख्यपृष्ठ बटण दाबा."</string>
+</resources>
diff --git a/res/values-ms-rMY/arrays.xml b/res/values-ms-rMY/arrays.xml
new file mode 100644
index 00000000..6c795d51
--- /dev/null
+++ b/res/values-ms-rMY/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Biasa"</item>
+ <item msgid="2533030282864800794">"Penuh"</item>
+ <item msgid="8568284598210500589">"Zum"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Semua saluran"</item>
+ <item msgid="928298872841713530">"Keluarga/Kanak-Kanak"</item>
+ <item msgid="2751606947569857164">"Sukan"</item>
+ <item msgid="7345749789651321496">"Beli-belah"</item>
+ <item msgid="167201149441442173">"Filem"</item>
+ <item msgid="525966731464264290">"Komedi"</item>
+ <item msgid="6096710741527327836">"Pelancongan"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Pendidikan"</item>
+ <item msgid="7221999662426308394">"Haiwan/Hidupan Liar"</item>
+ <item msgid="375300513250925001">"Berita"</item>
+ <item msgid="7746320336582330410">"Permainan"</item>
+ <item msgid="1255741860568329178">"Seni"</item>
+ <item msgid="7603949681065702867">"Hiburan"</item>
+ <item msgid="4453821994746804366">"Gaya Hidup"</item>
+ <item msgid="3488534597567932843">"Muzik"</item>
+ <item msgid="7452153120614274095">"Perdana"</item>
+ <item msgid="8215762047341133299">"Teknikal/Sains"</item>
+ </string-array>
+</resources>
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..9bef55e6
--- /dev/null
+++ b/res/values-ms-rMY/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Dadah"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Seks"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Keganasan"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Dadah"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Seks"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Keganasan"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Bahasa Perancis"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Dialog tidak senonoh"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Bahasa kasar"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Kandungan seksual"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Keganasan"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Keganasan fantasi"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Penilaian filem"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
new file mode 100644
index 00000000..8fdf9ff4
--- /dev/null
+++ b/res/values-ms-rMY/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Kawalan main"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Saluran"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Saluran terbaharu"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Pilihan TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Pilihan PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Kawalan main tidak tersedia untuk saluran ini"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Main atau jeda"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Mara laju"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Gulung semula"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Seterusnya"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Sebelumnya"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Panduan program"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Saluran baharu tersedia"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Pautan tidak tersedia"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Buka <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Sari kata"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Mod paparan"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Hidupkan"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Matikan"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Berbilang audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Psdiaan saluran"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Kawalan ibu bapa"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Perihal"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Sumber"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Silih"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Hidupkan"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Matikan"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Bunyi"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Utama"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Tetingkap PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Reka Letak"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Kanan bawah"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Kanan atas"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Kiri atas"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Kiri bawah"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Bersebelahan"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Saiz"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Besar"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Kecil"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Sumber input"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antena/kabel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Maklumat program tiada"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Tiada maklumat"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Saluran disekat"</string>
+ <string name="default_language" msgid="4122326459624337928">"Bahasa tidak diketahui"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Sari kata"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Dimatikan"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Sesuaikan format"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Tetapkan pilihan sari kata seluruh sistem"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Mod paparan"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Berbilang audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 keliling"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 keliling"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d saluran"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Sesuaikan saluran"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Pilih kumpulan"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Nyahpilih kumpulan"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Kumpulkan mengikut"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Sumber saluran"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Kumpulkan mengikut"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Persediaan saluran"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Rancangan ini disekat"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Rancangan ini diberi rating <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Sumber saluran"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Saluran baharu tersedia"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Sesuaikan saluran"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Pilih saluran utk panduan rancangan anda"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Tidak disediakan"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Input tidak menyokong autoimbas"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Tidak dapat memulakan autoimbas untuk \'<xliff:g id="TV_INPUT">%s</xliff:g>\'"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Tidak dapat memulakan pilihan sari kata untuk seluruh sistem."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d saluran ditambahkan"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d saluran ditambahkan"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Tiada saluran ditambahkan"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Penala"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Kawalan ibu bapa"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Hidup"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Dimatikan"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Saluran disekat"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Sekat semua"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Nyahsekat semua"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Saluran tersembunyi"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Sekatan program"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Tukar PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Sistem penilaian"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Penilaian"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Lihat sistem penilaian"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Negara lain"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Tiada"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Tiada"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Tiada"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Sekatan ketat"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Sekatan sederhana"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Sekatan longgar"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Tersuai"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Kandungan sesuai untuk kanak-kanak"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Kandungan sesuai utk kanak-kanak berusia"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Kandungan yang sesuai untuk remaja"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Sekatan manual"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s &amp; subpenilaian"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Subpenilaian"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Masukkan PIN anda untuk menonton saluran ini"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Masukkan PIN anda untuk menonton rancangan ini"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Masukkan PIN anda"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Untuk menetapkan kawalan ibu bapa, buat PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Masukkan PIN baharu"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Sahkan PIN anda"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Masukkan PIN semasa anda"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Anda telah memasukkan PIN yang salah sebanyak 5 kali.\nCuba lagi dalam masa <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> saat."</item>
+ <item quantity="other" msgid="8829550842387756054">"Anda memasukkan PIN yang salah sebanyak 5 kali.\nCuba lagi dalam masa <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> saat."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"PIN itu salah. Cuba lagi."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Cuba lagi, PIN tidak sepadan"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Perihal"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Lesen sumber terbuka"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Lesen sumber terbuka"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versi"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Untuk menonton saluran ini, tekan Kanan dan masukkan PIN anda"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Untuk menonton rancangan ini, tekan Kanan dan masukkan PIN anda"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Rancangan ini diberi rating <xliff:g id="RATING">%1$s</xliff:g>.\nUntuk menonton rancangan ini, tekan Kanan, kemudian masukkan PIN anda."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Rancangan disekat"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Rancangan ini diberi rating <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Tiada tajuk"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Saluran disekat"</string>
+ <string name="episode_format" msgid="4881195874563241096">"M<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Sumber saluran"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Sediakan saluran langsung daripada sumber yang tersedia. Ini mungkin mengambil masa beberapa minit bergantung pada sumber saluran itu."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Selesai"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d saluran tersedia"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d saluran tersedia"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Tiada saluran yang tersedia"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Baharu"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Tekan PILIH"</b>" untuk mengakses menu TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Input TV tidak ditemui"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Tidak menemui input TV"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP tidak disokong"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Tiada input tersedia yang boleh ditunjukkan dengan PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Jenis penala tidak sesuai; Sila lancarkan apl Saluran Langsung untuk input TV jenis penala."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Penalaan gagal"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Tiada apl ditemui untuk mengendalikan tindakan ini."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Semua saluran sumber disembunyikan.\nPilih sekurang-kurangnya satu saluran untuk ditonton."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Tidak tersedia disebabkan isyarat video lemah"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Video ini tidak tersedia tanpa dijangka"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Kekunci BACK adalah untuk peranti yang tersambung. Tekan butang HOME untuk keluar."</string>
+</resources>
diff --git a/res/values-my-rMM/arrays.xml b/res/values-my-rMM/arrays.xml
new file mode 100644
index 00000000..249b20a2
--- /dev/null
+++ b/res/values-my-rMM/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"ပုံမှန်"</item>
+ <item msgid="2533030282864800794">"အပြည့်"</item>
+ <item msgid="8568284598210500589">"ချဲ့ချုံ့ရန်"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"ချာနယ်များ အားလုံး"</item>
+ <item msgid="928298872841713530">"မိသားစု/ကလေးများ"</item>
+ <item msgid="2751606947569857164">"အားကစား"</item>
+ <item msgid="7345749789651321496">"​ဈေးဝယ်ခြင်း"</item>
+ <item msgid="167201149441442173">"ရုပ်ရှင်များ"</item>
+ <item msgid="525966731464264290">"ဟာသ"</item>
+ <item msgid="6096710741527327836">"ခရီးလှည့်မှု"</item>
+ <item msgid="2851882187117833883">"ပြဇာတ်"</item>
+ <item msgid="78492781188719038">"ပညာရေး"</item>
+ <item msgid="7221999662426308394">"တိရိစ္ဆာန်/တောရိုင်း"</item>
+ <item msgid="375300513250925001">"သတင်း"</item>
+ <item msgid="7746320336582330410">"ဂိမ်းကစားခြင်း"</item>
+ <item msgid="1255741860568329178">"အနုပညာများ"</item>
+ <item msgid="7603949681065702867">"ဖြေဖျော်မှု"</item>
+ <item msgid="4453821994746804366">"ဘဝပုံစံ"</item>
+ <item msgid="3488534597567932843">"ဂီတ"</item>
+ <item msgid="7452153120614274095">"ပွဲဦး"</item>
+ <item msgid="8215762047341133299">"စက်မှု/သိပ္ပံ"</item>
+ </string-array>
+</resources>
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..73d35990
--- /dev/null
+++ b/res/values-my-rMM/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"မူးယစ်ဆေးဝါးများ"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"လိင်"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"အကြမ်းဖက်မှု"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"မူးယစ်ဆေးဝါးများ"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"လိင်"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"အကြမ်းဖက်မှု"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"ပြင်သစ်"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"အကြံပြုထားသည့် စကားပြောဆိုမှု"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"ကြမ်းတမ်းသော စကားလုံးများ"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"လိင်ဆက်ဆံမှု အကြောင်းအရာ"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"အကြမ်းဖက်မှု"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"ဆန်းပြားသော အကြမ်းဖက်မှု"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"ရုပ်ရှင် အဆင့်သတ်မှတ်ချက်များ"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
new file mode 100644
index 00000000..05fa42f5
--- /dev/null
+++ b/res/values-my-rMM/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"တိုက်ရိုက်လွှင့် ချန်နယ်များ"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"မိုနို"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"စတီရီယို"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Play ထိန်းချုပ်မှုများ"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"ချာနယ်များ"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"မကြာမီက ချာနယ်များ"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV ရွေးစရာများ"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP ရွေးချယ်စရာများ"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"ဤလိုင်းအတွက် အဖွင့်ထိန်းချုပ်ခြင်းများ မရနိုင်ပါ"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"ဖွင့်ပါ သို့မဟုတ် ခဏရပ်ပါ"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"ရှေ့သို့ အမြန်သွားရန်"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"ပြန်ရစ်ရန်"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"နောက်တစ်ခု"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"ယခင်"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"အစီအစဉ် လမ်းညွှန်"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"ရရှိနိုင်သည့် ချန်နယ်အသစ်များ"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"လင့်ခ်မရနိုင်ပါ"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> ဖွင့်ရန်"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"စာတမ်းထိုးများ"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"မြင်ကွင်း မုဒ်"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"ဖွင့်ထား"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"ပိတ်ထား"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"အသံစုံ"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"ချန်နယ်ချိန်ညှိစနစ်"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"မိဘ ထိန်းချုပ်မှု"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"အကြောင်း"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"ရင်းမြစ်"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"လဲပြောင်းသည်"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"ဖွင့်ထား"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"ပိတ်ထား"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"အသံ"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"အဓိက"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP ဝင်းဒိုး"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"အဆင်အပြင်"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"အောက်ညာ"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"အပေါ်ညာ"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"အပေါ်ဘယ်"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"အောက်ဘယ်"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"ကပ်လျက်"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"ဆိုက်"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"ကြီး"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"သေး"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"ထည့်သွင်းမှု ရင်းမြစ်"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"တီဗီ (ဧရီယာတိုင်/ကြိုး)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"အစီအစဉ် အချက်အလက်များ မရှိ"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"သတင်းအချက်အလက် မရှိပါ"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"ပိတ်ဆို့ ချာနယ်"</string>
+ <string name="default_language" msgid="4122326459624337928">"အမည်မသိဘာသာစကား"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"စာတမ်းထိုးများ"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"ပိတ်ထား"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"စိတ်ကြိုက်ပုံစံချရန်"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"စာတမ်းထိုးအတွက် ရွေးစရာများ သတ်မှတ်ရန်"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"မြင်ကွင်း မုဒ်"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"အသံစုံ"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"မိုနို"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"စတီရီယို"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"၅.၁ ပတ်လည်"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"၇.၁ ပတ်လည်"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d ချာနယ်"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"ချာနယ်စာရင်း စိတ်တိုင်းကျ"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"အုပ်စု ရွေးရန်"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"အုပ်စု ရွေးမှု ဖျက်ရန်"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"အုပ်စုဖွဲ့မှု"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"ချာနယ် ရင်းမြစ်များ"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"အုပ်စုဖွဲ့မှု"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"ချန်နယ်တပ်ဆင်စနစ်"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"ဤအစီအစဉ်အား ပိတ်ဆို့ထားသည်။"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"ဤအစီအစဉ်သည် အဆင့် <xliff:g id="RATING">%1$s</xliff:g> ရှိ၏"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"ချန်နယ်ရင်းမြစ်များ"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"ရရှိနိုင်သည့် ချန်နယ်အသစ်များ"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"ချာနယ်စာရင်း စိတ်တိုင်းကျ လုပ်"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"အစီအစဉ်လမ်းညွှန်အတွက် ချာနယ်များရွေးရန်"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"စဖွင့်မသတ်မှတ်ရသေးပါ"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"ထည့်သွင်းမှုက အော်တို-စကင် ပံ့ပိုးမပေး"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\'<xliff:g id="TV_INPUT">%s</xliff:g>\' အတွက် အော်တို−စကင် မစနိုင်ပါ"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"ပိတ်ထားသည့် စာတန်းများ အတွက် စနစ်ဆိုင်ရာ ဦးစားပေးမှုများကို စတင်မရပါ။"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"ချာနယ် %1$d ထည့်ပြီး"</item>
+ <item quantity="other" msgid="1078861616751739285">"ချာနယ် %1$d ထည့်ပြီး"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"ချာနယ်များ ထည့်မထား"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"တျူနား"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"မိဘ ထိန်းချုပ်မှု"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"ဖွင့်ရန်"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"ပိတ်ရန်"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"ချာနယ်များ ပိတ်ရန်"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"အားလုံး ပိတ်ဆို့ရန်"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"အားလုံး ပြန်ဖွင့်ရန်"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"ဝှက်ထားသည့် ချာနယ်များ"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"အစီအစဉ်ကန့်သတ်ချက်"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN ပြောင်းရန်"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"အဆင့်ပေး စနစ်များ"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"အမှတ်ပေးမှုများ"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"အဆင့်ပေးစနစ်များပြပါ"</string>
+ <string name="other_countries" msgid="8342216398676184749">"အခြားနိုင်ငံများ"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"မရှိ"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"မရှိ"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"မရှိ"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"ကန့်သတ်ချက်မြှင့်ရန်"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"ကန့်သတ်ချက်များ အလတ်"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"ကန့်သတ်ချက်များ နိမ့်"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"စိတ်တိုင်းကျ"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"ကလေးများအတွက် သင့်တော်သည့် အကြောင်းအရာ"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"ကလေးကြီးများ အတွက် သင့်သော အကြောင်းအရာ"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"ဆယ်ကျော်သက်များ အတွက် ဆီလျော်သည့် အကြောင်းအရာ"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"လက်ဖြင့် သတ်မှတ်ခဲ့သည့် ကန့်သတ်ချက်များ"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s နှင့် ထပ်ဆင့်မှတ်"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"ထပ်ဆင့်-အမှတ်ပေးမှု"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"ဒီချာနယ်ကို ကြည့်ရန် သင့် PIN ရိုက်ထည့်ပါ"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"ဒီအစီအစဉ်ကို ကြည့်ရန် သင့် PIN ရိုက်ထည့်ပါ"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"သင့် PIN ရိုက်ထည့်ပါ"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"မိဘ ထိန်းချုပ်မှုကို သတ်မှတ်ရန်၊ PIN ကို ဖန်တီးပါ"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"PIN အသစ်ကို ထည့်သွင်းရန်"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"သင့် PIN ကို အတည်ပြုပါ"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"သင့် လက်ရှိ PIN ရိုက်ထည့်ပါ"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"PIN အမှား ၅ ကြိမ် သင်ရိုက်ထည့်ခဲ့၏။\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> စက္ကန့်အကြာတွင် ထပ်မံကြိုးစားပါ။"</item>
+ <item quantity="other" msgid="8829550842387756054">"PIN အမှား ၅ ကြိမ် သင်ရိုက်ထည့်ခဲ့သည်။\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> စက္ကန့်အကြာတွင် ထပ်မံကြိုးစားပါ။"</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"ထို PIN မှာ မှားနေသည်။ ထပ်ကြိုးစားပါ။"</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"PIN မှာ မတိုက်ဆိုင်ပါ၊ ထပ်ပြီး စမ်းပါ။"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"အကြောင်း"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"အခမဲ့ ရင်းမြစ် လိုင်စင်များ"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"အခမဲ့ ရင်းမြစ် လိုင်စင်များ"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"ဗားရှင်း"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"ဤချာနယ်ကို ကြည့်ရန်၊ ညာဘက် နှိပ်ပြီး PIN ရိုက်ထည့်ပါ"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"ဤအစီအစဉ်ကို ကြည့်ရန်၊ ညာဘက် နှိပ်ပြီး PIN ရိုက်ထည့်ပါ"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"ဤအစီအစဉ်သည် အဆင့် <xliff:g id="RATING">%1$s</xliff:g> ရှိ၏။ \nဤအစီအစဉ်အား ကြည့်ရန်၊ ညာဘက် နှိပ်ပြီး PIN ရိုက်ထည့်ပါ။"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"အစီအစဉ်ကို ပိတ်ဆို့ထား"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"ဤအစီအစဉ်သည် အဆင့် <xliff:g id="RATING">%1$s</xliff:g> ရှိ၏"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"ခေါင်းစဉ် မပါ"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"ချာနယ် ပိတ်ဆို့ထား"</string>
+ <string name="episode_format" msgid="4881195874563241096">"ရာသီ<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: အပိုင်း − <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"ချန်နယ်ရင်းမြစ်များ"</string>
+ <string name="setup_description" msgid="8728423605912915099">"ရရှိနိုင်သည့်ရင်းမြစ်များမှတိုက်ရိုက်လွင့်သောချန်နယ်များသတ်မှတ်ပါ။ချန်နယ်ရင်းမြစ်အပေါ် မူတည်၍မိနစ်အနည်းငယ်ကြာနိုင်ပါသည်။"</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"လုပ်ပြီး၏"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d လိုင်းရှိပါသည်"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d လိုင်းများရှိပါသည်"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"လိုင်းမရှိပါ"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"အသစ်"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>" ရွေးချယ်ပါအားနှိပ်ပြီး"</b>" တီဗီမန်နယူးကိုဝင်ရောက်ကြည့်ရှုပါ။"</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"တီဗီ ထည့်သွင်းမှု ရှာမတွေ့ပါ။"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"တီဗီ ထည့်သွင်းမှု ရှာမတွေ့နိုင်ပါ။"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP ကို ပံ့ပိုးမထားပါ။"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP နှင့် ပြနိုင်သည့် ထည့်သွင်းစရာ မရှိပါ။"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"သင့်တော်သည့် တျူနာ အမျိုးအစား မဟုတ်ပါ။ တီဗွီ အဝင်ပေါက်အတွက် တိုက်ရိုက်လွှင့် ချန်နယ်များ app အား ဖွင့်ပါ။"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"ညှိမှု မအောင်မြင်ပါ"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"ဤလုပ်ဆောင်ချက်ကို ကိုင်တွယ်နိုင်သည့် အပလီကေးရှင်း မရှိပါ။"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"ရင်းမြစ် ချာနယ် အားလုံးကို ဝှက်ထားသည်။ \n ကြည့်ရန် အနည်းဆုံး ချာနယ် တစ်ခုကို ရွေးပါ။"</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"ဗီဒီယိုအချက်ပြ အားနည်း၍ မရနိုင်ပါ။"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"ဗီဒီယို မမျှော်လင့်ဘဲ မရရှိနိုင်ပါ။"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"BACK ကီးသည် ချိတ်ဆက် စက်ကိရိယာ အတွက်ဖြစ်၏။ ထွက်ရန် HOME ခလုတ်ကို နှိပ်ပါ။"</string>
+</resources>
diff --git a/res/values-nb/arrays.xml b/res/values-nb/arrays.xml
new file mode 100644
index 00000000..823aade1
--- /dev/null
+++ b/res/values-nb/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Full"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Alle kanaler"</item>
+ <item msgid="928298872841713530">"Familie/barn"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Shopping"</item>
+ <item msgid="167201149441442173">"Filmer"</item>
+ <item msgid="525966731464264290">"Komedie"</item>
+ <item msgid="6096710741527327836">"Reise"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Utdanning"</item>
+ <item msgid="7221999662426308394">"Dyr/villmarksliv"</item>
+ <item msgid="375300513250925001">"Nyheter"</item>
+ <item msgid="7746320336582330410">"Spill"</item>
+ <item msgid="1255741860568329178">"Kunst"</item>
+ <item msgid="7603949681065702867">"Underholdning"</item>
+ <item msgid="4453821994746804366">"Livsstil"</item>
+ <item msgid="3488534597567932843">"Musikk"</item>
+ <item msgid="7452153120614274095">"Premiere"</item>
+ <item msgid="8215762047341133299">"Teknologi/vitenskap"</item>
+ </string-array>
+</resources>
diff --git a/res/values-nb/rating_system_strings.xml b/res/values-nb/rating_system_strings.xml
new file mode 100644
index 00000000..207ee0e4
--- /dev/null
+++ b/res/values-nb/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Rusmidler"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sex"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Vold"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Rusmidler"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sex"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Vold"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Fransk"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Usømmelig dialog"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Grovt språk"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Seksuelt innhold"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Vold"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantasivold"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Filmseertall"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
new file mode 100644
index 00000000..657ba0b4
--- /dev/null
+++ b/res/values-nb/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Play-kontroller"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanaler"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Nylige kanaler"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV-alternativer"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP-alternativer"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Play-kontroller er ikke tilgjengelige for denne kanalen"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Spill av eller sett på pause"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Spol fremover"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Spol tilbake"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Neste"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Forrige"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programoversikt"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Nye kanaler er tilgjengelig"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Ingen link tilgjengelig"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Åpne <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Teksting"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Visningsmodus"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"På"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Av"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Flere lydspor"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Kanalkonfig."</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Foreldrekontroll"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Info"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Kilde"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Bytt"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"På"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Av"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Lyd"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Hovedkontroll"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP-vindu"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Utforming"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Nede til høyre"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Oppe til høyre"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Oppe til venstre"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Nede til venstre"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Side om side"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Størrelse"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Stor"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Liten"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Inndatakilde"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antenne/kabel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Ingen programinformasjon"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Ingen informasjon"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Blokkert kanal"</string>
+ <string name="default_language" msgid="4122326459624337928">"Ukjent språk"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Teksting for hørselshemmede"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Av"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Tilpass formatering"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Angi systeminnstillinger for teksting"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Visningsmodus"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Flere lydspor"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 surround-lyd"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 surround-lyd"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d kanaler"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Tilpass kanallisten"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Velg gruppe"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Opphev gruppevalget"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Gruppér etter"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Kanalkilde"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Gruppér etter"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Kanalkonfigurasjon"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Dette programmet er blokkert."</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Dette programmet er vurdert som <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Kanalkilder"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Nye kanaler er tilgjengelig"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Tilpass kanallisten"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Velg kanaler for programoversikten din"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Ikke konfigurert"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Inngang støtter ikke auto-kanalsøk"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Kunne ikke starte automatisk skanning for «<xliff:g id="TV_INPUT">%s</xliff:g>»"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Kunne ikke starte systeminnstillingene for teksting."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d kanal er lagt til"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d kanaler er lagt til"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Ingen kanaler er lagt til"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Foreldrekontroll"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"På"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Av"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Blokkerte kanaler"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Blokkér alle"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Fjern blok. for alle"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Skjulte kanaler"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Programbegrensning"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Endre PIN-kode"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Vurderingssystemer"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Vurderinger"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Se alle vurd.syst."</string>
+ <string name="other_countries" msgid="8342216398676184749">"Andre land"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Ingen"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Ingen"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Ingen"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Høye begrensninger"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Medium begrensninger"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Lave begrensninger"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Tilpasset"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Innhold egnet for barn"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Innhold egnet for eldre barn"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Innhold egnet for tenåringer"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Manuelle begrensninger"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s og ekstravurd."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Ekstravurderinger"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Skriv inn PIN-koden din for å se på denne kanalen"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Skriv inn PIN-koden din for å se på dette programmet"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Skriv inn PIN-koden din"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"For å konfigurere foreldrekontroll, angi en PIN-kode"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Skriv inn en ny PIN-kode"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Bekreft PIN-koden din"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Skriv inn den gjeldende PIN-koden din"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Du skrev inn feil PIN-kode 5 ganger.\nPrøv på nytt om <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekund."</item>
+ <item quantity="other" msgid="8829550842387756054">"Du skrev inn feil PIN-kode 5 ganger.\nPrøv på nytt om <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekunder."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Prøv på nytt, PIN-koden er feil."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Prøv på nytt, PIN-koden er feil"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Info"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Åpen kildekode-lisenser"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Åpen kildekode-lisenser"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versjon"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"For å se på denne kanalen, trykk til høyre og skriv inn PIN-koden din"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"For å se på dette programmet, trykk til høyre og skriv inn PIN-koden din"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Dette programmet har vurderingen <xliff:g id="RATING">%1$s</xliff:g>.\nFor å se på dette programmet, trykk til høyre og skriv inn PIN-koden din."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Programmet er blokkert"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Dette programmet er vurdert som <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Ingen tittel"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanalen er blokkert"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Kanalkilder"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Konfigurer live-kanaler fra de tilgjengelige kildene. Dette kan ta opptil flere minutter, avhengig av kanalkilden."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Ferdig"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d kanal er tilgjengelig"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d kanaler er tilgengelig"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Ingen kanaler er tilgjengelig"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Ny"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Trykk på «SELECT» (VELG)"</b>" for å åpne TV-menyen."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Kunne ikke finne noen TV-inngang"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Kunne ikke finne TV-inngangen"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP støttes ikke"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Det finnes ingen tilgjengelige inndata som kan vises med PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tuner-typen kan ikke brukes. Kjør Live Channels-appen med tuner-typen for TV-inndata."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Justering mislyktes."</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Kunne ikke finne noen app som kan håndtere denne handlingen."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Alle kildekanalene er skjult.\nDu må velge minst én kanal å se på."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Utilgjengelig på grunn av svakt videosignal"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Videoen er utilgjengelig på grunn av en uventet feil"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"TILBAKE-tasten er for den tilkoblede enheten. Trykk på STARTSIDE-knappen for å avslutte."</string>
+</resources>
diff --git a/res/values-ne-rNP/arrays.xml b/res/values-ne-rNP/arrays.xml
new file mode 100644
index 00000000..b75c407e
--- /dev/null
+++ b/res/values-ne-rNP/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"सामान्य"</item>
+ <item msgid="2533030282864800794">"पूर्ण"</item>
+ <item msgid="8568284598210500589">"जूम"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"सबै च्यानलहरू"</item>
+ <item msgid="928298872841713530">"पारिवार/बच्चाहरु"</item>
+ <item msgid="2751606947569857164">"खेलकुदहरू"</item>
+ <item msgid="7345749789651321496">"किनमेल"</item>
+ <item msgid="167201149441442173">"चलचित्रहरू"</item>
+ <item msgid="525966731464264290">"हास्य"</item>
+ <item msgid="6096710741527327836">"यात्रा"</item>
+ <item msgid="2851882187117833883">"नाटक"</item>
+ <item msgid="78492781188719038">"शिक्षा"</item>
+ <item msgid="7221999662426308394">"जनावर/वन्यजन्तु"</item>
+ <item msgid="375300513250925001">"समाचार"</item>
+ <item msgid="7746320336582330410">"खेल"</item>
+ <item msgid="1255741860568329178">"कलाहरू"</item>
+ <item msgid="7603949681065702867">"मनोरञ्जन"</item>
+ <item msgid="4453821994746804366">"जीवन शैली"</item>
+ <item msgid="3488534597567932843">"संगीत"</item>
+ <item msgid="7452153120614274095">"प्रिमियर"</item>
+ <item msgid="8215762047341133299">"प्रविधि/विज्ञान"</item>
+ </string-array>
+</resources>
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..a43eebdc
--- /dev/null
+++ b/res/values-ne-rNP/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"लागूपदार्थ"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"यौन"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"हिंसा"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"लागूपदार्थ"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"यौन"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"हिंसा"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"फ्रान्सेली"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"अश्लील संवाद"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"भद्दा भाषा"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"यौन सामग्री"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"हिंसा"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"काल्पनिक हिंसा"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"फिल्म मूल्याङ्कनहरू"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
new file mode 100644
index 00000000..5d4f64ec
--- /dev/null
+++ b/res/values-ne-rNP/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"लाइभ च्यानलहरू"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"मोनो"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"स्टेरियो"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"प्ले नियन्त्रणहरु"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"च्यानलहरू"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"हालैका च्यानलहरू"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"टिभी विकल्पहरू"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP विकल्पहरू"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"यस च्यानलका लागि प्ले नियन्त्रणहरू अनुपलब्ध"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"प्ले वा पज"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"फास्ट फर्वार्ड"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"रिवाइन्ड"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"अर्को"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"अघिल्लो"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"कार्यक्रम गाइड"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"नयाँ च्यानलहरू उपलब्ध"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"कुनै लिंक उपलब्ध छैनन्"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> खोल्नुहोस्"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"बन्द क्याप्सनहरु"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"डिस्प्ले मोड"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"खुला"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"बन्द"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"मल्टि-अडियो"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"च्यानल सेटअप"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"मुख्य नियन्त्रणहरू"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"बारेमा"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"स्रोत"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"स्वाप"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"खुला"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"बन्द"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"आवाज"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"मुख्य"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP सन्झ्याल"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"लेआउट"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"तल्लो दायाँ"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"माथिल्लो दायाँ"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"माथिल्लो बायाँ"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"तल्लो बायाँ"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"सँगसँगै"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"आकार"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"ठूलो"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"सानो"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"इनपुट स्रोत"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"टिभी (एन्टेना/केबल)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"कुनै पनि कार्यक्रम जानकारी छैन"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"केही सूचना छैन"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"अवरुद्ध च्यानल"</string>
+ <string name="default_language" msgid="4122326459624337928">"अज्ञात भाषा"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"उपशीर्षक"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"बन्द गर्नुहोस्"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"फर्‍म्याटिङ अनुकूलित"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"बन्द क्याप्सनका लागि प्रणाली-विस्तृत प्राथमिकताहरू सेट गर्नुहोस्"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"डिस्प्ले मोड"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"मल्टि-अडियो"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"मोनो"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"स्टेरियो"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"५.१ सराउन्ड"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"७.१ सराउन्ड"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d च्यानलहरु"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"च्यानल सूची अनुकूलन गर्नुहोस्"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"समूह छान्नुहोस्"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"समूह अचयन नगर्नुहोस्"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"द्वारा समूह"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"च्यानल स्रोत"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"द्वारा समूह"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"च्यानल सेटअप"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"यो कार्यक्रम निषेध गरिएको छ।"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"यो कार्यक्रम मूल्याङ्कन <xliff:g id="RATING">%1$s</xliff:g> गरिएको छ"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"च्यानल स्रोतहरू"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"नयाँ च्यानलहरू उपलब्ध छन्"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"च्यानल सूची अनुकूलन गर्नुहोस्"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"आफ्नो कार्यक्रम गाइडका लागि च्यानलहरू छनौट गर्नुहोस्"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"सेटअप छैन"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"इनपुटले स्वतः स्क्यान समर्थन गर्दैन"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\'<xliff:g id="TV_INPUT">%s</xliff:g>\' का लागि स्वतः स्क्यान सुरु गर्न असमर्थ"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"बन्द क्याप्सनका लागि प्रणाली-विस्तृत प्राथमिकताहरू सुरु गर्न असमर्थ।"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d च्यानल थपियो"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d च्यानल थपियो"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"कुनै पनि च्यानल थपिएन"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"ट्यूनर"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"मुख्य नियन्त्रणहरू"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"खोल्नुहोस्"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"बन्द गर्नुहोस्"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"च्यानलहरू अवरुद्ध"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"सबै ब्लक गर्नुहोस्"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"सबै अनब्लक गर्नुहोस्"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"लुकेका च्यानलहरू"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"कार्यक्रम प्रतिबन्धहरू"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN फेर्नुहोस्"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"मूल्याङ्कन प्रणालीहरू"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"मूल्याङ्कन"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"सबै मूल्याङ्कन प्रणाली हेर्नुहोस्"</string>
+ <string name="other_countries" msgid="8342216398676184749">"अन्य देशहरू"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"कुनै पनि होइन"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"कुनै पनि होइन"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"कुनै पनि होइन"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"उच्च प्रतिबन्धहरू"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"मध्यम प्रतिबन्धहरू"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"न्यून प्रतिबन्धहरू"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"अनुकूलन"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"बालबालिकाका लागि उपयुक्त सामग्री"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"ठूला बच्चाहरुका लागि उपयुक्त सामग्री"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"किशोर किशोरीहरुका लागि उपयुक्त सामग्री"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"मेनुअल प्रतिबन्धहरु"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s र सहायक स्तर निर्धारण"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"सहायक स्तर निर्धारण"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"यो च्यानल हेर्न तपाईँको PIN प्रविष्टि गर्नुहोस्"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"यो कार्यक्रम हेर्न तपाईँको PIN प्रविष्टि गर्नुहोस्"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"आफ्नो पिन प्रविष्ट गर्नुहोस्"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"अभिभावकत्वको नियन्त्रण सेट गर्न, एउटा PIN सिर्जना गर्नुहोस्"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"नयाँ PIN प्रविष्टि गर्नुहोस्"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"तपाईँको PIN निश्चित गर्नुहोस्"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"तपाईंको हालको पिन प्रविष्ट गर्नुहोस्"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"तपाईँले गलत PIN ५ पटक प्रविष्टि गर्नुभयो।\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> सेकेन्डमा पुन: प्रयास गर्नुहोस्।"</item>
+ <item quantity="other" msgid="8829550842387756054">"तपाईँले गलत PIN ५ पटक प्रविष्टि गर्नुभयो।\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> सेकेन्डमा पुन: प्रयास गर्नुहोस्।"</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"त्यो PIN गलत थियो। पुनः प्रयास गर्नुहोस्।"</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"पुनः प्रयास गर्नुहोस्, PIN मेल खाँदैन"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"बारेमा"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"खुला स्रोत इजाजतपत्रहरू"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"खुला स्रोत इजाजतपत्रहरू"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"संस्करण"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"यो च्यानल हेर्न, दायाँ प्रेस गर्नुहोस् र आफ्नो पिन प्रविष्ट गर्नुहोस्"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"यो कार्यक्रम हेर्न, दायाँ प्रेस गर्नुहोस् र आफ्नो पिन प्रविष्ट गर्नुहोस्"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"यो कार्यक्रम मूल्याङ्कन <xliff:g id="RATING">%1$s</xliff:g> गरिएको छ।\nयो कार्यक्रम हेर्न, दायाँ थिच्नुहोस् र आफ्नो PIN प्रविष्ट गर्नुहोस्।"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"प्रोग्राम ब्लक गरिएको छ"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"यो कार्यक्रम मूल्याङ्कन <xliff:g id="RATING">%1$s</xliff:g> गरिएको छ"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"शीर्षक छैन"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"च्यानल अवरुद्ध"</string>
+ <string name="episode_format" msgid="4881195874563241096">"संस्करण <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: भाग <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"च्यानल स्रोतहरू"</string>
+ <string name="setup_description" msgid="8728423605912915099">"उपलब्ध स्रोतहरूबाट लाइभ च्यानलहरू सेट अप गर्नुहोस्। च्यानल स्रोत अनुसार केहि मिनेट लाग्‍न सक्छ।"</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"सम्पन्न भयो"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d च्यानल उपलब्ध"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d च्यानलहरू उपलब्ध"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"कुनै च्यानलहरू उपलब्ध छैनन्"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"नयाँ"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"टिभी मेनु खोल्न "<b>"SELECT थिच्नुहोस्"</b>"।"</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"कुनै पनि टिभी स्रोत भेटिएन।"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"टिभी स्रोत भेटिएन लागेन"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP समर्थित छैन"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP सँग देखाउन सकिने कुनै स्रोत उपलब्ध छैन।"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"ट्यूनर प्रकार अनुपयुक्त। कृपया ट्यूनर प्रकार टिभी स्रोतको लागि Live Channels को अनुप्रयोग सुरु गर्नुहोस।"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"ट्युन गर्न असफल"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"यो कार्य सम्हाल्न कुनै पनि अनुप्रयोग भेटिएन।"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"सबै स्रोत च्यानलहरू लुकेको छन्। \n हेर्नको लागि कम्तिमा एक च्यानल चयन गर्नुहोस्।"</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"कमजोर भिडियो संकेतका कारण अनुपलब्ध।"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"भिडियो अप्रत्याशित रूपमा अनुपलब्ध।"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"BACK कुञ्जी जडान भएका उपकरणका लागि हो। बाहिर निस्कनका लागि गृह बटन थिच्नुहोस्।"</string>
+</resources>
diff --git a/res/values-nl/arrays.xml b/res/values-nl/arrays.xml
new file mode 100644
index 00000000..0cc63ed4
--- /dev/null
+++ b/res/values-nl/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normaal"</item>
+ <item msgid="2533030282864800794">"Volledig"</item>
+ <item msgid="8568284598210500589">"Zoomen"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Alle kanalen"</item>
+ <item msgid="928298872841713530">"Familie/kinderen"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Winkelen"</item>
+ <item msgid="167201149441442173">"Films"</item>
+ <item msgid="525966731464264290">"Komedie"</item>
+ <item msgid="6096710741527327836">"Reizen"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Onderwijs"</item>
+ <item msgid="7221999662426308394">"Dieren/natuur"</item>
+ <item msgid="375300513250925001">"Nieuws"</item>
+ <item msgid="7746320336582330410">"Games"</item>
+ <item msgid="1255741860568329178">"Kunst"</item>
+ <item msgid="7603949681065702867">"Amusement"</item>
+ <item msgid="4453821994746804366">"Lifestyle"</item>
+ <item msgid="3488534597567932843">"Muziek"</item>
+ <item msgid="7452153120614274095">"Premier"</item>
+ <item msgid="8215762047341133299">"Techniek/wetenschap"</item>
+ </string-array>
+</resources>
diff --git a/res/values-nl/rating_system_strings.xml b/res/values-nl/rating_system_strings.xml
new file mode 100644
index 00000000..c74b643a
--- /dev/null
+++ b/res/values-nl/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drugs"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Seks"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Geweld"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drugs"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Seks"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Geweld"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Frans"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Suggestieve dialogen"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Grof taalgebruik"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Pornografische inhoud"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Geweld"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Niet-realistisch geweld"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Filmclassificaties"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
new file mode 100644
index 00000000..e24679d5
--- /dev/null
+++ b/res/values-nl/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live kanalen"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Afspeelknoppen"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanalen"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Recente kanalen"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Tv-opties"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP-opties"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Er zijn geen afspeelknoppen beschikbaar voor dit kanaal"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Afspelen of onderbreken"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Vooruitspoelen"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Terugspoelen"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Volgende"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Vorige"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programmagids"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Nieuwe kanalen beschikbaar"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Geen link beschikbaar"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> openen"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Ondertiteling"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Weergavemodus"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Aan"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Uit"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Kanaalconfiguratie"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Ouderlijk toezicht"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Over"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Bron"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Wisselen"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Aan"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Uit"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Geluid"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Hoofd"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP-venster"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Lay-out"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Rechtsonder"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Rechtsboven"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Linksboven"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Linksonder"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Naast elkaar"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Formaat"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Groot"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Klein"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Invoerbron"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Tv (antenne/kabel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Geen programmagegevens"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Geen informatie"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Geblokkeerd kanaal"</string>
+ <string name="default_language" msgid="4122326459624337928">"Onbekende taal"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Ondertiteling"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Uit"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Opmaak aanpassen"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Ondertit.voorkeur v. systeem inst."</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Weergavemodus"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1-kanaals surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1-kanaals surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d kanalen"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Kanaallijst aanp."</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Groep selecteren"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Groep deselecteren"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Groeperen op"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Kanaalbron"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Groeperen op"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Kanaalconfiguratie"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Dit programma is geblokkeerd"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Dit programma is beoordeeld als <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Kanaalbronnen"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Nieuwe kanalen beschikbaar"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Kanaallijst aanp."</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Kanalen kiezen voor uw programmagids"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Niet ingesteld"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"De invoer biedt geen ondersteuning voor automatisch scannen"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Kan automatische scan voor \'<xliff:g id="TV_INPUT">%s</xliff:g>\' niet starten"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Kan de ondertitelingsvoorkeuren voor het hele systeem niet starten."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d kanaal toegevoegd"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d kanalen toegevoegd"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Geen kanalen toegevoegd"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Ouderlijk toezicht"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Aan"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Uit"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Geblokk. kanalen"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Alles blokkeren"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Alle blokk. opheffen"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Verborgen kanalen"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Progr.beperkingen"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Pincode wijzigen"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Kijkwijzers"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Beoordelingen"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Alle beoord.syst. wrg."</string>
+ <string name="other_countries" msgid="8342216398676184749">"Andere landen"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Geen"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Geen"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Geen"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Strenge beperkingen"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Gem. beperkingen"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Lage beperkingen"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Aangepast"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Inhoud geschikt voor kinderen"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Inhoud geschikt voor oudere kinderen"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Inhoud geschikt voor tieners"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Handmatige beperkingen"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s + subkijkwijzer"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Subkijkwijzer"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Uw pincode opgeven om dit kanaal te bekijken"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Uw pincode opgeven om dit programma te bekijken"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Uw pincode opgeven"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Stel een pincode in om ouderlijk toezicht in te stellen"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Nieuwe pincode opgeven"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Uw pincode bevestigen"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Uw huidige pincode opgeven"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"U heeft vijf keer een verkeerde pincode ingevoerd.\nProbeer het over <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> seconde opnieuw."</item>
+ <item quantity="other" msgid="8829550842387756054">"U heeft vijf keer een verkeerde pincode ingevoerd.\nProbeer het over <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> seconden opnieuw."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Die pincode is onjuist. Probeer het opnieuw."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Probeer het opnieuw; de pincode komt niet overeen"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Over"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Open-sourcelicenties"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Open-sourcelicenties"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versie"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Als u dit kanaal wilt bekijken, drukt u rechts en geeft u uw pincode op"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Als u dit programma wilt bekijken, drukt u rechts en geeft u uw pincode op"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Dit programma is beoordeeld als <xliff:g id="RATING">%1$s</xliff:g>.\nAls u dit programma wilt bekijken, drukt u rechts en geeft u uw pincode op."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Programma is geblokkeerd"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Dit programma is beoordeeld als <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Geen titel"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanaal geblokkeerd"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S. <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: afl. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Kanaalbronnen"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Configureer live kanalen van de beschikbare bronnen. Dit kan enkele minuten duren afhankelijk van de kanaalbron."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Gereed"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d kanaal beschikbaar"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d kanalen beschikbaar"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Geen kanalen beschikbaar"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nieuw"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Druk op SELECTEREN"</b>" voor toegang tot het tv-menu."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Geen tv-invoer gevonden"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Kan de tv-invoer niet vinden"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP wordt niet ondersteund"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Geen beschikbare invoer die kan worden weergegeven met PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tunertype is geen geschikte optie. Start de app Live kanalen voor tv-invoer van het type tuner."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Afstemmen mislukt"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Er is geen app gevonden om deze actie uit te voeren."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Alle bronkanalen zijn verborgen.\nSelecteer ten minste één kanaal om te bekijken."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Niet beschikbaar wegens zwak videosignaal"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"De video is onverwacht niet beschikbaar"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"De toets TERUG is voor verbonden apparaten. Druk op de knop HOME om te sluiten."</string>
+</resources>
diff --git a/res/values-pl/arrays.xml b/res/values-pl/arrays.xml
new file mode 100644
index 00000000..d7e052c9
--- /dev/null
+++ b/res/values-pl/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normalny"</item>
+ <item msgid="2533030282864800794">"Pełny"</item>
+ <item msgid="8568284598210500589">"Powiększenie"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Wszystkie kanały"</item>
+ <item msgid="928298872841713530">"Rodzina/dzieci"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Zakupy"</item>
+ <item msgid="167201149441442173">"Filmy"</item>
+ <item msgid="525966731464264290">"Komedia"</item>
+ <item msgid="6096710741527327836">"Podróże"</item>
+ <item msgid="2851882187117833883">"Dramat"</item>
+ <item msgid="78492781188719038">"Edukacja"</item>
+ <item msgid="7221999662426308394">"Zwierzęta/natura"</item>
+ <item msgid="375300513250925001">"Wiadomości"</item>
+ <item msgid="7746320336582330410">"Gry"</item>
+ <item msgid="1255741860568329178">"Sztuka"</item>
+ <item msgid="7603949681065702867">"Rozrywka"</item>
+ <item msgid="4453821994746804366">"Styl życia"</item>
+ <item msgid="3488534597567932843">"Muzyka"</item>
+ <item msgid="7452153120614274095">"Specjalne"</item>
+ <item msgid="8215762047341133299">"Nauka i technika"</item>
+ </string-array>
+</resources>
diff --git a/res/values-pl/rating_system_strings.xml b/res/values-pl/rating_system_strings.xml
new file mode 100644
index 00000000..bae72985
--- /dev/null
+++ b/res/values-pl/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Narkotyki"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Seks"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Przemoc"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Narkotyki"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Seks"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Przemoc"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"francuski"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Dwuznaczne dialogi"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Wulgarny język"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Treści pornograficzne"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Przemoc"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Bajkowa przemoc"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Klasyfikacja wiekowa filmów"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
new file mode 100644
index 00000000..3e856cb2
--- /dev/null
+++ b/res/values-pl/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Kanały na żywo"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Sterowanie odtwarzaniem"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanały"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Ostatnie kanały"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Opcje TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Opcje PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Elementy sterujące Play są niedostępne dla tego kanału"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Odtwórz lub wstrzymaj"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Przewiń do przodu"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Przewiń do tyłu"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Dalej"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Wstecz"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Przewodnik TV"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Dostępne są nowe kanały"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Brak linku"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Otwórz <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Napisy"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Tryb wyświetl."</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Włączony"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Wyłączony"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Wiele kan. audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Konfiguracja kanału"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Kontr. rodzic."</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Informacje"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Źródło"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Przełącz"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Włączone"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Wyłączone"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Dźwięk"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Główne okno"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Okno PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Układ"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Prawy dolny róg"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Prawy górny róg"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Lewy górny róg"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Lewy dolny róg"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Obok siebie"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Rozmiar"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Duży"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Mały"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Źródło sygnału"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antena/kabel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Brak informacji o programach"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Brak informacji"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Kanał zablokowany"</string>
+ <string name="default_language" msgid="4122326459624337928">"Nieznany język"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Napisy"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Wył."</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Dostosuj format"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Określ ogólne ustawienia napisów"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Tryb wyświetlania"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Wiele kan. audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"Kanały: %d"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Dostosuj listę"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Wybierz grupę"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Odznacz grupę"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Grupuj według"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Źródło kanału"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Grupuj według"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Konfiguracja kanału"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Ten program jest zablokowany"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Ten program ma ocenę <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Źródła kanałów"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Dostępne są nowe kanały"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Dostosuj listę"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Wybierz kanały do przewodnika TV"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Nieskonfigurowany"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Wejście nie obsługuje automatycznego skanowania"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Nie można rozpocząć automatycznego skanowania dla „<xliff:g id="TV_INPUT">%s</xliff:g>”"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Nie można otworzyć systemowych ustawień napisów."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d kanał został dodany"</item>
+ <item quantity="other" msgid="1078861616751739285">"Dodane kanały: %1$d"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Nie dodano kanałów"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Kontr. rodziciel."</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Wł."</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Wył."</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Kanały zablokowane"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Zablokuj wszystkie"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Odblokuj wszystkie"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Ukryte kanały"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Ograniczenia"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Zmień kod PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Systemy ocen"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Oceny"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Wszystkie systemy ocen"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Inne kraje"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Brak"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Brak"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Brak"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Duże ograniczenia"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Średnie ograniczenia"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Małe ograniczenia"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Niestandardowe"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Treści odpowiednie dla dzieci"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Treści odpowiednie dla starszych dzieci"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Treści odpowiednie dla nastolatków"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Ręczne ograniczenia"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s i oceny podrzędne"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Oceny podrzędne"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Wpisz kod PIN, by oglądać ten kanał"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Wpisz kod PIN, by oglądać ten program"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Wpisz kod PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Aby ustawić kontrolę rodzicielską, utwórz PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Wpisz nowy PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Potwierdź kod PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Wpisz bieżący kod PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Wpisałeś nieprawidłowy kod PIN 5 razy.\nSpróbuj ponownie za <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> s."</item>
+ <item quantity="other" msgid="8829550842387756054">"Wpisałeś nieprawidłowy kod PIN 5 razy.\nSpróbuj ponownie za <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> s."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Nieprawidłowy PIN. Spróbuj ponownie"</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Spróbuj ponownie. Niezgodny kod PIN"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Informacje"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Licencje open source"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Licencje open source"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Wersja"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Aby oglądać ten kanał, naciśnij Prawo i wpisz kod PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Aby oglądać ten program, naciśnij Prawo i wpisz kod PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Ten program ma ocenę <xliff:g id="RATING">%1$s</xliff:g>.\nAby go oglądać, naciśnij Prawo i wpisz kod PIN"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Program jest zablokowany"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Ten program ma ocenę <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Bez tytułu"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanał zablokowany"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>E<xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> – <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Źródła kanałów"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Skonfiguruj kanały na żywo, korzystając z dostępnych źródeł. Może to potrwać kilka minut w zależności od źródła kanału."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Gotowe"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Dostępny jest %1$d kanał"</item>
+ <item quantity="other" msgid="2386588423841837714">"Dostępne kanały: %1$d"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Brak dostępnych kanałów"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nowe"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Naciśnij SELECT"</b>", by otworzyć menu TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Nie znaleziono wejścia TV"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Nie można znaleźć wejścia TV"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Tryb PIP nie jest obsługiwany"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Brak sygnału wejściowego do wyświetlenia w trybie PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Nieodpowiedni typ tunera. Uruchom aplikację Kanały na żywo na jego wejściu TV."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Strojenie nie powiodło się"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Nie znaleziono aplikacji do obsługi tego działania."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Wszystkie kanały źródłowe są ukryte.\nWybierz co najmniej jeden kanał do oglądania."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Niedostępne z powodu słabego sygnału wideo"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Sygnał wideo jest nieoczekiwanie niedostępny"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Klawisz BACK steruje podłączonym urządzeniem. Naciśnij przycisk HOME, by wyjść."</string>
+</resources>
diff --git a/res/values-pt-rPT/arrays.xml b/res/values-pt-rPT/arrays.xml
new file mode 100644
index 00000000..27324526
--- /dev/null
+++ b/res/values-pt-rPT/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Completo"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Todos os canais"</item>
+ <item msgid="928298872841713530">"Família/infantil"</item>
+ <item msgid="2751606947569857164">"Desporto"</item>
+ <item msgid="7345749789651321496">"Compras"</item>
+ <item msgid="167201149441442173">"Filmes"</item>
+ <item msgid="525966731464264290">"Comédia"</item>
+ <item msgid="6096710741527327836">"Viagens"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Educação"</item>
+ <item msgid="7221999662426308394">"Animais/vida selv."</item>
+ <item msgid="375300513250925001">"Notícias"</item>
+ <item msgid="7746320336582330410">"Jogos"</item>
+ <item msgid="1255741860568329178">"Arte"</item>
+ <item msgid="7603949681065702867">"Entretenimento"</item>
+ <item msgid="4453821994746804366">"Estilo de vida"</item>
+ <item msgid="3488534597567932843">"Música"</item>
+ <item msgid="7452153120614274095">"Estreia"</item>
+ <item msgid="8215762047341133299">"Tecnologia/ciência"</item>
+ </string-array>
+</resources>
diff --git a/res/values-pt-rPT/rating_system_strings.xml b/res/values-pt-rPT/rating_system_strings.xml
new file mode 100644
index 00000000..abd1a3e9
--- /dev/null
+++ b/res/values-pt-rPT/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drogas"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sexo"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Violência"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drogas"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sexo"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Violência"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Francês"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Diálogo sugestivo"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Linguagem grosseira"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Conteúdos de natureza sexual"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Violência"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Violência de fantasia"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Classificações de filmes"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
new file mode 100644
index 00000000..ada00a5b
--- /dev/null
+++ b/res/values-pt-rPT/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Canais em direto"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"estéreo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Controlos de reprodução"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Canais"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Canais recentes"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Opções de TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Opções de PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Controlos de reprodução indisponíveis para este canal"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Reproduzir ou colocar em pausa"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Avançar"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Recuar"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Seguinte"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Anterior"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Guia de programação"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Novos canais dispon."</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Nenhum link disponível."</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Abrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Legendas"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Modo de apres."</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Ativado"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Desativado"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multiáudio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Config. canais"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Contr. Parental"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Acerca de"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Fonte"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Alternar"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Ativado"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Desativado"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Som"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Principal"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Janela de PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Esquema"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Canto inf. dir."</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Canto sup. dir."</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Canto sup. esq."</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Canto inf. esq."</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Lado a lado"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Tamanho"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Grande"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Pequeno"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Fonte de entrada"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antena/cabo)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Sem informação de programação"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Sem informações"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Canal bloqueado"</string>
+ <string name="default_language" msgid="4122326459624337928">"Idioma desconhecido"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Legendas"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Desativado"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Person. a formatação"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Definir pref. ao nível do sistema p/ legendas"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Modo de apres."</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multiáudio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"estéreo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Surround 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Surround 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d canais"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Personal. lista canais"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Selecionar grupo"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Desmarcar grupo"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Agrupar por"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Fonte do canal"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Agrupar por"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Config. canais"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Este programa está bloqueado"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Este programa tem a classificação <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Fontes dos canais"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Novos canais disponíveis"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Pers. lista canais"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Escolher canais para guia de programação"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Não configurado"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"A entrada não suporta procura automática"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Não é possível iniciar a verificação automática para \"<xliff:g id="TV_INPUT">%s</xliff:g>\""</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Não é possível iniciar as preferências do sistema para as legendas."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d canal adicionado"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d canais adicionados"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Nenhum canal adicionado"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Sintonizador"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Controlo parental"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Ativado"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Desativado"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Canais bloqueados"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Bloquear tudo"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Desbloquear tudo"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Canais ocultos"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Restr. de progr."</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Alterar PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Sist. de classif."</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Classificações"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Ver td. sist. class."</string>
+ <string name="other_countries" msgid="8342216398676184749">"Outros países"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Nenhum"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Nenhum"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Nenhum"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Restrições elevadas"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Restrições médias"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Restrições reduzidas"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Personalizado"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Conteúdo adequado para crianças"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Conteúdo adeq. para crianças mais velhas"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Conteúdo adequado para adolescentes"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Restrições manuais"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s e subclassific."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Subclassificações"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Introduzir o PIN para ver este canal"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Introduzir o PIN para ver este programa"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Introduzir o PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Para definir os controlos parentais, crie um PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Introduzir o novo PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Confirmar o PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Introduzir o PIN atual"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Introduziu o PIN incorreto 5 vezes.\nTente novamente daqui a <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segundo."</item>
+ <item quantity="other" msgid="8829550842387756054">"Introduziu o PIN incorreto 5 vezes.\nTente novamente daqui a <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segundos."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Esse PIN estava errado. Tente novamente."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Tente novamente, o PIN não corresponde"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Acerca de"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Licenças de código aberto"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Licenças de código aberto"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versão"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Para ver este canal, prima Direito e introduza o PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Para ver este programa, prima Direito e introduza o PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Este programa tem a classificação <xliff:g id="RATING">%1$s</xliff:g>.\nPara ver este programa, prima Direito e introduza o PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"O programa está bloqueado"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Este programa tem a classificação <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Sem título"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Canal bloqueado"</string>
+ <string name="episode_format" msgid="4881195874563241096">"T<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Fontes dos canais"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Configure canais em direto a partir das fontes disponíveis. Esta ação pode demorar vários minutos consoante a fonte do canal."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Concluído"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d canal disponível"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d canais disponíveis"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Nenhum canal disponível"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nova"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Prima SELECT"</b>" para aceder ao menu da TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Nenhuma entrada de TV encontrada"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Não é possível localizar a entrada de TV"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Não é suportada a opção PIP"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Nenhuma entrada disponível para apresentar com PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tipo de sintonizador inadequado. Inicie a aplicação Canais em direito para a entrada de TV do tipo de sintonizador."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Falha ao sintonizar"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Não foram encontradas aplicações para executar esta ação."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Todos os canais de origem estão ocultos.\nSelecione pelo menos um canal para ver."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Não disponível devido ao fraco sinal de vídeo"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"O vídeo encontra-se inesperadamente não disponível"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"A tecla RETROCEDER destina-se ao dispositivo ligado. Prima o botão INÍCIO para sair."</string>
+</resources>
diff --git a/res/values-pt/arrays.xml b/res/values-pt/arrays.xml
new file mode 100644
index 00000000..8b3a7693
--- /dev/null
+++ b/res/values-pt/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Tela cheia"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Todos os canais"</item>
+ <item msgid="928298872841713530">"Família/crianças"</item>
+ <item msgid="2751606947569857164">"Esportes"</item>
+ <item msgid="7345749789651321496">"Compras"</item>
+ <item msgid="167201149441442173">"Filmes"</item>
+ <item msgid="525966731464264290">"Comédia"</item>
+ <item msgid="6096710741527327836">"Viagem"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Educação"</item>
+ <item msgid="7221999662426308394">"Anim./vida selvagem"</item>
+ <item msgid="375300513250925001">"Notícias"</item>
+ <item msgid="7746320336582330410">"Jogos"</item>
+ <item msgid="1255741860568329178">"Artes"</item>
+ <item msgid="7603949681065702867">"Entretenimento"</item>
+ <item msgid="4453821994746804366">"Estilo de vida"</item>
+ <item msgid="3488534597567932843">"Música"</item>
+ <item msgid="7452153120614274095">"Alto nível"</item>
+ <item msgid="8215762047341133299">"Tecnologia/ciência"</item>
+ </string-array>
+</resources>
diff --git a/res/values-pt/rating_system_strings.xml b/res/values-pt/rating_system_strings.xml
new file mode 100644
index 00000000..41ad9024
--- /dev/null
+++ b/res/values-pt/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drogas"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sexo"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Violência"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drogas"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sexo"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Violência"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Francês"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Diálogo sugestivo"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Linguagem vulgar"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Conteúdo sexual"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Violência"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Violência em contexto fantástico"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Classificações de filmes"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
new file mode 100644
index 00000000..6d8a16a2
--- /dev/null
+++ b/res/values-pt/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Canais ao vivo"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"estéreo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Controles de reprodução"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Canais"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Canais recentes"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Opções da TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Opções de PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Controles de reprodução indisponíveis para este canal"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Reproduzir ou pausar"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Avançar"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Retroceder"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Próxima"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Anterior"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Guia de programação"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Novos canais disponíveis"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Nenhum link disponível"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Abrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Closed captions"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Modo de exibiç."</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Ativado"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Desativado"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Múltip. áudios"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Configuração de canal"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Contr. p/ os pais"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Sobre"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Origem"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Trocar"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Ativadas"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Desativadas"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Som"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Principal"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Janela de PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Layout"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Inferior direito"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Superior direito"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Superior esquerdo"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Inferior esquerdo"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Lado a lado"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Tamanho"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Grande"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Pequeno"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Origem da entrada"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antena/a cabo)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Nenhuma informação sobre o programa"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Nenhuma informação"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Canal bloqueado"</string>
+ <string name="default_language" msgid="4122326459624337928">"Idioma desconhecido"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Closed captions"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Desativado"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Person. formatação"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Definir preferências gerais do sistema para closed captions"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Modo de exibição"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Múltiplos áudios"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"estéreo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Surround 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Surround 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d canais"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Pers. lista canais"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Selecionar grupo"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Desmarcar grupo"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Agrupar por"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Fonte do canal"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"Alta definição"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"Padrão"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Agrupar por"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Configuração de canal"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Este programa está bloqueado"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Este programa foi classificado como <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Fontes do canal"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Novos canais disponíveis"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Pers. lista canais"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Escolher canais para guia de programação"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Não configurado"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"A entrada não suporta verificação automática"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Não foi possível iniciar a verificação automática de \"<xliff:g id="TV_INPUT">%s</xliff:g>\""</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Não foi possível iniciar as preferências do sistema para closed captions."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d canal adicionado"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d canais adicionados"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Nenhum canal adicionado"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Sintonizador"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Contr. p/ os pais"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Ativado"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Desativado"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Canais bloqueados"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Bloquear todos"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Desbloquear todos"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Canais ocultos"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Restr. de progr."</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Alterar PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Sist. de classif."</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Classificações"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Ver sist. de class."</string>
+ <string name="other_countries" msgid="8342216398676184749">"Outros países"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Nenhum"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Nenhum"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Nenhum"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Restrições altas"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Restrições médias"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Restrições baixas"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Personalizado"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Conteúdo adequado para crianças"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Conteúdo adequado para crianças maiores"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Conteúdo adequado para adolescentes"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Restrições manuais"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s e sub-classif."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Sub-classificações"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Digite seu PIN para assistir a este canal"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Digite seu PIN para assistir a este programa"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Informe seu PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Para definir os controles para os pais, crie um PIN."</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Inserir novo PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Confirme seu PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Informe seu PIN atual"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Um PIN incorreto foi digitado cinco vezes.\nTente novamente em <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segundo."</item>
+ <item quantity="other" msgid="8829550842387756054">"Um PIN incorreto foi digitado cinco vezes.\nTente novamente em <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segundos."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"O PIN estava errado. Tente novamente."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Tente novamente, o PIN não corresponde"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Sobre"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Licenças de código aberto"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Licenças de código aberto"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versão"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Para assistir a este canal, pressione para a direita e digite o PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Para assistir a este programa, pressione para a direita e digite o PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Este programa foi classificado como <xliff:g id="RATING">%1$s</xliff:g>.\nPara assistir a este programa, pressione para a direita e digite o PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Programa bloqueado"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Este programa foi classificado como <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Sem título"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Canal bloqueado"</string>
+ <string name="episode_format" msgid="4881195874563241096">"T<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Fontes do canal"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Defina canais ao vivo a partir das fontes disponíveis. Isso pode demorar alguns minutos, dependendo da fonte do canal."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Concluído"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d canal disponível"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d canais disponíveis"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Nenhum canal disponível"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Novas"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Pressione \"SELECIONAR\""</b>" para acessar o menu da TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Nenhuma entrada de TV encontrada"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Não foi possível encontrar a entrada de TV"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP não é suportado"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Não há entrada disponível que possa ser mostrada com PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tipo de sintonizador não adequado. Inicie o app \"Canais ao vivo\" para abrir o tipo de sintonizador para entrada de TV."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Falha na sintonia"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Nenhum app foi encontrado para executar esta ação."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Todos os canais de origem estão ocultos.\nSelecione pelo menos um canal para assistir."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Indisponível devido a um sinal fraco de vídeo"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"O vídeo está inesperadamente indisponível"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"A tecla VOLTAR é para dispositivos conectados. Pressione o botão INÍCIO para sair."</string>
+</resources>
diff --git a/res/values-ro/arrays.xml b/res/values-ro/arrays.xml
new file mode 100644
index 00000000..ef2562ef
--- /dev/null
+++ b/res/values-ro/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Complet"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Toate canalele"</item>
+ <item msgid="928298872841713530">"Familie/Copii"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Cumpărături"</item>
+ <item msgid="167201149441442173">"Filme"</item>
+ <item msgid="525966731464264290">"Comedie"</item>
+ <item msgid="6096710741527327836">"Călătorii"</item>
+ <item msgid="2851882187117833883">"Dramă"</item>
+ <item msgid="78492781188719038">"Educație"</item>
+ <item msgid="7221999662426308394">"Animale/Faună"</item>
+ <item msgid="375300513250925001">"Știri"</item>
+ <item msgid="7746320336582330410">"Jocuri"</item>
+ <item msgid="1255741860568329178">"Artă"</item>
+ <item msgid="7603949681065702867">"Divertisment"</item>
+ <item msgid="4453821994746804366">"Stil de viață"</item>
+ <item msgid="3488534597567932843">"Muzică"</item>
+ <item msgid="7452153120614274095">"Premiere"</item>
+ <item msgid="8215762047341133299">"Tehnică/Știință"</item>
+ </string-array>
+</resources>
diff --git a/res/values-ro/rating_system_strings.xml b/res/values-ro/rating_system_strings.xml
new file mode 100644
index 00000000..060932b9
--- /dev/null
+++ b/res/values-ro/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Droguri"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sex"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Violență"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Droguri"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sex"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Violență"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Franceză"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Dialog sugestiv sexual"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Limbaj grosolan"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Conținut sexual"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Violență"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Violență ficțională"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Evaluările filmelor"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
new file mode 100644
index 00000000..4b82c720
--- /dev/null
+++ b/res/values-ro/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Canale live"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Comenzi de redare"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Canale"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Canale recente"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Opțiuni TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Opțiuni PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Comenzile de redare nu sunt disponibile pentru acest canal"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Redați sau întrerupeți redarea"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Derulați rapid înainte"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Derulați înapoi"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Înainte"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Înapoi"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Ghid de programe"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Noi canale disponibile"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Niciun link disponibil"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Deschideți <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Subtitrări"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Mod de afișare"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Activat"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Dezactivat"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Configurați canal"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Control parent."</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Despre"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Sursă"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Schimbați"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Activat"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Dezactivat"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Sunet"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Principală"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Fereastră PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Aspect"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Dreapta jos"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Dreapta sus"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Stânga sus"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Stânga jos"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Alăturat"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Dimensiuni"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Mare"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Mic"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Sursă de intrare"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antenă/cablu)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Nu există informații despre program"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Nicio informație"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Canal blocat"</string>
+ <string name="default_language" msgid="4122326459624337928">"Limbă necunoscută"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Subtitrări"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Dezactivat"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Personaliz. format."</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Setați preferințe sistem pt. subtitrări"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Mod de afișare"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Surround 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Surround 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d (de) canale"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Person. listă canale"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Selectați un grup"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Deselectați grupul"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Grupați după"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Sursa canalului"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Grupați după"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Configurați canal"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Acest program este blocat"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Acest program este clasificat ca <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Sursele canalelor"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Noi canale disponibile"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Person. listă canale"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Alegeți canale pentru ghidul de programe"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Neconfigurat"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Intrarea nu acceptă scanarea automată"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Nu poate fi inițiată scanarea automată pentru „<xliff:g id="TV_INPUT">%s</xliff:g>”"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Eroare la inițierea preferințelor pentru subtitrări la nivel de sistem."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"S-a adăugat %1$d canal"</item>
+ <item quantity="other" msgid="1078861616751739285">"S-au adăugat %1$d de canale"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Niciun canal adăugat"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Control parental"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Activat"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Dezactivat"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Canale blocate"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Blocaţi toate"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Deblocați toate"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Canale ascunse"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Restricții program"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Schimbați codul PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Sisteme de clasif."</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Evaluări"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Vedeți sist. clasif."</string>
+ <string name="other_countries" msgid="8342216398676184749">"Alte țări"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Niciunul"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Niciunul"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Niciuna"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Restricții majore"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Restricții medii"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Restricții reduse"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Personalizat"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Conținut potrivit pentru copii"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Conținut adecvat pentru copii mai mari"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Conținut potrivit pentru adolescenți"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Restricții manuale"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s și clasificări secundare"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Clasif. secundare"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Introduceți codul PIN pentru a viziona acest canal"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Introduceți codul PIN pt. a viziona acest program"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Introduceți codul PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Pentru a seta controlul parental, creați un cod PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Introduceți un nou cod PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Confirmați codul PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Introduceți codul PIN actual"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Ați introdus greșit codul PIN de 5 ori.\nÎncercați din nou peste <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> secundă."</item>
+ <item quantity="other" msgid="8829550842387756054">"Ați introdus greșit codul PIN de 5 ori.\nÎncercați din nou peste <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> (de) secunde."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Codul PIN a fost greșit. Încercați din nou."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Încercați din nou. Codul PIN nu se potrivește."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Despre"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Licențe open source"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Licențe open source"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versiune"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Pentru a viziona acest canal, apăsați la dreapta și introduceți codul PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Pentru a viziona acest program, apăsați la dreapta și introduceți codul PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Acest program este clasificat ca <xliff:g id="RATING">%1$s</xliff:g>.\nPentru a viziona acest program, apăsați pe săgeata spre dreapta și introduceți codul PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Programul este blocat"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Acest program este clasificat ca <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Fără titlu"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Canal blocat"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Sezonul <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>, episodul <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>: <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Sursele canalelor"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Configurați canalele live din sursele disponibile. Poate dura câteva minute, în funcție de sursa canalului."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Terminat"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d canal disponibil"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d (de) canale disponibile"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Niciun canal disponibil"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nou"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Apăsați pe SELECTAȚI"</b>" pentru a accesa meniul TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Nu s-a găsit nicio intrare TV"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Nu se poate găsi intrarea TV"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Funcția PIP nu este acceptată"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Nu există intrări disponibile pentru afișarea cu PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tipul tuner nu este corespunzător. Lansați aplicația Canale live pentru intrarea TV tip tuner."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Eroare la optimizare"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Nu s-a găsit o aplicație care să îndeplinească această acțiune."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Toate canalele sursă sunt ascunse.\nSelectați cel puțin un canal pentru vizionare."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Indisponibil din cauza semnalului video slab"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Videoclipul este indisponibil în mod neașteptat"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Tasta BACK este pentru dispozitivul conectat. Apăsați pe butonul HOME pentru a ieși."</string>
+</resources>
diff --git a/res/values-ru/arrays.xml b/res/values-ru/arrays.xml
new file mode 100644
index 00000000..fdd92b18
--- /dev/null
+++ b/res/values-ru/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Обычный"</item>
+ <item msgid="2533030282864800794">"Панорамный"</item>
+ <item msgid="8568284598210500589">"Увеличение"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Все каналы"</item>
+ <item msgid="928298872841713530">"Для всей семьи"</item>
+ <item msgid="2751606947569857164">"Спорт"</item>
+ <item msgid="7345749789651321496">"Покупки"</item>
+ <item msgid="167201149441442173">"Фильмы"</item>
+ <item msgid="525966731464264290">"Юмор"</item>
+ <item msgid="6096710741527327836">"Путешествия"</item>
+ <item msgid="2851882187117833883">"Драма"</item>
+ <item msgid="78492781188719038">"Образование"</item>
+ <item msgid="7221999662426308394">"Природа и животные"</item>
+ <item msgid="375300513250925001">"Новости"</item>
+ <item msgid="7746320336582330410">"Компьютерные игры"</item>
+ <item msgid="1255741860568329178">"Искусство"</item>
+ <item msgid="7603949681065702867">"Развлечения"</item>
+ <item msgid="4453821994746804366">"Стиль жизни"</item>
+ <item msgid="3488534597567932843">"Музыка"</item>
+ <item msgid="7452153120614274095">"Новинки"</item>
+ <item msgid="8215762047341133299">"Наука и техника"</item>
+ </string-array>
+</resources>
diff --git a/res/values-ru/rating_system_strings.xml b/res/values-ru/rating_system_strings.xml
new file mode 100644
index 00000000..a294171d
--- /dev/null
+++ b/res/values-ru/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Наркотики"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Секс"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Насилие"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Наркотики"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Секс"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Насилие"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"французский"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Непристойные разговоры"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Нецензурная лексика"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Секс"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Насилие"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Причинение вреда фантастическим персонажам"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"США"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
new file mode 100644
index 00000000..dfd5b348
--- /dev/null
+++ b/res/values-ru/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Прямой эфир"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"Моно"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"Стерео"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Управление"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Каналы"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Недавние каналы"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Настройки ТВ"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Настройки PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Команды управления недоступны для этого канала"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Воспроизведение/пауза"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Перемотать вперед"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Перемотать назад"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Далее"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Назад"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Телегид"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Доступны новые каналы"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Ссылка не найдена"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Открыть приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Субтитры"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Режим"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"Кадр в кадре"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Вкл."</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Выкл."</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Многоканальный"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Настройка каналов"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Родительский контроль"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Информация"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Источник"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Поменять"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Вкл."</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Выкл."</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Звук"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Главное окно"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Кадр в кадре"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Расположение"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Справа внизу"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Справа вверху"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Слева вверху"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Слева внизу"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Рядом"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Размер"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Крупный"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Мелкий"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Источник"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"ТВ (антенна/кабель)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Нет информации о программах"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Неизвестно"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Заблокированный канал"</string>
+ <string name="default_language" msgid="4122326459624337928">"Неизвестный язык"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Субтитры"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Выкл."</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Настройка субтитров"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Задать системные настройки субтитров"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Режим"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Многоканальный"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"Моно"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"Стерео"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Объемный звук (5.1)"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Объемный звук (7.1)"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"Каналы: %d"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Настроить список"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Включить всю группу"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Отключить всю группу"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Группировать по"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Источнику"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"Качеству видео"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Группировать по"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Настройка каналов"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Эта программа заблокирована"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Возрастное ограничение этой программы: <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Источники каналов"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Доступны новые каналы"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Настроить список"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Выбрать каналы для телегида"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Не настроено"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Этот вход не поддерживает автоматическое сканирование"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Не удалось начать автосканирование для входа \"<xliff:g id="TV_INPUT">%s</xliff:g>\""</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Не удалось открыть системные настройки для субтитров"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"Добавлено каналов: %1$d"</item>
+ <item quantity="other" msgid="1078861616751739285">"Добавлено каналов: %1$d"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Нет каналов"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Тюнер"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Родительский контроль"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Вкл."</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Выкл."</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Заблокированные каналы"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Блокировать все"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Разблокировать все"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Скрытые каналы"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Возрастные ограничения"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Изменить PIN-код"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Страны"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Возрастные ограничения"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Весь список"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Другие страны"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Нет"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Без рейтинга"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Без рейтинга"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Строгие ограничения"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Средние ограничения"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Слабые ограничения"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Пользовательский рейтинг"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Контент подходит для детей"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Подходит для детей среднего возраста"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Контент подходит для подростков"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Пользовательские ограничения"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s: доп. ограничения"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Доп. ограничения"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Введите PIN-код, чтобы посмотреть этот канал"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Введите PIN-код, чтобы посмотреть эту программу"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Введите PIN-код"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Чтобы установить родительский контроль, создайте PIN-код"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Введите новый PIN-код"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Подтвердите PIN-код"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Введите текущий PIN-код"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Вы ввели неправильный PIN-код 5 раз.\nПовторите попытку через <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> сек."</item>
+ <item quantity="other" msgid="8829550842387756054">"Вы ввели неправильный PIN-код 5 раз.\nПовторите попытку через <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> сек."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Неверный PIN-код. Повторите попытку."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"PIN-коды не совпадают. Повторите попытку."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Информация"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Лицензии открытого ПО"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Лицензии открытого ПО"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Версия"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Чтобы смотреть этот канал, нажмите стрелку вправо и введите PIN-код."</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Чтобы смотреть эту программу, нажмите стрелку вправо и введите PIN-код."</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Возрастное ограничение этой программы: <xliff:g id="RATING">%1$s</xliff:g>.\nЧтобы посмотреть ее, нажмите на стрелку вправо и введите PIN-код."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Программа заблокирована"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Возрастное ограничение этой программы: <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Без названия"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Канал заблокирован"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Сезон <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: серия <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>, \"<xliff:g id="EPISODE_TITLE">%3$s</xliff:g>\""</string>
+ <string name="setup_title" msgid="7268875010986705651">"Источники каналов"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Настройка каналов прямого эфира из доступных источников. Она может занять несколько минут в зависимости от источника."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Готово"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Каналы доступны (%1$d)"</item>
+ <item quantity="other" msgid="2386588423841837714">"Каналы доступны (%1$d)"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Каналы недоступны"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Новое"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Нажмите кнопку \"ВЫБРАТЬ\""</b>", чтобы открыть меню телевизора."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"ТВ-вход не найден"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Не удается найти ТВ-вход"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP не поддерживается"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Нет источника для режима PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Для ТВ-входа типа \"тюнер\" используйте приложение \"Прямой эфир\""</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Не удалось выполнить настройку"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Действие не поддерживается ни в одном приложении."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Все каналы скрыты.\nВыберите хотя бы один."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Видео недоступно из-за слабого сигнала"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Видео недоступно из-за непредвиденной ошибки"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Кнопка \"Назад\" управляет подключенным устройством. Чтобы выйти, нажмите \"Главный экран\"."</string>
+</resources>
diff --git a/res/values-si-rLK/arrays.xml b/res/values-si-rLK/arrays.xml
new file mode 100644
index 00000000..70221dcc
--- /dev/null
+++ b/res/values-si-rLK/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"සාමාන්‍ය"</item>
+ <item msgid="2533030282864800794">"පූර්ණ"</item>
+ <item msgid="8568284598210500589">"විශාලනය කරන්න"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"සියළුම නාලිකා"</item>
+ <item msgid="928298872841713530">"පවුල/ළමයින්"</item>
+ <item msgid="2751606947569857164">"ක්‍රීඩා"</item>
+ <item msgid="7345749789651321496">"සාප්පුයාම"</item>
+ <item msgid="167201149441442173">"චිත්‍රපට"</item>
+ <item msgid="525966731464264290">"විකට"</item>
+ <item msgid="6096710741527327836">"සංචාර"</item>
+ <item msgid="2851882187117833883">"නාට්‍ය"</item>
+ <item msgid="78492781188719038">"අධ්‍යාපනය"</item>
+ <item msgid="7221999662426308394">"සතුන්/වනසතුන්"</item>
+ <item msgid="375300513250925001">"ප්‍රවෘත්ති"</item>
+ <item msgid="7746320336582330410">"වීඩියෝ ක්‍රීඩා"</item>
+ <item msgid="1255741860568329178">"කලාව"</item>
+ <item msgid="7603949681065702867">"විනෝදාස්වාදය"</item>
+ <item msgid="4453821994746804366">"ජීවන රටාව"</item>
+ <item msgid="3488534597567932843">"සංගීත"</item>
+ <item msgid="7452153120614274095">"ප්‍රමුඛ"</item>
+ <item msgid="8215762047341133299">"තාක්ෂණය/විද්‍යාව"</item>
+ </string-array>
+</resources>
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..2ecb0ef0
--- /dev/null
+++ b/res/values-si-rLK/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"මත්ද්‍රව්‍ය"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"ලිංගිකත්වය"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"ප‍්‍රචණ්ඩත්වය"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"මත්ද්‍රව්‍ය"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"ලිංගිකත්වය"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"ප‍්‍රචණ්ඩත්වය"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"ප්‍රංශ"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"යෝජිත සංවාදය"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"රළු භාෂාව"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"ලිංගිකත්වය හඟවන අන්තර්ගතය"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"ප‍්‍රචණ්ඩත්වය"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"කාල්පනික ප්‍රචණ්ඩත්වය"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"චිත්‍රපට ඇගයීම්"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
new file mode 100644
index 00000000..31be1c08
--- /dev/null
+++ b/res/values-si-rLK/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"සජීවී නාලිකා"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"මොනෝ"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"ස්ටීරියෝ"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"ධාවක පාලන"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"නාලිකා"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"මෑත නාලිකා"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"රූපවාහිනී විකල්ප"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP විකල්ප"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"මෙම නාලිකාව සඳහා Play පාලන ලද නොහැකිය"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"ධාවනය හෝ විරාමය කරන්න"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"වේගයෙන් ඉදිරියට"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"නැවත ඔතන්න"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"ඊළඟ"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"පෙර"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"වැඩසටහන් නියාමකය"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"නව නාලිකා දැන් තිබේ"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"සබැඳිය ලබාගත නොහැක"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> විවෘත කරන්න"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"වසන ලද ශිර්ෂ"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"දර්ශන ආකාරය"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"සක්‍රියයි"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"අක්‍රිය කරන්න"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"බහු-ශ්‍රව්‍ය"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"නාලිකා පිහිටුවීම්"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"මව්පිය පාලක"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"පිළිබඳ"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"මුල්‍ය"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"මාරු කරන්න"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"සක්‍රියයි"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"අක්‍රිය කරන්න"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"ශබ්දය"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"මූලික"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP කවුළුව"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"පිරිසැලසුම"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"පහළ දකුණ"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"ඉහළ දකුණ"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"ඉහළ වම"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"පහළ වම"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"පැතෙන් පැත්තට"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"ප්‍රමාණය"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"ලොකු"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"කුඩා"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"මූලය අදානය කරන්න"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (ඇන්ටනාව/කේබලය)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"වැඩසටහන් තොරතුරු නැත"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"තොරතුරු නැත."</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"නාලිකාව අවහිර කරන ලදි"</string>
+ <string name="default_language" msgid="4122326459624337928">"නොදන්නා භාෂාව"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"වැසූ සිරස්තල"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"අක්‍රිය කරන්න"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"ආකෘතිකරණය අභිරුචි කරන්න"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"වසන ලද ශීර්ෂ සඳහා පද්ධතිය-පුරා මනාපයන් සකසන්න"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"දර්ශන ආකාරය"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"බහු-ශ්‍රව්‍ය"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"මොනෝ"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"ස්ටීරියෝ"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 සරවුන්ඩ්"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 සරවුන්ඩ්"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"නාලිකා %1$d"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"අභිරුචිකරණය කළ නාලිකා ලැයිස්තුව"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"වර්ගය තෝරන්න"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"වර්ග තේරීම ඉවත් කරන්න"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"වර්ග මගින්"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"නාලිකාවේ මූලය"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"වර්ග මගින්"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"නාලිකා පිහිටුවීම්"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"මෙම වැඩසටහන අවහිර කර තිබේ"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"මෙම වැඩසටහන <xliff:g id="RATING">%1$s</xliff:g> ලෙස අගයා ඇත"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"නාලිකා ප්‍රභව"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"නව නාළිකා දැන් තිබේ"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"අභිරුචිකරණය කළ නාලිකා ලැයිස්තුව"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"ඔබගේ වැඩසටහන් මාර්ගෝපදේශය සඳහා නාලිකා තෝරන්න"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"සකසා නැත"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"ආදානය ස්වයංක්‍රිය-සම්මුර්ත කිරීමටසහාය නොදේ"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\'<xliff:g id="TV_INPUT">%s</xliff:g>\' සඳහා ස්වයංක්‍රිය-පරිලෝකනය ආරම්භ කිරීමට නොහැකි විය"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"උපසිරැසි සඳහා පද්ධතිය-පුරා මනාපයන් ආරම්භ කළ නොහැක."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"නාලිකාව %1$d එකතු කරන ලදි"</item>
+ <item quantity="other" msgid="1078861616751739285">"නාලිකා %1$d එකතු කරන ලදි"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"නාලිකා එකතු කළේ නැත"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"සුසරකය"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"මව්පිය පාලනය"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"සක්‍රිය කරන්න"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"අක්‍රිය කරන්න"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"නාලිකා අවහිර කරන ලදි"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"සියල්ල අවහිර කරන්න"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"සියල්ල අවහිර නොකරන්න"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"සැඟවුණු නාලිකා"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"වැඩ සටහන සීමා කිරීම්"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN වෙනස් කරන්න"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"ඇගයීම පද්ධති"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"ශ්‍රේණිගත කිරීම්"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"සියළුම ඇගයීම පද්ධති බලන්න"</string>
+ <string name="other_countries" msgid="8342216398676184749">"වෙනත් රටවල්"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"කිසිවක් නැත"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"කිසිවක් නැත"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"කිසිවක් නැත"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"ඉහළ සීමා කිරීම්"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"මධ්‍යම සීමා කිරීම්"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"අඩු සීමා කිරීම්"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"අභිරුචි"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"යොවුන් ළමයි සඳහා අන්තර්ගතයන් සුදුසුය"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"යොවුන් ළමයි සඳහා අන්තර්ගතයන් සුදුසුය"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"යොවුන් ළමයි සඳහා අන්තර්ගතයන් සුදුසුය"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"අයාන්ත්‍රික සීමා කිරීම්"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s සහ උප-ඇගයීම්"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"උප-ඇගයීම්"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"මෙම නාලිකාව නැරඹිමට ඔබගේ PIN එක ඇතුළු කරන්න"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"මෙම වැඩසටහන නැරඹිමට ඔබගේ PIN එක ඇතුළු කරන්න"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"ඔබගේ PIN එක ඇතුළු කරන්න"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"මව්පිය පාලකයක් සකසන්න, PIN එකක් සාදන්න"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"අලුත් PIN එක ඇතුළු කරන්න"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"ඔබගේ PIN එක තහවුරු කරන්න"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"ඔබගේ දැනට තිබෙන PIN එක ඇතුළු කරන්න"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"ඔබ වැරදි PIN අංකයක් 5 වරක් ඇතුළු කරන ලදී.\nතත්පර <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> කින් නැවත උත්සාහ කරන්න."</item>
+ <item quantity="other" msgid="8829550842387756054">"ඔබ වැරදි PIN අංකයක් 5 වරක් ඇතුළු කරන ලදී.\nතත්පර <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> කින් නැවත උත්සාහ කරන්න."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"එම PIN එක වැරදිය. නැවත උත්සාහ කරන්න."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"PIN එක ගැලපී නැත"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"පිළිබඳ"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"විවෘත මූලාශ්‍ර වරපත්"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"විවෘත මූලාශ්‍ර වරපත්"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"අනුවාදය"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"මෙම නාලිකාව නැරඹිමට දකුණ ඔබා PIN එක ඇතුළු කරන්න"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"මෙම වැඩසටහන නැරඹිමට දකුණ ඔබා PIN එක ඇතුළු කරන්න"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"මෙම වැඩසටහන <xliff:g id="RATING">%1$s</xliff:g> අගයන ලදි.\nමෙම වැඩසටහන නැරඹීමට දකුණ ඔබා ඔබගේ PIN එක ඇතුළු කරන්න."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"වැඩසටහන අවහිර කරන ලදි"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"මෙම වැඩසටහන <xliff:g id="RATING">%1$s</xliff:g> ලෙස අගයා ඇත"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"මාතෘකාවක් නොමැත"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"නාලිකාව අවහිර කරන ලදි"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"නාලිකා ප්‍රභව"</string>
+ <string name="setup_description" msgid="8728423605912915099">"ලබා ගත හැකි මූලාශ්‍ර වෙතින් සජීවී නාලිකා පිහිටුවන්න. නාලිකා මූලාශ්‍රය අනුව මෙය මිනිත්තු කිහිපයක් ගත හැකිය."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"නිමයි"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"නාලිකා %1$d ක් ලබා ගත හැකිය"</item>
+ <item quantity="other" msgid="2386588423841837714">"නාලිකා %1$d ක් ලබා ගත හැකිය"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"ලබා ගත හැකි නාලිකා නැත"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"නව"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"TV මෙනුවට පිවිසීමට "<b>"SELECT ඔබන්න"</b>"."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"TV ආදානය සොයාගැනීමට නොහැකි විය"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"TV ආදානය සොයාගත නොහැක"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP සහාය දක්වන්නේ නැත"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP සමඟ පෙන්වූ විට අදානයක් එහි නොතිබේ"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"සුසරක වර්ගය ගැලපෙන්නේ නැත; කරුණාකර සුසර කරන වර්ගයේ TV අදානය සඳහා සජීවී නාලිකා යෙදුම දමන්න."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"සුසර කිරීම අසාර්ථක වුණි"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"මෙම ක්‍රියාව හැසිරවීමට යෙදුමක් සොයාගත්තේ නැත"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"සියලුම මූල නාලිකා සඟවන ලදි.\nනැරඹීමට අඩුම තරමේ එක නාලිකාවක් වත් තෝරන්න."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"දුර්වල වීඩියෝ සංඥාව හේතුවෙන් නොතිබේ"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"වීඩියෝව බලාපොරොත්තු නොවූ ලෙස නොතිබේ"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"සම්බන්ධිත උපාංගය සඳහා BACK යතුර. පිටවීමට Home බොත්තම ඔබන්න."</string>
+</resources>
diff --git a/res/values-sk/arrays.xml b/res/values-sk/arrays.xml
new file mode 100644
index 00000000..81cae65f
--- /dev/null
+++ b/res/values-sk/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normálne"</item>
+ <item msgid="2533030282864800794">"Úplné"</item>
+ <item msgid="8568284598210500589">"Priblíženie"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Všetky kanály"</item>
+ <item msgid="928298872841713530">"Rodina a deti"</item>
+ <item msgid="2751606947569857164">"Šport"</item>
+ <item msgid="7345749789651321496">"Nákupy"</item>
+ <item msgid="167201149441442173">"Filmy"</item>
+ <item msgid="525966731464264290">"Komédie"</item>
+ <item msgid="6096710741527327836">"Cestovanie"</item>
+ <item msgid="2851882187117833883">"Dráma"</item>
+ <item msgid="78492781188719038">"Vzdelávanie"</item>
+ <item msgid="7221999662426308394">"Zvieratá a príroda"</item>
+ <item msgid="375300513250925001">"Spravodajstvo"</item>
+ <item msgid="7746320336582330410">"Hranie hier"</item>
+ <item msgid="1255741860568329178">"Umenie"</item>
+ <item msgid="7603949681065702867">"Zábava"</item>
+ <item msgid="4453821994746804366">"Životný štýl"</item>
+ <item msgid="3488534597567932843">"Hudba"</item>
+ <item msgid="7452153120614274095">"Premiéra"</item>
+ <item msgid="8215762047341133299">"Veda a technika"</item>
+ </string-array>
+</resources>
diff --git a/res/values-sk/rating_system_strings.xml b/res/values-sk/rating_system_strings.xml
new file mode 100644
index 00000000..e3c62ce9
--- /dev/null
+++ b/res/values-sk/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Drogy"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sex"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Násilie"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Drogy"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sex"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Násilie"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Francúzština"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Naznačujúce dialógy"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Hrubý jazyk"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Sexuálny obsah"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Násilie"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantazijné násilie"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Hodnotenia filmov"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
new file mode 100644
index 00000000..ca3c0533
--- /dev/null
+++ b/res/values-sk/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Aktívne kanály"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Ovládanie prehrávania"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanály"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Nedávne kanály"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Možnosti TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Možnosti PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Pre tento kanál nie sú k dispozícii ovládacie prvky prehrávania"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Prehrať alebo pozastaviť"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Pretočiť dopredu"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Pretočiť späť"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Ďalej"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Naspäť"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Program. sprievodca"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Dostupné sú nové kanály"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Odkaz nie je dostupný"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Spustiť aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Skryté titulky"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Režim zobrazenia"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Zapnuté"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Vypnuté"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Nastav. kanála"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Rodič. kontrola"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Informácie"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Zdroj"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Zameniť"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Zapnuté"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Vypnuté"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Zvuk"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Hlavné"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Okno PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Rozloženie"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Vpravo dole"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Vpravo hore"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Vľavo hore"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Vľavo dole"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Vedľa seba"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Rozmery"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Veľké"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Malé"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Zdroj vstupu"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (anténa/kábel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Žiadne informácie o programe"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Žiadne informácie"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Zablokovaný kanál"</string>
+ <string name="default_language" msgid="4122326459624337928">"Neznámy jazyk"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Skryté titulky"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Vypnuté"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Prispôsobiť formát"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Nastavenie systémových predvolieb pre skryté titulky"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Režim zobrazenia"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Priestorový zvuk 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Priestorový zvuk 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"Kanály: %d"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Prispôsobiť zoznam"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Vybrať skupinu"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Zrušiť výber skupiny"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Zoskupiť podľa"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Zdroj kanála"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Zoskupiť podľa"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Nastaviť kanál"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Tento program je zablokovaný."</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Hodnotenie tohto programu je <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Zdroje kanálov"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"K dispozícii sú nové kanály."</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Prispôsobiť zoznam"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Vybrať kanály pre sprievodcu programom"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Nenastavené"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Vstup nepodporuje automatické vyhľadávanie"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Nepodarilo sa automaticky prehľadať vstup <xliff:g id="TV_INPUT">%s</xliff:g>"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Celosystémové nastavenia skrytých titulkov nie je možné spustiť"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d pridaný kanál"</item>
+ <item quantity="other" msgid="1078861616751739285">"Pridané kanály: %1$d"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Žiadne pridané kanály"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Rodič. kontrola"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Zapnuté"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Vypnuté"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Blokované kanály"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Blokovať všetko"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Odblokovať všetky"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Skryté kanály"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Obmedzenia progr."</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Zmeniť kód PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Systémy hodnotenia"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Hodnotenia"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Systémy hodnotenia"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Iné krajiny"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Žiadny"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Žiadny"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Žiadne"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Silné obmedzenia"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Stredné obmedzenia"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Nízke obmedzenia"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Vlastné"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Obsah vhodný pre deti"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Obsah vhodný pre staršie deti"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Obsah vhodný pre mladistvých"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Ručné obmedzenia"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s a čiastkové hodnotenia"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Čiastkové hodnotenia"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Zadanie kódu PIN na pozeranie tohto kanála"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Zadanie kódu PIN na pozeranie tohto programu"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Zadanie kódu PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Ak chcete nastaviť rodičovskú kontrolu, vytvorte kód PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Zadajte nový kód PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Potvrdenie kódu PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Zadanie súčasného kódu PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Zadali ste päťkrát nesprávny kód PIN.\nSkúste to znova o <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> s."</item>
+ <item quantity="other" msgid="8829550842387756054">"Zadali ste päťkrát nesprávny kód PIN.\nSkúste to znova o <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> s."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Kód PIN bol zadaný chybne. Skúste to znova."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Kód PIN nesúhlasí. Skúste to znova."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Informácie"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Licencie open source"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Licencie open source"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Verzia"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Ak chcete sledovať tento kanál, stlačte šípku vpravo a zadajte kód PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Ak chcete sledovať tento program, stlačte šípku doprava a zadajte kód PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Hodnotenie tohto programu je <xliff:g id="RATING">%1$s</xliff:g>.\nAk chcete sledovať tento program, stlačte šípku doprava a zadajte kód PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Program je zablokovaný"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Hodnotenie tohto programu je <xliff:g id="RATING">%1$s</xliff:g>."</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Bez názvu"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanál bol zablokovaný"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Zdroje kanálov"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Nastavte aktívne kanály z dostupných zdrojov. V závislosti od zdroja kanála môže táto akcia trvať aj niekoľko minút."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Hotovo"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Dostupný je %1$d kanál"</item>
+ <item quantity="other" msgid="2386588423841837714">"Počet dostupných kanálov: %1$d"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Nie sú k dispozícii žiadne kanály"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nové"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Stlačením tlačidla VYBRAŤ"</b>" prejdete do TV ponuky."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Nenašiel sa žiadny TV vstup"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"TV vstup sa nenašiel"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Funkcia Obraz v obraze (PIP) nie je podporovaná"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Neexistuje vstup, ktorý by mohla funkcia PIP zobraziť"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Typ tunera nie je vhodný. Pre TV vstup typu tunera spustite aplikáciu Aktívne kanály."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Ladenie zlyhalo"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Aplikácia potrebná na spracovanie tejto akcie sa nenašla."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Všetky zdrojové kanály sú skryté.\nVyberte aspoň jeden kanál, ktorý chcete sledovať."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Nedostupné z dôvodu slabého signálu videa"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Video neočakávane prestalo byť dostupné"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Kláves Späť je určený pre pripojené zariadenie. Aplikáciu ukončite stlačením tlačidla Domov."</string>
+</resources>
diff --git a/res/values-sl/arrays.xml b/res/values-sl/arrays.xml
new file mode 100644
index 00000000..32070bcf
--- /dev/null
+++ b/res/values-sl/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Običajno"</item>
+ <item msgid="2533030282864800794">"V celoti"</item>
+ <item msgid="8568284598210500589">"Povečava"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Vsi kanali"</item>
+ <item msgid="928298872841713530">"Družina/otroci"</item>
+ <item msgid="2751606947569857164">"Šport"</item>
+ <item msgid="7345749789651321496">"Nakupovanje"</item>
+ <item msgid="167201149441442173">"Filmi"</item>
+ <item msgid="525966731464264290">"Komedija"</item>
+ <item msgid="6096710741527327836">"Potovanja"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Izobraževanje"</item>
+ <item msgid="7221999662426308394">"Živalski svet"</item>
+ <item msgid="375300513250925001">"Novice"</item>
+ <item msgid="7746320336582330410">"Igre"</item>
+ <item msgid="1255741860568329178">"Umetnost"</item>
+ <item msgid="7603949681065702867">"Razvedrilo"</item>
+ <item msgid="4453821994746804366">"Življenjski slog"</item>
+ <item msgid="3488534597567932843">"Glasba"</item>
+ <item msgid="7452153120614274095">"Premiera"</item>
+ <item msgid="8215762047341133299">"Tehnika/znanost"</item>
+ </string-array>
+</resources>
diff --git a/res/values-sl/rating_system_strings.xml b/res/values-sl/rating_system_strings.xml
new file mode 100644
index 00000000..5b2d873b
--- /dev/null
+++ b/res/values-sl/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Mamila"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Spolnost"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Nasilje"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Mamila"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Spolnost"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Nasilje"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"francoščina"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Sugestivni dialog"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Grobo izražanje"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Prizori spolnosti"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Nasilje"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantazijsko nasilje"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Kategorije filmov"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
new file mode 100644
index 00000000..448b778d
--- /dev/null
+++ b/res/values-sl/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Kanali v živo"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Kontrolniki predvajanja"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanali"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Nedavni kanali"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Možnosti za TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Možnosti za PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Kontrolniki za predvajanje niso na voljo za ta kanal"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Predvajanje ali zaustavitev"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Previjanje naprej"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Previjanje nazaj"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Naprej"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Nazaj"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programski vodnik"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Na voljo so novi kanali"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Ni povezav"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Odpri <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Podnapisi"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Način prikaza"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Vklopljeno"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Izklopljeno"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multizvok"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Nastav. kanalov"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Staršev. nadzor"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Vizitka"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Vir"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Zamenjaj"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Vklopljeno"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Izklopljeno"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Zvok"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Glavno"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Okno PIP-a"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Postavitev"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Spodaj desno"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Zgoraj desno"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Zgoraj levo"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Spodaj levo"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Vzporedno"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Velikost"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Veliko"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Majhno"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Vir vhoda"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antena/kabelska)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Ni informacij o programu"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Ni informacij"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Blokiran kanal"</string>
+ <string name="default_language" msgid="4122326459624337928">"Neznan jezik"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Podnapisi"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Izklopljeno"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Oblikovanje po meri"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Določite sistemske nastavitve za podnapise"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Način prikaza"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multizvok"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Prostorski zvok 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Prostorski zvok 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"Št. kanalov: %d"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Prilagajanje seznama kanalov"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Izberi skupino"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Počisti skupino"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Razvrsti po:"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Vir kanala"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Razvrsti po:"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Nastavitev kanalov"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Ta program je blokiran"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Ta program ima kategorijo vsebine <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Viri kanalov"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Na voljo so novi kanali"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Prilagajanje seznama kanalov"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Izberite kanale za programski vodnik"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Ni nastavljeno"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Vhod ne podpira samodejnega iskanja"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Samodejnega iskanja za »<xliff:g id="TV_INPUT">%s</xliff:g>« ni mogoče začeti"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Ni mogoče začeti sistemskih nastavitev za podnapise."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"Št. dodanih kanalov: %1$d"</item>
+ <item quantity="other" msgid="1078861616751739285">"Št. dodanih kanalov: %1$d"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Ni dodanih kanalov"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Sprejemnik"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Starševski nadzor"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Vklopljeno"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Izklopljeno"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Blokirani kanali"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Blokiraj vse"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Odblokiraj vse"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Skriti kanali"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Programske omejitve"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Sprememba kode PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Sistemi ocenjevanja"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Ocene"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Prikaz vseh sistemov omejitev"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Druge države"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Brez"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Brez"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Brez"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Visoke omejitve"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Srednje omejitve"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Nizke omejitve"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Po meri"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Vsebina, primerna za otroke"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Vsebina, primerna za otroke"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Vsebina, primerna za najstnike"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Ročne omejitve"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s in podocene"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Podocene"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Vnesite kodo PIN, če želite gledati ta kanal"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Vnesite kodo PIN, če želite gledati to oddajo"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Vnos kode PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Če želite nastaviti starševski nadzor, ustvarite kodo PIN."</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Vnesite novo kodo PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Potrditev kode PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Vnos trenutne kode PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Petkrat ste vnesli napačno kodo PIN.\nPoskusite znova čez <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekundo."</item>
+ <item quantity="other" msgid="8829550842387756054">"Petkrat ste vnesli napačno kodo PIN.\nPoskusite znova čez toliko sekund: <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g>."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Koda PIN je bila napačna. Poskusite znova."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Poskusite znova. Koda PIN se ne ujema."</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Vizitka"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Odprtokodne licence"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Odprtokodne licence"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Različica"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Če želite gledati ta kanal, pritisnite v desno in vnesite kodo PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Če želite gledati ta program, pritisnite v desno in vnesite kodo PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Ta program ima kategorijo vsebine <xliff:g id="RATING">%1$s</xliff:g>.\nČe ga želite gledati, pritisnite v desno in vnesite kodo PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Oddaja je blokirana"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Ta program ima kategorijo vsebine <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Brez naslova"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanal je blokiran"</string>
+ <string name="episode_format" msgid="4881195874563241096">"<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>. sezona: <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>. epizoda – <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Viri kanalov"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Nastavitev kanalov v živo iz razpoložljivih virov. To lahko traja nekaj minut, odvisno od vira kanala."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Končano"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Na voljo je %1$d kanal"</item>
+ <item quantity="other" msgid="2386588423841837714">"Na voljo je toliko kanalov: %1$d"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Ni kanalov"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Novo"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Pritisnite »IZBIRA«"</b>", če želite dostopati do menija TV-ja."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Ni TV-vhodov"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Ni mogoče najti TV-vhoda"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Slika v sliki ni podprta"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Ni vhoda, ki bi omogočal prikaz s sliko v sliki (PIP)"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Vrsta sprejemnika ni ustrezna. Zaženite aplikacijo Kanali v živo za uporabo TV-vhoda, ki deluje kot sprejemnik."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Nastavljanje kanalov ni uspelo"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Za to dejanje ni bilo mogoče najti nobene aplikacije."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Vsi kanali vira so skriti.\nIzberite vsaj en kanal za gledanje."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Ni na voljo zaradi šibkega signala za video"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Video nepričakovano ni na voljo"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Tipka NAZAJ je za priključena naprave. Pritisnite DOMOV za izhod."</string>
+</resources>
diff --git a/res/values-sr/arrays.xml b/res/values-sr/arrays.xml
new file mode 100644
index 00000000..1638027b
--- /dev/null
+++ b/res/values-sr/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Нормалан"</item>
+ <item msgid="2533030282864800794">"Пун"</item>
+ <item msgid="8568284598210500589">"Зумиран"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Сви канали"</item>
+ <item msgid="928298872841713530">"Породица/деца"</item>
+ <item msgid="2751606947569857164">"Спорт"</item>
+ <item msgid="7345749789651321496">"Куповина"</item>
+ <item msgid="167201149441442173">"Филмови"</item>
+ <item msgid="525966731464264290">"Комедија"</item>
+ <item msgid="6096710741527327836">"Путовања"</item>
+ <item msgid="2851882187117833883">"Драма"</item>
+ <item msgid="78492781188719038">"Образовање"</item>
+ <item msgid="7221999662426308394">"Животиње/дивљи свет"</item>
+ <item msgid="375300513250925001">"Вести"</item>
+ <item msgid="7746320336582330410">"Видео игре"</item>
+ <item msgid="1255741860568329178">"Уметност"</item>
+ <item msgid="7603949681065702867">"Забава"</item>
+ <item msgid="4453821994746804366">"Животни стил"</item>
+ <item msgid="3488534597567932843">"Музика"</item>
+ <item msgid="7452153120614274095">"Премијер"</item>
+ <item msgid="8215762047341133299">"Технологија/наука"</item>
+ </string-array>
+</resources>
diff --git a/res/values-sr/rating_system_strings.xml b/res/values-sr/rating_system_strings.xml
new file mode 100644
index 00000000..ab9670ef
--- /dev/null
+++ b/res/values-sr/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Дрога"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Секс"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Насиље"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Дрога"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Секс"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Насиље"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"француски"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Сугестивни дијалог"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Непристојан језик"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Сексуални садржај"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Насиље"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Насиље у контексту фантазије"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Оцене филмова"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
new file mode 100644
index 00000000..54d42b78
--- /dev/null
+++ b/res/values-sr/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Канали уживо"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"моно"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"стерео"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Play контроле"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Канали"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Недавни канали"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"ТВ опције"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Опц. сл. у сл."</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Контроле за пуштање нису доступне за овај канал"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Пусти или паузирај"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Премотај унапред"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Премотај уназад"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Следећe"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Претходнo"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Водич за програм"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Доступни су нови канали"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Линк није доступан"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Отвори <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Титлови"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Режим приказа"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"Слика у слици"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Укључено"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Искључено"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Вишеструк аудио"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Подешав. канала"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Родитељски надзор"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Основни подаци"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Извор"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Замени"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Укључено"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Искључено"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Звук"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Главни"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Прозор слике у слици"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Распоред"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Доњи десни угао"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Горњи десни угао"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Горњи леви угао"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Доњи леви угао"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Упоредо"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Величина"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Велика"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Мала"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Извор улаза"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"ТВ (антенска/кабловска)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Нема информација о програму"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Нема информација"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Блокирани канал"</string>
+ <string name="default_language" msgid="4122326459624337928">"Непознат језик"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Опционални титлови"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Искључи"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Прилагоди формат"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Изаберите подешавања титлова за цео систем"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Режим приказа"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Вишеструки аудио"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"моно"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"стерео"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Звучни систем 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Звучни систем 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d канали"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Прилагоди листу канала"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Изаберите групу"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Опозови избор групе"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Групиши према"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Извор канала"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Групиши према"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Подешавање канала"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Овај програм је блокиран"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Овај програм има оцену <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Извори канала"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Доступни су нови канали"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Прилагод. листу канала"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Изаберите канале за водич за програм"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Није подешен"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Улаз не подржава аутоматско скенирање"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Није успело покретање аутоматског скенирања за „<xliff:g id="TV_INPUT">%s</xliff:g>“"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Покретање подешавања за титлове која се примењују на цео систем није успело."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"Додат је %1$d канал"</item>
+ <item quantity="other" msgid="1078861616751739285">"Додатих канала: %1$d"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Нема додатих канала"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Тјунер"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Родитељски надзор"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Укључи"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Искључи"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Блокирани канали"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Блокирај све"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Деблокирај све"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Скривени канали"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Ограничења програма"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Промени PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Системи оцењивања"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Оцене"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Прикажи све системе"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Друге земље"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Ништа"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Ништа"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Ништа"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Велика ограничења"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Средња ограничења"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Слаба ограничења"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Прилагођено"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Садржај прикладан за децу"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Садржај прикладан за старију децу"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Садржај прикладан за тинејџере"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Ручна ограничења"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s и подоцене"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Подоцене"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Унесите PIN да бисте гледали овај канал"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Унесите PIN да бисте гледали овај програм"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Унесите PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Да бисте подесили родитељску контролу, направите PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Унесите нови PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Потврдите PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Унесите актуелни PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Унели сте погрешан PIN 5 пута.\n Покушајте поново за <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> секунду."</item>
+ <item quantity="other" msgid="8829550842387756054">"Унели сте погрешан PIN 5 пута.\nПокушајте поново за <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> секунди."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Тај PIN је погрешан. Покушајте поново."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Покушајте поново, PIN се не подудара"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Основни подаци"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Лиценце отвореног кода"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Лиценце отвореног кода"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Верзија"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Да бисте гледали овај канал, притисните дугме Десно и унесите PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Да бисте гледали овај програм, притисните дугме Десно и унесите PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Овај програм има оцену <xliff:g id="RATING">%1$s</xliff:g>.\nДа бисте гледали овај програм, притисните дугме Десно и унесите PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Програм је блокиран"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Овај програм има оцену <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Нема наслова"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Канал је блокиран"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Серијал <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>, епизода <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>, <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Извори канала"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Подесите канале уживо из доступних извора. Ово може да потраје неколико минута у зависности од извора канала."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Готово"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Доступан је %1$d канал"</item>
+ <item quantity="other" msgid="2386588423841837714">"Број доступних канала је %1$d"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Није доступан ниједан канал"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Ново"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Притисните ИЗАБЕРИ"</b>" да бисте приступили TV менију."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Нису пронађени ТВ улази"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Не можемо да пронађемо ТВ улаз"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Слика у слици није подржана"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Нема улаза који може да се прикаже као слика у слици"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Тип тјунера не одговара. Покрените апликацију Канали уживо за тип ТВ улаза са тјунером."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Подешавање није успело"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Није пронађена ниједна апликација која би могла да обави ову радњу."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Сви изворни канали су сакривени.\nИзаберите бар један канал који хоћете да гледате."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Није доступно због слабог видео сигнала"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Видео је неочекивано недоступан"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Тастер за НАЗАД је за повезане уређаје. Притисните дугме ПОЧЕТНА да бисте изашли."</string>
+</resources>
diff --git a/res/values-sv/arrays.xml b/res/values-sv/arrays.xml
new file mode 100644
index 00000000..7afb9410
--- /dev/null
+++ b/res/values-sv/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Medelstor"</item>
+ <item msgid="2533030282864800794">"Helskärm"</item>
+ <item msgid="8568284598210500589">"Zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Alla kanaler"</item>
+ <item msgid="928298872841713530">"Familj/barn"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Shopping"</item>
+ <item msgid="167201149441442173">"Filmer"</item>
+ <item msgid="525966731464264290">"Komedi"</item>
+ <item msgid="6096710741527327836">"Resor"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Utbildning"</item>
+ <item msgid="7221999662426308394">"Djur/natur"</item>
+ <item msgid="375300513250925001">"Nyheter"</item>
+ <item msgid="7746320336582330410">"Spel"</item>
+ <item msgid="1255741860568329178">"Konst"</item>
+ <item msgid="7603949681065702867">"Underhållning"</item>
+ <item msgid="4453821994746804366">"Livsstil"</item>
+ <item msgid="3488534597567932843">"Musik"</item>
+ <item msgid="7452153120614274095">"Premiär"</item>
+ <item msgid="8215762047341133299">"Teknik/vetenskap"</item>
+ </string-array>
+</resources>
diff --git a/res/values-sv/rating_system_strings.xml b/res/values-sv/rating_system_strings.xml
new file mode 100644
index 00000000..8f72876e
--- /dev/null
+++ b/res/values-sv/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Droger"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Sex"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Våld"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Droger"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Sex"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Våld"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"franska"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Provokativ dialog"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Grovt språk"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Sexuellt innehåll"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Våld"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantasyvåld"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Klassificering av film"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
new file mode 100644
index 00000000..9b39da0e
--- /dev/null
+++ b/res/values-sv/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Livekanaler"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Uppspelningskontroller"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanaler"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Senaste kanalerna"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Tv-alternativ"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP-alternativ"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Uppspelningskontrollerna är inte tillgängliga för den här kanalen"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Spela upp eller pausa"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Snabbspola framåt"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Spola tillbaka"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Nästa"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Föregående"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Programguide"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Det finns nya kanaler"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Det finns ingen länk"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Öppna <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Textning"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Visningsläge"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"På"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Av"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Flera ljudkäll."</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Kanalinställn."</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Barnfilter"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Om"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Källa"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Byt"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"På"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Av"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Ljud"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Primär"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP-fönster"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Layout"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Nere till h."</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Uppe till h."</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Uppe till v."</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Nere till v."</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Sida vid sida"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Storlek"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Stor"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Liten"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Ingångskälla"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Tv (antenn/kabel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Ingen programinformation"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Ingen information"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Blockerad kanal"</string>
+ <string name="default_language" msgid="4122326459624337928">"Okänt språk"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Textning"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Av"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Anpassa formatering"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Ange textningsinställningar för systemet"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Visningsläge"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Flera ljudkäll."</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1-surroundljud"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1-surroundljud"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d kanaler"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Anpassa kanallista"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Välj en grupp"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Avmarkera grupp"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Ordna efter"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Kanalkälla"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Ordna efter"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Kanalinställningar"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Programmet är blockerat"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Det här programmet har kategoriserats som <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Kanalkällor"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Det finns nya kanaler"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Anpassa kanallista"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Välj kanaler för programguiden"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Har inte konfigurerats"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Ingången stöder inte automatisk sökning"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Det går inte att starta automatisk sökning för <xliff:g id="TV_INPUT">%s</xliff:g>"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Det går inte att öppna systeminställningarna för textning."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d kanal har lagts till"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d kanaler har lagts till"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Inga kanaler har lagts till"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Mottagare"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Barnfilter"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"På"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Av"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Blockerade kanaler"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Blockera alla"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Ta bort blockeringen"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Dolda kanaler"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Programbegräns."</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Byt pinkod"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Kategorisystem"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Klassificering"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Se alla klassificeringssystem"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Övriga länder"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Inga"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Inga"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Inga"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Höga begränsningar"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Medelbegränsningar"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Låga begränsningar"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Anpassad"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Innehållet är lämpligt för barn"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Innehållet är lämpligt för äldre barn"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Innehållet är lämpligt för tonåringar"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Manuella begränsningar"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s, underklassif."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Underklassificering"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Ange pinkoden om du vill titta på den här kanalen"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Ange pinkoden om du vill titta på det här programmet"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Ange din pinkod"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Skapa en pinkod om du vill konfigurera barnfiltret"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Ange en ny pinkod"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Bekräfta din pinkod"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Ange din aktuella pinkod"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Du har angett fel pinkod fem gånger.\nFörsök igen om <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekund."</item>
+ <item quantity="other" msgid="8829550842387756054">"Du har angett fel pinkod fem gånger.\nFörsök igen om <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> sekunder."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Det var fel pinkod. Försök igen."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Försök igen. Pinkoden stämmer inte"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Om"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Öppen källkod"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Öppen källkod"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Version"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Tryck till höger och ange pinkoden om du vill titta på den här kanalen"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Tryck till höger och ange pinkoden om du vill titta på det här programmet"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Det här programmet har kategoriserats som <xliff:g id="RATING">%1$s</xliff:g>\nTryck till höger och ange pinkoden om du vill titta på det här programmet."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Programmet har blockerats"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Det här programmet har kategoriserats som <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Ingen titel"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanalen har blockerats"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Säsong <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Avsnitt <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> – <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Kanalkällor"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Ställ in livekanaler från de tillgängliga källorna. Det här kan ta några minuter beroende på kanalkällan."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Klar"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d kanal är tillgänglig"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d kanaler är tillgängliga"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Inga kanaler är tillgängliga"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Nytt"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Tryck på VÄLJ"</b>" för att öppna TV-menyn."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Ingen tv-ingång hittades"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Det går inte att hitta tv-ingången"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP stöds inte"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Det finns ingen tillgänglig ingång som kan visas med PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Olämplig insignal. Starta appen Livekanaler om du vill kunna ta emot insignaler av tv-kortstyp."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Kanaljusteringen misslyckades"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Ingen app som kan hantera åtgärden hittades"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Alla källkanaler är dolda.\nVälj minst en kanal som du vill titta på."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Inte tillgänglig på grund av svag videosignal"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Videon är inte tillgänglig"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Knappen BACK (bakåt) gäller en ansluten enhet. Avsluta genom att trycka på knappen HOME (start)."</string>
+</resources>
diff --git a/res/values-sw/arrays.xml b/res/values-sw/arrays.xml
new file mode 100644
index 00000000..a4106176
--- /dev/null
+++ b/res/values-sw/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Kawaida"</item>
+ <item msgid="2533030282864800794">"Kamili"</item>
+ <item msgid="8568284598210500589">"Kuza"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Vituo vyote"</item>
+ <item msgid="928298872841713530">"Familia/Watoto"</item>
+ <item msgid="2751606947569857164">"Michezo"</item>
+ <item msgid="7345749789651321496">"Ununuzi"</item>
+ <item msgid="167201149441442173">"Filamu"</item>
+ <item msgid="525966731464264290">"Vichekesho"</item>
+ <item msgid="6096710741527327836">"Usafiri"</item>
+ <item msgid="2851882187117833883">"Uigizaji"</item>
+ <item msgid="78492781188719038">"Elimu"</item>
+ <item msgid="7221999662426308394">"Wanyama/Wanyamapori"</item>
+ <item msgid="375300513250925001">"Habari"</item>
+ <item msgid="7746320336582330410">"Kamari"</item>
+ <item msgid="1255741860568329178">"Sanaa"</item>
+ <item msgid="7603949681065702867">"Burudani"</item>
+ <item msgid="4453821994746804366">"Mitindo ya maisha"</item>
+ <item msgid="3488534597567932843">"Muziki"</item>
+ <item msgid="7452153120614274095">"Zinazoongoza"</item>
+ <item msgid="8215762047341133299">"Teknolojia/Sayansi"</item>
+ </string-array>
+</resources>
diff --git a/res/values-sw/rating_system_strings.xml b/res/values-sw/rating_system_strings.xml
new file mode 100644
index 00000000..b6583df8
--- /dev/null
+++ b/res/values-sw/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Dawa za Kulevya"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Ngono"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Vurugu"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Dawa za Kulevya"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Ngono"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Vurugu"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Kifaransa"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Mazungumzo yanayochochea ngono"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Lugha ya matusi"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Maudhui ya ngono"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Vurugu"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Vurugu ya dhahania"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Ukadiriaji wa filamu"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
new file mode 100644
index 00000000..4e59a046
--- /dev/null
+++ b/res/values-sw/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Vituo vya Moja kwa moja"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Vidhibiti vya kucheza"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Vituo"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Vituo vya hivi majuzi"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Chaguo za Runinga"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Chaguo za PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Vidhibiti vya kucheza havipatikani kwa kituo hiki"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Cheza au usitishe"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Peleka mbele kwa kasi"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Rudisha nyuma"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Inayofuata"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Iliyotangulia"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Mwongozo wa programu"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Vituo vipya vinapatikana"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Hakuna kiungo"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Fungua <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Manukuu"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Hali ya onyesho"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Imewashwa"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Imezimwa"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Sauti nyingi"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Kusanidi kituo"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Udhibiti wa wazazi"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Kuhusu"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Chanzo"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Badili"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Imewashwa"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Imezimwa"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Sauti"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Kuu"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Dirisha la PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Muundo"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Chini kulia"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Juu kulia"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Juu kushoto"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Chini kushoto"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Upande kwa upande"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Ukubwa"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Kubwa"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Ndogo"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Chanzo cha data"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"Runinga (antena/kebo)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Hakuna maelezo ya programu"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Hakuna maelezo"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Kituo kilichozuiwa"</string>
+ <string name="default_language" msgid="4122326459624337928">"Lugha Isiyojulikana"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Manukuu"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Imezimwa"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Geuza muundo ukufae"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Wekea mapendeleo mfumo mzima kwa manukuu"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Hali ya onyesho"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Sauti nyingi"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Sauti ya mzunguko 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Sauti ya mzunguko 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"Vituo %d"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Geuza orodha ya vituo iwe unavyotaka"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Chagua kikundi"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Usichague kikundi"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Panga kikundi kwa"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Chanzo cha kituo"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD au SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Panga kikundi kwa"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Kusanidi kituo"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Kipindi hiki kimezuiwa"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Kipindi hiki kimekadiriwa <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Vyanzo vya vituo"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Vituo vipya vinapatikana"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Geuza orodha ya vituo iwe unavyotaka"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Chagua vituo kwa ajili ya mwongozo wa programu yako"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Haijasanidiwa"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Kifaa cha kuingiza sauti hakitumii uchanganuzi wa kiotomatiki"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Haikuweza kuanzisha uchanganuzi kiotomatiki kwa \'<xliff:g id="TV_INPUT">%s</xliff:g>\'"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Haikuweza kuanzisha mapendeleo ya mfumo mzima kwa manukuu."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"Kituo %1$d kimeongezwa"</item>
+ <item quantity="other" msgid="1078861616751739285">"Vituo %1$d vimeongezwa"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Hakuna vituo vimeongezwa"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Kirekebisha mawimbi"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Udhibiti wa wazazi"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Imewashwa"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Imezimwa"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Vituo vimezuiwa"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Zuia zote"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Ondoa kizuizi"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Vituo vilivyofichwa"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Zuio za programu"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Badilisha PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Mifumo ya ukadiriaji"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Makadirio"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Angalia mifumo yote ya ukadiriaji"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Nchi nyingine"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Hakuna"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Hakuna"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Hakuna"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Vizuizi kiwango cha juu"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Vikwazo vya kati"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Vikwazo vya chini"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Maalum"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Maudhui yanayowafaa watoto"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Maudhui yanayofaa kwa ajili ya watoto wakubwa"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Maudhui yanayowafaa vijana"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Vizuizi maalum"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s na ukadiriaji mdogo"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Ukadiriaji mdogo"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Weka PIN yako ili uangalie kituo hiki"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Weka PIN yako ili uangalie kipindi hiki"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Weka PIN yako"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Ili uweke udhibiti wa wazazi, unda PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Weka PIN mpya"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Thibitisha PIN yako"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Weka PIN yako ya sasa"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Uliweka PIN isiyo sahihi mara 5. \nJaribu tena baada ya sekunde <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g>."</item>
+ <item quantity="other" msgid="8829550842387756054">"Uliweka PIN isiyo sahihi mara 5. \n Jaribu tena baada ya sekunde <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g>."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"PIN uliyoweka si sahihi. Jaribu tena."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Jaribu tena, PIN hailingani"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Kuhusu"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Leseni za programu huria"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Leseni za programu huria"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Toleo"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Ili uangalie kituo hiki, bonyeza Kulia na uweke PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Ili uangalie kipindi hiki, bonyeza Kulia na uweke PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Kipindi hiki kimekadiriwa <xliff:g id="RATING">%1$s</xliff:g>.\nIli utazame kipindi hiki, bonyeza Kulia na uweke PIN yako"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Kipindi kimezuiwa"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Kipindi hiki kimekadiriwa <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Kichwa hakijaongezwa"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kituo kimezuiwa"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Msimu wa <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Kipindi cha <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g><xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Vyanzo vya vituo"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Panga vituo vya moja kwa moja kutoka vyanzo vinavyopatikana. Hatua hii inaweza kuchukua dakika kadhaa kulingana na chanzo cha kituo."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Mwisho"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Kituo %1$d kinapatikana"</item>
+ <item quantity="other" msgid="2386588423841837714">"Vituo %1$d vinapatikana"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Hakuna kituo kinachopatikana"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Mpya"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Bonyeza CHAGUA "</b>" ili ufikie menyu ya televisheni."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Hakuna vifaa vya kuingiza maudhui ya runinga vinavyopatikana"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Haiwezi kupata vifaa vya kuingiza maudhui kwenye runinga"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP haiwezi kutumika"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Hakuna maudhui yanayoweza kuonyeshwa pamoja na PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Aina ya kirekebishaji haifai. Fungua programu ya Vituo vya Moja kwa Moja ya vifaa vya kuingiza maudhui ya runinga."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Haikuweza kurekebisha"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Hakuna programu iliyopatikana inayoweza kushughulikia kitendo hiki."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Vituo chanzo vyote vimefichwa.\nChagua angalau kituo kimoja utazame."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Haipatikani kutokana na mawimbi dhaifu ya video"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Jambo lisilotarajiwa limetokea, video haipatikani"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Kitufe cha NYUMA ni kwa ajili ya kifaa kilichounganishwa. Bonyeza kitufe cha MWANZO ili uondoe."</string>
+</resources>
diff --git a/res/values-ta-rIN/arrays.xml b/res/values-ta-rIN/arrays.xml
new file mode 100644
index 00000000..3281292f
--- /dev/null
+++ b/res/values-ta-rIN/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"இயல்பு"</item>
+ <item msgid="2533030282864800794">"முழு"</item>
+ <item msgid="8568284598210500589">"பெரிதாக்கு"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"எல்லா சேனல்களும்"</item>
+ <item msgid="928298872841713530">"குடும்பம்/சிறுவர்கள்"</item>
+ <item msgid="2751606947569857164">"விளையாட்டு"</item>
+ <item msgid="7345749789651321496">"ஷாப்பிங்"</item>
+ <item msgid="167201149441442173">"மூவிகள்"</item>
+ <item msgid="525966731464264290">"நகைச்சுவை"</item>
+ <item msgid="6096710741527327836">"பயணம்"</item>
+ <item msgid="2851882187117833883">"நாடகம்"</item>
+ <item msgid="78492781188719038">"கல்வி"</item>
+ <item msgid="7221999662426308394">"விலங்குகள்"</item>
+ <item msgid="375300513250925001">"செய்திகள்"</item>
+ <item msgid="7746320336582330410">"கேமிங்"</item>
+ <item msgid="1255741860568329178">"கலைகள்"</item>
+ <item msgid="7603949681065702867">"பொழுதுபோக்கு"</item>
+ <item msgid="4453821994746804366">"வாழ்க்கை முறை"</item>
+ <item msgid="3488534597567932843">"இசை"</item>
+ <item msgid="7452153120614274095">"ப்ரீமியர்"</item>
+ <item msgid="8215762047341133299">"தொழில்நுட்பம்/அறிவியல்"</item>
+ </string-array>
+</resources>
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..f4796168
--- /dev/null
+++ b/res/values-ta-rIN/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"போதைப் பொருட்கள்"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"பாலியல் உறவு"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"வன்முறை"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"போதைப் பொருட்கள்"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"பாலியல் உறவு"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"வன்முறை"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"பிரெஞ்சு"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"பாலுணர்வைத் தூண்டும் உரையாடல்"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"கொச்சை மொழி"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"பாலியல் உள்ளடக்கம்"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"வன்முறை"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"ஃபேண்டஸி வன்முறை"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"திரைப்பட மதிப்பீடுகள்"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
new file mode 100644
index 00000000..b43cb5d5
--- /dev/null
+++ b/res/values-ta-rIN/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"லைவ் சேனல்ஸ்"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"மோனோ"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"ஸ்டீரியோ"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"இயக்கக் கட்டுப்பாடுகள்"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"சேனல்கள்"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"சமீபத்திய சேனல்கள்"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"டிவி விருப்பங்கள்"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP விருப்பங்கள்"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"இந்தச் சேனலுக்கு இயக்கக் கட்டுப்பாடுகள் இல்லை"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"இயக்கு அல்லது இடைநிறுத்து"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"வேகமாக முன் நகர்த்து"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"வேகமாக பின் நகர்த்து"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"அடுத்து"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"முந்தையது"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"நிகழ்ச்சிக் கையேடு"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"புதிய சேனல்கள் உள்ளன"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"இணைப்பு இல்லை"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g>ஐத் திற"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"விரிவான வசனங்கள்"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"காட்சிப் பயன்முறை"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"இயக்கத்தில்"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"முடக்கத்தில்"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"மல்டி-ஆடியோ"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"சேனல் அமைவு"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"பெற்றோர் கட்டுப்பாடுகள்"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"அறிமுகம்"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"மூலம்"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"மாற்று"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"இயக்கத்தில்"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"முடக்கத்தில்"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"ஒலி"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"முதன்மை"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP சாளரம்"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"தளவமைப்பு"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"கீழ் வலதுபுறம்"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"மேல் வலதுபுறம்"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"மேல் இடதுபுறம்"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"கீழ் இடதுபுறம்"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"அருகருகே"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"அளவு"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"பெரியது"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"சிறியது"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"உள்ளீட்டு மூலம்"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"டிவி (ஆண்டெனா/கேபிள்)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"நிகழ்ச்சி தகவல் இல்லை"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"தகவல் இல்லை"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"தடுக்கப்பட்ட சேனல்"</string>
+ <string name="default_language" msgid="4122326459624337928">"தெரியாத மொழி"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"விரிவான வசனங்கள்"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"முடக்கு"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"வடிவமைப்பைத் தனிப்பயனாக்கவும்"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"விரிவான வசனங்களுக்கான முன்னுரிமைகளை முறைமை முழுவதும் அமைக்கவும்"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"காட்சிப் பயன்முறை"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"மல்டி-ஆடியோ"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"மோனோ"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"ஸ்டீரியோ"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 சரவுண்ட்"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 சரவுண்ட்"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d சேனல்கள்"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"சேனலை தனிப்படுத்து"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"குழுவைத் தேர்வுசெய்க"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"குழுவின் தேர்வைநீக்கு"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"இதன்படி குழுவாக்கு"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"சேனல் மூலம்"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"இதன்படி குழுவாக்கு"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"சேனல் அமைவு"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"இந்த நிகழ்ச்சி தடுக்கப்பட்டுள்ளது"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"இந்த நிகழ்ச்சி <xliff:g id="RATING">%1$s</xliff:g> என மதிப்பிடப்பட்டுள்ளது"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"சேனல் மூலங்கள்"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"புதிய சேனல்கள் உள்ளன"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"சேனலை தனிப்படுத்து"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"நிகழ்ச்சி வழிகாட்டி சேனல்களைத் தேர்வுசெய்"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"அமைக்கப்படவில்லை"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"தன்னியக்க ஸ்கேனை உள்ளீடு ஆதரிக்காது"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"’<xliff:g id="TV_INPUT">%s</xliff:g>’க்கான தன்னியக்க ஸ்கேனைத் தொடங்க முடியவில்லை"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"விரிவான வசனங்களுக்கான சாதன விருப்பத்தேர்வுகளைத் தொடங்க முடியவில்லை."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d சேனல் சேர்க்கப்பட்டது"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d சேனல்கள் சேர்க்கப்பட்டன"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"சேனல்கள் எதுவும் சேர்க்கப்படவில்லை"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"ட்யூனர்"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"பெற்றோர் கட்டுப்பாடுகள்"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"இயக்கு"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"முடக்கு"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"சேனல்கள் தடுக்கப்பட்டன"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"எல்லாவற்றையும் தடு"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"எல்லாவற்றிலும் தடைநீக்கு"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"மறைக்கப்பட்ட சேனல்கள்"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"நிகழ்ச்சிக் கட்டுப்பாடுகள்"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN ஐ மாற்று"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"மதிப்பிடல் அமைப்புகள்"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"மதிப்பீடுகள்"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"அனைத்து மதிப்பீட்டு அமைப்புகளையும் பார்க்கவும்"</string>
+ <string name="other_countries" msgid="8342216398676184749">"பிற நாடுகள்"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"ஏதுமில்லை"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"ஏதுமில்லை"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"ஏதுமில்லை"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"அதிகக் கட்டுப்பாடுகள்"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"நடுத்தரக் கட்டுப்பாடுகள்"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"குறைவான கட்டுப்பாடுகள்"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"தனிப்பயன்"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"குழந்தைகளுக்கு ஏற்றது"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"குழந்தைகளுக்கு ஏற்றது"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"பதின்ம வயதினருக்கு ஏற்றது"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"கைமுறைக் கட்டுப்பாடுகள்"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s மற்றும் துணை மதிப்பீடுகள்"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"துணை மதிப்பிடல்கள்"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"இந்தச் சேனலைப் பார்க்க பின்னை உள்ளிடவும்"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"இந்த நிகழ்ச்சியைப் பார்க்க பின்னை உள்ளிடவும்"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"பின்னை உள்ளிடவும்"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"பெற்றோர் கட்டுப்பாடுகளை அமைக்க, PINஐ உருவாக்கவும்"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"புதிய PINஐ உள்ளிடவும்"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"PINஐ உறுதிசெய்யவும்"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"உங்கள் தற்போதைய PINஐ உள்ளிடவும்"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"5 முறை தவறாக பின்னை உள்ளிட்டுள்ளீர்கள்.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> வினாடி கழித்து மீண்டும் முயற்சிக்கவும்."</item>
+ <item quantity="other" msgid="8829550842387756054">"5 முறை தவறாக பின்னை உள்ளிட்டுள்ளீர்கள்.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> வினாடிகள் கழித்து மீண்டும் முயற்சிக்கவும்."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"PIN தவறானது. மீண்டும் முயலவும்."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"மீண்டும் முயலவும், PIN பொருந்தவில்லை"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"அறிமுகம்"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"ஓப்பன் சோர்ஸ் உரிமங்கள்"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"ஓப்பன் சோர்ஸ் உரிமங்கள்"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"பதிப்பு"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"இந்தச் சேனலைப் பார்க்க, வலது பக்கம் அழுத்தி, உங்கள் PINஐ உள்ளிடவும்"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"இந்த நிகழ்ச்சியைப் பார்க்க, வலது பக்கம் அழுத்தி PINஐ உள்ளிடவும்"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"இந்த நிகழ்ச்சி <xliff:g id="RATING">%1$s</xliff:g> என மதிப்பிடப்பட்டுள்ளது.\nஅதைப் பார்க்க, வலது பக்கம் அழுத்தி, பின்னை உள்ளிடவும்."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"நிகழ்ச்சி தடுக்கப்பட்டுள்ளது"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"இந்த நிகழ்ச்சி <xliff:g id="RATING">%1$s</xliff:g> என மதிப்பிடப்பட்டுள்ளது"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"தலைப்பு இல்லை"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"சேனல் தடுக்கப்பட்டது"</string>
+ <string name="episode_format" msgid="4881195874563241096">"சீ<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: எபி. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"சேனல் மூலங்கள்"</string>
+ <string name="setup_description" msgid="8728423605912915099">"கிடைக்கக்கூடிய மூலங்களிலிருந்து நேரலைச் சேனல்களை அமைக்கவும். சேனல் மூலத்தைப் பொறுத்து, இதற்கு சில நிமிடங்கள் ஆகலாம்."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"முடிந்தது"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d சேனல் உள்ளது"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d சேனல்கள் உள்ளன"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"சேனல்கள் எதுவுமில்லை"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"புதியவை"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"TV மெனுவை அணுக, "<b>"SELECTஐ அழுத்தவும்"</b>"."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"டிவி உள்ளீடு இல்லை"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"டிவி உள்ளீடு இல்லை"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP ஆதரிக்கப்படவில்லை"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP உடன் காண்பிக்கத்தக்க உள்ளீடு எதுவுமில்லை"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"ட்யூனர் வகை பொருந்தவில்லை. ட்யூனர் வகை டிவி உள்ளீட்டிற்கு நேரலைச் சேனல்கள் பயன்பாட்டைத் துவங்கவும்."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"ட்யூன் செய்ய முடியவில்லை"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"இந்தச் செயலைச் செய்வதற்கான பயன்பாடு எதுவுமில்லை."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"எல்லா சேனல்களும் மறைக்கப்பட்டுள்ளன.\nபார்க்க, ஒரு சேனலையாவது தேர்ந்தெடுக்கவும்."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"வலுவற்ற சிக்னலால் வீடியோ கிடைக்கவில்லை"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"வீடியோ கிடைக்கவில்லை"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Back விசை இணைத்த சாதனத்திற்கானது. வெளியேற, Home பட்டனை அழுத்துக."</string>
+</resources>
diff --git a/res/values-te-rIN/arrays.xml b/res/values-te-rIN/arrays.xml
new file mode 100644
index 00000000..b5104952
--- /dev/null
+++ b/res/values-te-rIN/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"సాధారణం"</item>
+ <item msgid="2533030282864800794">"పూర్తిగా"</item>
+ <item msgid="8568284598210500589">"జూమ్"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"అన్ని ఛానెల్‌లు"</item>
+ <item msgid="928298872841713530">"కుటుంబం/పిల్లలు"</item>
+ <item msgid="2751606947569857164">"క్రీడలు"</item>
+ <item msgid="7345749789651321496">"షాపింగ్"</item>
+ <item msgid="167201149441442173">"చలనచిత్రాలు"</item>
+ <item msgid="525966731464264290">"హాస్యం"</item>
+ <item msgid="6096710741527327836">"ప్రయాణం"</item>
+ <item msgid="2851882187117833883">"నాటకం"</item>
+ <item msgid="78492781188719038">"విద్య"</item>
+ <item msgid="7221999662426308394">"జంతువు/వన్యప్రాణులు"</item>
+ <item msgid="375300513250925001">"వార్తలు"</item>
+ <item msgid="7746320336582330410">"గేమింగ్"</item>
+ <item msgid="1255741860568329178">"కళలు"</item>
+ <item msgid="7603949681065702867">"వినోదం"</item>
+ <item msgid="4453821994746804366">"జీవన శైలి"</item>
+ <item msgid="3488534597567932843">"సంగీతం"</item>
+ <item msgid="7452153120614274095">"ప్రీమియర్"</item>
+ <item msgid="8215762047341133299">"సాంకేతికం/శాస్త్రం"</item>
+ </string-array>
+</resources>
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..4414134f
--- /dev/null
+++ b/res/values-te-rIN/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"మాదక ద్రవ్యాలు"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"శృంగారం"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"హింస"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"మాదక ద్రవ్యాలు"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"శృంగారం"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"హింస"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"ఫ్రెంచ్"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"సరస శృంగార వ్యాఖ్యలు"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"అనాగరిక భాష"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"శృంగార విషయాలు"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"హింస"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"కాల్పనిక హింస"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"చలన చిత్ర రేటింగ్‌లు"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
new file mode 100644
index 00000000..d4f96378
--- /dev/null
+++ b/res/values-te-rIN/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"ప్రత్యక్ష ప్రసార ఛానెల్‌లు"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"మోనో"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"స్టీరియో"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"ప్లే నియంత్రణలు"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"ఛానెల్‌లు"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"ఇటీవలి ఛానెళ్లు"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"టీవీ ఎంపికలు"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP ఎంపికలు"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"ఈ ఛానెల్ యొక్క ప్లే నియంత్రణలు అందుబాటులో లేవు"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"ప్లే చేస్తుంది లేదా పాజ్ చేస్తుంది"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"ఫాస్ట్ ఫార్వార్డ్ చేస్తుంది"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"రివైండ్ చేస్తుంది"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"తదుపరి"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"మునుపటి"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"ప్రోగ్రామ్ గైడ్"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"కొత్త ఛానె. ఉన్నప్పుడు"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"లింక్ అందుబాటులో లేదు"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g>ని తెరువు"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"సంవృత శీర్షికలు"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"ప్రదర్శన మోడ్"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"ఆన్‌లో ఉంది"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"ఆఫ్‌లో ఉంది"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"బహుళ-ఆడియో"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"ఛానెల్ సెటప్"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"పేరెం. నియంత్ర."</string>
+ <string name="options_item_about" msgid="3023532413252052050">"పరిచయం"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"మూలం"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"మార్చు"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"ఆన్‌లో ఉంది"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"ఆఫ్‌లో ఉంది"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"ధ్వని"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"ప్రధానమైనది"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP విండో"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"లేఅవుట్"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"దిగువ కుడివైపు"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"ఎగువ కుడివైపు"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"ఎగువ ఎడమవైపు"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"దిగువ ఎడమవైపు"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"పక్కపక్కన"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"పరిమాణం"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"పెద్దది"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"చిన్నది"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"ఇన్‌పుట్ మూలం"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"టీవీ (యాంటెన్నా/కేబుల్)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"కార్యక్రమ సమాచారం లేదు"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"సమాచారం లేదు"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"బ్లాక్ చేసిన ఛానెల్"</string>
+ <string name="default_language" msgid="4122326459624337928">"భాష తెలియదు"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"సంవృత శీర్షికలు"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"ఆఫ్ చేయి"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"ఆకృతీకరణను అనుకూలీకరించు"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"సంవృత శీర్షికల కోసం సిస్టమ్-వ్యాప్త ప్రాధాన్యతలను సెట్ చేయి"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"ప్రదర్శన మోడ్"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"బహుళ-ఆడియో"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"మోనో"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"స్టీరియో"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 సరౌండ్"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 సరౌండ్"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d ఛానెల్‌లు"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"ఛానెల్‌ జాబితా అనుకూలీ."</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"సమూహాన్ని ఎంచుకోండి"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"సమూహం ఎంపిక తీసివేయి"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"ఈ ప్రకారం సమూహం చేయి"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"ఛానెల్ మూలం"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"ఈ ప్రకారం సమూహం చేయి"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"ఛానెల్ సెటప్"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"ఈ కార్యక్రమం బ్లాక్ చేయబడింది"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"ఈ కార్యక్రమం <xliff:g id="RATING">%1$s</xliff:g> అని రేట్ చేయబడింది"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"ఛానెల్ సోర్స్‌లు"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"కొత్త ఛానెల్‌లు అందుబాటులో ఉన్నప్పుడు"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"ఛానెల్‌ జాబితాను అనుకూలీకరించండి"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"మీ ప్రోగ్రామ్ గైడ్ కోసం ఛానె. ఎంచుకోండి"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"సెటప్ చేయలేదు"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"స్వయంచాలకంగా స్కాన్ చేయడానికి ఇన్‌పుట్ మద్దతు ఇవ్వలేదు"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\'<xliff:g id="TV_INPUT">%s</xliff:g>\' కోసం ఆటో-స్కాన్‌ని ప్రారంభించడం సాధ్యపడలేదు"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"సంవృత శీర్షికల కోసం సిస్టమ్-వ్యాప్త ప్రాధాన్యతలను ప్రారంభించడం సాధ్యపడలేదు."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d ఛానెల్ జోడించబడింది"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d ఛానెల్‌లు జోడించబ."</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"ఛానెల్‌లు జోడించబడలేదు"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"ట్యూనర్"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"తల్లిదండ్రుల నియంత్రణలు"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"ఆన్ చేయి"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"ఆఫ్ చేయి"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"ఛానె. బ్లాక్ చేయబ."</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"అన్నీ బ్లాక్ చేయి"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"అన్నీ అన్‌బ్లాక్ చే."</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"దాచబడిన ఛానెల్‌లు"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"ప్రోగ్రామ్ పరిమితులు"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"పిన్‌ను మార్చు"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"రేటింగ్ సిస్టమ్‌లు"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"రేటింగ్‌లు"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"అన్ని రేటింగ్ సిస్టమ్‌లను చూడండి"</string>
+ <string name="other_countries" msgid="8342216398676184749">"ఇతర దేశాలు"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"ఏదీ లేదు"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"ఏదీ లేదు"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"ఏదీ లేదు"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"అధిక పరిమితులు"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"మధ్యస్థ పరిమితులు"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"తక్కువ పరిమితులు"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"అనుకూలం"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"కంటెంట్ పిల్లలకు తగినది"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"కంటెంట్ యుక్తవయస్సు పిల్లలకు తగినది"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"కంటెంట్ టీనేజ్ వాళ్లకు తగినది"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"మాన్యువల్ పరిమితులు"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s, సబ్-రేటింగ్‌లు"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"సబ్-రేటింగ్‌లు"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"ఈ ఛానెల్‌ని చూడటానికి మీ పిన్‌ని నమోదు చేయండి"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"ఈ కార్యక్రమాన్ని చూడటానికి మీ పిన్‌ని నమోదు చేయండి"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"మీ పిన్‌ని నమోదు చేయండి"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"తల్లిదండ్రుల నియంత్రణలను సెట్ చేయడానికి, పిన్‌ని సృష్టించండి"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"కొత్త పిన్‌ని నమోదు చేయండి"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"మీ పిన్‌ను నిర్ధారించండి"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"మీ ప్రస్తుత పిన్‌ని నమోదు చేయండి"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"మీరు 5 సార్లు తప్పు PIN నమోదు చేసారు.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> సెకనులో మళ్లీ ప్రయత్నించండి."</item>
+ <item quantity="other" msgid="8829550842387756054">"మీరు 5 సార్లు తప్పు PIN నమోదు చేసారు.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> సెకన్లలో మళ్లీ ప్రయత్నించండి."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"ఆ పిన్ తప్పు. మళ్లీ ప్రయత్నించండి."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"మళ్లీ ప్రయత్నించండి, పిన్ సరిపోలలేదు"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"పరిచయం"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"ఓపెన్ సోర్స్ లైసెన్స్‌లు"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"ఓపెన్ సోర్స్ లైసెన్స్‌లు"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"సంస్కరణ"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"ఈ ఛానెల్‌ను చూడటానికి, కుడివైపు బటన్ నొక్కి, మీ పిన్‌ని నమోదు చేయండి"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"ఈ ప్రోగ్రామ్‌ని చూడటానికి, కుడివైపు బటన్ నొక్కి, మీ పిన్‌ని నమోదు చేయండి"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"ఈ కార్యక్రమం <xliff:g id="RATING">%1$s</xliff:g> అని రేట్ చేయబడింది.\nఈ కార్యక్రమాన్ని చూడటానికి, కుడివైపు బటన్ నొక్కి, మీ PINని నమోదు చేయండి."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"కార్యక్రమం బ్లాక్ చేయబడింది"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"ఈ కార్యక్రమం <xliff:g id="RATING">%1$s</xliff:g> అని రేట్ చేయబడింది"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"శీర్షిక లేదు"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"ఛానెల్ బ్లాక్ చేయబడింది"</string>
+ <string name="episode_format" msgid="4881195874563241096">"సీ.<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: ఎపి. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"ఛానెల్ సోర్స్‌లు"</string>
+ <string name="setup_description" msgid="8728423605912915099">"అందుబాటులో ఉన్న సోర్స్‌ల నుండి లైవ్ ఛానెల్‌లను సెటప్ చేయండి. ఛానెల్ సోర్స్‌ను బట్టి దీనికి కొన్ని నిమిషాలు పట్టవచ్చు."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"పూర్తయింది"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d ఛానెల్ అందుబాటులో ఉంది"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d ఛానెల్‌లు అందుబాటులో ఉన్నాయి"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"ఛానెల్‌లు ఏవీ అందుబాటులో లేవు"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"కొత్తవి"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"టీవీ మెనుని ప్రాప్యత చేయడానికి "<b>"ఎంచుకోండి నొక్కండి"</b>"."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"టీవీ ఇన్‌పుట్ కనుగొనబడలేదు"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"టీవీ ఇన్‌పుట్‌ను కనుగొనడం సాధ్యపడదు"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIPకి మద్దతు లేదు"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIPతో చూపబడే ఇన్‌పుట్ ఏదీ అందుబాటులో లేదు"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"ట్యూనర్ రకం తగినది కాదు. దయచేసి ట్యూనర్ రకం టీవీ ఇన్‌పుట్ కోసం లైవ్ ఛానెల్‌లు అనువర్తనాన్ని ప్రారంభించండి."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"ట్యూన్ విఫలమైంది"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"ఈ చర్యను నిర్వహించడానికి అనువర్తనం ఏదీ కనుగొనబడలేదు."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"అన్ని మూల ఛానెల్‌లు దాచబడ్డాయి.\nచూడటానికి కనీసం ఒక ఛానెల్‌ను ఎంచుకోండి."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"వీడియో సిగ్నల్ బలహీనంగా ఉన్నందున అందుబాటులో లేదు"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"వీడియో ఊహించని విధంగా అందుబాటులో లేకుండా పోయింది"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"BACK కీ కనెక్ట్ చేయబడిన పరికరం కోసం ఉద్దేశించినది. నిష్క్రమించడానికి HOME బటన్‌ను నొక్కండి."</string>
+</resources>
diff --git a/res/values-th/arrays.xml b/res/values-th/arrays.xml
new file mode 100644
index 00000000..4a3f7236
--- /dev/null
+++ b/res/values-th/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"ปกติ"</item>
+ <item msgid="2533030282864800794">"เต็มจอ"</item>
+ <item msgid="8568284598210500589">"ซูม"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"ช่องทั้งหมด"</item>
+ <item msgid="928298872841713530">"ครอบครัว/เด็ก"</item>
+ <item msgid="2751606947569857164">"กีฬา"</item>
+ <item msgid="7345749789651321496">"ช็อปปิ้ง"</item>
+ <item msgid="167201149441442173">"ภาพยนตร์"</item>
+ <item msgid="525966731464264290">"ตลก"</item>
+ <item msgid="6096710741527327836">"การเดินทาง"</item>
+ <item msgid="2851882187117833883">"ดราม่า"</item>
+ <item msgid="78492781188719038">"การศึกษา"</item>
+ <item msgid="7221999662426308394">"สัตว์/สัตว์ป่า"</item>
+ <item msgid="375300513250925001">"ข่าวสาร"</item>
+ <item msgid="7746320336582330410">"เกม"</item>
+ <item msgid="1255741860568329178">"ศิลปะ"</item>
+ <item msgid="7603949681065702867">"บันเทิง"</item>
+ <item msgid="4453821994746804366">"ไลฟ์สไตล์"</item>
+ <item msgid="3488534597567932843">"เพลง"</item>
+ <item msgid="7452153120614274095">"ปฐมทัศน์"</item>
+ <item msgid="8215762047341133299">"วิทยาศาสตร์/เทคฯ"</item>
+ </string-array>
+</resources>
diff --git a/res/values-th/rating_system_strings.xml b/res/values-th/rating_system_strings.xml
new file mode 100644
index 00000000..151cc028
--- /dev/null
+++ b/res/values-th/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"ยาเสพติด"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"เรื่องเพศ"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"ความรุนแรง"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"ยาเสพติด"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"เรื่องเพศ"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"ความรุนแรง"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"ภาษาฝรั่งเศส"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"การสนทนาที่มีการชี้นำทางเพศ"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"ภาษาหยาบคาย"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"เนื้อหาเกี่ยวกับเรื่องเพศ"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"ความรุนแรง"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"ภาพจินตนาการที่แสดงความรุนแรง"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"การจัดประเภทภาพยนตร์"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
new file mode 100644
index 00000000..0359b8ac
--- /dev/null
+++ b/res/values-th/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"โมโน"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"สเตอริโอ"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"การควบคุมการเล่น"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"ช่อง"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"ช่องล่าสุด"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"ตัวเลือกทีวี"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"ตัวเลือกของ PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"ไม่มีการควบคุมการเล่นสำหรับช่องนี้"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"เล่นหรือหยุดชั่วคราว"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"กรอไปข้างหน้า"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"กรอกลับ"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"ถัดไป"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"ก่อนหน้า"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"คู่มือโปรแกรม"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"มีช่องใหม่ให้บริการ"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"ไม่มีลิงก์"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"เปิด <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"คำบรรยาย"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"โหมดการแสดงผล"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"เปิด"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"ปิด"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"หลายเสียง"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"การตั้งค่าช่อง"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"ผู้ปกครองควบคุม"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"เกี่ยวกับ"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"แหล่งที่มา"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"สลับ"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"เปิด"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"ปิด"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"เสียง"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"หลัก"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"หน้าต่าง PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"การจัดวาง"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"ขวาล่าง"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"ขวาบน"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"ซ้ายบน"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"ซ้ายล่าง"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"แสดงคู่กัน"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"ขนาด"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"ใหญ่"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"เล็ก"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"แหล่งที่มาอินพุต"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (สายอากาศ/เคเบิล)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"ไม่มีข้อมูลโปรแกรม"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"ไม่มีข้อมูล"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"ช่องที่ถูกบล็อก"</string>
+ <string name="default_language" msgid="4122326459624337928">"ภาษาที่ไม่รู้จัก"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"คำบรรยาย"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"ปิด"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"กำหนดค่าการจัดรูปแบบ"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"ตั้งค่ากำหนดทั้งระบบสำหรับคำบรรยาย"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"โหมดการแสดงผล"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"หลายเสียง"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"โมโน"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"สเตอริโอ"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 เซอร์ราวด์"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 เซอร์ราวด์"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d ช่อง"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"กำหนดค่ารายการช่อง"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"เลือกกลุ่ม"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"ยกเลิกการเลือกกลุ่ม"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"จัดกลุ่มตาม"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"ที่มาของช่อง"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"จัดกลุ่มตาม"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"การตั้งค่าช่อง"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"โปรแกรมนี้ถูกบล็อก"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"โปรแกรมนี้เรต <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"แหล่งที่มาของช่อง"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"มีช่องใหม่ให้บริการ"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"กำหนดค่ารายการช่อง"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"เลือกช่องสำหรับคู่มือโปรแกรมของคุณ"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"ไม่ได้ตั้งค่า"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"อินพุตไม่สนับสนุนการสแกนอัตโนมัติ"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"ไม่สามารถเริ่มการสแกนอัตโนมัติสำหรับ \"<xliff:g id="TV_INPUT">%s</xliff:g>\""</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"ไม่สามารถเริ่มใช้ค่ากำหนดสำหรับทั้งระบบกับคำบรรยายได้"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"เพิ่มแล้ว %1$d ช่อง"</item>
+ <item quantity="other" msgid="1078861616751739285">"เพิ่มแล้ว %1$d ช่อง"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"ไม่ได้เพิ่มช่อง"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"ตัวรับสัญญาณ"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"ควบคุมโดยผู้ปกครอง"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"เปิด"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"ปิด"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"ช่องถูกบล็อก"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"บล็อกทั้งหมด"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"เลิกบล็อกทั้งหมด"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"ช่องที่ซ่อนอยู่"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"ข้อจำกัดของโปรแกรม"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"เปลี่ยน PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"ระบบการจัดประเภท"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"คะแนน"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"ดูทุกการจัดประเภท"</string>
+ <string name="other_countries" msgid="8342216398676184749">"ประเทศอื่นๆ"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"ไม่มี"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"ไม่มี"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"ไม่มี"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"ข้อจำกัดสูง"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"ข้อจำกัดปานกลาง"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"ข้อจำกัดต่ำ"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"กำหนดเอง"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"เนื้อหาที่เหมาะสำหรับเด็ก"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"เนื้อหาเหมาะสำหรับเด็กโต"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"เนื้อหาที่เหมาะสำหรับวัยรุ่น"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"ข้อจำกัดที่ตั้งขึ้นเอง"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s และประเภทย่อย"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"การจัดประเภทย่อย"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"ป้อน PIN ของคุณเพื่อดูช่องนี้"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"ป้อน PIN ของคุณเพื่อดูโปรแกรมนี้"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"ป้อน PIN ของคุณ"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"หากต้องการตั้งค่าการควบคุมโดยผู้ปกครอง โปรดสร้าง PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"ป้อน PIN ใหม่"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"ยืนยัน PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"ป้อน PIN ปัจจุบันของคุณ"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"คุณป้อน PIN ผิด 5 ครั้งแล้ว\nลองอีกครั้งใน <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> วินาที"</item>
+ <item quantity="other" msgid="8829550842387756054">"คุณป้อน PIN ผิด 5 ครั้งแล้ว\nลองอีกครั้งใน <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> วินาที"</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"PIN ไม่ถูกต้อง ลองอีกครั้ง"</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"ลองอีกครั้ง PIN ไม่ตรงกัน"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"เกี่ยวกับ"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"ใบอนุญาตโอเพนซอร์ส"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"ใบอนุญาตโอเพนซอร์ส"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"เวอร์ชัน"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"หากต้องการดูช่องนี้ ให้กดขวาและป้อน PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"หากต้องการดูโปรแกรมนี้ ให้กดขวาและป้อน PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"โปรแกรมนี้เรต <xliff:g id="RATING">%1$s</xliff:g>\nในการดูโปรแกรมนี้ โปรดกดขวาและป้อน PIN"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"ระบบบล็อกโปรแกรมไว้"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"โปรแกรมนี้เรต <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"ไม่มีชื่อ"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"บล็อกช่องแล้ว"</string>
+ <string name="episode_format" msgid="4881195874563241096">"ซีซัน <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: ตอนที่ <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"แหล่งที่มาของช่อง"</string>
+ <string name="setup_description" msgid="8728423605912915099">"ตั้งค่าช่องสดจากแหล่งที่มาที่พร้อมใช้งาน ขั้นตอนนี้อาจใช้เวลาหลายนาทีขึ้นอยู่กับแหล่งที่มาของช่อง"</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"เสร็จสิ้น"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"มี %1$d ช่อง"</item>
+ <item quantity="other" msgid="2386588423841837714">"มี %1$d ช่อง"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"ไม่มีช่องให้บริการ"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"ใหม่"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"กด \"เลือก\""</b>" เพื่อเข้าถึงเมนู TV"</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"ไม่พบอินพุต TV"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"ไม่พบอินพุต TV"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"ไม่สนับสนุน PIP"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"ไม่มีอินพุตที่พร้อมใช้งานซึ่งสามารถแสดงด้วย PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"ประเภทตัวรับสัญญาณไม่เหมาะสม โปรดเปิดแอป Live Channels สำหรับอินพุต TV ที่เป็นประเภทตัวรับสัญญาณ"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"การรับสัญญาณล้มเหลว"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"ไม่พบแอปสำหรับการทำงานนี้"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"ช่องแหล่งที่มาทั้งหมดซ่อนอยู่\nเลือกอย่างน้อย 1 ช่องเพื่อดู"</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"ไม่พร้อมใช้งานเนื่องจากสัญญาณวิดีโอไม่ดี"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"วิดีโอไม่พร้อมใช้งานด้วยเหตุบางประการ"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"แป้นกลับมีไว้สำหรับอุปกรณ์ที่เชื่อมต่อ กดปุ่มหน้าแรกเพื่อออก"</string>
+</resources>
diff --git a/res/values-tl/arrays.xml b/res/values-tl/arrays.xml
new file mode 100644
index 00000000..19be2040
--- /dev/null
+++ b/res/values-tl/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Buo"</item>
+ <item msgid="8568284598210500589">"Mag-zoom"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Lahat ng channel"</item>
+ <item msgid="928298872841713530">"Pampamilya/Pambata"</item>
+ <item msgid="2751606947569857164">"Sports"</item>
+ <item msgid="7345749789651321496">"Pamimili"</item>
+ <item msgid="167201149441442173">"Mga Pelikula"</item>
+ <item msgid="525966731464264290">"Komedya"</item>
+ <item msgid="6096710741527327836">"Paglalakbay"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Edukasyon"</item>
+ <item msgid="7221999662426308394">"Hayop/Wildlife"</item>
+ <item msgid="375300513250925001">"Balita"</item>
+ <item msgid="7746320336582330410">"Paglalaro"</item>
+ <item msgid="1255741860568329178">"Sining"</item>
+ <item msgid="7603949681065702867">"Entertainment"</item>
+ <item msgid="4453821994746804366">"Estilo ng Pamumuhay"</item>
+ <item msgid="3488534597567932843">"Musika"</item>
+ <item msgid="7452153120614274095">"Premier"</item>
+ <item msgid="8215762047341133299">"Teknolohiya/Agham"</item>
+ </string-array>
+</resources>
diff --git a/res/values-tl/rating_system_strings.xml b/res/values-tl/rating_system_strings.xml
new file mode 100644
index 00000000..03e9c58f
--- /dev/null
+++ b/res/values-tl/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Mga Bawal na Gamot"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Pagtatalik"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Karahasan"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Mga Bawal na Gamot"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Pagtatalik"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Karahasan"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"French"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Magpahiwatig na usapan"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Magaspang na pananalita"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Sekswal na content"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Karahasan"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Karahasan sa fantasy"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Mga rating ng pelikula"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
new file mode 100644
index 00000000..d299e59e
--- /dev/null
+++ b/res/values-tl/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Mga Live na Channel"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Mga kontrol sa Play"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Mga Channel"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Mga kamakailang channel"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Opsyon sa TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Mga opsyon sa PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Hindi available para sa channel na ito ang mga kontrol ng laro"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"I-play o i-pause"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"I-fast forward"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"I-rewind"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Susunod"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Nakaraan"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Gabay sa programa"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"May mga bagong channel"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Walang available link"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Buksan ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Closed captions"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Display mode"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Naka-on"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Naka-off"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Multi-audio"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Channel setup"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Parental controls"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Tungkol dito"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Pinagmulan"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Pagpalitin"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Naka-on"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Naka-off"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Tunog"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Pangunahin"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP window"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Layout"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Kanang ibaba"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Kanang itaas"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Kaliwang itaas"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Kaliwang ibaba"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Magkatabi"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Laki"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Malaki"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Maliit"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Pinagmulan ng input"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antenna/cable)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Walang impormasyon ng programa"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Walang impormasyon"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Naka-block na channel"</string>
+ <string name="default_language" msgid="4122326459624337928">"Hindi kilalang wika"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Mga closed caption"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Naka-off"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Customize formatting"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"I-set kagustuhan para sa closed captions"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Display mode"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Multi-audio"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d (na) channel"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Customize channel list"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Pumili ng pangkat"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Alisin ang pangkat"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Ipangkat ayon sa"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Pinagmulan ng channel"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Ipangkat ayon sa"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Setup ng channel"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Naka-block ang programang ito"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Ang programang ito ay na-rate na <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Mga channel source"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Available ang mga bagong channel"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Customize channel list"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Pumili ng mga channel para sa gabay sa programa"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Hindi naka-set up"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Hindi sinusuportahan ng input ang auto-scan"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Hindi maumpisahan ang auto-scan para sa \'<xliff:g id="TV_INPUT">%s</xliff:g>\'"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Hindi masimulan ang mga kagustuhan sa buong system para sa mga closed caption."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d channel ang nadagdag"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d channel ang nadagdag"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Wala naidagdag na channel"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Parental controls"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Naka-on"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Naka-off"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Channels blocked"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"I-block lahat"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"I-unblock lahat"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Mga nakatagong channel"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Program restrictions"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Baguhin ang PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"System ng rating"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Mga Rating"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Tingnan lahat"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Iba pang bansa"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Wala"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Wala"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Wala"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"High restrictions"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Bahagyang pinaghihigpitan"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Di-gaanong pinaghihigpitan"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Custom"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Naaangkop ang content para sa mga bata"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Naaangkop sa mga batang edad 9-11"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Naaangkop ang content para sa mga teenager"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Manu-manong mga paghihigpit"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s at sub-ratings"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Mga sub-rating"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Ilagay iyong PIN upang mapanood ang channel na ito"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Ilagay iyong PIN upang mapanood ang programang ito"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Ilagay ang iyong PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Upang itakda ang mga parental control, gumawa ng PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Ilagay ang bagong PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Kumpirmahin ang iyong PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Ilagay ang iyong kasalukuyang PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"5 beses kang naglagay ng maling PIN.\nSubukang muli sa loob ng <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> segundo."</item>
+ <item quantity="other" msgid="8829550842387756054">"5 beses kang naglagay ng maling PIN.\nSubukang muli sa loob ng <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> (na) segundo."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Mali ang PIN na iyon. Subukang muli."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Subukang muli, hindi tumutugma ang PIN"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Tungkol dito"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Mga lisensyang open source"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Mga lisensyang open source"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Bersyon"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Upang mapanood ang channel na ito, pindutin ang Kanan at ilagay ang iyong PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Upang mapanood ang programang ito, pindutin ang Kanan at ilagay ang iyong PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Ang programang ito ay na-rate na <xliff:g id="RATING">%1$s</xliff:g>.\nUpang mapanood ang programang ito, pindutin ang Kanan at ilagay ang iyong PIN."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Naka-block ang programa"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Ang programang ito ay na-rate na <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Walang pamagat"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Naka-block ang channel"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Mga pinagmumulan ng channel"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Mag-set up ng mga live na channel mula sa mga available na pinagmulan. Maaari itong umabot ng ilang minuto depende sa pinagmulan ng channel."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Tapos na"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d channel ang available"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d (na) channel ang available"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Walang mga channel na available"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Bago"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Pindutin ang SELECT"</b>" upang i-access ang menu ng TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Walang nahanap na TV input"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Hindi mahanap ang TV input"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"Hindi sinusuportahan ang PIP"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Walang available na input na maipapakita sa PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Hindi naaangkop ang uri ng tuner. Pakilunsad ang app na Mga Live na Channel para sa uri ng tuner na input ng TV."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Hindi na-tune"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Walang nakitang app na gagawa sa aksyong ito."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Nakatago ang lahat ng pinagmumulang channel.\nPumili ng kahit isang channel lang na papanoorin."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Hindi available dahil sa mahinang signal ng video"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Hindi inaasahang hindi available ang video"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Ang BACK key ay para sa nakakonektang device. Pindutin ang HOME button upang lumabas."</string>
+</resources>
diff --git a/res/values-tr/arrays.xml b/res/values-tr/arrays.xml
new file mode 100644
index 00000000..6e29e7f0
--- /dev/null
+++ b/res/values-tr/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"Tam"</item>
+ <item msgid="8568284598210500589">"Zum"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Tüm kanallar"</item>
+ <item msgid="928298872841713530">"Aile/Çocuk"</item>
+ <item msgid="2751606947569857164">"Spor"</item>
+ <item msgid="7345749789651321496">"Alışveriş"</item>
+ <item msgid="167201149441442173">"Filmler"</item>
+ <item msgid="525966731464264290">"Komedi"</item>
+ <item msgid="6096710741527327836">"Seyahat"</item>
+ <item msgid="2851882187117833883">"Dram"</item>
+ <item msgid="78492781188719038">"Eğitim"</item>
+ <item msgid="7221999662426308394">"Hayvanlar/Vahşi Yaşam"</item>
+ <item msgid="375300513250925001">"Haberler"</item>
+ <item msgid="7746320336582330410">"Oyun"</item>
+ <item msgid="1255741860568329178">"Sanat"</item>
+ <item msgid="7603949681065702867">"Eğlence"</item>
+ <item msgid="4453821994746804366">"Yaşam Tarzı"</item>
+ <item msgid="3488534597567932843">"Müzik"</item>
+ <item msgid="7452153120614274095">"İlk Gösterim"</item>
+ <item msgid="8215762047341133299">"Teknoloji/Bilim"</item>
+ </string-array>
+</resources>
diff --git a/res/values-tr/rating_system_strings.xml b/res/values-tr/rating_system_strings.xml
new file mode 100644
index 00000000..8e25de47
--- /dev/null
+++ b/res/values-tr/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Uyuşturucular"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Cinsellik"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Şiddet"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Uyuşturucular"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Cinsellik"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Şiddet"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Fransızca"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Müstehcen konuşma"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Kaba dil"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Cinsel içerik"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Şiddet"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantastik şiddet"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Film derecelendirmeleri"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
new file mode 100644
index 00000000..77a9921c
--- /dev/null
+++ b/res/values-tr/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Canlı Yayın Kanalları"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Oynatma denetimleri"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanallar"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Son kanallar"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV seçenekleri"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP seçenekleri"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Bu kanal için oynatma denetimleri kullanılamıyor"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Oynat veya duraklat"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"İleri sar"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Geri sar"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Sonraki"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Önceki"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Program rehberi"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Yeni kanallar mevcut"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Bağlantı mevcut değil"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasını aç"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Altyazılar"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Görüntü modu"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Açık"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Kapalı"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Çoklu ses"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Kanal kurulumu"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Ebeveyn denetimi"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Hakkında"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Kaynak"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Değiştir"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Açık"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Kapalı"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Ses"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Ana"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP penceresi"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Düzen"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Sağ alt"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Sağ üst"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Sol üst"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Sol alt"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Yan yana"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Boyut"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Büyük"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Küçük"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Giriş kaynağı"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (anten/kablo)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Hiçbir program bilgisi yok"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Bilgi yok"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Engellenen kanal"</string>
+ <string name="default_language" msgid="4122326459624337928">"Bilinmeyen dil"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Altyazılar"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Kapalı"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Biçimi özelleştir"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Sistem genelinde altyazı tercihlerini ayarlayın"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Görüntü modu"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Çoklu ses"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 surround"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 surround"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d kanal"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Kanal listesini özelleştir"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Grup seç"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Grubun seçimini kaldır"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Gruplama ölçütü"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Kanal kaynağı"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Gruplama ölçütü"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Kanal kurulumu"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Bu program engellendi"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Bu program <xliff:g id="RATING">%1$s</xliff:g> olarak derecelendirilmiştir"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Kanal kaynakları"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Yeni kanallar mevcut"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Kanal listesini özelleştir"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Program rehberiniz için kanalları seçin"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Yapılandırılmadı"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Giriş, otomatik taramayı desteklemiyor"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\"<xliff:g id="TV_INPUT">%s</xliff:g>\" için otomatik tarama başlatılamıyor"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Altyazılar için sistem genelinde tercihler başlatılamıyor."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d kanal eklendi"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d kanal eklendi"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Hiçbir kanal eklenmedi"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Ebeveyn denetimi"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Açık"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Kapalı"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Kanallr engellendi"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Tümünü engelle"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Tüm engel. kaldır"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Gizli kanallar"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Prog kısıtlamaları"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN\'i değiştir"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Derece. sistemleri"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Derecelendirmeler"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Tüm derec. sis. gös."</string>
+ <string name="other_countries" msgid="8342216398676184749">"Diğer ülkeler"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Yok"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Yok"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Yok"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Yüksek kısıtlama"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Orta düzey kısıtlama"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Düşük kısıtlama"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Özel"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Çocuklar için uygun içerik"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Büyük çocuklar için uygun içerik"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"12 yaşından büyükler için uygun içerik"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Manuel kısıtlamalar"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s ve alt derece."</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Alt derecelendirme"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Bu kanalı izlemek için PIN\'inizi girin"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Bu programı izlemek için PIN\'inizi girin"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"PIN\'inizi girin"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Ebeveyn denetimlerini ayarlamak için PIN oluşturun"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Yeni PIN\'i girin"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"PIN\'inizi onaylayın"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Geçerli PIN\'inizi girin"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"PIN\'i 5 kez yanlış girdiniz.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> saniye içinde tekrar deneyin."</item>
+ <item quantity="other" msgid="8829550842387756054">"PIN\'i 5 kez yanlış girdiniz.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> saniye içinde tekrar deneyin."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Girdiğiniz PIN hatalıydı. Tekrar deneyin."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Tekrar deneyin, PIN eşleşmiyor"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Hakkında"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Açık kaynak lisansları"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Açık kaynak lisansları"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Sürüm"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Bu kanalı izlemek için Sağ tuşuna basın ve PIN\'inizi girin"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Bu programı izlemek için Sağ tuşuna basın ve PIN\'inizi girin"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Bu program <xliff:g id="RATING">%1$s</xliff:g> olarak derecelendirilmiştir.\nBu programı izlemek için Sağ tuşuna basın ve PIN\'inizi girin."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Program engellendi"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Bu program <xliff:g id="RATING">%1$s</xliff:g> olarak derecelendirilmiştir"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Başlıksız"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanal engellendi"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Böl. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Kanal kaynakları"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Kullanılabilir kaynaklardan canlı yayın kanallarını kurun. Bu işlem, kanal kaynağına bağlı olarak birkaç dakika sürebilir."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Bitti"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d kanal kullanılabilir"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d kanal kullanılabilir"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Kullanılabilir kanal yok"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Yeni"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"TV menüsüne erişmek için "<b>"SEÇ\'e basın"</b>"."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"TV girişi bulunamadı"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"TV girişi bulunamadı"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP desteklenmiyor"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP ile göstermeye uygun giriş yok"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"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."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Tarama işlemi başarısız oldu"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Bu işlemi gerçekleştirecek uygulama bulunamadı."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Tüm kaynak kanalları gizli.\nİzlemek için en az bir tane kanal seçin."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Zayıf video sinyali nedeniyle kullanılamıyor"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Video beklenmedik şekilde kullanılamıyor"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"GERİ tuşu bağlı cihazlar içindir. Çıkmak için ANA SAYFA düğmesine basın."</string>
+</resources>
diff --git a/res/values-uk/arrays.xml b/res/values-uk/arrays.xml
new file mode 100644
index 00000000..052fff6b
--- /dev/null
+++ b/res/values-uk/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Звичайний режим"</item>
+ <item msgid="2533030282864800794">"На весь екран"</item>
+ <item msgid="8568284598210500589">"Масштабування"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Усі канали"</item>
+ <item msgid="928298872841713530">"Сім’я та діти"</item>
+ <item msgid="2751606947569857164">"Спорт"</item>
+ <item msgid="7345749789651321496">"Покупки"</item>
+ <item msgid="167201149441442173">"Фільми"</item>
+ <item msgid="525966731464264290">"Комедія"</item>
+ <item msgid="6096710741527327836">"Подорожі"</item>
+ <item msgid="2851882187117833883">"Драма"</item>
+ <item msgid="78492781188719038">"Освіта"</item>
+ <item msgid="7221999662426308394">"Тварини та природа"</item>
+ <item msgid="375300513250925001">"Новини"</item>
+ <item msgid="7746320336582330410">"Ігри"</item>
+ <item msgid="1255741860568329178">"Мистецтво"</item>
+ <item msgid="7603949681065702867">"Розваги"</item>
+ <item msgid="4453821994746804366">"Стиль життя"</item>
+ <item msgid="3488534597567932843">"Музика"</item>
+ <item msgid="7452153120614274095">"Популярне"</item>
+ <item msgid="8215762047341133299">"Наука й техніка"</item>
+ </string-array>
+</resources>
diff --git a/res/values-uk/rating_system_strings.xml b/res/values-uk/rating_system_strings.xml
new file mode 100644
index 00000000..118bcc90
--- /dev/null
+++ b/res/values-uk/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Наркотики"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Секс"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Насильство"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Наркотики"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Секс"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Насильство"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"французька"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Непристойні діалоги"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Вульгарні висловлювання"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Вміст сексуального характеру"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Насильство"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Вигадане насильство"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Категорії фільмів"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
new file mode 100644
index 00000000..f2587efe
--- /dev/null
+++ b/res/values-uk/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Live Channels"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"моно"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"стерео"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Керування відтворенням"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Канали"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Останні канали"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Опції ТБ"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Опції PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Елементи керування відтворенням, яких немає в цьому каналі"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Відтворити або призупинити"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Перемотати вперед"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Перемотати назад"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Далі"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Назад"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Програма передач"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Доступні нові канали"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Немає посилання"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Відкрити додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Субтитри"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Режим показу"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Увімкнено"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Вимкнено"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Кілька аудіо"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Налашт. каналів"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Батьк. контроль"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Інформація"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Джерело"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Заміна"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Увімкнено"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Вимкнено"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Звук"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Основне вікно"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Вікно PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Схема"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Унизу праворуч"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Угорі праворуч"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Угорі ліворуч"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Унизу ліворуч"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Поруч"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Розмір"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Великий розмір"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Малий розмір"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Джерело сигналу"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"ТБ (ефірне або кабельне)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Немає інформації про програму"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Немає інформації."</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Заблокований канал"</string>
+ <string name="default_language" msgid="4122326459624337928">"Невідома мова"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Субтитри"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Вимкнути"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Налаштувати формат"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Вибрати системні параметри для субтитрів"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Режим показу"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Кілька аудіо"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"моно"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"стерео"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Об’ємний звук 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Об’ємний звук 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"Каналів: %d"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Налашт.спис.канал."</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Вибрати групу"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Не вибирати групу"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Параметри групування"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Джерело каналу"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Параметри групування"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Налаштув. каналів"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Цю телепередачу заблоковано"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Ця телепередача належить до категорії <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Джерела каналів"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Доступні нові канали"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Налашт.спис.канал."</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Вибір каналів для програми телепередач"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Не налаштовано"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Джерело вхідного сигналу не підтримує автоматичне сканування"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Не вдалось автоматично просканувати джерело сигналу \"<xliff:g id="TV_INPUT">%s</xliff:g>\""</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Не вдається запустити системні параметри для субтитрів."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"Додано %1$d канал"</item>
+ <item quantity="other" msgid="1078861616751739285">"Додано каналів: %1$d"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Немає каналів"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Тюнер"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Контроль батьків"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Увімкнути"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Вимкнути"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Канали заблоковано"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Блокувати всі канали"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Розблокувати всі"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Сховані канали"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Обмеження програми"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Змінити PIN-код"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Системи категорій"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Рейтинги"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Див. усі категорії"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Інші країни"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Немає"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Немає"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Немає"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Суворі обмеження"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Помірні обмеження"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Мінімальні обмеження"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Вибирає користувач"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Вміст для дітей"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Вміст для дітей старшого віку"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Вміст для підлітків"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Обмеження, налаштовані вручну"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s і підкатегорії"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Підкатегорії"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Введіть PIN-код, щоб дивитися цей канал"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Введіть PIN-код, щоб дивитися цю телепередачу"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Введіть PIN-код"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Щоб налаштувати батьківський контроль, створіть PIN-код"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Введіть новий PIN-код"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Підтвердьте PIN-код"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Введіть поточний PIN-код"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Ви 5 разів неправильно ввели PIN-код.\nПовторіть спробу через <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> с."</item>
+ <item quantity="other" msgid="8829550842387756054">"Ви 5 разів неправильно ввели PIN-код.\nПовторіть спробу через <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> с."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Цей PIN-код неправильний. Повторіть спробу."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"PIN-коди не збігаються. Повторіть спробу"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Інформація"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Ліцензії ПЗ з відкритим кодом"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Ліцензії ПЗ з відкритим кодом"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Версія"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Щоб дивитися цей канал, натисніть стрілку праворуч і введіть PIN-код"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Щоб дивитися цю телепередачу, натисніть стрілку праворуч і введіть PIN-код"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Ця телепередача належить до категорії <xliff:g id="RATING">%1$s</xliff:g>.\nЩоб дивитися її, натисніть стрілку праворуч і введіть PIN-код."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Телепередачу заблоковано"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Ця телепередача належить до категорії <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Без назви"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Канал заблоковано"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Сезон <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>, серія <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>: \"<xliff:g id="EPISODE_TITLE">%3$s</xliff:g>\""</string>
+ <string name="setup_title" msgid="7268875010986705651">"Джерела каналів"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Налаштуйте активні канали з доступних джерел. Залежно від джерела, це може зайняти декілька хвилин."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Готово"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Доступний %1$d канал"</item>
+ <item quantity="other" msgid="2386588423841837714">"Доступно каналів: %1$d"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Немає доступних каналів"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Нові"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Натисніть \"ВИБРАТИ\""</b>", щоб відкрити меню телевізора."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Не знайдено джерел вхідного телесигналу"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Не вдається знайти джерело вхідного телесигналу"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP не підтримується"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Немає джерел вхідного сигналу для PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Тюнер не підтримується. Щоб використовувати тюнер, запустіть додаток Live Channels."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Не вдалося налаштувати"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Не знайдено додатка для цієї дії."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Усі канали джерела сховано.\nВиберіть принаймні один канал для перегляду."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Відео недоступне через слабкий сигнал"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Відео недоступне з невідомої причини"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Клавіша \"НАЗАД\" діє на підключеному пристрої. Натисніть \"ГОЛОВНИЙ ЕКРАН\", щоб вийти."</string>
+</resources>
diff --git a/res/values-ur-rPK/arrays.xml b/res/values-ur-rPK/arrays.xml
new file mode 100644
index 00000000..fd6a0bed
--- /dev/null
+++ b/res/values-ur-rPK/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"حسب معمول"</item>
+ <item msgid="2533030282864800794">"مکمل"</item>
+ <item msgid="8568284598210500589">"زوم"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"سبھی چینلز"</item>
+ <item msgid="928298872841713530">"خاندان/بچے"</item>
+ <item msgid="2751606947569857164">"کھیل"</item>
+ <item msgid="7345749789651321496">"خریداری"</item>
+ <item msgid="167201149441442173">"موویز"</item>
+ <item msgid="525966731464264290">"کامیڈی"</item>
+ <item msgid="6096710741527327836">"سفر"</item>
+ <item msgid="2851882187117833883">"ڈراما"</item>
+ <item msgid="78492781188719038">"تعلیم"</item>
+ <item msgid="7221999662426308394">"جانور/وائلڈ لائف"</item>
+ <item msgid="375300513250925001">"خبریں"</item>
+ <item msgid="7746320336582330410">"گیم سازی"</item>
+ <item msgid="1255741860568329178">"فنون"</item>
+ <item msgid="7603949681065702867">"تفریح"</item>
+ <item msgid="4453821994746804366">"طرز زندگی"</item>
+ <item msgid="3488534597567932843">"موسیقی"</item>
+ <item msgid="7452153120614274095">"پریمیئر"</item>
+ <item msgid="8215762047341133299">"ٹیک/سائنس"</item>
+ </string-array>
+</resources>
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..8d22f3b1
--- /dev/null
+++ b/res/values-ur-rPK/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"منشیات"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"جنس"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"تشدد"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"منشیات"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"جنس"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"تشدد"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"فرانسیسی"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"ہیجان خیز بات چیت"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"نامناسب زبان"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"جنسی مواد"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"تشدد"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"فینٹیسی تشدد"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"فلم کی درجہ بندیاں"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
new file mode 100644
index 00000000..ac8aae53
--- /dev/null
+++ b/res/values-ur-rPK/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"لائیو چینلز"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"مونو"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"اسٹیریو"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"پلے کنٹرولز"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"چینلز"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"حالیہ چینلز"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"‏TV کے اختیارات"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"‏PIP کے اختیارات"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"چلانے کے کنٹرولز اس چینل کیلئے غیر دستیاب ہیں"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"چلائیں یا موقوف کریں"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"تیزی سے فارورڈ کریں"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"ریوائینڈ کریں"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"آگے"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"پچھلا"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"پروگرام گائیڈ"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"نئے چینلز دستیاب ہیں"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"کوئی لنک دستیاب نہیں ہے"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> کھولیں"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"سب ٹائٹلز"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"ڈسپلے وضع"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"آن"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"آف"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"کثیر آڈیو"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"چینل سیٹ اپ"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"والدین کے کنٹرولز"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"تفصیل"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"ماخذ"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"تبادلہ کریں"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"آن"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"آف"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"آواز"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"مرکزی"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"‏PIP ونڈو"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"لے آؤٹ"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"نیچے دائیں"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"اوپری دائیں"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"اوپری بائیں"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"نیچے بائیں"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"سمت بہ سمت"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"سائز"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"بڑا"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"چھوٹا"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"ان پٹ ماخذ"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"‏TV (اینٹینا/کیبل)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"پروگرام کی معلومات نہیں"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"کوئی معلومات نہیں ہے"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"مسدود چینل"</string>
+ <string name="default_language" msgid="4122326459624337928">"نامعلوم زبان"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"سب ٹائٹلز"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"آف"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"فارمیٹنگ کسٹمائز کریں"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"سب ٹائٹل کیلئے سسٹم وار ترجیحات سیٹ کریں"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"ڈسپلے وضع"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"کثیر آڈیو"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"مونو"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"اسٹیریو"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 گھیر"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 گھیر"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"‏‎%d چینلز"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"چینل لسٹ کسٹمائز کریں"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"گروپ منتخب کریں"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"گروپ غیر منتخب کریں"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"گروپ بلحاظ"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"چینل ماخذ"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"گروپ بلحاظ"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"چینل سیٹ اپ"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"یہ پروگرام مسدود ہے"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"اس پروگرام کی درجہ بندی <xliff:g id="RATING">%1$s</xliff:g> کی گئی ہے"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"چینل کے مآخذ"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"نئے چینلز دستیاب ہیں"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"چینل لسٹ کسٹمائز کریں"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"اپنی پروگرام گائیڈ کیلئے چینلز منتخب کریں"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"ترتیب نہیں دیا گیا"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"ان پٹ آٹو اسکین کو سپورٹ نہیں کرتا"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"\'<xliff:g id="TV_INPUT">%s</xliff:g>\' کیلئے آٹو اسکین شروع کرنے سے قاصر ہے"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"سب ٹائٹلز کیلئے سسٹم وار ترجیحات شروع کرنے سے قاصر ہے۔"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"‏%1$d چینل شامل کیا گیا"</item>
+ <item quantity="other" msgid="1078861616751739285">"‏%1$d چینلز شامل کیے گئے"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"کوئی چینلز شامل نہیں کیے گئے"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"ٹیونر"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"والدین کے کنٹرولز"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"آن"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"آف"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"چینلز مسدود کر دیے"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"سبھی کو مسدود کریں"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"سبھی غیر مسدود کریں"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"مخفی چینلز"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"پروگرام پابندیاں"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"‏PIN تبدیل کریں"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"درجہ بندی کے سسٹمز"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"درجہ بندیاں"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"درجہ بندی کے سبھی سسٹمز دیکھیں"</string>
+ <string name="other_countries" msgid="8342216398676184749">"دیگر ممالک"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"کوئی نہیں"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"کوئی نہیں"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"کوئی نہیں"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"زیادہ پابندیاں"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"متوسط پابندیاں"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"کم پابندیاں"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"حسب ضرورت"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"بچوں کیلئے مناسب مواد"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"بڑی عمر کے بچوں کیلئے مناسب مواد"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"نوعمروں کیلئے مناسب مواد"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"دستی پابندیاں"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"‏%1$s اور ذیلی درجہ بندیاں"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"ذیلی درجہ بندیاں"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"‏یہ چینل دیکھنے کیلئے اپنا PIN درج کریں"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"‏یہ پروگرام دیکھنے کیلئے اپنا PIN درج کریں"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"‏اپنا PIN درج کریں"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"‏والدین کے کنٹرولز سیٹ کرنے کیلئے، ایک PIN بنائیں"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"‏نیا PIN درج کریں"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"‏اپنے PIN کی توثیق کریں"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"‏اپنا موجودہ PIN درج کریں"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"‏آپ نے 5 بار غلط PIN درج کر دیا ہے۔\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> سیکنڈ بعد دوبارہ کوشش کریں۔"</item>
+ <item quantity="other" msgid="8829550842387756054">"‏آپ نے 5 بار غلط PIN درج کر دیا ہے۔\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> سیکنڈ بعد دوبارہ کوشش کریں۔"</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"‏وہ PIN غلط تھا۔ دوبارہ کوشش کریں۔"</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"‏دوبارہ کوشش کریں، PIN مماثل نہیں ہے"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"تفصیل"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"اوپن سورس لائسنسز"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"اوپن سورس لائسنسز"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"ورژن"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"‏یہ چینل دیکھنے کیلئے Right دبائیں اور اپنا PIN درج کریں"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"‏یہ پروگرام دیکھنے کیلئے Right دبائیں اور اپنا PIN درج کریں"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"‏اس پروگرام کی درجہ بندی <xliff:g id="RATING">%1$s</xliff:g> کی گئی ہے۔\nاس پروگرام کو دیکھنے کیلئے Right دبائیں اور اپنا PIN درج کریں"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"پروگرام مسدود ہے"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"اس پروگرام کی درجہ بندی <xliff:g id="RATING">%1$s</xliff:g> کی گئی ہے"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"کوئی عنوان نہیں ہے"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"چینل مسدود کر دیا گیا"</string>
+ <string name="episode_format" msgid="4881195874563241096">"سیزن <xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: قسط <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"چینل کے مآخذ"</string>
+ <string name="setup_description" msgid="8728423605912915099">"دستیاب مآخذ سے لائیو چینلز کو ترتیب دیں۔ چینل کے ماخذ کی بنیاد پر اس میں کئی منٹ لگ سکتے ہیں۔"</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"ہوگیا"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"‏%1$d چینل دستیاب ہے"</item>
+ <item quantity="other" msgid="2386588423841837714">"‏%1$d چینلز دستیاب ہیں"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"کوئی چینلز دستیاب نہیں ہیں"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"نئے"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"‏TV مینو تک رسائی حاصل کرنے کیلئے "<b>"منتخب کریں کو دبائیں"</b>"۔"</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"‏کوئی TV ان پٹ نہیں ملا"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"‏TV ان پٹ تلاش نہیں کیا جا سکتا"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"‏PIP تعاون یافتہ نہیں ہے"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"‏ایسا کوئی ان پٹ دستیاب نہیں ہے جسے PIP کے ساتھ دکھایا جا سکے"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"‏ٹیونر کی قسم مناسب نہیں ہے۔ براہ کرم ٹیونر کی قسم والے TV ان پٹ کیلئے لائیو چینلز ایپ شروع کریں۔"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"ٹیون کرنا ناکام ہوگیا"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"اس کارروائی کو نمٹانے کیلئے کوئی ایپ نہیں ملی۔"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"سبھی ماخذ چینلز مخفی ہیں۔ \nدیکھنے کیلئے کم از کم ایک چینل منتخب کریں۔"</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"کمزور ویڈیو سگنل کی وجہ سے دستیاب نہیں ہے"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"ویڈیو غیر متوقع طور پر دستیاب نہیں ہے"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"‏BACK کلید منسلک آلہ کیلئے ہے۔ باہر نکلنے کیلئے HOME بٹن دبائیں۔"</string>
+</resources>
diff --git a/res/values-uz-rUZ/arrays.xml b/res/values-uz-rUZ/arrays.xml
new file mode 100644
index 00000000..ca5bb774
--- /dev/null
+++ b/res/values-uz-rUZ/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Normal"</item>
+ <item msgid="2533030282864800794">"To‘liq"</item>
+ <item msgid="8568284598210500589">"Miqyos"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Barcha kanallar"</item>
+ <item msgid="928298872841713530">"Oila va bolalar"</item>
+ <item msgid="2751606947569857164">"Sport"</item>
+ <item msgid="7345749789651321496">"Xarid"</item>
+ <item msgid="167201149441442173">"Filmlar"</item>
+ <item msgid="525966731464264290">"Komediya"</item>
+ <item msgid="6096710741527327836">"Sayohat"</item>
+ <item msgid="2851882187117833883">"Drama"</item>
+ <item msgid="78492781188719038">"Ta’lim"</item>
+ <item msgid="7221999662426308394">"Tabiat va jonivorlar"</item>
+ <item msgid="375300513250925001">"Yangiliklar"</item>
+ <item msgid="7746320336582330410">"O‘yinlar"</item>
+ <item msgid="1255741860568329178">"San’at"</item>
+ <item msgid="7603949681065702867">"Ko‘ngilochar"</item>
+ <item msgid="4453821994746804366">"Turmush tarzi"</item>
+ <item msgid="3488534597567932843">"Musiqa"</item>
+ <item msgid="7452153120614274095">"Premyera"</item>
+ <item msgid="8215762047341133299">"Fan va texnika"</item>
+ </string-array>
+</resources>
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..12507d84
--- /dev/null
+++ b/res/values-uz-rUZ/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Giyohvand moddalar"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Seks"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Zo‘ravonlik"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Giyohvand moddalar"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Seks"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Zo‘ravonlik"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Fransuz"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Behayo gaplar"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Qo‘pol nutq"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Shahvoniy tasvirlar"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Zo‘ravonlik"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Fantastik qahramonlarning zo‘ravonligi"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Film reytinglari"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
new file mode 100644
index 00000000..be615587
--- /dev/null
+++ b/res/values-uz-rUZ/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Jonli efir"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Boshqaruv"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kanallar"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Yaqinda ko‘rilgan kanallar"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"TV sozlamalari"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP sozlamalari"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Ushbu kanal uchun ijro etish boshqaruvlari mavjud emas"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Ijro yoki pauza"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Oldinga tezkor o‘tkazish"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Orqaga o‘tkazish"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Keyingi"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Avvalgi"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Tele-yo‘lboshchi"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Yangi kanallar mavjud"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Hech qanday havola yo‘q"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasini ochish"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Taglavhalar"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Ekran rejimi"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"Kadr ustida kadr"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Yoniq"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"O‘chiq"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Ko‘p kanalli"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Kanalni sozlash"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Ota-ona nazorati"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Ilova haqida"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Manba"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Almashtirish"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Yoniq"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"O‘chiq"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Ovoz"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Asosiy oyna"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP oynasi"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Joylashuvi"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Pastda o‘ngda"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Tepada o‘ngda"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Tepada chapda"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Pastda chapda"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Yonma-yon"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"O‘lchami"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Katta"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Kichik"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Kirish manbasi"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (antenna/kabel)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Dastur haqida ma’l. yo‘q"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Ma’lumot yo‘q"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Bloklangan kanal"</string>
+ <string name="default_language" msgid="4122326459624337928">"Noma’lum til"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Taglavhalar"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"O‘chiq"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Formatlarni sozlash"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Taglavhalar uchun tizim sozlamalari"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Ekran rejimi"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Ko‘p kanalli"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 qamrovli"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 qamrovli"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d ta kanal"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Ro‘yxatni sozlash"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Guruhni tanlash"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Guruhni o‘chirish"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Guruhlash tartibi"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Kanal manbasi"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Guruhlash tartibi"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Kanalni sozlash"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Bu dastur bloklangan"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Bu dastur uchun yosh cheklovi: <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Kanal manbalari"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Yangi kanallar qo‘shildi"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Ro‘yxatni sozlash"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Tele-yo‘lboshchi u-n kanallarni tanlang"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Sozlanmagan"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Bu kirish avtomatik qidiruvni qo‘llab-quvvatlamaydi"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"“<xliff:g id="TV_INPUT">%s</xliff:g>” uchun avtomatik qidiruvni boshlab bo‘lmadi"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Taglavhalar uchun tizim sozlamalarini ochib bo‘lmadi."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d ta kanal qo‘shildi"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d ta kanal qo‘shildi"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Kanal qo‘shilmadi"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Tyuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Ota-ona nazorati"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Yoniq"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"O‘chiq"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Kanallar bloklandi"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Barchasini bloklash"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Blokdan chiqarish"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Berkitilgan kanallar"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Yosh cheklovlari"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"PIN kodni o‘zgartirish"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Reyting tizimlari"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Reytinglar"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Barcha reyting tizimlari"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Boshqa mamlakatlar"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Hech biri"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Hech biri"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Hech biri"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Qat’iy cheklovlar"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"O‘rtacha cheklovlar"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Yengil cheklovlar"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Boshqa"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Kontent bolalar uchun mos"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Kontent o‘rta yoshdagi bolalar uchun mos"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Kontent o‘smirlar uchun mos"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Foydalanuvchi cheklovlari"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s: sub-reytinglar"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Sub-reytinglar"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Bu kanalni ko‘rish uchun PIN kodni kiriting"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Bu dasturni ko‘rish uchun PIN kodni kiriting"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"PIN kodni kiriting"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Ota-ona nazoratini o‘rnatish uchun PIN-kod yarating"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Yangi PIN kodni kiriting"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"PIN kodni tasdiqlang"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Joriy PIN kodni kiriting"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Siz PIN kodni 5 marta noto‘g‘ri kiritdingiz.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> soniyadan keyin qayta urinib ko‘ring."</item>
+ <item quantity="other" msgid="8829550842387756054">"Siz PIN kodni 5 marta noto‘g‘ri kiritdingiz.\n<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> soniyadan so‘ng qayta urinib ko‘ring."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"PIN-kod noto‘g‘ri, qaytadan urining."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Qaytadan urinib ko‘ring, PIN-kod noto‘g‘ri"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Ilova haqida"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Ochiq kodli DT litsenziyalari"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Ochiq kodli DT litsenziyalari"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Versiyasi"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Bu kanalni ko‘rish uchun o‘ngga qaragan chiziqni bosing va PIN kodni kiriting"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Bu dasturni ko‘rish uchun o‘ngga qaragan chiziqni bosing va PIN kodni kiriting"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Bu dastur uchun yosh cheklovi: <xliff:g id="RATING">%1$s</xliff:g>.\nUshbu dasturni ko‘rish uchun o‘ngga qaragan milni bosing va PIN-kodni kiriting."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Dastur bloklangan"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Bu dastur uchun yosh cheklovi: <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Nomsiz"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Kanal bloklangan"</string>
+ <string name="episode_format" msgid="4881195874563241096">"<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>-fasl <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g>-qism: <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Kanal manbalari"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Mavjud manbalar asosida jonli efir kanallarini sozlang. U kanal manbasiga qarab bir necha daqiqa vaqt olishi mumkin."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Tayyor"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d ta kanal mavjud"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d ta kanal mavjud"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Hech qanday kanal mavjud emas"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Yangi"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"TV menyusiga kirish uchun "<b>"TANLASH tugmasini bosing"</b>"."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Hech qanday TV-kirish topilmadi"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"TV-kirishni topib bo‘lmadi"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP qo‘llab-quvvatlanmaydi"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"PIP bilan ko‘rsatish uchun hech qanday manba yo‘q"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"Tyuner turi mos kelmaydi. Agar TV-kirishdan foydalanayotgan bo‘lsangiz, “Jonli efir” ilovasini ishga tushiring."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Sozlashni amalga oshirib bo‘lmadi."</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Bu amalni bajara oladigan ilova topilmadi."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Barcha kanallar berkitilgan.\nTomosha qilish uchun kamida bitta kanalni tanlang."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Videoni signal kuchsiz bo‘lganligi tufayli ko‘rib bo‘lmaydi"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Video kutilmaganda yo‘q bo‘lib qoldi."</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"ORQAGA tugmasi ulangan qurilmani boshqaradi. Chiqish uchun BOSHI tugmasini bosing."</string>
+</resources>
diff --git a/res/values-vi/arrays.xml b/res/values-vi/arrays.xml
new file mode 100644
index 00000000..0b104fbf
--- /dev/null
+++ b/res/values-vi/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Bình thường"</item>
+ <item msgid="2533030282864800794">"Toàn màn hình"</item>
+ <item msgid="8568284598210500589">"Thu phóng"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Tất cả các kênh"</item>
+ <item msgid="928298872841713530">"Gia đình/Trẻ em"</item>
+ <item msgid="2751606947569857164">"Thể thao"</item>
+ <item msgid="7345749789651321496">"Mua sắm"</item>
+ <item msgid="167201149441442173">"Phim"</item>
+ <item msgid="525966731464264290">"Hài kịch"</item>
+ <item msgid="6096710741527327836">"Du lịch"</item>
+ <item msgid="2851882187117833883">"Kịch"</item>
+ <item msgid="78492781188719038">"Giáo dục"</item>
+ <item msgid="7221999662426308394">"Động vật/Động vật hoang dã"</item>
+ <item msgid="375300513250925001">"Tin tức"</item>
+ <item msgid="7746320336582330410">"Trò chơi"</item>
+ <item msgid="1255741860568329178">"Nghệ thuật"</item>
+ <item msgid="7603949681065702867">"Giải trí"</item>
+ <item msgid="4453821994746804366">"Lối sống"</item>
+ <item msgid="3488534597567932843">"Âm nhạc"</item>
+ <item msgid="7452153120614274095">"Cao cấp"</item>
+ <item msgid="8215762047341133299">"Công nghệ/Khoa học"</item>
+ </string-array>
+</resources>
diff --git a/res/values-vi/rating_system_strings.xml b/res/values-vi/rating_system_strings.xml
new file mode 100644
index 00000000..9b5e7821
--- /dev/null
+++ b/res/values-vi/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Ma túy"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Tình dục"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Bạo lực"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Ma túy"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Tình dục"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Bạo lực"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Tiếng Pháp"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Ngôn từ khêu khợi"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Ngôn từ thô tục"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Nội dung khiêu dâm"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Bạo lực"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Bạo lực giả tưởng"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Xếp hạng phim"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
new file mode 100644
index 00000000..3ea70c4c
--- /dev/null
+++ b/res/values-vi/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Kênh trực tiếp"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"đơn âm"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"âm thanh nổi"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Điều khiển phát"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Kênh"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Kênh gần đây"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Tùy chọn TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Tùy chọn PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Điều khiển phát không có sẵn cho kênh này"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Phát hoặc tạm dừng"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Tua đi"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Tua lại"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Tiếp theo"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Trước"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Hướng dẫn chương trình"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Đã có kênh mới"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Không có liên kết"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Mở <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Phụ đề"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Chế độ hiển thị"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Bật"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Tắt"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Âm thanh đa kênh"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Thiết lập kênh"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Kiểm soát của cha mẹ"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Giới thiệu"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Nguồn"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Hoán đổi"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Bật"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Tắt"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Âm thanh"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Cửa sổ chính"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Cửa sổ PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Bố cục"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Phía dưới cùng bên phải"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Phía trên cùng bên phải"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Phía trên cùng bên trái"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Phía dưới cùng bên trái"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"Cạnh nhau"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Kích thước"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Lớn"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Nhỏ"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Nguồn đầu vào"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"TV (ăng-ten/cáp)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Không có thông tin chương trình"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Không có thông tin"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Kênh bị chặn"</string>
+ <string name="default_language" msgid="4122326459624337928">"Tiếng không xác định"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Phụ đề"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Tắt"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Tùy chỉnh định dạng"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Đặt tùy chọn trên toàn hệ thống cho phụ đề"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Chế độ hiển thị"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Âm thanh đa kênh"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"đơn âm"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"âm thanh nổi"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"Âm thanh vòm 5.1"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"Âm thanh vòm 7.1"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d kênh"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Tùy chỉnh ds kênh"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Chọn nhóm"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Bỏ chọn nhóm"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Nhóm theo"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Nguồn kênh"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Nhóm theo"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Thiết lập kênh"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Chương trình này đã bị chặn"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Chương trình này được xếp hạng <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Nguồn kênh"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Đã có kênh mới"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Tùy chỉnh DS kênh"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Chọn kênh cho hướng dẫn chương trình"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Chưa thiết lập"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Đầu vào không hỗ trợ tự động quét"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Không thể bắt đầu quét tự động cho \'<xliff:g id="TV_INPUT">%s</xliff:g>\'"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Không thể bắt đầu các tùy chọn trên toàn hệ thống cho phụ đề."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"Đã thêm %1$d kênh"</item>
+ <item quantity="other" msgid="1078861616751739285">"Đã thêm %1$d kênh"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Chưa thêm kênh nào"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"Bộ dò"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Kiểm soát của cha mẹ"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Bật"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Tắt"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Kênh bị chặn"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Chặn tất cả"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Bỏ chặn tất cả"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Kênh bị ẩn"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Hạn chế chương trình"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Thay đổi PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Hệ thống xếp hạng"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Xếp hạng"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Xem tất cả hệ thống xếp hạng"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Các quốc gia khác"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Không có"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Không có"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Không có"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Hạn chế cao"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Hạn chế trung bình"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Hạn chế thấp"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Tùy chỉnh"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Nội dung phù hợp với trẻ em"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Nội dung phù hợp với trẻ em lớn hơn"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Nội dung phù hợp với thanh thiếu niên"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Hạn chế thủ công"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s và xếp hạng phụ"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Xếp hạng phụ"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Nhập mã PIN của bạn để xem kênh này"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Nhập mã PIN của bạn để xem chương trình này"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Nhập mã PIN của bạn"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Để đặt kiểm soát của phụ huynh, hãy tạo mã PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Nhập mã PIN mới"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Xác nhận mã PIN của bạn"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Nhập mã PIN hiện tại của bạn"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Bạn đã nhập sai mã PIN 5 lần.\nHãy thử lại sau <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> giây."</item>
+ <item quantity="other" msgid="8829550842387756054">"Bạn đã nhập sai mã PIN 5 lần.\nHãy thử lại sau <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> giây."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Mã PIN đó không đúng. Hãy thử lại."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Hãy thử lại, mã PIN không khớp"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Giới thiệu"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Giấy phép nguồn mở"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Giấy phép nguồn mở"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Phiên bản"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Để xem kênh này, hãy nhấn vào Quyền và nhập mã PIN của bạn"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Để xem chương trình này, hãy nhấn vào Quyền và nhập mã PIN của bạn"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Chương trình này được xếp hạng <xliff:g id="RATING">%1$s</xliff:g>.\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."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Chương trình bị chặn"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Chương trình này được xếp hạng <xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Không có tiêu đề"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Đã chặn kênh"</string>
+ <string name="episode_format" msgid="4881195874563241096">"Mùa<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: P<xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Nguồn kênh"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Thiết lập kênh trực tiếp từ những nguồn sẵn có. Quá trình này có thể mất vài phút tùy thuộc vào nguồn kênh."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Xong"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"Có %1$d kênh"</item>
+ <item quantity="other" msgid="2386588423841837714">"Có %1$d kênh"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Không có kênh nào"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Mới"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Nhấn CHỌN"</b>" để truy cập menu TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Không tìm thấy đầu vào TV nào"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Không tìm thấy đầu vào TV"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP không được hỗ trợ"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Không có đầu vào có thể hiển thị với PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"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ò."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Không dò được"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Không tìm thấy ứng dụng nào để xử lý tác vụ này."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Tất cả các kênh nguồn đều bị ẩn.\nHãy chọn ít nhất một kênh để xem."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Không có do tín hiệu video yếu"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Video đột nhiên không có"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Phím QUAY LẠI dành cho thiết bị đã kết nối. Nhấn nút HOME để thoát."</string>
+</resources>
diff --git a/res/values-zh-rCN/arrays.xml b/res/values-zh-rCN/arrays.xml
new file mode 100644
index 00000000..86105072
--- /dev/null
+++ b/res/values-zh-rCN/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"常规"</item>
+ <item msgid="2533030282864800794">"全屏"</item>
+ <item msgid="8568284598210500589">"缩放"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"所有频道"</item>
+ <item msgid="928298872841713530">"亲子"</item>
+ <item msgid="2751606947569857164">"体育"</item>
+ <item msgid="7345749789651321496">"购物"</item>
+ <item msgid="167201149441442173">"电影"</item>
+ <item msgid="525966731464264290">"喜剧"</item>
+ <item msgid="6096710741527327836">"旅游"</item>
+ <item msgid="2851882187117833883">"剧情类"</item>
+ <item msgid="78492781188719038">"教育"</item>
+ <item msgid="7221999662426308394">"动物/野生生物"</item>
+ <item msgid="375300513250925001">"新闻"</item>
+ <item msgid="7746320336582330410">"游戏"</item>
+ <item msgid="1255741860568329178">"艺术"</item>
+ <item msgid="7603949681065702867">"娱乐"</item>
+ <item msgid="4453821994746804366">"生活时尚"</item>
+ <item msgid="3488534597567932843">"音乐"</item>
+ <item msgid="7452153120614274095">"首映"</item>
+ <item msgid="8215762047341133299">"科技"</item>
+ </string-array>
+</resources>
diff --git a/res/values-zh-rCN/rating_system_strings.xml b/res/values-zh-rCN/rating_system_strings.xml
new file mode 100644
index 00000000..e96aee4f
--- /dev/null
+++ b/res/values-zh-rCN/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"毒品"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"性爱"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"暴力"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"毒品"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"性爱"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"暴力"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"法语"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"挑逗性对话"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"粗俗言语"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"色情内容"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"暴力"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"幻想暴力内容"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"电影分级"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
new file mode 100644
index 00000000..286c86bf
--- /dev/null
+++ b/res/values-zh-rCN/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"直播频道"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"单声道"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"立体声"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"播放控件"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"频道"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"最近观看的频道"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"电视选项"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP 选项"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"此频道无法使用播放控件"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"播放或暂停"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"快进"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"快退"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"下一项"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"上一项"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"收视指南"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"有新的频道"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"没有任何应用链接"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"打开<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"字幕"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"显示模式"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"开启"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"关闭"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"多音频"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"频道设置"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"家长控制"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"简介"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"来源"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"切换"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"开启"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"关闭"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"声音"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"主窗口"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"PIP 窗口"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"布局"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"右下方"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"右上方"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"左上方"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"左下方"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"并排"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"尺寸"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"大"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"小"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"输入源"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"电视(天线/有线)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"没有节目信息"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"无信息"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"已屏蔽的频道"</string>
+ <string name="default_language" msgid="4122326459624337928">"未知语言"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"字幕"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"关闭"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"自定义格式"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"设置系统通用的字幕偏好设置"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"显示模式"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"多音频"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"单声道"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"立体声"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1环绕声"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1环绕声"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d个频道"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"自定义频道列表"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"选择频道组"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"取消选择频道组"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"分组依据"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"频道来源"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"高清/标清"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"高清"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"标清"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"分组依据"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"频道设置"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"此节目已被屏蔽"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"此节目的分级为:<xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"频道来源"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"有新频道"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"自定义频道列表"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"为您的收视指南选择频道"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"未设置"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"该输入设备不支持自动扫描"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"无法为“<xliff:g id="TV_INPUT">%s</xliff:g>”启动自动扫描"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"无法启动系统通用的字幕偏好设置。"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"已添加%1$d个频道"</item>
+ <item quantity="other" msgid="1078861616751739285">"已添加%1$d个频道"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"未添加任何频道"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"调谐器"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"家长控制"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"开启"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"关闭"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"屏蔽的频道"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"全部屏蔽"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"全部取消屏蔽"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"隐藏的频道"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"节目限制"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"更改PIN码"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"分级系统"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"分级"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"查看所有分级系统"</string>
+ <string name="other_countries" msgid="8342216398676184749">"其他国家/地区"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"无"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"无"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"无"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"严格限制"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"中等限制"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"宽松限制"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"自定义"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"内容适合儿童观看"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"内容适合大龄儿童观看"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"内容适合青少年观看"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"手动限制"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s和二级分级"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"二级分级"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"输入PIN码即可观看此频道"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"输入PIN码即可观看此节目"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"输入您的PIN码"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"要设置家长控制功能,请创建一个PIN码"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"请输入新的PIN码"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"确认您的PIN码"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"输入当前的PIN码"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"您已连续 5 次输错 PIN 码。\n请在 <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> 秒后重试。"</item>
+ <item quantity="other" msgid="8829550842387756054">"您已连续 5 次输错 PIN 码。\n请在 <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> 秒后重试。"</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"PIN码不正确,请重试。"</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"PIN码不匹配,请重试"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"简介"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"开放源代码许可"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"开放源代码许可"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"版本"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"要观看此频道,请按“向右”按钮,然后输入您的PIN码"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"要观看此节目,请按“向右”按钮,然后输入您的PIN码"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"此节目的分级为:<xliff:g id="RATING">%1$s</xliff:g>。\n要观看此节目,请按“向右”按钮,然后输入您的 PIN 码。"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"节目已被屏蔽"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"此节目的分级为:<xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"无标题"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"频道已屏蔽"</string>
+ <string name="episode_format" msgid="4881195874563241096">"第 <xliff:g id="SEASON_NUMBER">%1$d</xliff:g> 季第 <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> 集《<xliff:g id="EPISODE_TITLE">%3$s</xliff:g>》"</string>
+ <string name="setup_title" msgid="7268875010986705651">"频道来源"</string>
+ <string name="setup_description" msgid="8728423605912915099">"从可用来源中设置直播频道。这可能需要几分钟的时间,具体取决于频道来源。"</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"完成"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"有 %1$d 个频道"</item>
+ <item quantity="other" msgid="2386588423841837714">"有 %1$d 个频道"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"没有任何频道"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"新的输入设备"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"按“选择”"</b>"可访问电视菜单。"</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"未检测到电视输入设备"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"找不到该电视输入设备"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"不支持 PIP"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"没有可通过 PIP 方式显示的输入"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"不支持调谐器类型。请启动支持调谐器类型电视输入端口的“直播频道”应用。"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"调谐失败"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"未找到可处理此操作的应用。"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"所有来源频道均处于隐藏状态。\n请至少选择一个频道来观看。"</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"视频信号弱,因此无法播放"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"此视频出现异常,无法播放"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"“返回”键用于控制连接的设备。按“主屏幕”按钮即可退出。"</string>
+</resources>
diff --git a/res/values-zh-rHK/arrays.xml b/res/values-zh-rHK/arrays.xml
new file mode 100644
index 00000000..7fc3cde0
--- /dev/null
+++ b/res/values-zh-rHK/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"正常"</item>
+ <item msgid="2533030282864800794">"全螢幕"</item>
+ <item msgid="8568284598210500589">"縮放"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"所有頻道"</item>
+ <item msgid="928298872841713530">"家庭/兒童"</item>
+ <item msgid="2751606947569857164">"體育"</item>
+ <item msgid="7345749789651321496">"購物"</item>
+ <item msgid="167201149441442173">"電影"</item>
+ <item msgid="525966731464264290">"喜劇"</item>
+ <item msgid="6096710741527327836">"旅遊"</item>
+ <item msgid="2851882187117833883">"戲劇"</item>
+ <item msgid="78492781188719038">"教育"</item>
+ <item msgid="7221999662426308394">"動物/野生動物"</item>
+ <item msgid="375300513250925001">"新聞"</item>
+ <item msgid="7746320336582330410">"遊戲"</item>
+ <item msgid="1255741860568329178">"藝術"</item>
+ <item msgid="7603949681065702867">"娛樂"</item>
+ <item msgid="4453821994746804366">"生活品味"</item>
+ <item msgid="3488534597567932843">"音樂"</item>
+ <item msgid="7452153120614274095">"首選"</item>
+ <item msgid="8215762047341133299">"科技/科學"</item>
+ </string-array>
+</resources>
diff --git a/res/values-zh-rHK/rating_system_strings.xml b/res/values-zh-rHK/rating_system_strings.xml
new file mode 100644
index 00000000..6e5554ea
--- /dev/null
+++ b/res/values-zh-rHK/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"毒品"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"色情"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"暴力"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"藥物"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"色情"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"暴力"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"法文"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"性暗示對話"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"言語粗俗"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"色情內容"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"暴力"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"幻想暴力"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"影片評分"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
new file mode 100644
index 00000000..a03af4f7
--- /dev/null
+++ b/res/values-zh-rHK/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"直播頻道"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"單聲道"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"立體聲"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"播放控制"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"頻道"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"最近收看頻道"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"電視選項"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"PIP 選項"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"播放控制功能不適用於此頻道"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"播放或暫停"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"向前快轉"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"向後倒轉"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"繼續"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"返回"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"節目指南"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"可供設定的新頻道"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"沒有可用連結"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"開啟「<xliff:g id="APP_NAME">%1$s</xliff:g>」"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"隱藏式字幕"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"顯示模式"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"子母畫面"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"開啟"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"關閉"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"多聲道"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"頻道設定"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"家長監護"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"關於"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"來源"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"切換"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"開啟"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"關閉"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"音效"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"主視窗"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"子母畫面視窗"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"版面配置"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"右下方"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"右上方"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"左上方"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"左下方"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"並排"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"大小"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"大"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"小"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"輸入源"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"電視 (天線/連接線)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"沒有節目資訊"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"無資訊"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"已封鎖的頻道"</string>
+ <string name="default_language" msgid="4122326459624337928">"不明語言"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"隱藏式字幕"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"關閉"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"自訂格式"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"設定全系統隱藏式字幕的偏好設定"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"顯示模式"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"多聲道"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"單聲道"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"立體聲"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 環迴音響"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 環迴音響"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d 個聲道"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"自訂頻道清單"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"選取群組"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"取消選取群組"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"分組依據"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"頻道來源"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"高清/標清畫質"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"高清"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"標清"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"分組依據"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"頻道設定"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"這個節目已被封鎖。"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"這個節目的評級為<xliff:g id="RATING">%1$s</xliff:g>。"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"頻道來源"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"可供設定的新頻道"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"自訂頻道清單"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"選擇要提供節目指南的頻道"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"未設定"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"輸入裝置不支援自動掃瞄功能"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"無法啟動「<xliff:g id="TV_INPUT">%s</xliff:g>」的自動掃瞄"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"無法啟動系統通用的字幕偏好設定。"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"已新增 %1$d 個頻道"</item>
+ <item quantity="other" msgid="1078861616751739285">"已新增 %1$d 個頻道"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"尚未新增任何頻道"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"調校器"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"家長監護"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"開啟"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"關閉"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"已封鎖的頻道"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"全部封鎖"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"全部解除封鎖"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"隱藏的頻道"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"節目限制"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"更改 PIN 碼"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"分級系統"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"評級"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"查看所有分級系統"</string>
+ <string name="other_countries" msgid="8342216398676184749">"其他國家/地區"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"無"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"無"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"無"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"嚴格限制"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"中等限制"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"寬鬆限制"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"自訂"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"適合兒童收看的內容"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"適合年齡較大兒童觀看的內容"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"適合青少年收看的內容"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"手動設定限制"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s 和子分級"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"子分級"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"輸入您的 PIN 即可觀看這個頻道"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"輸入您的 PIN 即可觀看這個節目"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"請輸入您的 PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"如要設定家長監護,請建立 PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"輸入新的 PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"確認 PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"請輸入目前的 PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"您已輸入 5 次錯誤的 PIN。\n請於 <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> 秒後再試一次。"</item>
+ <item quantity="other" msgid="8829550842387756054">"您已輸入 5 次錯誤的 PIN。\n請於 <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> 秒後再試一次。"</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"該 PIN 錯誤,請再試一次。"</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"PIN 碼不符,請再試一次"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"關於"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"開放原始碼授權"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"開放原始碼授權"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"版本"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"按向右鍵並輸入您的 PIN,以觀看這個頻道"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"按向右鍵並輸入您的 PIN,以觀看這個節目"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"這個節目的評級為<xliff:g id="RATING">%1$s</xliff:g>。\n要觀看這個節目,請按向右鍵並輸入您的 PIN。"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"節目被封鎖"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"這個節目的評級為<xliff:g id="RATING">%1$s</xliff:g>。"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"無標題"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"已封鎖的頻道"</string>
+ <string name="episode_format" msgid="4881195874563241096">"第 <xliff:g id="SEASON_NUMBER">%1$d</xliff:g> 季:第 <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> 集 <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"頻道來源"</string>
+ <string name="setup_description" msgid="8728423605912915099">"從可供來源建立直播頻道。過程約需數分鐘,視乎頻道來源而定。"</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"完成"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d 個可用頻道"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d 個可用頻道"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"沒有頻道可用"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"最新"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"按 [選擇]"</b>" 前往 [電視選單]。"</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"找不到電視輸入"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"找不到電視輸入"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"PIP 不受支援"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"沒有可供 PIP 顯示的輸入"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"調諧器類型不適用;請啟動調諧器類型電視輸入專用的「直播頻道」應用程式。"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"調校失敗"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"找不到可以處理這項操作的應用程式。"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"所有來源頻道均已隱藏。\n請選取至少一個要觀看的頻道。"</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"影片訊號微弱,因此無法播放"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"影片無法播放"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"[返回] 鍵適用於已連結的裝置。按一下 [主畫面] 按鈕即可結束。"</string>
+</resources>
diff --git a/res/values-zh-rTW/arrays.xml b/res/values-zh-rTW/arrays.xml
new file mode 100644
index 00000000..49e24dde
--- /dev/null
+++ b/res/values-zh-rTW/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"一般"</item>
+ <item msgid="2533030282864800794">"16:9 模式"</item>
+ <item msgid="8568284598210500589">"縮放模式"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"所有頻道"</item>
+ <item msgid="928298872841713530">"家庭/兒童"</item>
+ <item msgid="2751606947569857164">"體育"</item>
+ <item msgid="7345749789651321496">"購物"</item>
+ <item msgid="167201149441442173">"電影"</item>
+ <item msgid="525966731464264290">"喜劇"</item>
+ <item msgid="6096710741527327836">"旅遊"</item>
+ <item msgid="2851882187117833883">"劇情"</item>
+ <item msgid="78492781188719038">"教育"</item>
+ <item msgid="7221999662426308394">"動物/野生動物"</item>
+ <item msgid="375300513250925001">"新聞"</item>
+ <item msgid="7746320336582330410">"遊戲"</item>
+ <item msgid="1255741860568329178">"藝術"</item>
+ <item msgid="7603949681065702867">"娛樂"</item>
+ <item msgid="4453821994746804366">"生活品味"</item>
+ <item msgid="3488534597567932843">"音樂"</item>
+ <item msgid="7452153120614274095">"首映"</item>
+ <item msgid="8215762047341133299">"科技/科學"</item>
+ </string-array>
+</resources>
diff --git a/res/values-zh-rTW/rating_system_strings.xml b/res/values-zh-rTW/rating_system_strings.xml
new file mode 100644
index 00000000..6a202140
--- /dev/null
+++ b/res/values-zh-rTW/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"藥物"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"性愛"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"暴力"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"藥物"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"性愛"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"暴力"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"法文"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"性暗示對話"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"粗暴言語"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"情色內容"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"暴力"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"虛構暴力行為"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"電影分級"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 00000000..b8df36c8
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"直播頻道"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"單聲道"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"立體聲"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"播放控制介面"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"頻道"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"最近觀看的頻道"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"電視選項"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"子母畫面選項"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"這個頻道無法使用播放控制介面"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"播放或暫停"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"快轉"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"倒轉"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"下一個"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"上一個"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"節目指南"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"有新頻道"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"沒有可用的連結"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"開啟 <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"字幕"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"顯示模式"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"子母畫面"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"開啟"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"關閉"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"多聲道"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"頻道設定"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"家長監護"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"關於"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"來源"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"切換"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"開啟"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"關閉"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"音效"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"主視窗"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"子母畫面視窗"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"版面配置"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"右下角"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"右上角"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"左上角"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"左下角"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"並排"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"尺寸"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"大"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"小"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"輸入來源"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"電視 (有線/無線)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"沒有節目資訊"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"無資訊"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"封鎖的頻道"</string>
+ <string name="default_language" msgid="4122326459624337928">"不明語言"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"字幕"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"關閉"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"自訂格式設定"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"設定系統通用的字幕偏好設定"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"顯示模式"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"多聲道"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"單聲道"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"立體聲"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 環場音效"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 環場音效"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d 聲道"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"自訂頻道清單"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"選取群組"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"取消選取群組"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"分組依據"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"頻道來源"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD 高畫質/SD 標準畫質"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"HD 高畫質"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"SD 標準畫質"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"分組依據"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"頻道設定"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"這個節目遭到封鎖"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"這個節目的分級是「<xliff:g id="RATING">%1$s</xliff:g>」。"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"頻道來源"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"有新頻道"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"自訂頻道清單"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"為您的節目指南選擇頻道"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"尚未設定"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"輸入裝置不支援自動掃描"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"無法開始「<xliff:g id="TV_INPUT">%s</xliff:g>」的自動掃描作業"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"無法啟動系統通用的字幕偏好設定。"</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"新增了 %1$d 個頻道"</item>
+ <item quantity="other" msgid="1078861616751739285">"新增了 %1$d 個頻道"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"尚未新增任何頻道"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"協調器"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"家長監護"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"開啟"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"關閉"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"封鎖的頻道"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"全部封鎖"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"全部解除封鎖"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"隱藏的頻道"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"節目限制"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"變更 PIN"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"分級系統"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"分級"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"查看所有分級制度"</string>
+ <string name="other_countries" msgid="8342216398676184749">"其他國家/地區"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"無"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"無"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"無"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"高度限制"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"中度限制"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"低度限制"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"自訂"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"適合兒童收看的內容"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"適合年齡較大兒童的內容"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"適合青少年收看的內容"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"自訂限制"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s和子分級"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"子分級"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"輸入您的 PIN 即可觀看這個頻道"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"輸入您的 PIN 即可觀看這個節目"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"請輸入您的 PIN"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"如要設定家長監護,請建立 PIN"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"輸入新的 PIN"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"確認您的 PIN"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"請輸入您目前的 PIN"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"您已輸入 5 次錯誤的 PIN 碼。\n請於 <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> 秒後再試一次。"</item>
+ <item quantity="other" msgid="8829550842387756054">"您已輸入 5 次錯誤的 PIN 碼。\n請於 <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> 秒後再試一次。"</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"該 PIN 錯誤,請再試一次。"</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"PIN 不符,請再試一次"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"關於"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"開放原始碼授權"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"開放原始碼授權"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"版本"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"如要觀看這個頻道,請按向右鍵並輸入您的 PIN"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"如要觀看這個節目,請按向右鍵並輸入您的 PIN"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"這個節目的分級是「<xliff:g id="RATING">%1$s</xliff:g>」。\n如要觀看這個節目,請按向右鍵並輸入您的 PIN"</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"節目遭到封鎖"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"這個節目的分級是「<xliff:g id="RATING">%1$s</xliff:g>」。"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"無標題"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"頻道遭到封鎖"</string>
+ <string name="episode_format" msgid="4881195874563241096">"第 <xliff:g id="SEASON_NUMBER">%1$d</xliff:g> 季:第 <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> 集「<xliff:g id="EPISODE_TITLE">%3$s</xliff:g>」"</string>
+ <string name="setup_title" msgid="7268875010986705651">"頻道來源"</string>
+ <string name="setup_description" msgid="8728423605912915099">"從可用來源中設定直播頻道。取決於您的頻道來源,這可能需要一些時間。"</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"完成"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"提供 %1$d 個頻道"</item>
+ <item quantity="other" msgid="2386588423841837714">"提供 %1$d 個頻道"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"沒有任何頻道"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"新輸入裝置"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307">"按 [選取]"<b></b>" 即可使用 [電視選單]。"</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"找不到電視輸入裝置"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"找不到電視輸入裝置"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"不支援子母畫面"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"沒有可透過子母畫面顯示的輸入來源"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"協調器類型不適合;請啟動協調器類型電視輸入裝置專用的 Live Channels 應用程式。"</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"協調失敗"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"找不到可以處理這個動作的應用程式。"</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"所有來源頻道皆已隱藏。\n請選取至少一個要觀看的頻道。"</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"影片訊號微弱,因此無法播放"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"影片無法播放,原因不明"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"返回鍵適用於連線的裝置,按下主螢幕按鈕即可結束。"</string>
+</resources>
diff --git a/res/values-zu/arrays.xml b/res/values-zu/arrays.xml
new file mode 100644
index 00000000..95ded427
--- /dev/null
+++ b/res/values-zu/arrays.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="display_mode_labels">
+ <item msgid="2259828487212953611">"Jwayelekile"</item>
+ <item msgid="2533030282864800794">"Kugcwele"</item>
+ <item msgid="8568284598210500589">"Sondeza"</item>
+ </string-array>
+ <string-array name="genre_labels">
+ <item msgid="3241959001790384341">"Zonke iziteshi"</item>
+ <item msgid="928298872841713530">"Umndeni/abantwana"</item>
+ <item msgid="2751606947569857164">"Ezemidlalo"</item>
+ <item msgid="7345749789651321496">"Ukuthenga"</item>
+ <item msgid="167201149441442173">"Ama-movie"</item>
+ <item msgid="525966731464264290">"Awamahlaya"</item>
+ <item msgid="6096710741527327836">"Ukuvakasha"</item>
+ <item msgid="2851882187117833883">"Awomdlalo"</item>
+ <item msgid="78492781188719038">"Imfundo"</item>
+ <item msgid="7221999662426308394">"Izilwane/impilo yasendle"</item>
+ <item msgid="375300513250925001">"Awezindaba"</item>
+ <item msgid="7746320336582330410">"Awamageyimu"</item>
+ <item msgid="1255741860568329178">"Ezobuciko"</item>
+ <item msgid="7603949681065702867">"Ukuzijabulisa"</item>
+ <item msgid="4453821994746804366">"Indlela yokuphila"</item>
+ <item msgid="3488534597567932843">"Umculo"</item>
+ <item msgid="7452153120614274095">"I-Premier"</item>
+ <item msgid="8215762047341133299">"Ubuchwepheshe/isayensi"</item>
+ </string-array>
+</resources>
diff --git a/res/values-zu/rating_system_strings.xml b/res/values-zu/rating_system_strings.xml
new file mode 100644
index 00000000..e636fad3
--- /dev/null
+++ b/res/values-zu/rating_system_strings.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for description_age_4 (2260506104299774027) -->
+ <skip />
+ <!-- no translation found for description_age_5 (312882360850848623) -->
+ <skip />
+ <!-- no translation found for description_age_6 (7780895021659413979) -->
+ <skip />
+ <!-- no translation found for description_age_7 (7465836675187750087) -->
+ <skip />
+ <!-- no translation found for description_age_8 (8518389733138803056) -->
+ <skip />
+ <!-- no translation found for description_age_9 (1963837231871036561) -->
+ <skip />
+ <!-- no translation found for description_age_10 (9199865628328446876) -->
+ <skip />
+ <!-- no translation found for description_age_11 (1056140065021078183) -->
+ <skip />
+ <!-- no translation found for description_age_12 (2523883089926997157) -->
+ <skip />
+ <!-- no translation found for description_age_13 (4539863086843940979) -->
+ <skip />
+ <!-- no translation found for description_age_14 (276938165219847540) -->
+ <skip />
+ <!-- no translation found for description_age_15 (3875804533354572649) -->
+ <skip />
+ <!-- no translation found for description_age_16 (7068358107904351707) -->
+ <skip />
+ <!-- no translation found for description_age_17 (6090244054207338066) -->
+ <skip />
+ <!-- no translation found for description_age_18 (7827138562117972763) -->
+ <skip />
+ <!-- no translation found for description_age_19 (5739639668332937969) -->
+ <skip />
+ <!-- no translation found for description_age_20 (8099318760823109592) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_atp (547579136010287221) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_13 (4692291035708752548) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_16 (7723173815471130793) -->
+ <skip />
+ <!-- no translation found for description_ar_tv_sam_18 (6396037038854141228) -->
+ <skip />
+ <string name="description_ar_tv_d" msgid="1108913881840440081">"Izidakamizwa"</string>
+ <string name="description_ar_tv_s" msgid="6939530616220134707">"Ucansi"</string>
+ <string name="description_ar_tv_v" msgid="3593686925402404659">"Udlame"</string>
+ <!-- no translation found for description_au_tv_p (6450834908576229026) -->
+ <skip />
+ <!-- no translation found for description_au_tv_c (1308451360610501896) -->
+ <skip />
+ <!-- no translation found for description_au_tv_g (2120367806197069921) -->
+ <skip />
+ <!-- no translation found for description_au_tv_pg (3703098381263561194) -->
+ <skip />
+ <!-- no translation found for description_au_tv_m (3835952110876245500) -->
+ <skip />
+ <!-- no translation found for description_au_tv_ma (7991042881149741890) -->
+ <skip />
+ <!-- no translation found for description_au_tv_av (3792394272837573390) -->
+ <skip />
+ <!-- no translation found for description_au_tv_r (488701479788807290) -->
+ <skip />
+ <!-- no translation found for description_br_tv_l (1750755104141314952) -->
+ <skip />
+ <!-- no translation found for description_br_tv_10 (9206699509782547619) -->
+ <skip />
+ <!-- no translation found for description_br_tv_12 (7319572965100214976) -->
+ <skip />
+ <!-- no translation found for description_br_tv_14 (3743044568707308995) -->
+ <skip />
+ <!-- no translation found for description_br_tv_16 (763942958761698764) -->
+ <skip />
+ <!-- no translation found for description_br_tv_18 (2543086592733416771) -->
+ <skip />
+ <string name="description_br_tv_d" msgid="6117156505386584449">"Izidakamizwa"</string>
+ <string name="description_br_tv_s" msgid="2713003081092951596">"Ucansi"</string>
+ <string name="description_br_tv_v" msgid="8360268389564584952">"Udlame"</string>
+ <!-- no translation found for description_ca_tv_en_exempt (3869530797231884669) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c (6170724506314571912) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_c8 (7942870097063909046) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_g (7893908216230285607) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_pg (2736709384517099303) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_14 (8063534593921657009) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_en_18 (3552078692829803323) -->
+ <skip />
+ <string name="title_ca_tv_fr" msgid="5354916412253251759">"Isi-French"</string>
+ <!-- no translation found for description_ca_tv_fr_e (191173579009161428) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_g (5572294714137666887) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_8 (7142414766270693085) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_13 (5198848759504239143) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_16 (1034513066641057587) -->
+ <skip />
+ <!-- no translation found for description_ca_tv_fr_18 (2919990256513482762) -->
+ <skip />
+ <!-- no translation found for description_fr_dvb_u (8232083238497153682) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_all (4821183217465284547) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_c (1367395312248365855) -->
+ <skip />
+ <!-- no translation found for description_es_dvb_x (438890727939582714) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_all (3048365195196820853) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_7 (5892929693766504059) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_12 (1883893246941826597) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_15 (397719456648820574) -->
+ <skip />
+ <!-- no translation found for description_kr_tv_19 (2562374912814706392) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_g (1552875095194906311) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg (5180090945868922455) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_pg13 (3655542939788886420) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_nc16 (3484254252201121736) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_m18 (392321217022988206) -->
+ <skip />
+ <!-- no translation found for description_sg_tv_r21 (1466786788649950591) -->
+ <skip />
+ <string name="description_us_tv_d" msgid="12333789157204816">"Ingxoxo ephakanyiswayo"</string>
+ <string name="description_us_tv_l" msgid="4105102855627964319">"Ulimi olunezithuko"</string>
+ <string name="description_us_tv_s" msgid="7552447251273237176">"Okuphathelene nezocansi"</string>
+ <string name="description_us_tv_v" msgid="4799470820740236198">"Udlame"</string>
+ <string name="description_us_tv_fv" msgid="4760884520245003167">"Udlame olucatshangwayo"</string>
+ <!-- no translation found for description_us_tv_y (1352346035366881161) -->
+ <skip />
+ <!-- no translation found for description_us_tv_y7 (8074158131815307880) -->
+ <skip />
+ <!-- no translation found for description_us_tv_g (4129784125991285944) -->
+ <skip />
+ <!-- no translation found for description_us_tv_pg (5647218792100947596) -->
+ <skip />
+ <!-- no translation found for description_us_tv_14 (8682987672893857317) -->
+ <skip />
+ <!-- no translation found for description_us_tv_ma (8912237130594289176) -->
+ <skip />
+ <string name="title_us_mv" msgid="7861766023361439035">"Izilinganiso zefilimu"</string>
+ <!-- no translation found for description_us_mv_g (9185817407450418638) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg (8578620326248525384) -->
+ <skip />
+ <!-- no translation found for description_us_mv_pg13 (7077331984538950084) -->
+ <skip />
+ <!-- no translation found for description_us_mv_r (4482914375592527277) -->
+ <skip />
+ <!-- no translation found for description_us_mv_nc17 (1897668555117874369) -->
+ <skip />
+</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
new file mode 100644
index 00000000..8493607b
--- /dev/null
+++ b/res/values-zu/strings.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1379381957730211484">"Iziteshi ezibukhoma"</string>
+ <string name="audio_channel_mono" msgid="8812941280022167428">"i-mono"</string>
+ <string name="audio_channel_stereo" msgid="5798223286366598036">"i-stereo"</string>
+ <string name="menu_title_play_controls" msgid="2490237359425190652">"Izilawuli zokudlala"</string>
+ <string name="menu_title_channels" msgid="1949045451672990132">"Iziteshi"</string>
+ <string name="menu_title_channels_legacy" msgid="700324464656729334">"Iziteshi zakamuva"</string>
+ <string name="menu_title_options" msgid="7184594626814914022">"Izinketho ze-TV"</string>
+ <string name="menu_title_pip_options" msgid="4252934960762407689">"Izinketho ze-PIP"</string>
+ <string name="play_controls_unavailable" msgid="8900698593131693148">"Izilawuli zokudlala azitholakali kulesi siteshi"</string>
+ <string name="play_controls_description_play_pause" msgid="7225542861669250558">"Dlala noma umise isikhashana"</string>
+ <string name="play_controls_description_fast_forward" msgid="4414963867482448652">"Ukudlulisa ngokushesha"</string>
+ <string name="play_controls_description_fast_rewind" msgid="953488122681015803">"Buyisela emuva"</string>
+ <string name="play_controls_description_skip_next" msgid="1603587562124694592">"Okulandelayo"</string>
+ <string name="play_controls_description_skip_previous" msgid="3858447678278021381">"Okwangaphambilini"</string>
+ <string name="channels_item_program_guide" msgid="2889807207930678418">"Umhlahlandlela wohlelo"</string>
+ <string name="channels_item_setup" msgid="6557412175737379022">"Iziteshi ezintsha ziyatholakala"</string>
+ <string name="channels_item_app_link_no_app_link" msgid="1884830234777824408">"Asikho isixhumanisi esitholakalayo"</string>
+ <string name="channels_item_app_link_app_launcher" msgid="1395352122187670523">"Vula i-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="options_item_closed_caption" msgid="5945274655046367170">"Amazwibela avaliwe"</string>
+ <string name="options_item_display_mode" msgid="7989243076748680140">"Imodi yesibonisi"</string>
+ <string name="options_item_pip" msgid="3951350386626879645">"I-PIP"</string>
+ <string name="options_item_pip_on" msgid="4647247480009077381">"Vuliwe"</string>
+ <string name="options_item_pip_off" msgid="8799500161592387451">"Valiwe"</string>
+ <string name="options_item_multi_audio" msgid="5118851311937896923">"Umsindo omningi"</string>
+ <string name="options_item_channel_sources" msgid="2217574947817750560">"Ukusethwa kwesiteshi"</string>
+ <string name="options_item_parental_controls" msgid="7880060136509458287">"Ukulawula kwabazali"</string>
+ <string name="options_item_about" msgid="3023532413252052050">"Mayelana"</string>
+ <string name="pip_options_item_source" msgid="1050948525825308652">"Umthombo"</string>
+ <string name="pip_options_item_swap" msgid="7245362207353732048">"Shintsha"</string>
+ <string name="pip_options_item_swap_on" msgid="5647182616484983505">"Vuliwe"</string>
+ <string name="pip_options_item_swap_off" msgid="3023597229417709768">"Valiwe"</string>
+ <string name="pip_options_item_sound" msgid="3107034283791231648">"Umsindo"</string>
+ <string name="pip_options_item_sound_main" msgid="1063937534112558691">"Isisekelo"</string>
+ <string name="pip_options_item_sound_pip_window" msgid="435064142351853008">"Iwindil le-PIP"</string>
+ <string name="pip_options_item_layout" msgid="5126206342060967651">"Isakhiwo"</string>
+ <string name="pip_options_item_layout_bottom_right" msgid="5256839015192920238">"Phansi ngakwesokudla"</string>
+ <string name="pip_options_item_layout_top_right" msgid="3585067214787055279">"Phezulu ngakwesokudla"</string>
+ <string name="pip_options_item_layout_top_left" msgid="2173997010201637462">"Phezulu ngakwesokunxele"</string>
+ <string name="pip_options_item_layout_bottom_left" msgid="1727197877968915329">"Phansi ngakwesokunxele"</string>
+ <string name="pip_options_item_layout_side_by_side" msgid="9009784972740915779">"ingxenye nenxenye"</string>
+ <string name="pip_options_item_size" msgid="5662894110243750158">"Usayizi"</string>
+ <string name="pip_options_item_size_big" msgid="6303301565563444718">"Kukhulu"</string>
+ <string name="pip_options_item_size_small" msgid="7393731186585004206">"Kuncane"</string>
+ <string name="side_panel_title_pip_input_source" msgid="8722544967592970296">"Umthombo wokufaka"</string>
+ <string name="input_long_label_for_tuner" msgid="3423514011918382209">"I-TV (I-antenna/Intambo)"</string>
+ <string name="no_program_information" msgid="1049844207745145132">"Alukho ulwazi lohlelo"</string>
+ <string name="program_title_for_no_information" msgid="384451471906070101">"Alukho ulwazi"</string>
+ <string name="program_title_for_blocked_channel" msgid="5358005891746983819">"Isiteshi esivinjiwe"</string>
+ <string name="default_language" msgid="4122326459624337928">"Ulimi olungaziwa"</string>
+ <string name="side_panel_title_closed_caption" msgid="2513905054082568780">"Amazwibela avaliwe"</string>
+ <string name="closed_caption_option_item_off" msgid="4824009036785647753">"Valiwe"</string>
+ <string name="closed_caption_system_settings" msgid="1856974607743827178">"Yenza ngokwezifiso ukufometha"</string>
+ <string name="closed_caption_system_settings_description" msgid="6285276836057964524">"Setha okuncamelayo okubanzi kwesistimu yamazwibela avaliwe"</string>
+ <string name="side_panel_title_display_mode" msgid="6346286034015991229">"Imodi yesibonisi"</string>
+ <string name="side_panel_title_multi_audio" msgid="5970537894780855080">"Umsindo omningi"</string>
+ <string name="multi_audio_channel_mono" msgid="6229173848963557723">"i-mono"</string>
+ <string name="multi_audio_channel_stereo" msgid="3758995659214256587">"i-Stereo"</string>
+ <string name="multi_audio_channel_surround_6" msgid="6066304966228963942">"5.1 jikelele"</string>
+ <string name="multi_audio_channel_surround_8" msgid="2765140653768694313">"7.1 jikelele"</string>
+ <string name="multi_audio_channel_suffix" msgid="4443825738196093772">"%d iziteshi"</string>
+ <string name="side_panel_title_edit_channels_for_an_input" msgid="7334895164698222989">"Yenza ngezifiso uhlu lweziteshi"</string>
+ <string name="edit_channels_item_select_group" msgid="4953000352257999703">"Khetha iqembu"</string>
+ <string name="edit_channels_item_deselect_group" msgid="5092649099546997807">"Susa ukukhetha iqembu"</string>
+ <string name="edit_channels_item_group_by" msgid="7794571851966798199">"Qoqa nge-"</string>
+ <string name="edit_channels_group_by_sources" msgid="5481053601210461217">"Umthombo wesiteshi"</string>
+ <string name="edit_channels_group_by_hd_sd" msgid="5582719665718278819">"HD/SD"</string>
+ <string name="edit_channels_group_divider_for_hd" msgid="5311355566660389423">"I-HD"</string>
+ <string name="edit_channels_group_divider_for_sd" msgid="5846195382266436167">"I-SD"</string>
+ <string name="side_panel_title_group_by" msgid="1783176601425788939">"Qoqa nge-"</string>
+ <string name="side_panel_title_channel_sources" msgid="2171195037212553286">"Ukusethwa kwesiteshi"</string>
+ <string name="program_guide_content_locked" msgid="198056836554559553">"Lolu hlelo luvinjiwe"</string>
+ <string name="program_guide_content_locked_format" msgid="514915272862967389">"Lolu hlelo lulinganiselwe nge-<xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_source_item_setup" msgid="1330441915487807542">"Imithombo yeziteshi"</string>
+ <string name="channel_source_item_setup_new_inputs" msgid="2992665972254089976">"Iziteshi ezintsha ziyatholakala"</string>
+ <string name="channel_source_item_customize_channels" msgid="2836316051162501082">"Yenza ngezifiso uhlu lweziteshi"</string>
+ <string name="channel_source_item_customize_channels_description" msgid="3124895053900086630">"Khetha iziteshi zomhlahlandlela wohlelo lwakho"</string>
+ <string name="channel_description_setup_now" msgid="7643342362645622249">"Akusethiwe"</string>
+ <string name="msg_no_setup_activity" msgid="7746893144640239857">"Kokufaka akusekeli ukuskena okuzenzakalelayo"</string>
+ <string name="msg_unable_to_start_setup_activity" msgid="8402612466599977855">"Ayikwazi ukuqala ukuskena ngokuzenzakalela kwe-\'<xliff:g id="TV_INPUT">%s</xliff:g>\'"</string>
+ <string name="msg_unable_to_start_system_captioning_settings" msgid="705242616044165668">"Ayikwazi ukuqala okuncamelayo kwesistimu okubanzi kwemibhalo engezansi."</string>
+ <plurals name="msg_channel_added">
+ <item quantity="one" msgid="6074059986849579215">"%1$d isiteshi singeziwe"</item>
+ <item quantity="other" msgid="1078861616751739285">"%1$d iziteshi zingeziwe"</item>
+ </plurals>
+ <string name="msg_no_channel_added" msgid="2882586037409921925">"Azikho iziteshi ezingeziwe"</string>
+ <string name="input_selector_tuner_label" msgid="6631205039926880892">"I-Tuner"</string>
+ <string name="menu_parental_controls" msgid="2474294054521345840">"Ukulawula kwabazali"</string>
+ <string name="option_toggle_parental_controls_on" msgid="9122851821454622696">"Vuliwe"</string>
+ <string name="option_toggle_parental_controls_off" msgid="7797910199040440618">"Valiwe"</string>
+ <string name="option_channels_locked" msgid="5797855082297549907">"Iziteshi ezivinjelwe"</string>
+ <string name="option_channels_lock_all" msgid="6594512884477342940">"Vimbela konke"</string>
+ <string name="option_channels_unlock_all" msgid="6839513296447567623">"Vulela konke"</string>
+ <string name="option_channels_subheader_hidden" msgid="4669425935426972078">"Iziteshi ezifihliwe"</string>
+ <string name="option_program_restrictions" msgid="241342023067364108">"Imikhawulo yohlelo"</string>
+ <string name="option_change_pin" msgid="2881594075631152566">"Shintsha iphinikhodi"</string>
+ <string name="option_country_rating_systems" msgid="7288569813945260224">"Amasistimu wesilinganiso"</string>
+ <string name="option_ratings" msgid="4009116954188688616">"Izilinganiso"</string>
+ <string name="option_see_all_rating_systems" msgid="7702673500014877288">"Bona wonke amasistimu wesilinganiso"</string>
+ <string name="other_countries" msgid="8342216398676184749">"Amanye amazwe"</string>
+ <string name="option_no_locked_channel" msgid="2543094883927978444">"Lutho"</string>
+ <string name="option_no_enabled_rating_system" msgid="4139765018454678381">"Lutho"</string>
+ <string name="option_rating_none" msgid="5204552587760414879">"Lutho"</string>
+ <string name="option_rating_high" msgid="8898400296730158893">"Imikhawulo ephezulu"</string>
+ <string name="option_rating_medium" msgid="6455853836426497151">"Imikhawulo emaphakathi"</string>
+ <string name="option_rating_low" msgid="5800146024503377969">"Imikhawulo ephansi"</string>
+ <string name="option_rating_custom" msgid="3155377834510646436">"Ngokwezifiso"</string>
+ <string name="option_rating_high_description" msgid="609567565273278745">"Okuqukethwe okulungele izingane"</string>
+ <string name="option_rating_medium_description" msgid="7169199016608935280">"Okuqukethwe kulungele izingane ezindala"</string>
+ <string name="option_rating_low_description" msgid="4740109576615335045">"Okuqukethwe kulungele izingane"</string>
+ <string name="option_rating_custom_description" msgid="6180723522991233194">"Imikhawulo yokwenza"</string>
+ <!-- no translation found for option_attribution (2967657807178951562) -->
+ <skip />
+ <string name="option_subrating_title" msgid="5485055507818077595">"%1$s nezilinganiso zangaphansi"</string>
+ <string name="option_subrating_header" msgid="4637961301549615855">"Izilinganiso zangaphansi"</string>
+ <string name="pin_enter_unlock_channel" msgid="4797922378296393173">"Faka iphinikhodi yakho ukuze ubuke lesi siteshi"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Faka iphinikhodi yakho ukuze ubuke lolu hlelo"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Faka iphinikhodi yakho"</string>
+ <string name="pin_enter_create_pin" msgid="3385754356793309946">"Ukuze usethe izilawuli zomzali, dala iphinikhodi"</string>
+ <string name="pin_enter_new_pin" msgid="1739471585849790384">"Faka iphinikhodi entsha"</string>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Qinisekisa i-PIN yakho"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Faka i-PIN yakho yamanje"</string>
+ <plurals name="pin_enter_countdown">
+ <item quantity="one" msgid="5555357148779935468">"Ufake iphinikhodi engalungile izikhathi ezingu-5.\nZama futhi kusekhondi elingu-<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g>."</item>
+ <item quantity="other" msgid="8829550842387756054">"Ufake iphinikhodi engalungile izikhathi ezingu-5.\nZama futhi kumasekhondi angu-<xliff:g id="REMAINING_SECONDS">%1$d</xliff:g>."</item>
+ </plurals>
+ <string name="pin_toast_wrong" msgid="2126295626095048746">"Leyo phini ayilungile. Zama futhi."</string>
+ <string name="pin_toast_not_match" msgid="4283624338659521768">"Zama futhi, iphinikhodi ayifani"</string>
+ <string name="side_panel_title_about" msgid="4572960435017168299">"Mayelana"</string>
+ <string name="about_menu_licenses" msgid="4831892908710402185">"Amalayisense womthombo ovulekile"</string>
+ <string name="dialog_title_licenses" msgid="4471754920475076623">"Amalayisense womthombo ovulekile"</string>
+ <string name="about_menu_version" msgid="6303769835664868129">"Inguqulo"</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Ukuze ubuke lesi siteshi, cindezela Kwesokudla uphinde ufake i-PIN yakho"</string>
+ <string name="tvview_content_locked" msgid="391823084917017730">"Ukuze ubuke lolu hlelo, cindezela Kwesokudla uphinde ufake i-PIN yakho"</string>
+ <string name="tvview_content_locked_format" msgid="3741874636031338247">"Lolu hlelo lulinganiselwe ngo-<xliff:g id="RATING">%1$s</xliff:g>.\nUkuze ubuke lolu hlelo, cindezela Kwesokudla uphinde ufake i-PIN yakho."</string>
+ <string name="shrunken_tvview_content_locked" msgid="7686397981042364446">"Uhlelo luvinjiwe"</string>
+ <string name="shrunken_tvview_content_locked_format" msgid="3720284198877900916">"Lolu hlelo lulinganiselwe nge-<xliff:g id="RATING">%1$s</xliff:g>"</string>
+ <string name="channel_banner_no_title" msgid="8660301979190693176">"Asikho isihloko"</string>
+ <string name="channel_banner_locked_channel_title" msgid="2006564967318945980">"Isiteshi sivinjiw"</string>
+ <string name="episode_format" msgid="4881195874563241096">"S<xliff:g id="SEASON_NUMBER">%1$d</xliff:g>: Ep. <xliff:g id="EPISODE_NUMBER">%2$d</xliff:g> <xliff:g id="EPISODE_TITLE">%3$s</xliff:g>"</string>
+ <string name="setup_title" msgid="7268875010986705651">"Imithombo yeziteshi"</string>
+ <string name="setup_description" msgid="8728423605912915099">"Setha iziteshi ezibukhoma kusukela kumithombo etholakalayo. Lokhu kungathatha amaminithi ambalwa ngokuya ngomthombo wesiteshi."</string>
+ <string name="setup_done_button_label" msgid="3488134953528237729">"Kwenziwe"</string>
+ <plurals name="setup_input_channels">
+ <item quantity="one" msgid="7847402892516958129">"%1$d isiteshi esitholakalayo"</item>
+ <item quantity="other" msgid="2386588423841837714">"%1$d iziteshi ezitholakalayo"</item>
+ </plurals>
+ <string name="setup_input_no_channels" msgid="1669327912393163331">"Azikho iziteshi ezitholakalayo"</string>
+ <string name="setup_input_new" msgid="3337725672277046798">"Okusha"</string>
+ <!-- no translation found for intro_title (251772896916795556) -->
+ <skip />
+ <string name="intro_description" msgid="7806473686446937307"><b>"Cindezela okuthi KHETHA"</b>" ukuze ufinyelele imenyu ye-TV."</string>
+ <string name="msg_no_input" msgid="3897674146985427865">"Akukho kokufaka kwe-TV okutholakele"</string>
+ <string name="msg_no_specific_input" msgid="2688885987104249852">"Ayikwazi ukuthola kokufaka kwe-TV"</string>
+ <string name="msg_no_pip_support" msgid="161508628996629445">"I-PIP ayisekelwe"</string>
+ <string name="msg_no_available_input_by_pip" msgid="7038191654524679666">"Akutholakali okokufaka okungaboniswa nge-PIP"</string>
+ <string name="msg_not_passthrough_input" msgid="4502101097091087411">"uhlobo le-Tuner alufanelekile; Sicela uqalise uhlelo lokusebenza leziteshi ezibukhoma lokufakwayo kwe-TV yohlobo le-tuner."</string>
+ <string name="msg_tune_failed" msgid="3277419551849972252">"Ukushuna kwehlulekile"</string>
+ <string name="msg_missing_app" msgid="8291542072400042076">"Alukho uhlelo lokusebenza olutholakalele ukuphatha lesi senzo."</string>
+ <string name="msg_all_channels_hidden" msgid="777397634062471936">"Zonke iziteshi zomthombo zifihliwe.\nKhetha okungenani isiteshi esisodwa ukuze ubuke."</string>
+ <string name="msg_channel_unavailable_weak_signal" msgid="3935601939459171709">"Ayitholakali ngenxa yesignali yevidiyo engaqinile"</string>
+ <string name="msg_channel_unavailable_unknown" msgid="765586450831081871">"Ividiyo ayitholakali ngokungalindelekile"</string>
+ <string name="msg_back_key_guide" msgid="7404682718828721924">"Ukhiye we-EMUVA ungowedivayisi exhunyiwe. Cindezela kunkinobho ye-IKHAYA ukuze uphume."</string>
+</resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index b3181364..0ae24d90 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -1,22 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- The display_mode_label should be synced with src/com/android/tv/data/DisplayMode.java -->
- <string-array name="display_mode_label">
+ <eat-comment />
+ <!-- Option list of display mode. http://en.wikipedia.org/wiki/Widescreen_display_modes
+ [CHAR LIMIT=20] -->
+ <string-array name="display_mode_labels" translatable="true">
<item>Normal</item>
<item>Full</item>
<item>Zoom</item>
@@ -24,7 +28,9 @@
<!-- The category strings to be displayed in the channel guide.
The show_only_label should be synced with src/com/android/tv/data/ShowOnlyItems.java -->
- <string-array name="show_only_label" translatable="true">
+ <eat-comment />
+ <!-- Genre list [CHAR LIMIT=20] -->
+ <string-array name="genre_labels" translatable="true">
<item>All channels</item>
<item>Family/Kids</item>
<item>Sports</item>
@@ -37,11 +43,11 @@
<item>Animal/Wildlife</item>
<item>News</item>
<item>Gaming</item>
- </string-array>
-
- <!-- The command strings which are displayed in the "Edit Channels" side panel -->
- <string-array name="edit_channels_actions" translatable="true">
- <item>Show all</item>
- <item>Hide all</item>
+ <item>Arts</item>
+ <item>Entertainment</item>
+ <item>Lifestyle</item>
+ <item>Music</item>
+ <item>Premier</item>
+ <item>Tech/Science</item>
</string-array>
</resources>
diff --git a/res/values/attr.xml b/res/values/attr.xml
index c4c10112..1261ea41 100644
--- a/res/values/attr.xml
+++ b/res/values/attr.xml
@@ -1,6 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
<resources>
- <declare-styleable name="KeypadView">
- <attr name="keypadViewStyle" format="reference"/>
+ <declare-styleable name="CurrentProgramState" >
+ <attr name="state_current_program" format="boolean" />
</declare-styleable>
-</resources> \ No newline at end of file
+ <declare-styleable name="ProgramTooWideState" >
+ <attr name="state_program_too_wide" format="boolean" />
+ </declare-styleable>
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index bdebe531..67a75431 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -1,49 +1,138 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+ ~ 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.
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
+<resources>
+ <!-- Common -->
+ <color name="accent_color">#80D3FB</color>
+ <color name="control_activated_color">#80D3FB</color>
+ <color name="primary_color">#0285D3</color>
- http://www.apache.org/licenses/LICENSE-2.0
+ <!-- TvActivity -->
+ <color name="tvactivity_background">#000000</color>
+ <color name="tvactivity_background_on_shrunken_tvview">#111111</color>
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
+ <!-- Main menu -->
+ <color name="menu_row_title">#FFEEEEEE</color>
-<resources>
- <color name="channel_tile_guide_top">#455A64</color>
- <color name="channel_tile_guide_bottom">#384248</color>
+ <!-- Play controls -->
+ <color name="play_controls_time_text_color">#26C6DA</color>
+ <color name="play_controls_progress_bar_background">#33EEEEEE</color>
+ <color name="play_controls_progress_bar_watched">#26C6DA</color>
+ <color name="play_controls_progress_bar_buffered">#00ACC1</color>
+ <color name="play_controls_progress_time_indicator_color">#26C6DA</color>
+ <color name="play_controls_rec_time_text_color">#26C6DA</color>
+ <color name="play_controls_body_background_enabled">#0277BD</color>
+ <color name="play_controls_body_background_disabled">#800277BD</color>
+ <color name="play_controls_unavailable_message_text_color">#B3EEEEEE</color>
+
+ <!-- Base card -->
+ <color name="card_meta_text_color_enabled">#EEEEEE</color>
+ <color name="card_meta_text_color_disabled">#4DEEEEEE</color>
+
+ <!-- App link card -->
+ <color name="app_link_card_icon_color_filter">@color/card_meta_text_color_enabled</color>
+
+ <!-- Channel card -->
+ <color name="channel_card">#455A64</color>
+ <color name="channel_card_guide">#0288D1</color>
+ <color name="channel_card_meta_background">#384248</color>
+ <color name="channel_card_progress_bar_background">#1AEEEEEE</color>
+ <color name="channel_card_progress_bar_foreground">#00FFFF</color>
- <color name="channel_tile_top">#0288D1</color>
- <color name="channel_tile_top_on_image">#66000000</color>
- <color name="channel_tile_bottom">#0176BC</color>
- <color name="channel_tile_text_color">#FFEEEEEE</color>
+ <!-- Guide card -->
+ <color name="guide_card_meta_background">#0176BC</color>
- <color name="focused_list_title">#FFEEEEEE</color>
- <color name="unfocused_list_title">#7FEEEEEE</color>
- <color name="options_selection_indicator">#4CEEEEEE</color>
+ <!-- Action card -->
+ <color name="action_card_selected_background">#33EEEEEE</color>
+ <color name="action_card_deselected_background">#33455A64</color>
+ <color name="action_card_label_color">#EEEEEE</color>
+ <color name="action_card_state_color">#B3EEEEEE</color>
- <color name="menu_default_text_color">#FFEEEEEE</color>
- <color name="menu_default_disabled_text_color">#FF777777</color>
+ <!-- Side panel -->
+ <color name="side_panel_background">#263238</color>
+ <color name="side_panel_header_background">#0277BD</color>
- <color name="accent_color">#00A6F6</color>
+ <!-- More options -->
+ <color name="option_item_divider_color">#20EEEEEE</color>
+ <color name="option_item_text_color_enabled">#FFEEEEEE</color>
+ <color name="option_item_text_color_disabled">#4DEEEEEE</color>
+ <color name="option_item_secondary_text_color_enabled">#80EEEEEE</color>
+ <color name="option_item_secondary_text_color_disabled">#4DEEEEEE</color>
+ <color name="option_item_divider_text_color">#039BE5</color>
- <color name="side_panel_divider">#495257</color>
- <color name="side_panel_background">#384248</color>
- <color name="side_panel_header_background">#0288D1</color>
+ <!-- Program guide -->
+ <color name="program_guide_scrim">#99000000</color>
+ <color name="program_guide_side_panel_background">#0176BC</color>
+ <color name="program_guide_side_panel_text_color">#FFEEEEEE</color>
+ <color name="program_guide_side_panel_text_color_unfocused">#80EEEEEE</color>
+ <color name="program_guide_table_header_row_background">#F2455A64</color>
+ <color name="program_guide_table_header_row_current_time_text_color">#17FFFF</color>
+ <color name="program_guide_table_header_row_time_text_color">#80EEEEEE</color>
+ <color name="program_guide_table_header_column_background">#F237474F</color>
+ <color name="program_guide_table_header_column_channel_number_text_color">#EEEEEE</color>
+ <color name="program_guide_table_header_column_channel_number_blocked_text_color">#80EEEEEE</color>
+ <color name="program_guide_table_header_column_channel_name_text_color">#EEEEEE</color>
+ <color name="program_guide_table_border_color">#0DEEEEEE</color>
+ <color name="program_guide_table_grid_background">#F2263238</color>
+ <!-- Color code to make color #800176BC on top of #4D17FFFF.
+ Overlay is done in rest of progress because its color has higher alpha value. -->
+ <color name="program_guide_table_item_focus_progress_overlay">#49000F8A</color>
+ <color name="program_guide_table_item_program_title_text_color">#EEEEEE</color>
+ <color name="program_guide_table_item_program_episode_title_text_color">#B3EEEEEE</color>
+ <color name="program_guide_table_item_grayed_out_program_episode_title_text_color">#4CEEEEEE</color>
+ <color name="program_guide_table_item_grayed_out_program_text_color_not_focused">#4CEEEEEE</color>
+ <color name="program_guide_table_item_grayed_out_program_text_color_focused">#80EEEEEE</color>
+ <color name="program_guide_table_detail_background">#1A0176BC</color>
+ <color name="program_guide_table_detail_title_text_color">#EEEEEE</color>
+ <color name="program_guide_table_detail_episode_title_text_color">#B3EEEEEE</color>
+ <color name="program_guide_table_detail_title_grayed_text_color">#4CEEEEEE</color>
+ <color name="program_guide_table_detail_time_text_color">#B3EEEEEE</color>
+ <color name="program_guide_table_detail_desc_text_color">#B3EEEEEE</color>
+ <color name="program_guide_time_indicator_color">#17FFFF</color>
- <color name="option_item_background">#384248</color>
- <color name="option_item_focused_background">#5C6467</color>
+ <!-- TV view -->
+ <color name="tvview_block_text_color">#80EEEEEE</color>
+ <!-- Channel banner -->
+ <color name="channel_banner_text_color">#FFEEEEEE</color>
+ <color name="channel_banner_episode_text_color">#B3EEEEEE</color>
+ <color name="channel_banner_secondary_input_label_color">#B3EEEEEE</color>
+ <color name="channel_banner_dim_text_color">#80EEEEEE</color>
+
+ <!-- Keypad channel switch -->
+ <color name="keypad_channel_switch_text_color">@color/channel_banner_text_color</color>
+
+ <!-- Select input -->
+ <color name="select_input_text_color_primary">@color/channel_banner_text_color</color>
+ <color name="select_input_text_color_secondary">@color/channel_banner_secondary_input_label_color</color>
+ <color name="select_input_text_color_disabled">#4DEEEEEE</color>
+
+ <!-- Recommendation cards in LeanbackLauncher -->
<color name="recommendation_card_background">#0288D1</color>
- <color name="keypad_background">#455a64</color>
- <color name="key_red">#ff4336</color>
- <color name="key_green">#0e9c57</color>
- <color name="key_yellow">#ffbb00</color>
- <color name="key_blue">#4184f3</color>
+ <!-- PIN dialog -->
+ <color name="pin_dialog_background">#384248</color>
+ <color name="pin_dialog_text_color">#C0EEEEEE</color>
+ <color name="pin_number_picker_text_color">#FFEEEEEE</color>
+ <color name="pin_number_picker_focused_background">#4B5459</color>
+
+ <!-- Setup -->
+ <color name="setup_title">#EEEEEE</color>
+ <color name="setup_description">#B3EEEEEE</color>
+ <color name="setup_background">#E701579B</color>
+ <color name="setup_divider">#4DEEEEEE</color>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index a3e7f95f..b4f3e3b1 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -1,84 +1,268 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<resources>
- <dimen name="channel_list_view_height">160dp</dimen>
- <dimen name="action_list_view_height">144dp</dimen>
- <dimen name="selected_row_alignment">64dp</dimen>
+ <!-- Main menu -->
+ <!-- menu_height should be bigger than the height when all possible menu rows are
+ enabled and the focus is on the row which has the biggest height. -->
+ <dimen name="menu_height">500dp</dimen>
+ <dimen name="menu_row_title_width">300dp</dimen>
+ <dimen name="menu_row_title_height">48dp</dimen>
+ <dimen name="menu_row_title_padding_bottom">4dp</dimen>
+ <dimen name="menu_row_title_transform_pivot_x">0dp</dimen>
+ <dimen name="menu_row_title_transform_pivot_y">36dp</dimen>
+ <dimen name="menu_padding_start">56dp</dimen>
+ <dimen name="menu_padding_start_negative">-56dp</dimen>
+ <dimen name="menu_padding_end">56dp</dimen>
+ <dimen name="menu_padding_end_negative">-56dp</dimen>
+ <!-- The value in the redlines is 27dp but the items have padding tops and bottoms,
+ so need to adjust by 7dp. -->
+ <dimen name="menu_padding_bottom">20dp</dimen>
+ <dimen name="menu_list_margin_top">-1.5dp</dimen>
+ <dimen name="menu_list_margin_bottom">-30dp</dimen>
+ <dimen name="menu_list_margin_start">@dimen/menu_padding_start_negative</dimen>
+ <dimen name="menu_list_margin_end">@dimen/menu_padding_end_negative</dimen>
+ <!-- NOTE: The top and bottom padding size should be bigger than or equals to 8 dp.
+ otherwise it will make a clipping issue. See b/18632820 for more details. -->
+ <dimen name="menu_list_padding_top">8dp</dimen>
+ <dimen name="menu_list_padding_bottom">8dp</dimen>
+ <dimen name="menu_list_padding_start">@dimen/menu_padding_start</dimen>
+ <dimen name="menu_list_padding_end">@dimen/menu_padding_end</dimen>
+ <dimen name="menu_row_title_text_size_selected">20sp</dimen>
+ <dimen name="menu_row_title_text_size_deselected">16sp</dimen>
+ <dimen name="menu_inter_card_spacing">8dp</dimen>
+ <dimen name="menu_enter_offset_y">32dp</dimen>
+ <dimen name="menu_enter_offset_y_negative">-32dp</dimen>
+ <dimen name="menu_exit_offset_y">32dp</dimen>
- <dimen name="item_size_medium_threshold">128sp</dimen>
- <dimen name="item_size_large_threshold">256sp</dimen>
+ <!-- Play controls -->
+ <dimen name="play_controls_width">696dp</dimen>
+ <dimen name="play_controls_height">88dp</dimen>
+ <dimen name="play_controls_progress_height">4dp</dimen>
+ <dimen name="play_controls_button_background_width">90dp</dimen>
+ <dimen name="play_controls_button_background_height">90dp</dimen>
+ <dimen name="play_controls_button_width">90dp</dimen>
+ <dimen name="play_controls_button_height">64dp</dimen>
+ <dimen name="play_controls_button_start_margin">6dp</dimen>
+ <dimen name="play_controls_time_width">80dp</dimen>
+ <dimen name="play_controls_time_text_size">12sp</dimen>
+ <!-- The margin is different from the redlines due to the descenders.
+ This should be changed as the font change. -->
+ <dimen name="play_controls_time_bottom_margin">9dp</dimen>
+ <dimen name="play_controls_time_indicator_top_margin">-4dp</dimen>
+ <dimen name="play_controls_time_indicator_width">12dp</dimen>
+ <dimen name="play_controls_time_indicator_height">12dp</dimen>
+ <dimen name="play_controls_program_time_margin_top">8dp</dimen>
+ <dimen name="play_controls_program_time_margin_start">16dp</dimen>
+ <dimen name="play_controls_program_time_margin_end">16dp</dimen>
+ <dimen name="play_controls_rec_time_text_size">12sp</dimen>
+ <dimen name="play_controls_unavailable_message_text_size">14sp</dimen>
- <dimen name="inter_card_spacing">-25dp</dimen>
+ <!-- Base card -->
+ <dimen name="card_layout_width">142dp</dimen>
+ <!-- NOTE: card_layout_height MUST be card_image_layout_height + card_meta_layout_height -->
+ <dimen name="card_layout_height">108dp</dimen>
+ <!-- NOTE: card_layout_height_extended MUST be card_image_layout_height +
+ card_meta_layout_height_extended -->
+ <dimen name="card_layout_height_extended">121.5dp</dimen>
+ <dimen name="card_image_layout_width">@dimen/card_layout_width</dimen>
+ <dimen name="card_image_layout_height">80dp</dimen>
+ <dimen name="card_image_gradient_padding_top">40dp</dimen>
+ <dimen name="card_meta_layout_height">28dp</dimen>
+ <dimen name="card_meta_layout_height_extended">41.5dp</dimen>
+ <dimen name="card_meta_padding_start">12dp</dimen>
+ <dimen name="card_meta_padding_end">12dp</dimen>
+ <dimen name="card_meta_padding_top">5dp</dimen>
+ <dimen name="card_elevation_normal">4dp</dimen>
- <dimen name="focused_list_title_text_size">20sp</dimen>
- <dimen name="unfocused_list_title_text_size">16sp</dimen>
+ <!-- Channel card -->
+ <dimen name="channel_card_progress_bar_height">2dp</dimen>
+ <dimen name="channel_card_elevation_focused">12dp</dimen>
+ <dimen name="channel_card_round_radius">2dp</dimen>
- <dimen name="channel_tile_round_radius">2dp</dimen>
- <dimen name="channel_tile_poster_art_width">160dp</dimen>
- <dimen name="channel_tile_poster_art_height">90dp</dimen>
+ <!-- App link card -->
+ <dimen name="app_link_card_icon_width">16dp</dimen>
+ <dimen name="app_link_card_icon_height">16dp</dimen>
+ <dimen name="app_link_card_icon_padding">4dp</dimen>
- <!-- Action tile dimensions -->
- <dimen name="action_tile_inter_card_spacing">8dp</dimen>
- <dimen name="action_tile_outer_length">116dp</dimen>
- <dimen name="action_tile_inner_length">92dp</dimen>
- <dimen name="action_tile_label_font_size">14sp</dimen>
+ <!-- Action card -->
+ <dimen name="action_card_width">142dp</dimen>
+ <dimen name="action_card_height">96dp</dimen>
+ <dimen name="action_card_icon_width">32dp</dimen>
+ <dimen name="action_card_icon_height">32dp</dimen>
+ <dimen name="action_card_icon_padding">10dp</dimen>
+ <dimen name="action_card_padding">12dp</dimen>
+ <dimen name="action_card_label_font_size">16sp</dimen>
+ <dimen name="action_card_state_font_size">12sp</dimen>
- <!-- Side panel common dimensions -->
- <dimen name="side_panel_shadow_width">500dp</dimen>
- <dimen name="side_panel_shadow_margin_right">270dp</dimen>
+ <!-- Side panel -->
<dimen name="side_panel_width">360dp</dimen>
- <dimen name="side_panel_padding_right">56dp</dimen>
- <dimen name="side_panel_padding_left">32dp</dimen>
+ <dimen name="side_panel_elevation">8dp</dimen>
+ <dimen name="side_panel_padding_start">32dp</dimen>
+ <dimen name="side_panel_padding_end">56dp</dimen>
+ <dimen name="side_panel_header_height">87dp</dimen>
<dimen name="side_panel_header_padding_top">27dp</dimen>
+ <dimen name="side_panel_header_elevation">4dp</dimen>
<dimen name="side_panel_header_text_size">24sp</dimen>
- <dimen name="side_panel_header_text_height">60dp</dimen>
+ <dimen name="side_panel_list_padding_top">0dp</dimen>
+ <dimen name="side_panel_list_padding_bottom">48dp</dimen>
+ <dimen name="side_panel_list_vertical_margin">0dp</dimen>
+ <dimen name="side_panel_enter_offset_x">100dp</dimen>
+ <dimen name="side_panel_exit_offset_x">100dp</dimen>
+ <dimen name="side_panel_fragment_enter_offset_x">32dp</dimen>
+ <dimen name="side_panel_fragment_exit_offset_x">-32dp</dimen>
+ <dimen name="side_panel_fragment_pop_enter_offset_x">
+ @dimen/side_panel_fragment_exit_offset_x</dimen>
+ <dimen name="side_panel_fragment_pop_exit_offset_x">
+ @dimen/side_panel_fragment_enter_offset_x</dimen>
- <!-- Option item list dimensions -->
+ <!-- More options -->
<dimen name="option_item_height">48dp</dimen>
- <dimen name="option_item_padding_left">16dp</dimen>
- <dimen name="option_item_text_size">18sp</dimen>
-
- <!-- Simple Guide demensions -->
- <dimen name="simple_guide_margin_left">32dp</dimen>
- <dimen name="simple_guide_channel_margin_top">11dp</dimen>
- <dimen name="simple_guide_channel_width">36dp</dimen>
- <dimen name="simple_guide_item_padding_left">20dp</dimen>
- <dimen name="simple_guide_item_padding_top">9dp</dimen>
- <dimen name="simple_guide_item_program_title_height">33dp</dimen>
- <dimen name="simple_guide_item_program_detail_height">18dp</dimen>
- <dimen name="simple_guide_item_height">64dp</dimen>
- <dimen name="simple_guide_item_small_text_size">12sp</dimen>
- <dimen name="simple_guide_item_large_text_size">18sp</dimen>
- <dimen name="simple_guide_progress_width">40dp</dimen>
- <dimen name="simple_guide_progress_height">4dp</dimen>
- <dimen name="show_only_item_margin_left">32dp</dimen>
- <dimen name="show_only_item_padding_left">16dp</dimen>
- <dimen name="show_only_item_text_size">18sp</dimen>
-
- <!-- Edit channels dimensions -->
- <dimen name="edit_channels_item_height">64dp</dimen>
-
- <!-- Channel banner dimensions -->
- <dimen name="channel_banner_title_large_text_size">54sp</dimen>
- <dimen name="channel_banner_title_medium_text_size">44sp</dimen>
- <dimen name="channel_banner_title_small_text_size">34sp</dimen>
- <dimen name="channel_banner_title_large_margin_top">-19sp</dimen>
- <dimen name="channel_banner_title_medium_margin_top">-15.5sp</dimen>
- <dimen name="channel_banner_title_small_margin_top">-11.5sp</dimen>
+ <dimen name="option_item_text_size">16sp</dimen>
+ <dimen name="option_item_text_margin_top">2.5dp</dimen>
+ <dimen name="option_item_secondary_text_size">12sp</dimen>
+ <dimen name="option_item_secondary_text_spacing">2sp</dimen>
+ <dimen name="option_item_check_box_margin_top">7dp</dimen>
+ <dimen name="option_item_common_padding_top">11dp</dimen>
+ <dimen name="option_item_common_padding_bottom">12.5dp</dimen>
+ <dimen name="option_item_compound_button_width">56dp</dimen>
+ <dimen name="option_item_compound_button_padding_top">16dp</dimen>
+ <dimen name="option_item_divider_height">1dp</dimen>
+ <dimen name="option_item_divider_margin_top">4dp</dimen>
+ <dimen name="option_item_divider_margin_bottom">3dp</dimen>
+ <dimen name="option_item_divider_text_size">14sp</dimen>
+ <dimen name="option_item_divider_text_margin_top">18dp</dimen>
+ <dimen name="option_item_channel_check_spacing">12dp</dimen>
+
+ <!-- Setup -->
+ <!-- 62.5 % is a position for top padding + non-padding area's height / 2. -->
+ <item name="setup_item_window_alignment_offset_percent" format="float" type="dimen">62.5</item>
+
+ <!-- Program guide -->
+ <dimen name="program_guide_shift_start_to_end_x">238dp</dimen>
+ <dimen name="program_guide_shift_end_to_start_x">-238dp</dimen>
+ <dimen name="program_guide_side_panel_width">270dp</dimen>
+ <dimen name="program_guide_side_panel_margin_start">-238dp</dimen>
+ <dimen name="program_guide_side_panel_padding_start">56dp</dimen>
+ <dimen name="program_guide_side_panel_padding_end">8dp</dimen>
+ <dimen name="program_guide_side_panel_item_width">206dp</dimen>
+ <dimen name="program_guide_side_panel_fading_edge">12dp</dimen>
+ <dimen name="program_guide_side_panel_elevation">8dp</dimen>
+ <dimen name="program_guide_side_panel_alignment_y">167dp</dimen>
+ <dimen name="program_guide_side_panel_row_height">48dp</dimen>
+ <dimen name="program_guide_side_panel_row_pivot_x">0dp</dimen>
+ <dimen name="program_guide_side_panel_row_pivot_y">15dp</dimen>
+ <dimen name="program_guide_side_panel_text_size">20sp</dimen>
+ <dimen name="program_guide_side_panel_exit_x">-32dp</dimen>
+ <dimen name="program_guide_table_elevation">8dp</dimen>
+ <dimen name="program_guide_table_background_round_radius_left">2dp</dimen>
+ <dimen name="program_guide_table_background_round_radius_right">0dp</dimen>
+ <dimen name="program_guide_table_margin_top">27dp</dimen>
+ <dimen name="program_guide_table_margin_bottom">27dp</dimen>
+ <dimen name="program_guide_table_margin_start">56dp</dimen>
+ <dimen name="program_guide_table_header_column_width">204dp</dimen>
+ <dimen name="program_guide_table_header_column_padding_start">16dp</dimen>
+ <dimen name="program_guide_table_header_column_padding_end">16dp</dimen>
+ <dimen name="program_guide_table_header_column_channel_number_width">84dp</dimen>
+ <dimen name="program_guide_table_header_column_channel_number_large_font_size">32sp</dimen>
+ <dimen name="program_guide_table_header_column_channel_number_small_font_size">24sp</dimen>
+ <dimen name="program_guide_table_header_column_channel_name_width">88dp</dimen>
+ <dimen name="program_guide_table_header_column_channel_name_font_size">14sp</dimen>
+ <dimen name="program_guide_table_header_column_channel_logo_width">70dp</dimen>
+ <dimen name="program_guide_table_header_column_channel_logo_height">40dp</dimen>
+ <dimen name="program_guide_table_header_column_channel_logo_margin_top">12dp</dimen>
+ <dimen name="program_guide_table_header_column_channel_block_width">
+ @dimen/program_guide_table_header_column_channel_block_height</dimen>
+ <dimen name="program_guide_table_header_column_channel_block_height">
+ @dimen/program_guide_table_header_column_channel_logo_height</dimen>
+ <dimen name="program_guide_table_header_column_channel_block_margin_top">
+ @dimen/program_guide_table_header_column_channel_logo_margin_top</dimen>
+ <dimen name="program_guide_table_header_row_height">40dp</dimen>
+ <dimen name="program_guide_table_header_row_font_size">16sp</dimen>
+ <dimen name="program_guide_table_header_row_fade_length">24dp</dimen>
+ <dimen name="program_guide_table_header_row_overlap">-52dp</dimen>
+ <dimen name="program_guide_table_item_row_height">64dp</dimen>
+ <dimen name="program_guide_table_item_padding">16dp</dimen>
+ <dimen name="program_guide_table_item_program_title_font_size">14sp</dimen>
+ <dimen name="program_guide_table_item_program_episode_title_font_size">12sp</dimen>
+ <dimen name="program_guide_table_item_min_program_display_width">192dp</dimen>
+ <dimen name="program_guide_table_item_visible_threshold">48dp</dimen>
+ <dimen name="program_guide_table_detail_height">112dp</dimen>
+ <dimen name="program_guide_table_detail_padding">16dp</dimen>
+ <dimen name="program_guide_table_detail_padding_end">56dp</dimen>
+ <dimen name="program_guide_table_detail_padding_negative">-16dp</dimen>
+ <dimen name="program_guide_table_detail_image_width">142dp</dimen>
+ <dimen name="program_guide_table_detail_image_height">80dp</dimen>
+ <dimen name="program_guide_table_detail_image_margin_end">16dp</dimen>
+ <dimen name="program_guide_table_detail_block_width">12dp</dimen>
+ <dimen name="program_guide_table_detail_block_height">16dp</dimen>
+ <dimen name="program_guide_table_detail_block_margin_end">8dp</dimen>
+ <dimen name="program_guide_table_detail_title_margin_top">-1dp</dimen>
+ <dimen name="program_guide_table_detail_title_text_size">16sp</dimen>
+ <dimen name="program_guide_table_detail_episode_title_text_size">16sp</dimen>
+ <dimen name="program_guide_table_detail_time_margin_top">22dp</dimen>
+ <dimen name="program_guide_table_detail_time_text_size">12sp</dimen>
+ <dimen name="program_guide_table_detail_meta_margin_start">12dp</dimen>
+ <dimen name="program_guide_table_detail_meta_margin_bottom">4sp</dimen>
+ <dimen name="program_guide_table_detail_desc_margin_top">46dp</dimen>
+ <dimen name="program_guide_table_detail_desc_text_size">12sp</dimen>
+ <dimen name="program_guide_table_width_per_hour">384dp</dimen>
+ <dimen name="program_guide_table_exit_x">368dp</dimen>
+
+ <!-- TV view -->
+ <dimen name="tvview_block_icon_width">200dp</dimen>
+ <dimen name="tvview_block_icon_height">120dp</dimen>
+ <dimen name="tvview_block_text_padding_top">15dp</dimen>
+ <dimen name="tvview_block_text_size">16sp</dimen>
+ <dimen name="shrunken_tvview_block_icon_width">60dp</dimen>
+ <dimen name="shrunken_tvview_block_icon_height">60dp</dimen>
+ <dimen name="shrunken_tvview_block_text_padding_top">24dp</dimen>
+
+ <!-- PIP view -->
+ <dimen name="pipview_margin_horizontal">56dp</dimen>
+ <dimen name="pipview_margin_top">27dp</dimen>
+ <dimen name="pipview_margin_bottom">27dp</dimen>
+ <dimen name="pipview_small_size_width">240dp</dimen>
+ <dimen name="pipview_small_size_height">135dp</dimen>
+ <dimen name="pipview_large_size_width">384dp</dimen>
+ <dimen name="pipview_large_size_height">216dp</dimen>
+
+ <!-- PAP View -->
+ <dimen name="papview_margin_horizontal">20dp</dimen>
+ <dimen name="papview_spacing">24dp</dimen>
+
+ <!-- Edit channels -->
+ <dimen name="shrunken_tvview_margin_start">56dp</dimen>
+ <dimen name="shrunken_tvview_margin_end">32dp</dimen>
+ <dimen name="shrunken_tvview_height">288dp</dimen>
+
+ <!-- Channel banner -->
+ <dimen name="channel_banner_width">696dp</dimen>
+ <dimen name="channel_banner_channel_number_large_text_size">54sp</dimen>
+ <dimen name="channel_banner_channel_number_medium_text_size">44sp</dimen>
+ <dimen name="channel_banner_channel_number_small_text_size">34sp</dimen>
+ <dimen name="channel_banner_channel_number_large_margin_top">-19sp</dimen>
+ <dimen name="channel_banner_channel_number_medium_margin_top">-15.5sp</dimen>
+ <dimen name="channel_banner_channel_number_small_margin_top">-11.5sp</dimen>
+ <dimen name="channel_banner_channel_logo_width">72dp</dimen>
+ <dimen name="channel_banner_channel_logo_height">44dp</dimen>
+ <dimen name="channel_banner_channel_logo_margin_start">8dp</dimen>
+ <dimen name="channel_banner_input_logo_size">16dp</dimen>
+ <dimen name="channel_banner_program_description_width">536dp</dimen>
<dimen name="channel_banner_program_large_text_size">28sp</dimen>
<dimen name="channel_banner_program_medium_text_size">20sp</dimen>
<dimen name="channel_banner_program_large_margin_top">-10sp</dimen>
@@ -88,9 +272,23 @@
<dimen name="channel_name_max_width">100dp</dimen>
<dimen name="channel_banner_small_text_size">14sp</dimen>
<dimen name="channel_banner_track_meta_text_size">8sp</dimen>
+ <dimen name="channel_banner_enter_offset_y">16dp</dimen>
+ <dimen name="channel_banner_exit_offset_y">16dp</dimen>
- <!-- Recommendation cards in LeanbackLauncher dimensions
- These values should be synchnorized with
+ <!-- Input banner -->
+ <dimen name="input_banner_item_height">48dp</dimen>
+
+ <!-- Keypad channel switch -->
+ <dimen name="keypad_channel_switch_channel_number_text_size">
+ @dimen/channel_banner_channel_number_large_text_size</dimen>
+ <dimen name="keypad_channel_switch_channel_number_margin_top">
+ @dimen/channel_banner_channel_number_large_margin_top</dimen>
+ <dimen name="keypad_channel_switch_item_height">48dp</dimen>
+ <!-- A height of Keypad channel switch view without channel_list -->
+ <dimen name="keypad_channel_switch_base_height">84dp</dimen>
+
+ <!-- Recommendation cards in LeanbackLauncher -->
+ <!-- These values should be synchronized with
//vendor/unbundled_google/packages/LeanbackLauncher/res/values/dimens.xml -->
<dimen name="notif_card_img_height">176dp</dimen>
<dimen name="notif_card_img_min_width">117dp</dimen>
@@ -98,6 +296,15 @@
<dimen name="notif_ch_logo_max_width">64dp</dimen>
<dimen name="notif_ch_logo_max_height">44dp</dimen>
- <dimen name="notif_ch_logo_padding_left">12dp</dimen>
+ <dimen name="notif_ch_logo_padding_start">12dp</dimen>
<dimen name="notif_ch_logo_padding_bottom">12dp</dimen>
+
+ <!-- PIN dialog -->
+ <dimen name="pin_dialog_width">380dp</dimen>
+ <dimen name="pin_dialog_text_size">16sp</dimen>
+ <dimen name="pin_dialog_text_line_spacing">5sp</dimen>
+ <dimen name="pin_dialog_title_width">300dp</dimen>
+ <dimen name="pin_number_picker_text_size">24sp</dimen>
+ <dimen name="pin_number_picker_text_view_width">48dp</dimen>
+ <dimen name="pin_number_picker_text_view_height">48dp</dimen>
</resources>
diff --git a/res/values/floats.xml b/res/values/floats.xml
new file mode 100644
index 00000000..7145b241
--- /dev/null
+++ b/res/values/floats.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <!-- A float point number denoting the alpha value for focused numbers -->
+ <item name="pin_alpha_for_focused_number" format="float" type="dimen">1.0</item>
+ <!-- A float point number denoting the alpha value for adjacent numbers -->
+ <item name="pin_alpha_for_adjacent_number" format="float" type="dimen">0.3</item>
+
+ <item name="menu_row_title_alpha_deselected" format="float" type="dimen">0.5</item>
+
+ <!-- Program guide -->
+ <item name="program_guide_table_header_column_channel_block_opacity" format="float"
+ type="dimen"> 0.5</item>
+ <item name="program_guide_table_detail_block_opacity" format="float" type="dimen">0.7</item>
+
+ <!-- Line spacing multiplier for the long description text in the CheckBoxItem -->
+ <item name="option_item_check_box_line_spacing_multiplier" format="float" type="dimen">1.14
+ </item>
+</resources>
diff --git a/res/values/ids.xml b/res/values/ids.xml
deleted file mode 100644
index e105315e..00000000
--- a/res/values/ids.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<resources>
- <item type="id" name="key_red" />
- <item type="id" name="key_green" />
- <item type="id" name="key_yellow" />
- <item type="id" name="key_blue" />
- <item type="id" name="key_1" />
- <item type="id" name="key_2" />
- <item type="id" name="key_3" />
- <item type="id" name="key_4" />
- <item type="id" name="key_5" />
- <item type="id" name="key_6" />
- <item type="id" name="key_7" />
- <item type="id" name="key_8" />
- <item type="id" name="key_9" />
- <item type="id" name="key_l" />
- <item type="id" name="key_0" />
- <item type="id" name="key_r" />
-</resources> \ No newline at end of file
diff --git a/res/values/integers.xml b/res/values/integers.xml
index 84981edc..f8af77e6 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -1,21 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+ ~ 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.
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
+<resources>
+ <integer name="ripple_anim_duration">200</integer>
- http://www.apache.org/licenses/LICENSE-2.0
+ <!-- TvActivity -->
+ <integer name="tvactivity_background_anim_duration">100</integer>
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
+ <!-- TV view -->
+ <integer name="tvview_anim_duration">250</integer>
+ <integer name="tvview_fade_out_duration">@integer/tvview_anim_duration</integer>
+ <integer name="tvview_fade_in_duration">500</integer>
+ <integer name="tvview_block_icon_anim_duration">250</integer>
-<resources>
- <integer name="action_item_anim_duration">100</integer>
- <integer name="channel_type_guide">0x10000000</integer>
+ <!-- Main menu -->
+ <integer name="menu_show_duration">7000</integer>
+ <integer name="menu_enter_duration">250</integer>
+ <integer name="menu_exit_duration">150</integer>
+ <integer name="menu_focus_anim_duration">100</integer>
+ <integer name="menu_row_selection_anim_duration">200</integer>
+
+ <!-- Side panel -->
+ <integer name="side_panel_show_duration">60000</integer>
+ <integer name="side_panel_anim_duration">250</integer>
+ <integer name="side_panel_anim_short_duration">150</integer>
+ <integer name="side_panel_ripple_anim_duration">@integer/ripple_anim_duration</integer>
+
+ <!-- Program guide -->
+ <integer name="program_guide_anim_duration">250</integer>
+ <integer name="program_guide_table_fade_anim_duration">150</integer>
+ <integer name="program_guide_table_detail_fade_anim_duration">150</integer>
+ <integer name="program_guide_table_detail_toggle_anim_duration">100</integer>
+ <integer name="program_guide_show_duration">60000</integer>
+ <integer name="program_guide_ripple_anim_duration">@integer/ripple_anim_duration</integer>
+
+ <!-- Channel banner -->
+ <integer name="channel_banner_show_duration">8000</integer>
+ <integer name="channel_banner_anim_duration">250</integer>
+ <integer name="channel_banner_fast_anim_duration">75</integer>
+
+ <!-- Keypad channel switch -->
+ <integer name="keypad_channel_switch_show_duration">2000</integer>
+ <integer name="keypad_channel_switch_ripple_anim_duration">@integer/ripple_anim_duration</integer>
+ <integer name="keypad_channel_switch_anim_duration">150</integer>
+
+ <!-- Select input -->
+ <integer name="select_input_show_duration">4000</integer>
+ <integer name="select_input_ripple_anim_duration">@integer/ripple_anim_duration</integer>
+
+ <!-- Recommendation cards in LeanbackLauncher -->
<integer name="notif_card_ch_logo_alpha">153</integer>
-</resources> \ No newline at end of file
+
+ <!-- Pin dialog -->
+ <integer name="pin_dialog_anim_duration">250</integer>
+ <integer name="pin_dialog_enter_offset_y">32</integer>
+ <integer name="pin_dialog_exit_offset_y">32</integer>
+ <integer name="pin_number_scroll_duration">250</integer>
+
+ <!-- Max recycle view pool size -->
+ <integer name="max_recycled_view_pool_epg_table_item">50</integer>
+ <integer name="max_recycled_view_pool_epg_table_row">15</integer>
+ <integer name="max_recycled_view_pool_epg_header_row_item">7</integer>
+ <integer name="max_recycled_view_pool_epg_side_panel_row">12</integer>
+
+ <!-- Fullscreen dialogs (e.g. Setup dialog) -->
+ <integer name="fullscreen_dialog_enter_translation_x">200</integer>
+ <integer name="fullscreen_dialog_exit_translation_x">-200</integer>
+</resources>
diff --git a/res/values/rating_system_strings.xml b/res/values/rating_system_strings.xml
new file mode 100644
index 00000000..895ad929
--- /dev/null
+++ b/res/values/rating_system_strings.xml
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Age based TV content rating strings used in DVB and ISDB.
+ For more info, please see STD-B10 in http://www.dibeg.org/techp/aribstd/aribstd.html (ISDB)
+ and Table 81 of DVB SI (EN 300 468 V1.14.1) in https://www.dvb.org/standards (DVB).-->
+ <string name="description_age_4">Recommended for ages 4 and over.</string>
+ <string name="description_age_5">Recommended for ages 5 and over.</string>
+ <string name="description_age_6">Recommended for ages 6 and over.</string>
+ <string name="description_age_7">Recommended for ages 7 and over.</string>
+ <string name="description_age_8">Recommended for ages 8 and over.</string>
+ <string name="description_age_9">Recommended for ages 9 and over.</string>
+ <string name="description_age_10">Recommended for ages 10 and over.</string>
+ <string name="description_age_11">Recommended for ages 11 and over.</string>
+ <string name="description_age_12">Recommended for ages 12 and over.</string>
+ <string name="description_age_13">Recommended for ages 13 and over.</string>
+ <string name="description_age_14">Recommended for ages 14 and over.</string>
+ <string name="description_age_15">Recommended for ages 15 and over.</string>
+ <string name="description_age_16">Recommended for ages 16 and over.</string>
+ <string name="description_age_17">Recommended for ages 17 and over.</string>
+ <string name="description_age_18">Recommended for ages 18 and over.</string>
+ <string name="description_age_19">Recommended for ages 19 and over.</string>
+ <string name="description_age_20">Recommended for ages 20 and over.</string>
+
+ <!-- TV content rating system strings for AR TV. These strings are from Wikipedia.
+ See http://en.wikipedia.org/wiki/Television_content_rating_systems -->
+ <string name="description_ar_tv_atp">Suitable for all audiences. Programs may contain mild violence, language and mature situations.</string>
+ <string name="description_ar_tv_sam_13">Suitable for ages 13 and up. Programs may contain mild to moderate language and mild violence and sexual references.</string>
+ <string name="description_ar_tv_sam_16">Suitable for ages 16 and up. Programs may contain more intensive violence and coarse language, partial nudity and moderate sexual references.</string>
+ <string name="description_ar_tv_sam_18">Suitable for mature audiences only. Programs contain strong violence, coarse language and explicit sexual references.</string>
+ <string name="description_ar_tv_d">Drugs</string>
+ <string name="description_ar_tv_s">Sex</string>
+ <string name="description_ar_tv_v">Violence</string>
+
+ <!-- TV content rating system strings for AU TV. These strings are from ABC classification.
+ See Appendix 4 of http://www.abc.net.au/mediawatch/transcripts/0935_industry.pdf -->
+ <string name="description_au_tv_p">Material classified P is intended for preschool children.</string>
+ <string name="description_au_tv_c">Material classified C is intended for children (other than preschool children) who are younger than 14 years of age.</string>
+ <string name="description_au_tv_g">Material classified G is not necessarily intended for children but it must be very mild in impact and must not contain any matter likely to be unsuitable for children to watch without supervision.</string>
+ <string name="description_au_tv_pg">Material classified PG may contain adult themes or concepts but must be mild in impact and remain suitable for children to watch with supervision.</string>
+ <string name="description_au_tv_m">Material classified M is for mature audiences. It is recommended for viewing only by persons aged 15 years or over.</string>
+ <string name="description_au_tv_ma">Material classified MA is suitable for viewing only by persons aged 15 years or over because of the intensity and/or frequency of violence, sexual depictions, or coarse language, adult themes or drug use. The impact may be strong.</string>
+ <string name="description_au_tv_av">Material classified AV is suitable for viewing only by persons aged 15 years or over. It is unsuitable for MA classification because of the intensity and/or frequency of violence, or because violence is central to the theme. In other respects, the requirements of the classification are identical to the MA classification.</string>
+ <string name="description_au_tv_r">Material classified R is legally restricted to adults. Some material classified R may be offensive to sections of the adult community.</string>
+
+ <!-- TV content rating system strings for BR TV. These strings are from
+ http://www.justica.gov.br/seus-direitos/classificacao/guia-pratico/practical-guide.pdf/view -->
+ <string name="title_br_tv_l" translatable="false">Livre</string>
+ <string name="title_br_tv_10" translatable="false">10 anos</string>
+ <string name="title_br_tv_12" translatable="false">12 anos</string>
+ <string name="title_br_tv_14" translatable="false">14 anos</string>
+ <string name="title_br_tv_16" translatable="false">16 anos</string>
+ <string name="title_br_tv_18" translatable="false">18 anos</string>
+ <string name="description_br_tv_l">General audiences</string>
+ <string name="description_br_tv_10">Not recommended for ages under 10.</string>
+ <string name="description_br_tv_12">Not recommended for ages under 12.</string>
+ <string name="description_br_tv_14">Not recommended for ages under 14.</string>
+ <string name="description_br_tv_16">Not recommended for ages under 16.</string>
+ <string name="description_br_tv_18">Not recommended for ages under 18.</string>
+ <string name="description_br_tv_d">Drugs</string>
+ <string name="description_br_tv_s">Sex</string>
+ <string name="description_br_tv_v">Violence</string>
+
+ <!-- TV content rating system strings for CA TV. These strings are from
+ http://www.cbsc.ca/english/agvot/englishsystem.php -->
+ <string name="description_ca_tv_en_exempt">Exempt programming includes: news, sports, documentaries and other information programming; talk shows, music videos, and variety programming.</string>
+ <string name="description_ca_tv_en_c">This programming is intended for younger children under the age of 8 years.</string>
+ <string name="description_ca_tv_en_c8">This programming is intended for youngsters 8 years and over. It is suggested that a parent/guardian co-view programming assigned this classification with younger children under the age of 8.</string>
+ <string name="description_ca_tv_en_g">This programming is intended for all age groups and appropriate viewing for the entire family.</string>
+ <string name="description_ca_tv_en_pg">This programming, while intended for a general audience, may not be suitable for younger children (under the age of 8). Parents/guardians should be aware that there might be content elements which some could consider inappropriate for unsupervised viewing by children in the 8-13 age range.</string>
+ <string name="description_ca_tv_en_14">Programming with this classification contains themes or content elements which might not be suitable for viewers under the age of 14. Parents are strongly cautioned to exercise discretion in permitting viewing by pre-teens and early teens without parent/guardian supervision, as programming with this classification could deal with mature themes and societal issues in a realistic fashion.</string>
+ <string name="description_ca_tv_en_18">This programming is intended for viewers 18 years and older.</string>
+
+ <!-- TV content rating system strings for CA TV (French). -->
+ <!-- A short title of the rating system that appears in the parentheses right next to the
+ country name as in "Canada (French)". This corresponds to the "Ratings Classification for
+ Canadian French-Language Broadcasters". See more details in
+ http://www.cbsc.ca/english/agvot/frenchsystem.php -->
+ <string name="title_ca_tv_fr">French</string>
+ <string name="description_ca_tv_fr_e">Programs exempt from classification.</string>
+ <string name="description_ca_tv_fr_g">The program is appropriate for viewing by persons of all ages. A program with a "G" rating might offend the sensibilities of children under eight years of age.</string>
+ <string name="description_ca_tv_fr_8">The program is suitable for the general public but could contain mild or occasional violence that may disturb young children. Viewing with adult supervision is therefore recommended for young children (age 8 and under).</string>
+ <string name="description_ca_tv_fr_13">The program is appropriate for viewing by persons 13 years of age or older. Children under 13 may be admitted only if accompanied by an adult.</string>
+ <string name="description_ca_tv_fr_16">The program is appropriate for viewing by persons 16 years of age or older. Programs with this rating present troubling themes, situations or behaviours and adopt a more direct point of view about things.</string>
+ <string name="description_ca_tv_fr_18">The program is appropriate for viewing by persons 18 years of age or older. Programs reserved for adults most often deal primarily with the representation of explicit sexual encounters. They may also be extremely violent, showing scenes of hyperrealistic cruelty, torture and horror.</string>
+
+ <!-- TV content rating system strings for DVB -->
+ <string name="description_fr_dvb_u">Recommended for all ages.</string>
+ <string name="description_es_dvb_all">Recommended for all ages.</string>
+ <string name="description_es_dvb_c">Recommended for children.</string>
+ <string name="description_es_dvb_x">Recommended for adults.</string>
+
+ <!-- TV content rating system strings for KR TV. These strings are from
+ http://www.law.go.kr/admRulLsInfoP.do?admRulSeq=2000000118507 but they are translated
+ from Korean to English. -->
+ <string name="title_kr_tv_all" translatable="false">모든연령시청가</string>
+ <string name="title_kr_tv_7" translatable="false">7세이상시청가</string>
+ <string name="title_kr_tv_12" translatable="false">12세이상시청가</string>
+ <string name="title_kr_tv_15" translatable="false">15세이상시청가</string>
+ <string name="title_kr_tv_19" translatable="false">19세이상시청가</string>
+ <string name="description_kr_tv_all">Programs do not contain material inappropriate for all ages, and thus are suitable for all ages.</string>
+ <string name="description_kr_tv_7">Programs may contain material inappropriate for children under 7, and therefore parental discretion should be used for them.</string>
+ <string name="description_kr_tv_12">Programs may contain material inappropriate for children under 12, and therefore parental discretion should be used for them.</string>
+ <string name="description_kr_tv_15">Programs may contain material inappropriate for youngsters under 15, and therefore parental discretion should be used for them.</string>
+ <string name="description_kr_tv_19">Programs may contain material inappropriate for youngsters under 19, and thus are not suitable for youngsters under 19.</string>
+
+ <!-- TV content rating system strings for SG TV. These strings are from
+ http://www.mda.gov.sg/RegulationsAndLicensing/ContentStandardsAndClassification/FilmsAndVideos/Pages/default.aspx -->
+ <string name="description_sg_tv_g">Suitable for all ages.</string>
+ <string name="description_sg_tv_pg">Suitable for all but parents should guide their young.</string>
+ <string name="description_sg_tv_pg13">Suitable for persons aged 13 and above but parental guidance is advised for children below 13.</string>
+ <string name="description_sg_tv_nc16">Suitable for persons aged 16 and above.</string>
+ <string name="description_sg_tv_m18">Suitable for persons aged 18 and above.</string>
+ <string name="description_sg_tv_r21">Suitable for adults aged 21 and above.</string>
+
+ <!-- TV content rating system strings for US TV. These strings are from
+ http://www.tvguidelines.org/ratings.htm -->
+ <string name="description_us_tv_d">Suggestive dialogue</string>
+ <string name="description_us_tv_l">Coarse language</string>
+ <string name="description_us_tv_s">Sexual content</string>
+ <string name="description_us_tv_v">Violence</string>
+ <string name="description_us_tv_fv">Fantasy violence</string>
+ <string name="description_us_tv_y">This program is designed to be appropriate for all children.</string>
+ <string name="description_us_tv_y7">This program is designed for children age 7 and above.</string>
+ <string name="description_us_tv_g">Most parents would find this program suitable for all ages.</string>
+ <string name="description_us_tv_pg">This program contains material that parents may find unsuitable for younger children. Many parents may want to watch it with their younger children.</string>
+ <string name="description_us_tv_14">This program contains some material that many parents would find unsuitable for children under 14 years of age.</string>
+ <string name="description_us_tv_ma">This program is specifically designed to be viewed by adults and therefore may be unsuitable for children under 17.</string>
+
+ <!-- Movie content rating system strings for US. These string are from
+ http://www.mpaa.org/film-ratings-->
+ <string name="title_us_mv">Film ratings</string>
+ <string name="description_us_mv_g">General audiences. Nothing that would offend parents for viewing by children.</string>
+ <string name="description_us_mv_pg">Parental guidance suggested. May contain some material parents might not like for their young children.</string>
+ <string name="description_us_mv_pg13">Parents strongly cautioned. Some material may be inappropriate for pre-teenagers.</string>
+ <string name="description_us_mv_r">Restricted, Contains some adult material. Parents are urged to learn more about the film before taking their young children with them.</string>
+ <string name="description_us_mv_nc17">No one 17 and under admitted. Clearly adult. Children are not admitted.</string>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c644e926..af08c5ab 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1,169 +1,503 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- DO NOT TRANSLATE -->
<string name="font" translatable="false">sans-serif</string>
+ <string name="light_font" translatable="false">sans-serif-light</string>
+ <string name="thin_font" translatable="false">sans-serif-thin</string>
<string name="condensed_font" translatable="false">sans-serif-condensed</string>
- <string name="tv_app">TV</string>
+ <string name="option_item_text_font" translatable="false">@string/condensed_font</string>
+ <string name="option_item_secondary_text_font" translatable="false">@string/condensed_font</string>
+
+ <string name="option_item_divider_font" translatable="false">@string/condensed_font</string>
+
+ <!-- Name of application [CHAR LIMIT=NONE] -->
+ <string name="app_name">Live Channels</string>
<!-- Title of an application permission, listed so the user can choose
whether they want to allow the application to do this. -->
- <string name="permlab_receiveInputEvent">receive input events from TV app</string>
+ <string name="permlab_receiveInputEvent" translatable="false">receive input events from Live Channels app</string>
<!-- Description of an application permission, listed so the user can choose
whether they want to allow the application to do this. -->
- <string name="permdesc_receiveInputEvent">Allows the app to receive input events from TV app.</string>
-
- <string name="select_input_device">Select TV Input</string>
- <!-- Used in the "Select Input Device" dialog to indicate that the user is already watching TV
- from the current TV input, i.e. this input device is already selected. -->
- <string name="selected">(Selected)</string>
- <!-- Used in the "Select Input Device" dialog to indicate that the user is already watching TV
- in the picture-in-piture mode from the current TV input, i.e. this input device is already
- selected. -->
- <string name="selected_picture_in_picture">(Selected/Picture-In-Picture)</string>
- <string name="not_passthrough_input">It is not a passthrough TV input</string>
- <string name="no_input_device_found">No input device found</string>
- <string name="no_available_input_device">No input device is available</string>
- <string name="input_is_not_available">The TV input is not available. Please change the TV input.</string>
- <string name="please_select_input_device">Please select input device</string>
+ <string name="permdesc_receiveInputEvent" translatable="false">Allows the app to receive input events from Live Channels app</string>
+
+ <!-- Program information, mainly used for channel banner and program guide. -->
+ <eat-comment />
+ <!-- Mark for closed caption when a program has closed caption tracks -->
+ <string name="closed_caption" translatable="false">CC</string>
+ <!-- Mark indicating that the current program has ultra-high-definition video. -->
+ <string name="video_definition_level_ultra_hd" translatable="false">Ultra HD</string>
+ <!-- Mark indicating that the current program has full-high-definition video. -->
+ <string name="video_definition_level_full_hd" translatable="false">Full HD</string>
+ <!-- Mark indicating that the current program has high-definition video. -->
+ <string name="video_definition_level_hd" translatable="false">HD</string>
+ <!-- Mark indicating that the current program has standard-definition video. -->
+ <string name="video_definition_level_sd" translatable="false">SD</string>
+ <!-- Mark indicating that the current program has only one audio channel (mono).
+ [CHAR LIMIT=8] -->
+ <string name="audio_channel_mono">mono</string>
+ <!-- Mark indicating that the current program has two audio channels (stereo).
+ [CHAR LIMIT=8] -->
+ <string name="audio_channel_stereo">stereo</string>
+ <!-- Mark indicating that the current program has 5.1 surround audio channels. -->
+ <string name="audio_channel_5_1" translatable="false">5.1</string>
+ <!-- Mark indicating that the current program has 7.1 surround audio channels. -->
+ <string name="audio_channel_7_1" translatable="false">7.1</string>
+
+ <!-- Title of the play controls row in the main menu.
+ The play means a playback of video and audio. [CHAR LIMIT=NONE] -->
+ <string name="menu_title_play_controls">Play controls</string>
+ <!-- Title of the channels row in the main menu. [CHAR LIMIT=NONE] -->
+ <string name="menu_title_channels">Channels</string>
+ <!-- Legacy title of the channels row in the main menu before MCN. [CHAR LIMIT=NONE] -->
+ <string name="menu_title_channels_legacy">Recent channels</string>
+ <!-- Title of the TV option row in the main menu. [CHAR LIMIT=NONE] -->
+ <string name="menu_title_options">TV options</string>
+ <!-- Title of the PIP option row in the main menu. [CHAR LIMIT=NONE] -->
+ <string name="menu_title_pip_options">PIP options</string>
+
+ <!-- Play speed of 2X, 3X, 4X, 5X -->
+ <string name="play_controls_speed" translatable="false">%1$dX</string>
+ <!-- Message when the trick play is not available [CHAR LIMIT=NONE] -->
+ <string name="play_controls_unavailable">Play controls unavailable for this channel</string>
+ <!-- Description of the Play Pause button on the play controls. -->
+ <string name="play_controls_description_play_pause">Play or pause</string>
+ <!-- Description of the Fast Forward button on the play controls. -->
+ <string name="play_controls_description_fast_forward">Fast forward</string>
+ <!-- Description of the Rewind button on the play controls. -->
+ <string name="play_controls_description_fast_rewind">Rewind</string>
+ <!-- Description of the Next button on the play controls. -->
+ <string name="play_controls_description_skip_next">Next</string>
+ <!-- Description of the Previous button on the play controls. -->
+ <string name="play_controls_description_skip_previous">Previous</string>
+
+ <!-- Label of Program guide item in the channel list row. [CHAR LIMIT=23] -->
+ <string name="channels_item_program_guide">Program guide</string>
+ <!-- Label of setup item in the channel list row. The item is shown only
+ when new inputs are installed. [CHAR LIMIT=23] -->
+ <string name="channels_item_setup">New channels available</string>
+ <!-- Label of the app link card used for Google analytics. -->
+ <string name="channels_item_app_link" translatable="false">App link</string>
+ <!-- Default label of the app link card indicating there is no app link available for
+ the current channel. [CHAR LIMIT=23] -->
+ <string name="channels_item_app_link_no_app_link">No link available</string>
+ <!-- Label of app link card for launching app in the channel list row. [CHAR LIMIT=NONE] -->
+ <string name="channels_item_app_link_app_launcher">Open <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+
+ <!-- Label of "Closed captions" item in the TV option row. [CHAR LIMIT=17] -->
+ <string name="options_item_closed_caption">Closed captions</string>
+ <!-- Label of "Display mode" item in the TV option row. Inside display mode,
+ there are three items: Full, Normal, Zoom. [CHAR LIMIT=17] -->
+ <string name="options_item_display_mode">Display mode</string>
+ <!-- Label of "PIP (Picture in picture)" item in the TV option row. [CHAR LIMIT=17] -->
+ <string name="options_item_pip">PIP</string>
+ <!-- Description of the "PIP" item in the TV option row indicating that the currently the PIP
+ is turned on. [CHAR LIMIT=17] -->
+ <string name="options_item_pip_on">On</string>
+ <!-- Description of the "PIP" item in the TV option row indicating that the currently the PIP
+ is turned off. [CHAR LIMIT=20] -->
+ <string name="options_item_pip_off">Off</string>
+ <!-- Label of "Multi audio" item in the TV option row. [CHAR LIMIT=17] -->
+ <string name="options_item_multi_audio">Multi-audio</string>
+ <!-- Label of "Channel Setup" item in the TV option row. [CHAR LIMIT=17] -->
+ <string name="options_item_channel_sources">Channel setup</string>
+ <!-- Label of "Parental controls" item in the TV option row. [CHAR LIMIT=17] -->
+ <string name="options_item_parental_controls">Parental controls</string>
+ <!-- Label of "About the Live Channels application" item in the TV option row. [CHAR LIMIT=17] -->
+ <string name="options_item_about">About</string>
+
+ <!-- Label of "Source" item in the PIP option row. The item enables the user to select
+ a specific input (e.g. HDMI 1) for the PIP window. [CHAR LIMIT=17] -->
+ <string name="pip_options_item_source">Source</string>
+ <!-- Label of "Swap" item in the PIP option row.
+ After swapping, the main window and PIP window will show the channels of each other. [CHAR LIMIT=17] -->
+ <string name="pip_options_item_swap">Swap</string>
+ <!-- Description of the "Swap" item in the PIP option row indicating that the currently the
+ windows are swapped. [CHAR LIMIT=20] -->
+ <string name="pip_options_item_swap_on">On</string>
+ <!-- Description of the "Swap" item in the PIP option row indicating that the currently the
+ windows are not swapped. [CHAR LIMIT=20] -->
+ <string name="pip_options_item_swap_off">Off</string>
+ <!-- Label of "Sound" item in the PIP option row. [CHAR LIMIT=17] -->
+ <string name="pip_options_item_sound">Sound</string>
+ <!-- Description of the "Sound" item in the PIP option row indicating that the current sound
+ source is main window. [CHAR LIMIT=20] -->
+ <string name="pip_options_item_sound_main">Main</string>
+ <!-- Description of the "Sound" item in the PIP option row indicating that the current sound
+ source is PIP window. [CHAR LIMIT=20] -->
+ <string name="pip_options_item_sound_pip_window">PIP window</string>
+ <!-- Label of "Layout" item in the PIP option row. [CHAR LIMIT=17] -->
+ <string name="pip_options_item_layout">Layout</string>
+ <!-- Description of the "Layout" item in the PIP option row indicating that the current PIP
+ window is at the bottom right corner of the screen. [CHAR LIMIT=20] -->
+ <string name="pip_options_item_layout_bottom_right">Bottom right</string>
+ <!-- Description of the "Layout" item in the PIP option row indicating that the current PIP
+ window is at the top right corner of the screen. [CHAR LIMIT=20] -->
+ <string name="pip_options_item_layout_top_right">Top right</string>
+ <!-- Description of the "Layout" item in the PIP option row indicating that the current PIP
+ window is at the top left corner of the screen. [CHAR LIMIT=20] -->
+ <string name="pip_options_item_layout_top_left">Top left</string>
+ <!-- Description of the "Layout" item in the PIP option row indicating that the current PIP
+ window is at the bottom left corner of the screen. [CHAR LIMIT=20] -->
+ <string name="pip_options_item_layout_bottom_left">Bottom left</string>
+ <!-- Description of the "Layout" item in the PIP option row indicating that the current PIP
+ layout is side-by-side (i.e. Picture and Picture). [CHAR LIMIT=20] -->
+ <string name="pip_options_item_layout_side_by_side">Side by side</string>
+ <!-- Label of "Size" item in the PIP option row. [CHAR LIMIT=17] -->
+ <string name="pip_options_item_size">Size</string>
+ <!-- Description of the "Size" item in the PIP option row indicating that the current PIP size
+ is big. [CHAR LIMIT=20] -->
+ <string name="pip_options_item_size_big">Big</string>
+ <!-- Description of the "Size" item in the PIP option row indicating that the current PIP size
+ is small. [CHAR LIMIT=20] -->
+ <string name="pip_options_item_size_small">Small</string>
+
+ <!-- Title of "Input source" option to select an input for PIP. [CHAR LIMIT=30] -->
+ <string name="side_panel_title_pip_input_source">Input source</string>
+ <!-- Label of a unified input for tuner type inputs. [CHAR LIMIT=30] -->
+ <string name="input_long_label_for_tuner">TV (antenna/cable)</string>
+
+ <!-- Default program name when there is no program title. [CHAR LIMIT=30] -->
<string name="no_program_information">No program information</string>
- <string name="previously_selected_input_unavailable">Previously selected input unavailable</string>
- <string name="edit_input_device_name">Edit Input Device Name</string>
- <string name="recently_watched">Recently Watched</string>
- <string name="new_input_device_name">New Input Device Name</string>
- <string name="name_already_exists">Name already exists</string>
- <string name="OK">OK</string>
- <string name="not_implemented_yet">The feature will be implemented soon</string>
-
- <!-- menu option title. [CHAR LIMIT=30] -->
- <string name="menu_title">TV Options</string>
-
- <!-- Program guide label -->
- <string name="menu_program_guide">Program Guide</string>
- <!-- "Closed Caption" menu option [CHAR LIMIT=15] -->
- <string name="menu_closed_caption">Closed Caption</string>
- <!-- "Display Mode" menu option [CHAR LIMIT=15] -->
- <string name="menu_display_mode">Display Mode</string>
- <!-- "Select TV Input" menu option [CHAR LIMIT=15] -->
- <string name="menu_select_input">TV Input</string>
- <!-- Toggle PIP [CHAR LIMIT=15] -->
- <string name="menu_toggle_pip">PIP</string>
- <!-- Locate PIP [CHAR LIMIT=15] -->
- <string name="menu_select_pip_location">Locate PIP</string>
- <!-- Source specific settings menu option [CHAR LIMIT=15] -->
- <string name="source_specific_setting">More</string>
-
- <!-- "Edit Channels" menu option [CHAR LIMIT=15] -->
- <string name="menu_edit_channels">Edit Channel List</string>
- <!-- "Auto-Scan Channels" menu option [CHAR LIMIT=15] -->
- <string name="menu_auto_scan">Auto-scan Channels</string>
- <!-- "Privacy settings" menu option [CHAR LIMIT=15] -->
- <string name="menu_privacy_setting">Privacy Setting</string>
- <!-- "Input Setting" menu option [CHAR LIMIT=15] -->
- <string name="menu_input_setting">Input Setting</string>
-
- <!-- Unified TV input label -->
- <string name="unified_tv_input_label">Unified TV Input</string>
-
- <!-- Edit channel list (enable/disable individual channels) -->
- <string name="edit_channels_title">Edit <xliff:g id="tv_input">%s</xliff:g> channels</string>
- <string name="edit_channels_show_all">Show all</string>
- <string name="edit_channels_hide_all">Hide all</string>
-
- <!-- Text displayed in edit channels activity when there aren't any channels in the TV input.
- [CHAR LIMIT=20] -->
- <string name="empty_channel_tvinput">No channels in the TV input.</string>
- <string name="empty_channel_tvinput_and_no_setup_activity">No channels in the TV input \'<xliff:g id="tv_input">%s</xliff:g>\'.\nAnd setup activity for the TV input is not found.\nMaybe you need to setup the TV input manually or select another TV input.</string>
- <string name="all_the_channels_are_unchecked">If all the channels are unchecked, you can watch all the channels.</string>
-
- <!-- Format string for an item in the channel selection list. [CHAR LIMIT=30] -->
- <string name="channel_item"><xliff:g id="channel_number">%s</xliff:g> <xliff:g id="channel_name">%s</xliff:g></string>
-
- <!-- Privacy setting dialog title-->
- <string name="privacy_dialog_title">Privacy Settings</string>
-
- <!-- Channel list title -->
- <string name="channel_list_title">Channels</string>
-
- <!-- Confirmation of TV watch logging -->
- <string name="privacy_watch_logging">Send TV watch history to Google</string>
-
- <string name="channel_banner_channel_title">&lt;font color="black"&gt;<xliff:g id="channel_number">%s</xliff:g>&lt;/font&gt; &lt;font color="#FF969495"&gt;<xliff:g id="channel_name">%s</xliff:g>&lt;/font&gt;</string>
- <!-- Time format string at the channel banner [DO NOT TRANSLATE] -->
- <string name="channel_banner_time_format">h:mm a</string>
- <!-- Program time format string at the channel banner [DO NOT TRANSLATE] -->
- <string name="channel_banner_program_time_format">%1$s - %2$s</string>
- <!-- Video unavailable message at the channel banner. Unknown reason [CHAR LIMIT=30] -->
- <string name="channel_banner_video_unavailable_unknown">Video unavailable</string>
- <!-- Video unavailable message at the channel banner. Weak or no signal. [CHAR LIMIT=30] -->
- <string name="channel_banner_video_unavailable_weak_signal">Weak signal</string>
- <!-- Video unavailable message at the channel banner. Buffering. [CHAR LIMIT=30] -->
- <string name="channel_banner_video_unavailable_buffering">Buffering</string>
-
- <!-- Label of reset button. [CHAR LIMIT=20] -->
- <string name="reset">Reset</string>
-
- <!-- Title of the simple guide. [CHAR LIMIT=16]-->
- <string name="simple_guide_title">Program Guide</string>
-
- <!-- Title of the show only simple guide. [CHAR LIMIT=16]-->
- <string name="show_only_title">Show Only</string>
-
- <!-- Title of Display Mode option. [CHAR LIMIT=16]-->
- <string name="display_mode_option_title">Display Mode</string>
-
- <!-- Title of Closed Caption option. [CHAR LIMIT=16]-->
- <string name="closed_caption_option_title">Closed Caption</string>
- <!-- Option items for on -->
- <string name="option_item_on">On</string>
- <!-- Option items for off -->
- <string name="option_item_off">Off</string>
-
- <!-- Title of PIP location option. [CHAR LIMIT=16]-->
- <string name="pip_location_option_title">PIP Location</string>
-
- <!-- Text displayed in a toast to tell the user to long-press BACK key to exit the application
- instead of the normal press. -->
- <string name="long_press_back">Long-press BACK key instead to exit.</string>
-
- <!-- The text used when there is no program title. -->
+
+ <!-- Program title for no information. [CHAR LIMIT=23] -->
+ <string name="program_title_for_no_information">No information</string>
+ <!-- Program title for blocked channel [CHAR LIMIT=23] -->
+ <string name="program_title_for_blocked_channel">Blocked channel</string>
+
+ <!-- Default string for unknown language used in "Closed Caption" and "Multi Audio" options. [CHAR LIMIT=30] -->
+ <string name="default_language">Unknown language</string>
+
+ <!-- Inside "Closed captions" option side panel -->
+ <eat-comment />
+ <!-- Title of "Closed captions" option. [CHAR LIMIT=30] -->
+ <string name="side_panel_title_closed_caption">Closed captions</string>
+ <!-- Label of an option item for Closed Caption "off" [CHAR LIMIT=30] -->
+ <string name="closed_caption_option_item_off">Off</string>
+ <!-- Label of an option item to launch system closed caption settings [CHAR LIMIT=30] -->
+ <string name="closed_caption_system_settings">Customize formatting</string>
+ <!-- Description for "System Caption Settings" option [CHAR LIMIT=NONE] -->
+ <string name="closed_caption_system_settings_description">Set system-wide preferences for closed captions</string>
+
+ <!-- Inside "Display Mode" option side panel -->
+ <eat-comment />
+ <!-- Title of "Display Mode" option. It has three items: Normal, Full, Zoom. [CHAR LIMIT=30]-->
+ <string name="side_panel_title_display_mode">Display mode</string>
+
+ <!-- Inside "Multi Audio" option side panel -->
+ <eat-comment />
+ <!-- Title of "Multi-Audio" option. [CHAR LIMIT=30] -->
+ <string name="side_panel_title_multi_audio">Multi-audio</string>
+ <!-- Format of "Multi-Audio" item. E.g.) English (stereo) -->
+ <string name="multi_audio_display_string_with_channel" translatable="false">%1$s (%2$s)</string>
+ <!-- Label of audio track with one audio channel [CHAR LIMIT=30] -->
+ <string name="multi_audio_channel_mono">mono</string>
+ <!-- Label of audio track with two audio channels [CHAR LIMIT=30] -->
+ <string name="multi_audio_channel_stereo">stereo</string>
+ <!-- Label of audio track with six audio channels [CHAR LIMIT=30] -->
+ <string name="multi_audio_channel_surround_6">5.1 surround</string>
+ <!-- Label of audio track with eight audio channels [CHAR LIMIT=30] -->
+ <string name="multi_audio_channel_surround_8">7.1 surround</string>
+ <!-- Label of audio track with other audio channels than 1, 2, 6, and 8 [CHAR LIMIT=30] -->
+ <string name="multi_audio_channel_suffix">%d channels</string>
+
+ <!-- Inside "Edit Channels for a specific input" option side panel -->
+ <eat-comment />
+ <!-- Title of "Edit channels" option. [CHAR LIMIT=30] -->
+ <string name="side_panel_title_edit_channels_for_an_input">Customize channel list</string>
+ <!-- Label of "Select group" item to make all channels of a group enabled. The group can
+ be a channel source or video quality like HD or SD. [CHAR LIMIT=30] -->
+ <string name="edit_channels_item_select_group">Select group</string>
+ <!-- Label of "Deselect group" item to make all channels of a group disabled. The group can
+ be a channel source or video quality like HD or SD. [CHAR LIMIT=30] -->
+ <string name="edit_channels_item_deselect_group">Deselect group</string>
+ <!-- Label of "Group by" item to regroup channels [CHAR LIMIT=30] -->
+ <string name="edit_channels_item_group_by">Group by</string>
+ <!-- Type of grouping by channel source [CHAR LIMIT=30] -->
+ <string name="edit_channels_group_by_sources">Channel source</string>
+ <!-- Type of grouping by video quality [CHAR LIMIT=30] -->
+ <string name="edit_channels_group_by_hd_sd">HD/SD</string>
+ <!-- Subheader of HD channel group [CHAR LIMIT=30] -->
+ <string name="edit_channels_group_divider_for_hd">HD</string>
+ <!-- Subheader of SD channel group [CHAR LIMIT=40] -->
+ <string name="edit_channels_group_divider_for_sd">SD</string>
+ <!-- Title of "Group by" side panel to set grouping type [CHAR LIMIT=30] -->
+ <string name="side_panel_title_group_by">Group by</string>
+
+ <!-- Inside "Channel setup" option side panel -->
+ <eat-comment />
+ <!-- Title of "Channel setup" option. [CHAR LIMIT=30] -->
+ <string name="side_panel_title_channel_sources">Channel setup</string>
+
+ <!-- Inside full EPG -->
+ <eat-comment />
+ <!-- Description in the TV guide when the rating of the current content is restricted by parental control. [CHAR LIMIT=NONE] -->
+ <string name="program_guide_content_locked">This program is blocked</string>
+ <!-- Description in the TV guide with the rating when the rating of the current content is restricted by parental control. [CHAR LIMIT=NONE] -->
+ <string name="program_guide_content_locked_format">This program is rated <xliff:g id="rating" example="TV_MA">%1$s</xliff:g></string>
+
+ <!-- Inside menu for each input after selecting one input in "Channels" option -->
+ <eat-comment />
+ <!-- Label of "Channel sources" item to setup channels. [CHAR LIMIT=30] -->
+ <string name="channel_source_item_setup">Channel sources</string>
+ <!-- Description of "Channel sources" item only when new inputs are available.
+ [CHAR LIMIT=NONE] -->
+ <string name="channel_source_item_setup_new_inputs">New channels available</string>
+ <!-- Label of "Customize channels list" item to select and deselect channels.
+ [CHAR LIMIT=30] -->
+ <string name="channel_source_item_customize_channels">Customize channel list</string>
+ <!-- Description of "Customize channels list" item. [CHAR LIMIT=NONE] -->
+ <string name="channel_source_item_customize_channels_description">Choose channels for your program guide</string>
+
+ <!-- Channel description: channel scan is needed, because the input is just installed.
+ [CHAR LIMIT=40] -->
+ <string name="channel_description_setup_now">Not set up</string>
+ <!-- Message when a channel item is clicked for channel auto-scan, but the input doesn't
+ have auto-scan function. [CHAR LIMIT=NONE] -->
+ <string name="msg_no_setup_activity">The input doesn\'t support auto-scan</string>
+ <!-- Error message when Live Channels app cannnot launch auto-scan activity due to the wrong information
+ from the input. [CHAR LIMIT=NONE] -->
+ <string name="msg_unable_to_start_setup_activity">Unable to start auto-scan for \'<xliff:g id="tv_input" example="Built-in Tuner">%s</xliff:g>\'</string>
+ <!-- Error message when Live Channels app cannnot launch the system-wide preferences for closed captions. [CHAR LIMIT=NONE] -->
+ <string name="msg_unable_to_start_system_captioning_settings">Unable to start the system-wide preferences for closed captions.</string>
+ <!-- Message after some channels are added. [CHAR LIMIT=NONE] -->
+ <plurals name="msg_channel_added">
+ <item quantity="one">%1$d channel added</item>
+ <item quantity="other">%1$d channels added</item>
+ </plurals>
+ <!-- Message after no channel is added after setting up an input. [CHAR LIMIT=NONE] -->
+ <string name="msg_no_channel_added">No channels added</string>
+
+ <!-- Inside "Input Selector" option side panel -->
+ <eat-comment />
+ <!-- Label of a tuner input in "Input Selector" option. [CHAR LIMIT=30] -->
+ <string name="input_selector_tuner_label">Tuner</string>
+
+ <!-- Parental control -->
+ <eat-comment />
+ <!-- Title of "Parental Contrals" option. [CHAR LIMIT=30] -->
+ <string name="menu_parental_controls">Parental controls</string>
+ <!-- Label in On state of On/Off switch for "Parental Contrals" option. [CHAR LIMIT=30] -->
+ <string name="option_toggle_parental_controls_on">On</string>
+ <!-- Label in Off state of On/Off switch for "Parental Contrals" option. [CHAR LIMIT=30] -->
+ <string name="option_toggle_parental_controls_off">Off</string>
+ <!-- Item and title of "Channels blocked" option. This option provides an interface for parents
+ to block/unblock channels that are not suitable for their children. [CHAR LIMIT=30] -->
+ <string name="option_channels_locked">Channels blocked</string>
+ <!-- Label of an item to lock all channels. [CHAR LIMIT=30] -->
+ <string name="option_channels_lock_all">Block all</string>
+ <!-- Label of an item to unlock all channels. [CHAR LIMIT=30] -->
+ <string name="option_channels_unlock_all">Unblock all</string>
+ <!-- Subheader below a divider to separate hidden channels from visible channels. Visible
+ channels appear first in the list and this divider comes, followed by hidden channels.
+ [CHAR LIMIT=30] -->
+ <string name="option_channels_subheader_hidden">Hidden channels</string>
+ <!-- Item and title of "Program restrictions" option. It has two items: Rating Systems
+ and Ratings [CHAR LIMIT=30] -->
+ <string name="option_program_restrictions">Program restrictions</string>
+ <string name="option_change_pin">Change PIN</string>
+ <!-- Item and title of "Rating Systems" option. In this option, a user can choose the rating
+ system per country listed [CHAR LIMIT=30] -->
+ <string name="option_country_rating_systems">Rating systems</string>
+ <!-- Item and title of "Rating Systems" option. In this option, after choosing a rating system,
+ a user selects the actual rating to be restricted. [CHAR LIMIT=30] -->
+ <string name="option_ratings">Ratings</string>
+ <!-- Label of an item to show all rating systems in the system. [CHAR LIMIT=30] -->
+ <string name="option_see_all_rating_systems">See all rating systems</string>
+ <!-- Display country name for rating systems used in multiple countries.
+ [CHAR LIMIT=30] -->
+ <string name="other_countries">Other countries</string>
+
+ <!-- Description below "Channel Locked" item when there is no locked channel.
+ [CHAR LIMIT=NONE] -->
+ <string name="option_no_locked_channel">None</string>
+ <!-- Description below "Program Restrictions" item when there is no locked channel.
+ [CHAR LIMIT=NONE] -->
+ <string name="option_no_enabled_rating_system">None</string>
+
+ <!-- Label of an item not to select any restriction. [CHAR LIMIT=30] -->
+ <string name="option_rating_none">None</string>
+ <!-- Label of an item to select high restriction. [CHAR LIMIT=30] -->
+ <string name="option_rating_high">High restrictions</string>
+ <!-- Label of an item to select medium restriction. [CHAR LIMIT=30] -->
+ <string name="option_rating_medium">Medium restrictions</string>
+ <!-- Label of an item to select low restriction. [CHAR LIMIT=30] -->
+ <string name="option_rating_low">Low restrictions</string>
+ <!-- Label of an item to select custom restriction. In the restriction, a user
+ can finely tune the restriction with rating details. [CHAR LIMIT=30] -->
+ <string name="option_rating_custom">Custom</string>
+
+ <!-- The description used for high restrictions. [CHAR LIMIT=NONE] -->
+ <string name="option_rating_high_description">Content suitable for children</string>
+ <!-- The description used for medium restrictions. [CHAR LIMIT=NONE] -->
+ <string name="option_rating_medium_description">Content suitable for older children</string>
+ <!-- The description used for low restrictions. [CHAR LIMIT=NONE] -->
+ <string name="option_rating_low_description">Content suitable for teens</string>
+ <!-- The description used for custom restrictions. [CHAR LIMIT=NONE] -->
+ <string name="option_rating_custom_description">Manual restrictions</string>
+
+ <!-- Attribution text shown at the end of the 'Ratings' menu. It indicates where those rating
+ descriptions come from. [CHAR LIMIT=NONE] -->
+ <string name="option_attribution">Sources for rating descriptions</string>
+
+ <!-- Title of the menu where the user can select sub-ratings settings. Sub-ratings can be added
+ to the basic rating to let parents know additional information about the TV program such as
+ drug, violence, nudity, sex and coarse language. [CHAR LIMIT=30] -->
+ <string name="option_subrating_title">%1$s and sub-ratings</string>
+ <!-- Header of the divider in the sub-ratings menu. Sub-ratings can be added to the basic rating
+ to let parents know additional information about the TV program such as drug, violence,
+ nudity, sex and coarse language. [CHAR LIMIT=30] -->
+ <string name="option_subrating_header">Sub-ratings</string>
+
+ <!-- PIN UX -->
+ <eat-comment />
+ <!-- Title of PIN dialog when an user is asked to enter PIN to unlock channel.
+ [CHAR LIMIT=NONE] -->
+ <string name="pin_enter_unlock_channel">Enter your PIN to watch this channel</string>
+ <!-- Title of PIN dialog when an user is asked to enter PIN to unlock program.
+ [CHAR LIMIT=NONE] -->
+ <string name="pin_enter_unlock_program">Enter your PIN to watch this program</string>
+ <!-- Title of PIN dialog when an user tries to change Parental control setting.
+ [CHAR LIMIT=NONE] -->
+ <string name="pin_enter_pin">Enter your PIN</string>
+ <!-- Title of PIN dialog when an user is asked to set a PIN for the first time.
+ [CHAR LIMIT=NONE] -->
+ <string name="pin_enter_create_pin">To set parental controls, create a PIN</string>
+ <!-- Title of PIN dialog when an user is asked to change PIN [CHAR LIMIT=NONE] -->
+ <string name="pin_enter_new_pin">Enter new PIN</string>
+ <!-- Title of PIN dialog when an user is asked to set a PIN. [CHAR LIMIT=NONE] -->
+ <string name="pin_enter_again">Confirm your PIN</string>
+ <!-- Title of PIN dialog when an user is about to change older PIN. [CHAR LIMIT=NONE] -->
+ <string name="pin_enter_old_pin">Enter your current PIN</string>
+ <!-- Message shown in the dialog when the user entered the wrong. [CHAR LIMIT=NONE] -->
+ <plurals name="pin_enter_countdown">
+ <item quantity="one">You entered the wrong PIN 5 times.\nTry again in <xliff:g id="remaining_seconds" example="1">%1$d</xliff:g> second.</item>
+ <item quantity="other">You entered the wrong PIN 5 times.\nTry again in <xliff:g id="remaining_seconds" example="60">%1$d</xliff:g> seconds.</item>
+ </plurals>
+ <!-- Toast message when an user entered wrong PIN. [CHAR LIMIT=NONE] -->
+ <string name="pin_toast_wrong">That PIN was wrong. Try again.</string>
+ <!-- Toast message when an user couldn't pass the PIN confirmation. [CHAR LIMIT=NONE] -->
+ <string name="pin_toast_not_match">Try again, PIN doesn\'t match</string>
+
+ <!-- menu for "About" option -->
+ <eat-comment />
+ <!-- Title of "About the Live Channels application" option. [CHAR LIMIT=30] -->
+ <string name="side_panel_title_about">About</string>
+ <!-- Menu item to show licenses for open source code used in the app [CHAR LIMIT=35] -->
+ <string name="about_menu_licenses">Open source licenses</string>
+ <!--Title for a dialog box that shows licenses for open source code used in the app [CHAR LIMIT=35] -->
+ <string name="dialog_title_licenses">Open source licenses</string>
+ <!-- Menu item that shows the application version(eg 1.2.03-final) as a second line below this title [CHAR LIMIT=35] -->
+ <string name="about_menu_version">Version</string>
+
+ <!-- TunableTvView -->
+ <eat-comment />
+ <!-- Description on the locked screen when current channel is locked by parental control. [CHAR LIMIT=NONE] -->
+ <string name="tvview_channel_locked">To watch this channel, press Right and enter your PIN</string>
+ <!-- Description on the locked screen when the rating of the current content is restricted by parental control. [CHAR LIMIT=NONE] -->
+ <string name="tvview_content_locked">To watch this program, press Right and enter your PIN</string>
+ <!-- Description on the locked screen with the rating when the rating of the current content is restricted by parental control. [CHAR LIMIT=NONE] -->
+ <string name="tvview_content_locked_format">This program is rated <xliff:g id="rating" example="TV_MA">%1$s</xliff:g>.\nTo watch this program, press Right and enter your PIN.</string>
+
+ <!-- Description on the locked screen when the rating of the current content is restricted by parental control. [CHAR LIMIT=NONE] -->
+ <string name="shrunken_tvview_content_locked">Program is blocked</string>
+ <!-- Description on the locked screen with the rating when the rating of the current content is restricted by parental control. [CHAR LIMIT=NONE] -->
+ <string name="shrunken_tvview_content_locked_format">This program is rated <xliff:g id="rating" example="TV_MA">%1$s</xliff:g></string>
+
+ <!-- Channel Banner -->
+ <eat-comment />
+ <!-- The text used when there is no program title. [CHAR LIMIT=NONE] -->
<string name="channel_banner_no_title">No title</string>
+ <!-- The text used when a channel is locked and program title needs to be hidden. [CHAR LIMIT=NONE] -->
+ <string name="channel_banner_locked_channel_title">Channel blocked</string>
+ <!-- The episode title format displayed on the info banner. For example, "S1: Ep. 1 Winter is coming". -->
+ <string name="episode_format">S<xliff:g id="season_number" example="1">%1$d</xliff:g>: Ep. <xliff:g id="episode_number" example="1">%2$d</xliff:g> <xliff:g id="episode_title" example="Winder is coming">%3$s</xliff:g></string>
+
+ <!-- Setup dialog -->
+ <eat-comment />
+ <!-- Title of Setup Dialog [CHAR LIMIT=30] -->
+ <string name="setup_title">Channel sources</string>
+ <!-- Description of Setup Dialog. [CHAR LIMIT=NONE] -->
+ <string name="setup_description">Set up live channels from the available sources. This may take several minutes depending on the channel source.</string>
+ <!-- Label of done button [CHAR LIMIT=30] -->
+ <string name="setup_done_button_label">Done</string>
+ <!-- Description of an input to describe its channel count. [CHAR LIMIT=40] -->
+ <plurals name="setup_input_channels">
+ <item quantity="one">%1$d channel available</item>
+ <item quantity="other">%1$d channels available</item>
+ </plurals>
+ <!-- Description of an input with 0 channels. [CHAR LIMIT=40] -->
+ <string name="setup_input_no_channels">No channels available</string>
+ <!-- Description of newly installed inputs. [CHAR LIMIT=40] -->
+ <string name="setup_input_new">New</string>
+ <!-- Intro dialog -->
+ <eat-comment />
+ <string name="intro_title">@string/app_name</string>
+ <!-- Description of Intro Dialog [CHAR LIMIT=NONE] -->
+ <string name="intro_description"><b>Press SELECT</b> to access the TV menu.</string>
+
+ <!-- Toast messages -->
+ <eat-comment />
+ <!-- Error message when there is no TV input. [CHAR LIMIT=NONE] -->
+ <string name="msg_no_input">No TV input found</string>
+ <!-- Error message when a specific TV input isn't installed. [CHAR LIMIT=NONE] -->
+ <string name="msg_no_specific_input">Cannot find the TV input</string>
+ <!-- Error message when PIP is not supported. [CHAR LIMIT=NONE] -->
+ <string name="msg_no_pip_support">PIP is not supported</string>
+ <!-- Error message when there is no available input. [CHAR LIMIT=NONE] -->
+ <string name="msg_no_available_input_by_pip">There is no available input which can be shown with PIP</string>
+ <!-- Error message when the selected TV input is a tuner type TV input, not a pass-through type
+ input. Tuner type inputs such as built-in tuner, external usb tuner, network tuner or
+ virtual IPTV tuner provide live channels to tune to. As opposed to tuner type input,
+ pass-through type inputs such as HDMI, component, S-video, DVI or display port merely pass
+ the source video through to the display and don't provide ability to tune to a specific
+ channel unless the user directly controls the external source device (e.g. game console,
+ DVD player, settop box, etc) that is connected to the TV. [CHAR LIMIT=NONE] -->
+ <string name="msg_not_passthrough_input">Tuner type not suitable. Please launch Live Channels app for tuner type TV input.</string>
+ <!-- Error message when tune is failed. [CHAR LIMIT=NONE] -->
+ <string name="msg_tune_failed">Tune failed</string>
+ <!-- Error message when the user attempts an action (select TIS setup-activity, app-link,
+ custom options, etc.) and no application was found that could perform the action.
+ [CHAR LIMIT=NONE] -->
+ <string name="msg_missing_app">No app was found to handle this action.</string>
+ <!-- Message when all channels are hidden. [CHAR LIMIT=NONE] -->
+ <string name="msg_all_channels_hidden">All source channels are hidden.\nSelect at least one channel to watch.</string>
+ <!-- Message displayed when availability is changed by weak signal. [CHAR LIMIT=NONE] -->
+ <string name="msg_channel_unavailable_weak_signal">Unavailable due to weak video signal</string>
+ <!-- Message displayed when availability is changed by unknown reason. [CHAR LIMIT=NONE] -->
+ <string name="msg_channel_unavailable_unknown">The video is unexpectedly unavailable</string>
+ <!-- Message to notify the different use of Back Button: Home Button(To exit) Back button
+ (commands for external device) [CHAR LIMIT=NONE] -->
+ <string name="msg_back_key_guide">BACK key is for connected device. Press HOME button to exit.</string>
+
+ <!-- Strings for debug or not to be shown to users -->
+ <eat-comment />
<!-- Debug Options -->
- <string name="menu_debug_options">Debug Options</string>
- <string name="item_tv_input">TV Input</string>
- <string name="item_watch_history">Watch History</string>
-
- <!-- Keypad View -->
- <string name="key_red">red</string>
- <string name="key_green">green</string>
- <string name="key_yellow">yellow</string>
- <string name="key_blue">blue</string>
- <string name="key_1">1</string>
- <string name="key_2">2</string>
- <string name="key_3">3</string>
- <string name="key_4">4</string>
- <string name="key_5">5</string>
- <string name="key_6">6</string>
- <string name="key_7">7</string>
- <string name="key_8">8</string>
- <string name="key_9">9</string>
- <string name="key_l"> </string>
- <string name="key_0">0</string>
- <string name="key_r">-</string>
+ <string name="menu_debug_options" translatable="false">Debug options</string>
+ <string name="item_watch_history" translatable="false">Watch history</string>
+
+ <!-- Title of Recently watched dialog. It is used for debug purpose. -->
+ <string name="recently_watched" translatable="false">Recently watched</string>
+
+ <!-- EPG Search strings. -->
+ <!-- Remove translatable="false" once UI is finalized -->
+ <string name="search_result_no_result" translatable="false">No result</string>
+ <string name="search_result_title" translatable="false">Search results</string>
+
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index a8708308..b85d0892 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -1,53 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="channel_banner_track_meta_text">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">12dp</item>
- <item name="android:layout_marginRight">8dp</item>
+ <item name="android:layout_marginEnd">8dp</item>
<item name="android:gravity">center</item>
- <item name="android:paddingLeft">4dp</item>
- <item name="android:paddingRight">4dp</item>
+ <item name="android:paddingStart">4dp</item>
+ <item name="android:paddingEnd">4dp</item>
<item name="android:textSize">@dimen/channel_banner_track_meta_text_size</item>
<item name="android:textStyle">bold</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textColor">#455A64</item>
+ <item name="android:textAllCaps">true</item>
<item name="android:background">@drawable/track_meta_border</item>
</style>
- <style name="keypad_view_style">
- <item name="android:clipChildren">false</item>
- <item name="android:clipToPadding">false</item>
- <item name="android:elevation">8dp</item>
- <item name="android:padding">16dp</item>
- <item name="android:background">@drawable/keypad_background</item>
+ <style name="pin_dialog_animation" parent="@android:style/Animation.Activity">
+ <item name="android:windowEnterAnimation">@anim/pin_dialog_enter</item>
+ <item name="android:windowExitAnimation">@anim/pin_dialog_exit</item>
+ </style>
+
+ <style name="text_appearance_channel_banner_episode_title">
+ <item name="android:textColor">@color/channel_banner_episode_text_color</item>
</style>
- <style name="keypad_key_style">
- <item name="android:layout_width">32dp</item>
- <item name="android:layout_height">32dp</item>
- <item name="android:background">?android:attr/selectableItemBackground</item>
- <item name="android:clickable">true</item>
- <item name="android:focusable">true</item>
- <item name="android:focusableInTouchMode">true</item>
- <item name="android:scaleType">center</item>
+ <style name="pin_number_view">
+ <item name="android:layout_width">@dimen/pin_number_picker_text_view_width</item>
+ <item name="android:layout_height">@dimen/pin_number_picker_text_view_height</item>
<item name="android:gravity">center</item>
- <item name="android:fontFamily">sans-serif-light</item>
- <item name="android:textColor">#eeeeee</item>
- <item name="android:textSize">18sp</item>
+ <item name="android:textSize">@dimen/pin_number_picker_text_size</item>
+ <item name="android:textColor">@color/pin_number_picker_text_color</item>
+ <item name="android:fontFamily">@string/light_font</item>
</style>
</resources>
diff --git a/res/values/tags.xml b/res/values/tags.xml
index 42fa0d75..cadcb3f8 100644
--- a/res/values/tags.xml
+++ b/res/values/tags.xml
@@ -1,18 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<item name="TAG_OPTION_ITEM_POSITOIN" type="id"/>
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 6be89f10..da1a7472 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -1,22 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<resources>
<style name="Theme.TV" parent="@android:style/Theme.Material.NoActionBar.Fullscreen">
<item name="android:colorAccent">@color/accent_color</item>
- <item name="keypadViewStyle">@style/keypad_view_style</item>
+ <item name="android:colorControlActivated">@color/control_activated_color</item>
+ <item name="android:colorPrimary">@color/primary_color</item>
+
+ <!-- From Theme.Leanback to use Leanback library without applying Theme. -->
+ <!-- For SearchFragment -->
+ <item name="browsePaddingStart">@dimen/lb_browse_padding_start</item>
+ <item name="browsePaddingEnd">@dimen/lb_browse_padding_end</item>
+ <item name="browsePaddingBottom">@dimen/lb_browse_padding_bottom</item>
+ <item name="rowHorizontalGridStyle">@style/Widget.Leanback.Row.HorizontalGridView</item>
+ <item name="rowsVerticalGridStyle">@style/Widget.Leanback.Rows.VerticalGridView</item>
+
+ <!-- For ImageCardView -->
+ <item name="imageCardViewStyle">@style/Widget.Leanback.ImageCardViewStyle</item>
+ </style>
+ <style name="Theme.TV.dialog.Fullscreen" parent="@style/Theme.TV">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowAnimationStyle">@null</item>
</style>
</resources>
diff --git a/res/xml/searchable.xml b/res/xml/searchable.xml
index d020272b..92023293 100644
--- a/res/xml/searchable.xml
+++ b/res/xml/searchable.xml
@@ -1,23 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
+<!--
+ ~ 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.
+ -->
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:icon="@android:drawable/sym_def_app_icon"
- android:label="@string/tv_app"
+ android:label="@string/app_name"
android:includeInGlobalSearch="true"
android:queryAfterZeroResults="true"
android:searchSuggestAuthority="com.android.tv.search"
- android:searchSettingsDescription="@string/tv_app"/>
+ android:searchSettingsDescription="@string/app_name"/>
diff --git a/res/xml/tv_content_rating_systems.xml b/res/xml/tv_content_rating_systems.xml
new file mode 100644
index 00000000..aad9d610
--- /dev/null
+++ b/res/xml/tv_content_rating_systems.xml
@@ -0,0 +1,819 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<rating-system-definitions xmlns:android="http://schemas.android.com/apk/res/android"
+ android:versionCode="2">
+
+ <!-- TV content rating system for AR -->
+ <rating-system-definition android:name="AR_TV"
+ android:country="AR">
+ <sub-rating-definition android:name="AR_TV_D"
+ android:title="D"
+ android:description="@string/description_ar_tv_d" />
+ <sub-rating-definition android:name="AR_TV_S"
+ android:title="S"
+ android:description="@string/description_ar_tv_s" />
+ <sub-rating-definition android:name="AR_TV_V"
+ android:title="V"
+ android:description="@string/description_ar_tv_v" />
+ <rating-definition android:name="AR_TV_ATP"
+ android:title="ATP"
+ android:description="@string/description_ar_tv_atp"
+ android:contentAgeHint="0">
+ <sub-rating android:name="AR_TV_D" />
+ <sub-rating android:name="AR_TV_V" />
+ <sub-rating android:name="AR_TV_S" />
+ </rating-definition>
+ <rating-definition android:name="AR_TV_SAM_13"
+ android:title="SAM 13"
+ android:description="@string/description_ar_tv_sam_13"
+ android:contentAgeHint="13">
+ <sub-rating android:name="AR_TV_D" />
+ <sub-rating android:name="AR_TV_V" />
+ <sub-rating android:name="AR_TV_S" />
+ </rating-definition>
+ <rating-definition android:name="AR_TV_SAM_16"
+ android:title="SAM 16"
+ android:description="@string/description_ar_tv_sam_16"
+ android:contentAgeHint="16">
+ <sub-rating android:name="AR_TV_D" />
+ <sub-rating android:name="AR_TV_V" />
+ <sub-rating android:name="AR_TV_S" />
+ </rating-definition>
+ <rating-definition android:name="AR_TV_SAM_18"
+ android:title="SAM 18"
+ android:description="@string/description_ar_tv_sam_18"
+ android:contentAgeHint="18">
+ <sub-rating android:name="AR_TV_D" />
+ <sub-rating android:name="AR_TV_V" />
+ <sub-rating android:name="AR_TV_S" />
+ </rating-definition>
+ <rating-order>
+ <rating android:name="AR_TV_ATP" />
+ <rating android:name="AR_TV_SAM_13" />
+ <rating android:name="AR_TV_SAM_16" />
+ <rating android:name="AR_TV_SAM_18" />
+ </rating-order>
+ </rating-system-definition>
+
+ <!-- TV content rating system for AU.
+ According to ACB (http://www.classification.gov.au/Industry/Journey/Pages/TV/Step1.aspx),
+ material to be broadcast on television is not classified by the Classification Board.
+
+ The Australian Communications and Media Authority is responsible for classifying C (children’s)
+ and P (preschool) programs in Australia for broadcast on commercial free-to-air television.
+
+ Content on free-to-air television is classified by in-house classifiers.
+ SBS and ABC television have their own codes of practice, and the commercial networks operate
+ under the Free TV Code of Practice.
+
+ Pay TV stations classify content according to the Australian Subscription Television and Radio
+ Association (ASTRA) Code of Practice.
+
+ ABC classification: See Appendix 4 of http://www.abc.net.au/mediawatch/transcripts/0935_industry.pdf
+ SBS classification: See http://www.sbs.com.au/aboutus/faqs/index/id/115/h/Classification
+ Free TV Code of Practice: See Appendix 1 of Part B in http://www.freetv.com.au/media/Code_of_Practice/Commercial_Television_Industry_Code_of_Practice_2015_-_New_Code_and_Explanatory_Materials.pdf
+ Pay TV: See http://www.astra.org.au/advocacy/classification
+ -->
+ <rating-system-definition android:name="AU_TV"
+ android:country="AU">
+ <rating-definition android:name="AU_TV_P"
+ android:title="P"
+ android:description="@string/description_au_tv_p"
+ android:contentAgeHint="2" />
+ <rating-definition android:name="AU_TV_C"
+ android:title="C"
+ android:description="@string/description_au_tv_c"
+ android:contentAgeHint="5" />
+ <rating-definition android:name="AU_TV_G"
+ android:title="G"
+ android:description="@string/description_au_tv_g"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="AU_TV_PG"
+ android:title="PG"
+ android:description="@string/description_au_tv_pg"
+ android:contentAgeHint="15" />
+ <rating-definition android:name="AU_TV_M"
+ android:title="M"
+ android:description="@string/description_au_tv_m"
+ android:contentAgeHint="15" />
+ <rating-definition android:name="AU_TV_MA"
+ android:title="MA"
+ android:description="@string/description_au_tv_ma"
+ android:contentAgeHint="15" />
+ <rating-definition android:name="AU_TV_AV"
+ android:title="AV"
+ android:description="@string/description_au_tv_av"
+ android:contentAgeHint="15" />
+ <rating-definition android:name="AU_TV_R"
+ android:title="R"
+ android:description="@string/description_au_tv_r"
+ android:contentAgeHint="18" />
+ <rating-order>
+ <rating android:name="AU_TV_P" />
+ <rating android:name="AU_TV_C" />
+ </rating-order>
+ <rating-order>
+ <rating android:name="AU_TV_G" />
+ <rating android:name="AU_TV_PG" />
+ <rating android:name="AU_TV_M" />
+ <rating android:name="AU_TV_MA" />
+ <rating android:name="AU_TV_AV" />
+ <rating android:name="AU_TV_R" />
+ </rating-order>
+ </rating-system-definition>
+
+ <!-- TV content rating system for BR. See http://www.justica.gov.br/seus-direitos/classificacao/guia-pratico/practical-guide.pdf/view -->
+ <rating-system-definition android:name="BR_TV"
+ android:country="BR">
+ <sub-rating-definition android:name="BR_TV_D"
+ android:title="D"
+ android:description="@string/description_br_tv_d" />
+ <sub-rating-definition android:name="BR_TV_S"
+ android:title="S"
+ android:description="@string/description_br_tv_s" />
+ <sub-rating-definition android:name="BR_TV_V"
+ android:title="V"
+ android:description="@string/description_br_tv_v" />
+ <rating-definition android:name="BR_TV_L"
+ android:title="@string/title_br_tv_l"
+ android:description="@string/description_br_tv_l"
+ android:contentAgeHint="0">
+ <sub-rating android:name="BR_TV_D" />
+ <sub-rating android:name="BR_TV_S" />
+ <sub-rating android:name="BR_TV_V" />
+ </rating-definition>
+ <rating-definition android:name="BR_TV_10"
+ android:title="@string/title_br_tv_10"
+ android:description="@string/description_br_tv_10"
+ android:contentAgeHint="10">
+ <sub-rating android:name="BR_TV_D" />
+ <sub-rating android:name="BR_TV_S" />
+ <sub-rating android:name="BR_TV_V" />
+ </rating-definition>
+ <rating-definition android:name="BR_TV_12"
+ android:title="@string/title_br_tv_12"
+ android:description="@string/description_br_tv_12"
+ android:contentAgeHint="12">
+ <sub-rating android:name="BR_TV_D" />
+ <sub-rating android:name="BR_TV_S" />
+ <sub-rating android:name="BR_TV_V" />
+ </rating-definition>
+ <rating-definition android:name="BR_TV_14"
+ android:title="@string/title_br_tv_14"
+ android:description="@string/description_br_tv_14"
+ android:contentAgeHint="14">
+ <sub-rating android:name="BR_TV_D" />
+ <sub-rating android:name="BR_TV_S" />
+ <sub-rating android:name="BR_TV_V" />
+ </rating-definition>
+ <rating-definition android:name="BR_TV_16"
+ android:title="@string/title_br_tv_16"
+ android:description="@string/description_br_tv_16"
+ android:contentAgeHint="16">
+ <sub-rating android:name="BR_TV_D" />
+ <sub-rating android:name="BR_TV_S" />
+ <sub-rating android:name="BR_TV_V" />
+ </rating-definition>
+ <rating-definition android:name="BR_TV_18"
+ android:title="@string/title_br_tv_18"
+ android:description="@string/description_br_tv_18"
+ android:contentAgeHint="18">
+ <sub-rating android:name="BR_TV_D" />
+ <sub-rating android:name="BR_TV_S" />
+ <sub-rating android:name="BR_TV_V" />
+ </rating-definition>
+ <rating-order>
+ <rating android:name="BR_TV_L" />
+ <rating android:name="BR_TV_10" />
+ <rating android:name="BR_TV_12" />
+ <rating android:name="BR_TV_14" />
+ <rating android:name="BR_TV_16" />
+ <rating android:name="BR_TV_18" />
+ </rating-order>
+ </rating-system-definition>
+
+ <!-- TV content rating system for CA. See http://www.cbsc.ca/english/agvot/englishsystem.php -->
+ <rating-system-definition android:name="CA_TV_EN"
+ android:country="CA">
+ <rating-definition android:name="CA_TV_EN_EXEMPT"
+ android:title="E"
+ android:description="@string/description_ca_tv_en_exempt"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="CA_TV_EN_C"
+ android:title="C"
+ android:description="@string/description_ca_tv_en_c"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="CA_TV_EN_C8"
+ android:title="C8"
+ android:description="@string/description_ca_tv_en_c8"
+ android:contentAgeHint="8" />
+ <rating-definition android:name="CA_TV_EN_G"
+ android:title="G"
+ android:description="@string/description_ca_tv_en_g"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="CA_TV_EN_PG"
+ android:title="PG"
+ android:description="@string/description_ca_tv_en_pg"
+ android:contentAgeHint="14" />
+ <rating-definition android:name="CA_TV_EN_14"
+ android:title="14+"
+ android:description="@string/description_ca_tv_en_14"
+ android:contentAgeHint="14" />
+ <rating-definition android:name="CA_TV_EN_18"
+ android:title="18+"
+ android:description="@string/description_ca_tv_en_18"
+ android:contentAgeHint="18" />
+ <rating-order>
+ <rating android:name="CA_TV_EN_C" />
+ <rating android:name="CA_TV_EN_C8" />
+ </rating-order>
+ <rating-order>
+ <rating android:name="CA_TV_EN_G" />
+ <rating android:name="CA_TV_EN_PG" />
+ <rating android:name="CA_TV_EN_14" />
+ <rating android:name="CA_TV_EN_18" />
+ </rating-order>
+ </rating-system-definition>
+
+ <!-- TV content rating system for CA (French). See http://www.cbsc.ca/english/agvot/frenchsystem.php -->
+ <rating-system-definition android:name="CA_TV_FR"
+ android:country="CA"
+ android:title="@string/title_ca_tv_fr">
+ <rating-definition android:name="CA_TV_FR_E"
+ android:title="E"
+ android:description="@string/description_ca_tv_fr_e"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="CA_TV_FR_G"
+ android:title="G"
+ android:description="@string/description_ca_tv_fr_g"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="CA_TV_FR_8"
+ android:title="8ans+"
+ android:description="@string/description_ca_tv_fr_8"
+ android:contentAgeHint="8" />
+ <rating-definition android:name="CA_TV_FR_13"
+ android:title="13ans+"
+ android:description="@string/description_ca_tv_fr_13"
+ android:contentAgeHint="13" />
+ <rating-definition android:name="CA_TV_FR_16"
+ android:title="16ans+"
+ android:description="@string/description_ca_tv_fr_16"
+ android:contentAgeHint="16" />
+ <rating-definition android:name="CA_TV_FR_18"
+ android:title="18ans+"
+ android:description="@string/description_ca_tv_fr_18"
+ android:contentAgeHint="18" />
+ <rating-order>
+ <rating android:name="CA_TV_FR_G" />
+ <rating android:name="CA_TV_FR_8" />
+ <rating android:name="CA_TV_FR_13" />
+ <rating android:name="CA_TV_FR_16" />
+ <rating android:name="CA_TV_FR_18" />
+ </rating-order>
+ </rating-system-definition>
+
+ <!-- TV content rating system for DVB. See Table 81 of DVB SI (EN 300 468 V1.14.1) in https://www.dvb.org/standards -->
+ <rating-system-definition android:name="DVB"
+ android:country="AM, BG, CH, DE, DK, FI, GR, HU, ID, IE, IL, IS, MY, NL, NZ, PL, PT, RO, RU, RS, SI, TH, TR, TW, UA">
+ <rating-definition android:name="DVB_4"
+ android:title="4"
+ android:description="@string/description_age_4"
+ android:contentAgeHint="4" />
+ <rating-definition android:name="DVB_5"
+ android:title="5"
+ android:description="@string/description_age_5"
+ android:contentAgeHint="5" />
+ <rating-definition android:name="DVB_6"
+ android:title="6"
+ android:description="@string/description_age_6"
+ android:contentAgeHint="6" />
+ <rating-definition android:name="DVB_7"
+ android:title="7"
+ android:description="@string/description_age_7"
+ android:contentAgeHint="7" />
+ <rating-definition android:name="DVB_8"
+ android:title="8"
+ android:description="@string/description_age_8"
+ android:contentAgeHint="8" />
+ <rating-definition android:name="DVB_9"
+ android:title="9"
+ android:description="@string/description_age_9"
+ android:contentAgeHint="9" />
+ <rating-definition android:name="DVB_10"
+ android:title="10"
+ android:description="@string/description_age_10"
+ android:contentAgeHint="10" />
+ <rating-definition android:name="DVB_11"
+ android:title="11"
+ android:description="@string/description_age_11"
+ android:contentAgeHint="11" />
+ <rating-definition android:name="DVB_12"
+ android:title="12"
+ android:description="@string/description_age_12"
+ android:contentAgeHint="12" />
+ <rating-definition android:name="DVB_13"
+ android:title="13"
+ android:description="@string/description_age_13"
+ android:contentAgeHint="13" />
+ <rating-definition android:name="DVB_14"
+ android:title="14"
+ android:description="@string/description_age_14"
+ android:contentAgeHint="14" />
+ <rating-definition android:name="DVB_15"
+ android:title="15"
+ android:description="@string/description_age_15"
+ android:contentAgeHint="15" />
+ <rating-definition android:name="DVB_16"
+ android:title="16"
+ android:description="@string/description_age_16"
+ android:contentAgeHint="16" />
+ <rating-definition android:name="DVB_17"
+ android:title="17"
+ android:description="@string/description_age_17"
+ android:contentAgeHint="17" />
+ <rating-definition android:name="DVB_18"
+ android:title="18"
+ android:description="@string/description_age_18"
+ android:contentAgeHint="18" />
+ <rating-order>
+ <rating android:name="DVB_4" />
+ <rating android:name="DVB_5" />
+ <rating android:name="DVB_6" />
+ <rating android:name="DVB_7" />
+ <rating android:name="DVB_8" />
+ <rating android:name="DVB_9" />
+ <rating android:name="DVB_10" />
+ <rating android:name="DVB_11" />
+ <rating android:name="DVB_12" />
+ <rating android:name="DVB_13" />
+ <rating android:name="DVB_14" />
+ <rating android:name="DVB_15" />
+ <rating android:name="DVB_16" />
+ <rating android:name="DVB_17" />
+ <rating android:name="DVB_18" />
+ </rating-order>
+ </rating-system-definition>
+
+ <!-- TV content rating system for Spain DVB. -->
+ <rating-system-definition android:name="ES_DVB"
+ android:country="ES">
+ <rating-definition android:name="ES_DVB_ALL"
+ android:title="All"
+ android:description="@string/description_es_dvb_all"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="ES_DVB_C"
+ android:title="Children"
+ android:description="@string/description_es_dvb_c"
+ android:contentAgeHint="12" />
+ <rating-definition android:name="ES_DVB_X"
+ android:title="X-Rated"
+ android:description="@string/description_es_dvb_x"
+ android:contentAgeHint="18" />
+ <rating-definition android:name="ES_DVB_4"
+ android:title="4"
+ android:description="@string/description_age_4"
+ android:contentAgeHint="4" />
+ <rating-definition android:name="ES_DVB_5"
+ android:title="5"
+ android:description="@string/description_age_5"
+ android:contentAgeHint="5" />
+ <rating-definition android:name="ES_DVB_6"
+ android:title="6"
+ android:description="@string/description_age_6"
+ android:contentAgeHint="6" />
+ <rating-definition android:name="ES_DVB_7"
+ android:title="7"
+ android:description="@string/description_age_7"
+ android:contentAgeHint="7" />
+ <rating-definition android:name="ES_DVB_8"
+ android:title="8"
+ android:description="@string/description_age_8"
+ android:contentAgeHint="8" />
+ <rating-definition android:name="ES_DVB_9"
+ android:title="9"
+ android:description="@string/description_age_9"
+ android:contentAgeHint="9" />
+ <rating-definition android:name="ES_DVB_10"
+ android:title="10"
+ android:description="@string/description_age_10"
+ android:contentAgeHint="10" />
+ <rating-definition android:name="ES_DVB_11"
+ android:title="11"
+ android:description="@string/description_age_11"
+ android:contentAgeHint="11" />
+ <rating-definition android:name="ES_DVB_12"
+ android:title="12"
+ android:description="@string/description_age_12"
+ android:contentAgeHint="12" />
+ <rating-definition android:name="ES_DVB_13"
+ android:title="13"
+ android:description="@string/description_age_13"
+ android:contentAgeHint="13" />
+ <rating-definition android:name="ES_DVB_14"
+ android:title="14"
+ android:description="@string/description_age_14"
+ android:contentAgeHint="14" />
+ <rating-definition android:name="ES_DVB_15"
+ android:title="15"
+ android:description="@string/description_age_15"
+ android:contentAgeHint="15" />
+ <rating-definition android:name="ES_DVB_16"
+ android:title="16"
+ android:description="@string/description_age_16"
+ android:contentAgeHint="16" />
+ <rating-definition android:name="ES_DVB_17"
+ android:title="17"
+ android:description="@string/description_age_17"
+ android:contentAgeHint="17" />
+ <rating-definition android:name="ES_DVB_18"
+ android:title="18"
+ android:description="@string/description_age_18"
+ android:contentAgeHint="18" />
+ <rating-order>
+ <rating android:name="ES_DVB_ALL" />
+ <rating android:name="ES_DVB_4" />
+ <rating android:name="ES_DVB_5" />
+ <rating android:name="ES_DVB_6" />
+ <rating android:name="ES_DVB_7" />
+ <rating android:name="ES_DVB_8" />
+ <rating android:name="ES_DVB_9" />
+ <rating android:name="ES_DVB_10" />
+ <rating android:name="ES_DVB_11" />
+ <rating android:name="ES_DVB_C" />
+ <rating android:name="ES_DVB_12" />
+ <rating android:name="ES_DVB_13" />
+ <rating android:name="ES_DVB_14" />
+ <rating android:name="ES_DVB_15" />
+ <rating android:name="ES_DVB_16" />
+ <rating android:name="ES_DVB_17" />
+ <rating android:name="ES_DVB_18" />
+ <rating android:name="ES_DVB_X" />
+ </rating-order>
+ </rating-system-definition>
+
+ <!-- TV content rating system for France DVB. -->
+ <rating-system-definition android:name="FR_DVB"
+ android:country="FR">
+ <rating-definition android:name="FR_DVB_U"
+ android:title="Universal"
+ android:description="@string/description_fr_dvb_u"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="FR_DVB_4"
+ android:title="4"
+ android:description="@string/description_age_4"
+ android:contentAgeHint="4" />
+ <rating-definition android:name="FR_DVB_5"
+ android:title="5"
+ android:description="@string/description_age_5"
+ android:contentAgeHint="5" />
+ <rating-definition android:name="FR_DVB_6"
+ android:title="6"
+ android:description="@string/description_age_6"
+ android:contentAgeHint="6" />
+ <rating-definition android:name="FR_DVB_7"
+ android:title="7"
+ android:description="@string/description_age_7"
+ android:contentAgeHint="7" />
+ <rating-definition android:name="FR_DVB_8"
+ android:title="8"
+ android:description="@string/description_age_8"
+ android:contentAgeHint="8" />
+ <rating-definition android:name="FR_DVB_9"
+ android:title="9"
+ android:description="@string/description_age_9"
+ android:contentAgeHint="9" />
+ <rating-definition android:name="FR_DVB_10"
+ android:title="10"
+ android:description="@string/description_age_10"
+ android:contentAgeHint="10" />
+ <rating-definition android:name="FR_DVB_11"
+ android:title="11"
+ android:description="@string/description_age_11"
+ android:contentAgeHint="11" />
+ <rating-definition android:name="FR_DVB_12"
+ android:title="12"
+ android:description="@string/description_age_12"
+ android:contentAgeHint="12" />
+ <rating-definition android:name="FR_DVB_13"
+ android:title="13"
+ android:description="@string/description_age_13"
+ android:contentAgeHint="13" />
+ <rating-definition android:name="FR_DVB_14"
+ android:title="14"
+ android:description="@string/description_age_14"
+ android:contentAgeHint="14" />
+ <rating-definition android:name="FR_DVB_15"
+ android:title="15"
+ android:description="@string/description_age_15"
+ android:contentAgeHint="15" />
+ <rating-definition android:name="FR_DVB_16"
+ android:title="16"
+ android:description="@string/description_age_16"
+ android:contentAgeHint="16" />
+ <rating-definition android:name="FR_DVB_17"
+ android:title="17"
+ android:description="@string/description_age_17"
+ android:contentAgeHint="17" />
+ <rating-definition android:name="FR_DVB_18"
+ android:title="18"
+ android:description="@string/description_age_18"
+ android:contentAgeHint="18" />
+ <rating-order>
+ <rating android:name="FR_DVB_U" />
+ <rating android:name="FR_DVB_4" />
+ <rating android:name="FR_DVB_5" />
+ <rating android:name="FR_DVB_6" />
+ <rating android:name="FR_DVB_7" />
+ <rating android:name="FR_DVB_8" />
+ <rating android:name="FR_DVB_9" />
+ <rating android:name="FR_DVB_10" />
+ <rating android:name="FR_DVB_11" />
+ <rating android:name="FR_DVB_12" />
+ <rating android:name="FR_DVB_13" />
+ <rating android:name="FR_DVB_14" />
+ <rating android:name="FR_DVB_15" />
+ <rating android:name="FR_DVB_16" />
+ <rating android:name="FR_DVB_17" />
+ <rating android:name="FR_DVB_18" />
+ </rating-order>
+ </rating-system-definition>
+
+ <!-- TV content rating system for ISDB. See STD-B10 in http://www.dibeg.org/techp/aribstd/aribstd.html -->
+ <rating-system-definition android:name="ISDB"
+ android:country="JP">
+ <rating-definition android:name="ISDB_4"
+ android:title="4"
+ android:description="@string/description_age_4"
+ android:contentAgeHint="4" />
+ <rating-definition android:name="ISDB_5"
+ android:title="5"
+ android:description="@string/description_age_5"
+ android:contentAgeHint="5" />
+ <rating-definition android:name="ISDB_6"
+ android:title="6"
+ android:description="@string/description_age_6"
+ android:contentAgeHint="6" />
+ <rating-definition android:name="ISDB_7"
+ android:title="7"
+ android:description="@string/description_age_7"
+ android:contentAgeHint="7" />
+ <rating-definition android:name="ISDB_8"
+ android:title="8"
+ android:description="@string/description_age_8"
+ android:contentAgeHint="8" />
+ <rating-definition android:name="ISDB_9"
+ android:title="9"
+ android:description="@string/description_age_9"
+ android:contentAgeHint="9" />
+ <rating-definition android:name="ISDB_10"
+ android:title="10"
+ android:description="@string/description_age_10"
+ android:contentAgeHint="10" />
+ <rating-definition android:name="ISDB_11"
+ android:title="11"
+ android:description="@string/description_age_11"
+ android:contentAgeHint="11" />
+ <rating-definition android:name="ISDB_12"
+ android:title="12"
+ android:description="@string/description_age_12"
+ android:contentAgeHint="12" />
+ <rating-definition android:name="ISDB_13"
+ android:title="13"
+ android:description="@string/description_age_13"
+ android:contentAgeHint="13" />
+ <rating-definition android:name="ISDB_14"
+ android:title="14"
+ android:description="@string/description_age_14"
+ android:contentAgeHint="14" />
+ <rating-definition android:name="ISDB_15"
+ android:title="15"
+ android:description="@string/description_age_15"
+ android:contentAgeHint="15" />
+ <rating-definition android:name="ISDB_16"
+ android:title="16"
+ android:description="@string/description_age_16"
+ android:contentAgeHint="16" />
+ <rating-definition android:name="ISDB_17"
+ android:title="17"
+ android:description="@string/description_age_17"
+ android:contentAgeHint="17" />
+ <rating-definition android:name="ISDB_18"
+ android:title="18"
+ android:description="@string/description_age_18"
+ android:contentAgeHint="18" />
+ <rating-definition android:name="ISDB_19"
+ android:title="19"
+ android:description="@string/description_age_19"
+ android:contentAgeHint="19" />
+ <rating-definition android:name="ISDB_20"
+ android:title="20"
+ android:description="@string/description_age_20"
+ android:contentAgeHint="20" />
+ <rating-order>
+ <rating android:name="ISDB_4" />
+ <rating android:name="ISDB_5" />
+ <rating android:name="ISDB_6" />
+ <rating android:name="ISDB_7" />
+ <rating android:name="ISDB_8" />
+ <rating android:name="ISDB_9" />
+ <rating android:name="ISDB_10" />
+ <rating android:name="ISDB_11" />
+ <rating android:name="ISDB_12" />
+ <rating android:name="ISDB_13" />
+ <rating android:name="ISDB_14" />
+ <rating android:name="ISDB_15" />
+ <rating android:name="ISDB_16" />
+ <rating android:name="ISDB_17" />
+ <rating android:name="ISDB_18" />
+ <rating android:name="ISDB_19" />
+ <rating android:name="ISDB_20" />
+ </rating-order>
+ </rating-system-definition>
+
+ <!-- TV content rating system for KR. See http://www.law.go.kr/admRulLsInfoP.do?admRulSeq=2000000118507 -->
+ <rating-system-definition android:name="KR_TV"
+ android:country="KR">
+ <rating-definition android:name="KR_TV_ALL"
+ android:title="@string/title_kr_tv_all"
+ android:description="@string/description_kr_tv_all"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="KR_TV_7"
+ android:title="@string/title_kr_tv_7"
+ android:description="@string/description_kr_tv_7"
+ android:contentAgeHint="7" />
+ <rating-definition android:name="KR_TV_12"
+ android:title="@string/title_kr_tv_12"
+ android:description="@string/description_kr_tv_12"
+ android:contentAgeHint="12" />
+ <rating-definition android:name="KR_TV_15"
+ android:title="@string/title_kr_tv_15"
+ android:description="@string/description_kr_tv_15"
+ android:contentAgeHint="15" />
+ <rating-definition android:name="KR_TV_19"
+ android:title="@string/title_kr_tv_19"
+ android:description="@string/description_kr_tv_19"
+ android:contentAgeHint="19" />
+ <rating-order>
+ <rating android:name="KR_TV_ALL" />
+ <rating android:name="KR_TV_7" />
+ <rating android:name="KR_TV_12" />
+ <rating android:name="KR_TV_15" />
+ <rating android:name="KR_TV_19" />
+ </rating-order>
+ </rating-system-definition>
+
+ <!-- TV content rating system for SG. See http://www.mda.gov.sg/RegulationsAndLicensing/ContentStandardsAndClassification/FilmsAndVideos/Pages/default.aspx -->
+ <rating-system-definition android:name="SG_TV"
+ android:country="SG">
+ <rating-definition android:name="SG_TV_G"
+ android:title="G"
+ android:description="@string/description_sg_tv_g"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="SG_TV_PG"
+ android:title="PG"
+ android:description="@string/description_sg_tv_pg"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="SG_TV_PG13"
+ android:title="PG13"
+ android:description="@string/description_sg_tv_pg13"
+ android:contentAgeHint="13" />
+ <rating-definition android:name="SG_TV_NC16"
+ android:title="NC16"
+ android:description="@string/description_sg_tv_nc16"
+ android:contentAgeHint="16" />
+ <rating-definition android:name="SG_TV_M18"
+ android:title="M18"
+ android:description="@string/description_sg_tv_m18"
+ android:contentAgeHint="18" />
+ <rating-definition android:name="SG_TV_R21"
+ android:title="R21"
+ android:description="@string/description_sg_tv_r21"
+ android:contentAgeHint="21" />
+ <rating-order>
+ <rating android:name="SG_TV_G" />
+ <rating android:name="SG_TV_PG" />
+ <rating android:name="SG_TV_PG13" />
+ <rating android:name="SG_TV_NC16" />
+ <rating android:name="SG_TV_M18" />
+ <rating android:name="SG_TV_R21" />
+ </rating-order>
+ </rating-system-definition>
+
+ <!-- TV content rating system for US. See http://www.tvguidelines.org/ratings.htm -->
+ <rating-system-definition android:name="US_TV"
+ android:country="US">
+ <sub-rating-definition android:name="US_TV_D"
+ android:title="D"
+ android:description="@string/description_us_tv_d" />
+ <sub-rating-definition android:name="US_TV_L"
+ android:title="L"
+ android:description="@string/description_us_tv_l" />
+ <sub-rating-definition android:name="US_TV_S"
+ android:title="S"
+ android:description="@string/description_us_tv_s" />
+ <sub-rating-definition android:name="US_TV_V"
+ android:title="V"
+ android:description="@string/description_us_tv_v" />
+ <sub-rating-definition android:name="US_TV_FV"
+ android:title="FV"
+ android:description="@string/description_us_tv_fv" />
+
+ <rating-definition android:name="US_TV_Y"
+ android:title="TV-Y"
+ android:description="@string/description_us_tv_y"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="US_TV_Y7"
+ android:title="TV-Y7"
+ android:description="@string/description_us_tv_y7"
+ android:contentAgeHint="7">
+ <sub-rating android:name="US_TV_FV" />
+ </rating-definition>
+ <rating-definition android:name="US_TV_G"
+ android:title="TV-G"
+ android:description="@string/description_us_tv_g"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="US_TV_PG"
+ android:title="TV-PG"
+ android:description="@string/description_us_tv_pg"
+ android:contentAgeHint="14">
+ <sub-rating android:name="US_TV_D" />
+ <sub-rating android:name="US_TV_L" />
+ <sub-rating android:name="US_TV_S" />
+ <sub-rating android:name="US_TV_V" />
+ </rating-definition>
+ <rating-definition android:name="US_TV_14"
+ android:title="TV-14"
+ android:description="@string/description_us_tv_14"
+ android:contentAgeHint="14">
+ <sub-rating android:name="US_TV_D" />
+ <sub-rating android:name="US_TV_L" />
+ <sub-rating android:name="US_TV_S" />
+ <sub-rating android:name="US_TV_V" />
+ </rating-definition>
+ <rating-definition android:name="US_TV_MA"
+ android:title="TV-MA"
+ android:description="@string/description_us_tv_ma"
+ android:contentAgeHint="17">
+ <sub-rating android:name="US_TV_L" />
+ <sub-rating android:name="US_TV_S" />
+ <sub-rating android:name="US_TV_V" />
+ </rating-definition>
+ <rating-order>
+ <rating android:name="US_TV_Y" />
+ <rating android:name="US_TV_Y7" />
+ </rating-order>
+ <rating-order>
+ <rating android:name="US_TV_G" />
+ <rating android:name="US_TV_PG" />
+ <rating android:name="US_TV_14" />
+ <rating android:name="US_TV_MA" />
+ </rating-order>
+ </rating-system-definition>
+
+ <!-- Movie content rating system for US. See http://www.mpaa.org/film-ratings/ -->
+ <rating-system-definition android:name="US_MV"
+ android:country="US"
+ android:title="@string/title_us_mv">
+ <rating-definition android:name="US_MV_G"
+ android:title="G"
+ android:description="@string/description_us_mv_g"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="US_MV_PG"
+ android:title="PG"
+ android:description="@string/description_us_mv_pg"
+ android:contentAgeHint="0" />
+ <rating-definition android:name="US_MV_PG13"
+ android:title="PG-13"
+ android:description="@string/description_us_mv_pg13"
+ android:contentAgeHint="13" />
+ <rating-definition android:name="US_MV_R"
+ android:title="R"
+ android:description="@string/description_us_mv_r"
+ android:contentAgeHint="17" />
+ <rating-definition android:name="US_MV_NC17"
+ android:title="NC-17"
+ android:description="@string/description_us_mv_nc17"
+ android:contentAgeHint="17" />
+ <rating-order>
+ <rating android:name="US_MV_G" />
+ <rating android:name="US_MV_PG" />
+ <rating android:name="US_MV_PG13" />
+ <rating android:name="US_MV_R" />
+ <rating android:name="US_MV_NC17" />
+ </rating-order>
+ </rating-system-definition>
+</rating-system-definitions>
diff --git a/sample/Android.mk b/sample/Android.mk
deleted file mode 100644
index 03f4a51d..00000000
--- a/sample/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := SampleTvInput
-LOCAL_MODULE_TAGS := optional
-LOCAL_PROGUARD_ENABLED := disabled
-
-include $(BUILD_PACKAGE)
-
-ifeq ($(PRODUCT_IS_ATV),true)
- ifneq ($(PRODUCT_IS_ATV_SDK),true)
- $(call dist-for-goals,dist_files,$(LOCAL_BUILT_MODULE):SampleTvInput.apk)
- endif
-endif
diff --git a/sample/AndroidManifest.xml b/sample/AndroidManifest.xml
deleted file mode 100644
index d1a95872..00000000
--- a/sample/AndroidManifest.xml
+++ /dev/null
@@ -1,103 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.sampletvinput">
-
- <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="19"/>
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.READ_EPG_DATA" />
- <uses-permission android:name="android.permission.WRITE_EPG_DATA" />
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.RECEIVE_INPUT_EVENT" />
-
- <application android:label="@string/sample_tv_input"
- android:icon="@drawable/icon">
- <activity android:name=".SampleTvInputSetupActivity"
- android:theme="@android:style/Theme.NoDisplay">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- </intent-filter>
- </activity>
- <activity android:name=".SampleTvInputSettingsActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- </intent-filter>
- </activity>
- <activity android:name=".ExternalFileTvInputSettingsActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- </intent-filter>
- </activity>
- <service android:name="com.example.sampletvinput.LocalTvInputService"
- android:permission="android.permission.BIND_TV_INPUT"
- android:label="@string/local_input_label"
- android:process=":remoteLocalSampleTvInput">
- <intent-filter>
- <action android:name="android.media.tv.TvInputService" />
- </intent-filter>
- <meta-data android:name="android.media.tv.input"
- android:resource="@xml/tvinputservice" />
- </service>
- <service android:name="com.example.sampletvinput.HlsTvInputService"
- android:permission="android.permission.BIND_TV_INPUT"
- android:label="@string/hls_input_label"
- android:process=":remoteHlsSampleTvInput">
- <intent-filter>
- <action android:name="android.media.tv.TvInputService" />
- </intent-filter>
- <meta-data android:name="android.media.tv.input"
- android:resource="@xml/tvinputservice" />
- </service>
- <service android:name="com.example.sampletvinput.ExternalFileTvInputService"
- android:permission="android.permission.BIND_TV_INPUT"
- android:label="@string/external_input_label"
- android:process=":remoteExternalSampleTvInput">
- <intent-filter>
- <action android:name="android.media.tv.TvInputService" />
- </intent-filter>
- <meta-data android:name="android.media.tv.input"
- android:resource="@xml/externalfiletvinputservice" />
- </service>
- <activity
- android:name="com.example.android.sampleproxyservice.SampleProxySetupActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- </intent-filter>
- </activity>
- <service android:name="com.example.android.sampleproxyservice.FakeHdmiTvInputService"
- android:permission="android.permission.BIND_TV_INPUT"
- android:label="@string/fake_hdmi_input_label"
- android:process=":remoteProxyTvInput">
- <intent-filter>
- <action android:name="android.media.tv.TvInputService" />
- </intent-filter>
- <meta-data android:name="android.media.tv.input"
- android:resource="@xml/sampleproxyservice" />
- </service>
- <service android:name="com.example.android.sampleproxyservice.SampleProxyService"
- android:permission="android.permission.BIND_TV_INPUT"
- android:label="@string/proxy_input_label"
- android:process=":remoteProxyTvInput">
- <intent-filter>
- <action android:name="android.media.tv.TvInputService" />
- </intent-filter>
- <meta-data android:name="android.media.tv.input"
- android:resource="@xml/sampleproxyservice" />
- </service>
- </application>
-</manifest>
diff --git a/sample/res/drawable/layout_border.xml b/sample/res/drawable/layout_border.xml
deleted file mode 100644
index 254555ee..00000000
--- a/sample/res/drawable/layout_border.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <stroke android:width="10dip" android:color="#CCB1BCBE" />
- <corners android:radius="30dip"/>
- <padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
-</shape>
diff --git a/sample/res/layout/overlay.xml b/sample/res/layout/overlay.xml
deleted file mode 100644
index 818ffb15..00000000
--- a/sample/res/layout/overlay.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@drawable/layout_border" >
-
- <TextView
- android:layout_gravity="center_horizontal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="20dp"
- android:text="@string/overlay_view_test"
- android:textSize="32sp" />
-
- <LinearLayout
- android:id="@+id/ui_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="invisible"
- android:orientation="horizontal" >
-
- <WebView
- android:id="@+id/webview"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:layout_height="match_parent" />
-
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:layout_height="match_parent"
- android:gravity="center_vertical|center_horizontal" >
-
- <Button
- android:id="@+id/toggle_webview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:focusable="true"
- android:text="@string/webview_toggle_button" />
-
- <Button
- android:id="@+id/close_overlay_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:focusable="true"
- android:text="@string/overlay_view_close_button" />
- </LinearLayout>
- </LinearLayout>
-</LinearLayout>
diff --git a/sample/res/raw/hls_channels.xml b/sample/res/raw/hls_channels.xml
deleted file mode 100644
index 120c9dd6..00000000
--- a/sample/res/raw/hls_channels.xml
+++ /dev/null
@@ -1,78 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<Channels>
- <Channel
- display_number="2-1"
- display_name="NTSC(SD)"
- video_width="720"
- video_height="480"
- audio_channel_count="2"
- has_closed_caption="false" >
- <Program
- title="Bip-Bop"
- start_time="2014-06-06 10:00"
- duration_sec="3600"
- video_url="http://devimages.apple.com/iphone/samples/bipbop/gear1/prog_index.m3u8"
- description="Bip-Bop sample video - Low resolution"
- content_rating="android.media.tv/US/US_TVPG/US_TVPG_TV_G,
- android.media.tv/KR/KR_TV/KR_TV_ALL" />
- </Channel>
- <Channel
- display_number="2-2"
- display_name="NTSC(HD)"
- video_width="1280"
- video_height="720"
- audio_channel_count="2"
- has_closed_caption="false" >
- <Program
- title="Bip-Bop"
- start_time="2014-06-06 10:00"
- duration_sec="3600"
- video_url="http://devimages.apple.com/iphone/samples/bipbop/gear4/prog_index.m3u8"
- description="Bip-Bop sample video - High resolution"
- content_rating="android.media.tv/US/US_TVPG/US_TVPG_TV_PG/US_TVPG_D" />
- </Channel>
- <Channel
- display_number="2-3"
- display_name="NTSC(Full HD)"
- video_width="1920"
- video_height="1080"
- audio_channel_count="2"
- has_closed_caption="true" >
- <Program
- title="Bip-Bop"
- start_time="2014-06-06 10:00"
- duration_sec="3600"
- video_url="https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
- description="Bip-Bop sample video - High resolution"
- content_rating="android.media.tv/US/US_TVPG/US_TVPG_TV_14/US_TVPG_D/US_TVPG_L" />
- </Channel>
- <Channel
- display_number="3-1"
- display_name="BUNNY"
- video_width="1280"
- video_height="720"
- audio_channel_count="2"
- has_closed_caption="false" >
- <Program
- title="Big Buck Bunny"
- start_time="2014-06-06 10:00"
- duration_sec="3600"
- video_url="http://playertest.longtailvideo.com/adaptive/bbbfull/bbbfull.m3u8"
- description="Big Buck Bunny Sample Video"
- content_rating="android.media.tv/US/US_TVPG/US_TVPG_TV_G" />
- </Channel>
-</Channels>
diff --git a/sample/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp b/sample/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp
deleted file mode 100644
index c0bef563..00000000
--- a/sample/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp
+++ /dev/null
Binary files differ
diff --git a/sample/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4 b/sample/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4
deleted file mode 100644
index 63e25b83..00000000
--- a/sample/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4
+++ /dev/null
Binary files differ
diff --git a/sample/res/values/strings.xml b/sample/res/values/strings.xml
deleted file mode 100644
index 60bda09d..00000000
--- a/sample/res/values/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<resources>
- <string name="sample_tv_input">SampleTvInput</string>
- <string name="local_input_label">Sample Input 1: Overlay View Test</string>
- <string name="hls_input_label">Sample Input 2: HLS Streaming</string>
- <string name="external_input_label">Sample Input 3: External Files</string>
- <string name="fake_hdmi_input_label">Sample Input 4: Fake HDMI</string>
- <string name="proxy_input_label">Sample Input 5: Hardware Proxy </string>
- <string name="overlay_view_test">Overlay View Test: press U to toggle the menu</string>
- <string name="webview_toggle_button">Toggle WebView</string>
- <string name="overlay_view_close_button">Close OverlayView</string>
- <string name="update_channels_button">Update Channels</string>
- <string name="failed_to_load_channels">Failed to load channels from %s.</string>
-</resources>
diff --git a/sample/src/com/example/android/sampleproxyservice/ExternalSettopBox.java b/sample/src/com/example/android/sampleproxyservice/ExternalSettopBox.java
deleted file mode 100644
index f8591f4e..00000000
--- a/sample/src/com/example/android/sampleproxyservice/ExternalSettopBox.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.example.android.sampleproxyservice;
-
-import android.media.MediaPlayer;
-import android.util.Log;
-import android.view.Surface;
-
-import java.io.IOException;
-
-/**
- * Represents a external set-top box connected to a pass-through TV input.
- */
-public class ExternalSettopBox {
- private static final String TAG = ExternalSettopBox.class.getSimpleName();
- private static final boolean DEBUG = true;
-
- private static final String DEFAULT_URL =
- "http://devimages.apple.com/iphone/samples/bipbop/gear1/prog_index.m3u8";
- private static ExternalSettopBox sInstance;
-
- private MediaPlayer mPlayer;
- private String mUrl;
- private Surface mSurface;
-
- private ExternalSettopBox() {
- mUrl = DEFAULT_URL;
- }
-
- public synchronized static ExternalSettopBox getInstance() {
- if (sInstance == null) {
- sInstance = new ExternalSettopBox();
- }
- return sInstance;
- }
-
- public synchronized void setSurface(Surface surface) {
- if (DEBUG) Log.d(TAG, "setSurface(" + surface + ")");
- mSurface = surface;
- resetPlayback();
- }
-
- public synchronized void setVolume(float volume) {
- if (DEBUG) Log.d(TAG, "setVolume(" + volume + ")");
- mPlayer.setVolume(volume, volume);
- }
-
- public synchronized void tune(String url) {
- if (DEBUG) Log.d(TAG, "tune(" + url + ")");
- mUrl = url;
- resetPlayback();
- }
-
- private void resetPlayback() {
- if (DEBUG) Log.d(TAG, "resetPlayback(" + mUrl + ")");
- if (mPlayer != null) {
- if (DEBUG) Log.d(TAG, "Stopping");
- mPlayer.stop();
- mPlayer.release();
- mPlayer = null;
- }
- try {
- if (mSurface != null) {
- if (DEBUG) Log.d(TAG, "Starting " + mUrl);
- mPlayer = new MediaPlayer();
- mPlayer.setSurface(mSurface);
- mPlayer.setDataSource(mUrl);
- mPlayer.prepare();
- mPlayer.setLooping(true);
- mPlayer.start();
- }
- } catch (IllegalArgumentException | SecurityException | IllegalStateException
- | IOException e) {
- Log.e(TAG, "Failed to play: " + mUrl);
- }
- }
-}
diff --git a/sample/src/com/example/android/sampleproxyservice/FakeHdmiTvInputService.java b/sample/src/com/example/android/sampleproxyservice/FakeHdmiTvInputService.java
deleted file mode 100644
index 7f650b40..00000000
--- a/sample/src/com/example/android/sampleproxyservice/FakeHdmiTvInputService.java
+++ /dev/null
@@ -1,66 +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.example.android.sampleproxyservice;
-
-import android.content.Context;
-import android.media.tv.TvInputService;
-import android.net.Uri;
-import android.view.Surface;
-
-/**
- * Represents a simple HDMI input.
- */
-public class FakeHdmiTvInputService extends TvInputService {
- private ExternalSettopBox mExternalSettopBox = ExternalSettopBox.getInstance();
-
- @Override
- public Session onCreateSession(String inputId) {
- return new FakeHdmiSession(this);
- }
-
- private class FakeHdmiSession extends Session {
- public FakeHdmiSession(Context context) {
- super(context);
- }
-
- @Override
- public void onRelease() {
- }
-
- @Override
- public void onSetCaptionEnabled(boolean enabled) {
- // No-op.
- }
-
- @Override
- public void onSetStreamVolume(float volume) {
- mExternalSettopBox.setVolume(volume);
- }
-
- @Override
- public boolean onSetSurface(Surface surface) {
- mExternalSettopBox.setSurface(surface);
- return true;
- }
-
- @Override
- public boolean onTune(Uri channel) {
- // No-op.
- return false;
- }
- }
-}
diff --git a/sample/src/com/example/android/sampleproxyservice/SampleProxyService.java b/sample/src/com/example/android/sampleproxyservice/SampleProxyService.java
deleted file mode 100644
index eb2a54b5..00000000
--- a/sample/src/com/example/android/sampleproxyservice/SampleProxyService.java
+++ /dev/null
@@ -1,117 +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.example.android.sampleproxyservice;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputInfo;
-import android.media.tv.TvInputManager;
-import android.media.tv.TvInputService;
-import android.net.Uri;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Demonstrates how to implement a TV input service which represents a external device connected to
- * a hardware TV input.
- */
-public class SampleProxyService extends TvInputService {
- private static final String TAG = SampleProxyService.class.getSimpleName();
- private static final boolean DEBUG = true;
-
- private String mHardwareTvInputId;
- private TvInputManager mTvInputManager;
-
- @Override
- public void onCreate() {
- if (DEBUG) Log.d(TAG, "onCreate()");
- super.onCreate();
- mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
- }
-
- @Override
- public Session onCreateSession(String inputId) {
- return new SampleSession(this);
- }
-
- private class SampleSession extends HardwareSession {
- private ExternalSettopBox mExternalSettopBox = ExternalSettopBox.getInstance();
-
- public SampleSession(Context context) {
- super(context);
- }
-
- @Override
- public boolean onTune(Uri channel) {
- if (DEBUG) Log.d(TAG, "onTune(" + channel + ")");
- String[] projection = { TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA };
-
- Cursor cursor = null;
- try {
- cursor = getContentResolver().query(channel, projection, null, null, null);
- if (cursor == null || cursor.getCount() == 0) {
- if (DEBUG) Log.d(TAG, "Can't find channel in TvProvider.");
- return false;
- }
- cursor.moveToNext();
- String url = cursor.getString(0);
- if (DEBUG) Log.d(TAG, "Tuning to: " + url);
- mExternalSettopBox.tune(url);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return true;
- }
-
- @Override
- public void onRelease() {
- // Do nothing.
- }
-
- @Override
- public void onSetCaptionEnabled(boolean enabled) {
- // Do nothing.
- }
-
- @Override
- public void onSetStreamVolume(float volume) {
- // Do Nothing.
- }
-
- @Override
- public String getHardwareInputId() {
- if (mHardwareTvInputId != null) {
- return mHardwareTvInputId;
- }
- List<TvInputInfo> inputs = mTvInputManager.getTvInputList();
- // TODO: This should be set in the setup phase.
- String hdmiServiceName = FakeHdmiTvInputService.class.getSimpleName();
- for (TvInputInfo info : inputs) {
- if (info.getComponent().toString().contains(hdmiServiceName)) {
- mHardwareTvInputId = info.getId();
- return mHardwareTvInputId;
- }
- }
- if (DEBUG) Log.e(TAG, "Cannot find input for: " + hdmiServiceName);
- return null;
- }
- }
-}
diff --git a/sample/src/com/example/android/sampleproxyservice/SampleProxySetupActivity.java b/sample/src/com/example/android/sampleproxyservice/SampleProxySetupActivity.java
deleted file mode 100644
index 4d1e7ca1..00000000
--- a/sample/src/com/example/android/sampleproxyservice/SampleProxySetupActivity.java
+++ /dev/null
@@ -1,86 +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.example.android.sampleproxyservice;
-
-import android.app.Activity;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.media.tv.TvContract;
-import android.media.tv.TvContract.Channels;
-import android.media.tv.TvInputInfo;
-import android.net.Uri;
-import android.os.Bundle;
-
-public class SampleProxySetupActivity extends Activity {
- private static final String TAG = SampleProxySetupActivity.class.getSimpleName();
- private static final boolean DEBUG = true;
-
- private static final String CHANNEL_NUMBER_1 = "1";
- private static final String CHANNEL_NAME_1 = "BIPBOP";
- private static final String CHANNEL_URL_1 =
- "http://devimages.apple.com/iphone/samples/bipbop/gear1/prog_index.m3u8";
-
- private static final String CHANNEL_NUMBER_2 = "2";
- private static final String CHANNEL_NAME_2 = "BUNNY";
- private static final String CHANNEL_URL_2 =
- "http://playertest.longtailvideo.com/adaptive/bbbfull/bbbfull.m3u8";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- String inputId = getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
- createSampleChannels(inputId);
- setResult(Activity.RESULT_OK);
- finish();
- }
-
- private void createSampleChannels(String inputId) {
- Uri uri = TvContract.buildChannelsUriForInput(inputId);
- String[] projection = { TvContract.Channels._ID };
-
- Cursor cursor = null;
- try {
- while (true) {
- cursor = getContentResolver().query(uri, projection, null, null, null);
- if (cursor != null && cursor.getCount() > 0) {
- return;
- }
- if (cursor != null) {
- cursor.close();
- cursor = null;
- }
- ContentValues values = new ContentValues();
- values.put(Channels.COLUMN_INPUT_ID, inputId);
-
- values.put(Channels.COLUMN_DISPLAY_NUMBER, CHANNEL_NUMBER_1);
- values.put(Channels.COLUMN_DISPLAY_NAME, CHANNEL_NAME_1);
- values.put(Channels.COLUMN_INTERNAL_PROVIDER_DATA, CHANNEL_URL_1);
- getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);
-
- values.put(Channels.COLUMN_DISPLAY_NUMBER, CHANNEL_NUMBER_2);
- values.put(Channels.COLUMN_DISPLAY_NAME, CHANNEL_NAME_2);
- values.put(Channels.COLUMN_INTERNAL_PROVIDER_DATA, CHANNEL_URL_2);
- getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-}
diff --git a/sample/src/com/example/sampletvinput/BaseTvInputService.java b/sample/src/com/example/sampletvinput/BaseTvInputService.java
deleted file mode 100644
index 54a9b0fc..00000000
--- a/sample/src/com/example/sampletvinput/BaseTvInputService.java
+++ /dev/null
@@ -1,493 +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.example.sampletvinput;
-
-import android.content.ComponentName;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.database.Cursor;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.TrackInfo;
-import android.media.tv.TvContentRating;
-import android.media.tv.TvContract;
-import android.media.tv.TvContract.Programs;
-import android.media.tv.TvInputManager;
-import android.media.tv.TvInputService;
-import android.media.tv.TvTrackInfo;
-import android.net.Uri;
-import android.os.Handler;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.LongSparseArray;
-import android.view.KeyEvent;
-import android.view.Surface;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-abstract public class BaseTvInputService extends TvInputService {
- private static final String TAG = "BaseTvInputService";
- private static final boolean DEBUG = true;
-
- private final LongSparseArray<ChannelInfo> mChannelMap = new LongSparseArray<ChannelInfo>();
- private String mSelectedAudioTrack;
- private String mSelectedVideoTrack;
- private String mSelectedSubtitleTrack;
- private final Handler mHandler = new Handler();
-
- protected List<ChannelInfo> mChannels;
- private Uri mChannelUri;
-
- private boolean mIsPlaying;
-
- @Override
- public void onCreate() {
- if (DEBUG) Log.d(TAG, "onCreate()");
- super.onCreate();
-
- // TODO: Build channel map when the session is connected. At that point, we will come to
- // know the input ID.
- buildChannelMap();
- setTheme(android.R.style.Theme_Holo_Light_NoActionBar);
- }
-
- @Override
- public void onDestroy() {
- if (DEBUG) Log.d(TAG, "onDestroy()");
- super.onDestroy();
- }
-
- @Override
- public Session onCreateSession(String inputId) {
- if (DEBUG) Log.d(TAG, "onCreateSession(inputId=" + inputId + ")");
- return new BaseTvInputSessionImpl(this);
- }
-
- abstract public List<ChannelInfo> createSampleChannels();
-
- private synchronized void buildChannelMap() {
- Uri uri = TvContract.buildChannelsUriForInput(Utils.getInputIdFromComponentName(this,
- new ComponentName(this, this.getClass())), false);
- String[] projection = {
- TvContract.Channels._ID,
- TvContract.Channels.COLUMN_DISPLAY_NUMBER
- };
- // TODO: The app should write all channel information into DB and reconstruct channel map
- // from it.
- mChannels = createSampleChannels();
- if (mChannels == null || mChannels.isEmpty()) {
- Log.w(TAG, "No channel list.");
- return;
- }
- Cursor cursor = null;
- try {
- cursor = getContentResolver().query(uri, projection, null, null, null);
-
- if (cursor == null || cursor.getCount() == 0) {
- Log.e(TAG, "Couldn't find the channel list.");
- return;
- }
-
- while (cursor.moveToNext()) {
- long channelId = cursor.getLong(0);
- String channelNumber = cursor.getString(1);
- if (DEBUG) Log.d(TAG, "Channel mapping: ID(" + channelId + ") -> " + channelNumber);
- mChannelMap.put(channelId, getChannelByNumber(channelNumber, false));
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- private ChannelInfo getChannelByNumber(String channelNumber, boolean isRetry) {
- for (ChannelInfo info : mChannels) {
- if (info.mNumber.equals(channelNumber)) {
- return info;
- }
- }
- if (!isRetry) {
- buildChannelMap();
- return getChannelByNumber(channelNumber, true);
- }
- throw new IllegalArgumentException("Unknown channel: " + channelNumber);
- }
-
- private ChannelInfo getChannelByUri(Uri channelUri, boolean isRetry) {
- ChannelInfo info = mChannelMap.get(ContentUris.parseId(channelUri));
- if (info == null) {
- if (!isRetry) {
- buildChannelMap();
- return getChannelByUri(channelUri, true);
- }
- throw new IllegalArgumentException("Unknown channel: " + channelUri);
- }
- return info;
- }
-
- class BaseTvInputSessionImpl extends TvInputService.Session {
- private MediaPlayer mPlayer;
- private float mVolume;
- private boolean mMute;
- private Map<String, TvTrackInfo> mTracks;
-
-
- protected BaseTvInputSessionImpl(Context context) {
- super(context);
- mPlayer = new MediaPlayer();
- mVolume = 1.0f;
- mMute = false;
-
- mPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
- @Override
- public boolean onInfo(MediaPlayer player, int what, int arg) {
- if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
- notifyVideoUnavailable(
- TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING);
- return true;
- } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
- notifyVideoAvailable();
- return true;
- }
- return false;
- }
- });
- }
-
- @Override
- public void onRelease() {
- if (mPlayer != null) {
- mPlayer.release();
- mPlayer = null;
- }
- }
-
- @Override
- public boolean onSetSurface(Surface surface) {
- if (DEBUG) Log.d(TAG, "onSetSurface(" + surface + ")");
- mPlayer.setSurface(surface);
- return true;
- }
-
- @Override
- public void onSetStreamVolume(float volume) {
- if (DEBUG) Log.d(TAG, "onSetStreamVolume(" + volume + ")");
- mVolume = volume;
- mPlayer.setVolume(volume, volume);
- }
-
- private boolean setDataSource(MediaPlayer player, ChannelInfo channel) {
- ProgramInfo program = channel.mProgram;
- try {
- if (program.mUrl != null) {
- player.setDataSource(program.mUrl);
- } else {
- AssetFileDescriptor afd = getResources().openRawResourceFd(program.mResourceId);
- if (afd == null) {
- return false;
- }
- player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
- afd.getDeclaredLength());
- afd.close();
- }
- // Android media player does not support looping for HLS.
- if (program.mUrl == null || !program.mUrl.startsWith("http")) {
- player.setLooping(true);
- }
- } catch (IllegalArgumentException | IllegalStateException | IOException e) {
- // Do nothing.
- }
- return true;
- }
-
- private boolean changeChannel(Uri channelUri) {
- final ChannelInfo channel = getChannelByUri(channelUri, false);
- if (!startPlayback(channel)) {
- return false;
- }
- // Create empty program information and insert it into the database.
- // Delay intentionally to see whether the updated program information dynamically
- // replaces the previous one on the channel banner (for testing). This is to simulate
- // the actual case where we get parsed program data only after tuning is done.
- final long DELAY_FOR_TESTING_IN_MILLIS = 1000; // 1 second
- mHandler.postDelayed(
- new AddProgramRunnable(channelUri, channel.mProgram),
- DELAY_FOR_TESTING_IN_MILLIS);
- return true;
- }
-
- private boolean startPlayback(final ChannelInfo channel) {
- mIsPlaying = false;
- mPlayer.reset();
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
- if (!setDataSource(mPlayer, channel)) {
- if (DEBUG) Log.d(TAG, "Failed to set the data source");
- return false;
- }
- try {
- mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
- @Override
- public void onPrepared(MediaPlayer player) {
- if (mPlayer != null && !mPlayer.isPlaying()) {
- int duration = mPlayer.getDuration();
- if (duration > 0) {
- int seekPosition = (int) (System.currentTimeMillis() % duration);
- mPlayer.seekTo(seekPosition);
- }
- MediaPlayer.TrackInfo[] tracks = mPlayer.getTrackInfo();
- setupTrackInfo(tracks, channel);
- notifyVideoAvailable();
- try {
- mPlayer.start();
- mIsPlaying = true;
- } catch (IllegalStateException e) {
- mIsPlaying = false;
- }
- }
- }
- });
- mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- // Loop only if onPrepared has been called normally.
- if (mIsPlaying) {
- startPlayback(channel);
- }
- }
- });
- }
- });
- mPlayer.prepareAsync();
- } catch (IllegalStateException e1) {
- return false;
- }
- return true;
- }
-
- @Override
- public boolean onTune(Uri channelUri) {
- if (DEBUG) Log.d(TAG, "onTune(" + channelUri + ")");
- return changeChannel(channelUri);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_M) {
- mMute = !mMute;
- if (mMute) {
- mPlayer.setVolume(0.0f, 0.0f);
- } else {
- mPlayer.setVolume(mVolume, mVolume);
- }
- return true;
- } else if (event.getKeyCode() == KeyEvent.KEYCODE_C) {
- // This simulates the case TV input changes the channel without tune request from
- // TV app. e.g. Channel change from STB.
- if (mChannelUri != null) {
- int index = mChannelMap.indexOfKey(ContentUris.parseId(mChannelUri));
- long nextChannelId = mChannelMap.keyAt((index + 1) % mChannelMap.size());
- Uri nextChannelUri = TvContract.buildChannelUri(nextChannelId);
- changeChannel(TvContract.buildChannelUri(nextChannelId));
- notifyChannelRetuned(nextChannelUri);
- }
- return true;
- }
- return false;
- }
-
- @Override
- public void onSetCaptionEnabled(boolean enabled) {
- if (DEBUG) Log.d(TAG, "onSetCaptionEnabled(" + enabled + ")");
- }
-
- @Override
- public boolean onSelectTrack(int type, String trackId) {
- Log.d(TAG, "onSelectTrack(" + type + " ," + trackId + ")");
- if (mPlayer != null) {
- if (type == TvTrackInfo.TYPE_SUBTITLE) {
- // SelectTrack only works on subtitle tracks.
- if (trackId == null) {
- if (mSelectedSubtitleTrack != null) {
- mPlayer.deselectTrack(Integer.parseInt(mSelectedSubtitleTrack));
- }
- } else {
- TvTrackInfo track = mTracks.get(trackId);
- if (track == null) {
- return false;
- }
- mPlayer.selectTrack(Integer.parseInt(trackId));
- mSelectedSubtitleTrack = trackId;
- }
- notifyTrackSelected(TvTrackInfo.TYPE_SUBTITLE, trackId);
- return true;
- }
- }
- return false;
- }
-
- private void setupTrackInfo(MediaPlayer.TrackInfo[] infos, ChannelInfo channel) {
- Map<String, TvTrackInfo> tracks = new HashMap<String, TvTrackInfo>();
- // Add subtitle tracks from the real media.
- int i;
- for (i = 0; i < infos.length; ++i) {
- if (infos[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT
- || infos[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
- tracks.put(Integer.toString(i), new TvTrackInfo.Builder(
- TvTrackInfo.TYPE_SUBTITLE, Integer.toString(i))
- .setLanguage("und".equals(infos[i].getLanguage()) ? null
- : infos[i].getLanguage())
- .build());
- }
- Log.d(TAG, "tracks " + i + " " + infos[i].getTrackType() + " "
- + infos[i].getLanguage());
- }
- // Add predefine video and audio track.
- mSelectedVideoTrack = Integer.toString(i++);
- tracks.put(mSelectedVideoTrack, new TvTrackInfo.Builder(
- TvTrackInfo.TYPE_VIDEO, mSelectedVideoTrack)
- .setVideoWidth(channel.mVideoWidth)
- .setVideoHeight(channel.mVideoHeight)
- .build());
- mSelectedAudioTrack = Integer.toString(i++);
- tracks.put(mSelectedAudioTrack, new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO,
- mSelectedAudioTrack)
- .setAudioChannelCount(channel.mAudioChannel)
- .build());
-
- mTracks = tracks;
- notifyTracksChanged(new ArrayList<TvTrackInfo>(mTracks.values()));
- notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, mSelectedVideoTrack);
- notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, mSelectedAudioTrack);
- }
-
- private class AddProgramRunnable implements Runnable {
- private static final int PROGRAM_REPEAT_COUNT = 24;
- private final Uri mChannelUri;
- private final ProgramInfo mProgram;
-
- public AddProgramRunnable(Uri channelUri, ProgramInfo program) {
- mChannelUri = channelUri;
- mProgram = program;
- }
-
- @Override
- public void run() {
- if (mProgram.mDurationSec == 0) {
- return;
- }
- long nowSec = System.currentTimeMillis() / 1000;
- long startTimeSec = nowSec
- - positiveMod((nowSec - mProgram.mStartTimeSec), mProgram.mDurationSec);
- ContentValues values = new ContentValues();
- values.put(Programs.COLUMN_CHANNEL_ID, ContentUris.parseId(mChannelUri));
- values.put(Programs.COLUMN_TITLE, mProgram.mTitle);
- values.put(Programs.COLUMN_SHORT_DESCRIPTION, mProgram.mDescription);
- values.put(Programs.COLUMN_CONTENT_RATING,
- Utils.contentRatingsToString(mProgram.mContentRatings));
- if (!TextUtils.isEmpty(mProgram.mPosterArtUri)) {
- values.put(Programs.COLUMN_POSTER_ART_URI, mProgram.mPosterArtUri);
- }
-
- for (int i = 0; i < PROGRAM_REPEAT_COUNT; ++i) {
- if (!hasProgramInfo((startTimeSec + i * mProgram.mDurationSec + 1) * 1000)) {
- values.put(Programs.COLUMN_START_TIME_UTC_MILLIS,
- (startTimeSec + i * mProgram.mDurationSec) * 1000);
- values.put(Programs.COLUMN_END_TIME_UTC_MILLIS,
- (startTimeSec + (i + 1) * mProgram.mDurationSec) * 1000);
- getContentResolver().insert(TvContract.Programs.CONTENT_URI, values);
- }
- }
- }
-
- private long positiveMod(long x, long modulo) {
- return ((x % modulo) + modulo) % modulo;
- }
-
- private boolean hasProgramInfo(long timeMs) {
- Uri uri = TvContract.buildProgramsUriForChannel(mChannelUri, timeMs, timeMs);
- String[] projection = { TvContract.Programs._ID };
- Cursor cursor = null;
- try {
- cursor = getContentResolver().query(uri, projection, null, null, null);
- if (cursor.getCount() > 0) {
- return true;
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return false;
- }
- }
- }
-
- public static final class ChannelInfo {
- public final String mNumber;
- public final String mName;
- public final String mLogoUrl;
- public final int mVideoWidth;
- public final int mVideoHeight;
- public final int mAudioChannel;
- public final boolean mHasClosedCaption;
- public final ProgramInfo mProgram;
-
- public ChannelInfo(String number, String name, String logoUrl, int videoWidth,
- int videoHeight, int audioChannel, boolean hasClosedCaption, ProgramInfo program) {
- mNumber = number;
- mName = name;
- mLogoUrl = logoUrl;
- mVideoWidth = videoWidth;
- mVideoHeight = videoHeight;
- mAudioChannel = audioChannel;
- mHasClosedCaption = hasClosedCaption;
- mProgram = program;
- }
- }
-
- public static final class ProgramInfo {
- public final String mTitle;
- public final String mPosterArtUri;
- public final String mDescription;
- public final long mStartTimeSec;
- public final long mDurationSec;
- public final TvContentRating[] mContentRatings;
- public final String mUrl;
- public final int mResourceId;
-
- public ProgramInfo(String title, String posterArtUri, String description, long startTimeSec,
- long durationSec, TvContentRating[] contentRatings, String url, int resourceId) {
- mTitle = title;
- mPosterArtUri = posterArtUri;
- mDescription = description;
- mStartTimeSec = startTimeSec;
- mDurationSec = durationSec;
- mContentRatings = contentRatings;
- mUrl = url;
- mResourceId = resourceId;
- }
- }
-}
diff --git a/sample/src/com/example/sampletvinput/ChannelUtils.java b/sample/src/com/example/sampletvinput/ChannelUtils.java
deleted file mode 100644
index 961386f7..00000000
--- a/sample/src/com/example/sampletvinput/ChannelUtils.java
+++ /dev/null
@@ -1,74 +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.example.sampletvinput;
-
-import com.example.sampletvinput.BaseTvInputService.ChannelInfo;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.media.tv.TvContract;
-import android.media.tv.TvContract.Channels;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.text.TextUtils;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-class ChannelUtils {
- public static void populateChannels(
- Context context, String inputId, List<ChannelInfo> channels) {
- ContentValues values = new ContentValues();
- values.put(Channels.COLUMN_INPUT_ID, inputId);
- Map<Uri, String> logos = new HashMap<Uri, String>();
- for (ChannelInfo channel : channels) {
- values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.mNumber);
- values.put(Channels.COLUMN_DISPLAY_NAME, channel.mName);
- Uri uri = context.getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);
- if (!TextUtils.isEmpty(channel.mLogoUrl)) {
- logos.put(TvContract.buildChannelLogoUri(uri), channel.mLogoUrl);
- }
- }
-
- if (!logos.isEmpty()) {
- new InsertLogosTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, logos);
- }
- }
-
- public static class InsertLogosTask extends AsyncTask<Map<Uri, String>, Void, Void> {
- private final Context mContext;
-
- InsertLogosTask(Context context) {
- mContext = context;
- }
-
- @Override
- public Void doInBackground(Map<Uri, String>... logosList) {
- for (Map<Uri, String> logos : logosList) {
- for (Uri uri : logos.keySet()) {
- // For now, support only local file.
- Utils.insertFile(mContext, uri, new File(logos.get(uri)));
- }
- }
- return null;
- }
- }
-
- private ChannelUtils() {}
-}
diff --git a/sample/src/com/example/sampletvinput/ChannelXMLParser.java b/sample/src/com/example/sampletvinput/ChannelXMLParser.java
deleted file mode 100644
index 09fd5de1..00000000
--- a/sample/src/com/example/sampletvinput/ChannelXMLParser.java
+++ /dev/null
@@ -1,161 +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.example.sampletvinput;
-
-import android.media.tv.TvContentRating;
-import android.util.Log;
-import android.util.Xml;
-
-import com.example.sampletvinput.BaseTvInputService.ChannelInfo;
-import com.example.sampletvinput.BaseTvInputService.ProgramInfo;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.List;
-
-public class ChannelXMLParser {
- private static String TAG = "ChannelXmlParser";
-
- private static final String TAG_CHANNELS = "Channels";
- private static final String TAG_CHANNEL = "Channel";
- private static final String TAG_PROGRAM = "Program";
-
- private static final String ATTR_DISPLAY_NUMBNER = "display_number";
- private static final String ATTR_DISPLAY_NAME = "display_name";
- private static final String ATTR_VIDEO_WIDTH = "video_width";
- private static final String ATTR_VIDEO_HEIGHT = "video_height";
- private static final String ATTR_AUDIO_CHANNEL_COUNT = "audio_channel_count";
- private static final String ATTR_HAS_CLOSED_CAPTION = "has_closed_caption";
- private static final String ATTR_LOGO_URL = "logo_url";
-
- private static final String ATTR_TITLE = "title";
- private static final String ATTR_POSTER_ART_URI = "poster_art_uri";
- private static final String ATTR_START_TIME = "start_time";
- private static final String ATTR_DURATION_SEC = "duration_sec";
- private static final String ATTR_VIDEO_URL = "video_url";
- private static final String ATTR_DESCRIPTION = "description";
- private static final String ATTR_CONTENT_RATING = "content_rating";
-
- public static List<ChannelInfo> parseChannelXML(InputStream in)
- throws XmlPullParserException, IOException {
- Log.d(TAG, "parseChannelXML");
- List<ChannelInfo> list = new ArrayList<ChannelInfo>();
- XmlPullParser parser = Xml.newPullParser();
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
- parser.setInput(in, null);
- parser.nextTag();
-
- parser.require(XmlPullParser.START_TAG, null, TAG_CHANNELS);
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- if (parser.getEventType() == XmlPullParser.START_TAG
- && TAG_CHANNEL.equals(parser.getName())) {
- list.add(parseChannel(parser));
- }
- }
- return list;
- }
-
- private static ChannelInfo parseChannel(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- Log.d(TAG, "parseChannel " + parser.getAttributeCount());
- String displayNumber = null;
- String displayName = null;
- int videoWidth = 0;
- int videoHeight = 0;
- int audioChannelCount = 0;
- boolean hasClosedCaption = false;
- String logoUrl = null;
- for (int i = 0; i < parser.getAttributeCount(); ++i) {
- String attr = parser.getAttributeName(i);
- String value = parser.getAttributeValue(i);
- if (ATTR_DISPLAY_NUMBNER.equals(attr)) {
- displayNumber = value;
- } else if (ATTR_DISPLAY_NAME.equals(attr)) {
- displayName = value;
- } else if (ATTR_VIDEO_WIDTH.equals(attr)) {
- videoWidth = Integer.parseInt(value);
- } else if (ATTR_VIDEO_HEIGHT.equals(attr)) {
- videoHeight = Integer.parseInt(value);
- } else if (ATTR_AUDIO_CHANNEL_COUNT.equals(attr)) {
- audioChannelCount = Integer.parseInt(value);
- } else if (ATTR_HAS_CLOSED_CAPTION.equals(attr)) {
- hasClosedCaption = "true".equalsIgnoreCase(value);
- } else if (ATTR_LOGO_URL.equals(attr)) {
- logoUrl = value;
- }
- }
- ProgramInfo program = null;
- int depth = 0;
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- if (parser.getEventType() == XmlPullParser.START_TAG) {
- depth++;
- if (TAG_PROGRAM.equals(parser.getName()) && program == null) {
- program = parseProgram(parser);
- }
- } else if (parser.getEventType() == XmlPullParser.END_TAG) {
- depth--;
- if (depth == 0) {
- break;
- }
- }
- }
- return new ChannelInfo(displayNumber, displayName, logoUrl, videoWidth, videoHeight,
- audioChannelCount, hasClosedCaption, program);
- }
-
- private static ProgramInfo parseProgram(XmlPullParser parser) {
- SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
- String title = null;
- long startTimeSec = 0;
- long durationSec = 0;
- String videoUrl = null;
- String description = null;
- String posterArtUri = null;
- String contentRatings = null;
- for (int i = 0; i < parser.getAttributeCount(); ++i) {
- String attr = parser.getAttributeName(i);
- String value = parser.getAttributeValue(i);
- if (ATTR_TITLE.equals(attr)) {
- title = value;
- } else if (ATTR_POSTER_ART_URI.equals(attr)) {
- posterArtUri = value;
- } else if (ATTR_START_TIME.equals(attr)) {
- try {
- startTimeSec = format.parse(value).getTime() / 1000;
- } catch (ParseException e) {
- Log.w(TAG, "Malformed start time value - " + value);
- }
- } else if (ATTR_DURATION_SEC.equals(attr)) {
- durationSec = Integer.parseInt(value);
- } else if (ATTR_VIDEO_URL.equals(attr)) {
- videoUrl = value;
- } else if (ATTR_DESCRIPTION.equals(attr)) {
- description = value;
- } else if (ATTR_CONTENT_RATING.equals(attr)) {
- contentRatings = value;
- }
- }
- return new ProgramInfo(title, posterArtUri, description, startTimeSec, durationSec,
- Utils.stringToContentRatings(contentRatings), videoUrl, 0);
- }
-}
diff --git a/sample/src/com/example/sampletvinput/ExternalFileTvInputService.java b/sample/src/com/example/sampletvinput/ExternalFileTvInputService.java
deleted file mode 100644
index 7399132f..00000000
--- a/sample/src/com/example/sampletvinput/ExternalFileTvInputService.java
+++ /dev/null
@@ -1,66 +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.example.sampletvinput;
-
-import android.util.Log;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public class ExternalFileTvInputService extends BaseTvInputService {
- private static final String TAG = "ExternalFileTvInputService";
- public static final String CHANNEL_XML_PATH = "/sdcard/tvinput/channels.xml";
-
- private static List<ChannelInfo> sSampleChannels = null;
-
- @Override
- public List<ChannelInfo> createSampleChannels() {
- return parseSampleChannels();
- }
-
- public static List<ChannelInfo> parseSampleChannels() {
- synchronized (ExternalFileTvInputService.class) {
- if (sSampleChannels != null) {
- return sSampleChannels;
- }
- File file = new File(CHANNEL_XML_PATH);
- FileInputStream is = null;
- try {
- is = new FileInputStream(file);
- sSampleChannels = ChannelXMLParser.parseChannelXML(is);
- } catch (XmlPullParserException | IOException e) {
- Log.w(TAG, "failed to load channels.");
- sSampleChannels = Collections.emptyList();
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- // Ignore exception.
- }
- }
- }
- return sSampleChannels;
- }
- }
-}
diff --git a/sample/src/com/example/sampletvinput/ExternalFileTvInputSettingsActivity.java b/sample/src/com/example/sampletvinput/ExternalFileTvInputSettingsActivity.java
deleted file mode 100644
index 58e1e289..00000000
--- a/sample/src/com/example/sampletvinput/ExternalFileTvInputSettingsActivity.java
+++ /dev/null
@@ -1,77 +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.example.sampletvinput;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-import android.media.tv.TvContract;
-import android.media.tv.TvContract.Channels;
-import android.media.tv.TvInputInfo;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.Toast;
-
-import com.example.sampletvinput.BaseTvInputService.ChannelInfo;
-
-import java.util.List;
-
-public class ExternalFileTvInputSettingsActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- LinearLayout layout = new LinearLayout(this);
- layout.setOrientation(LinearLayout.VERTICAL);
- setContentView(layout);
-
- Button btn = new Button(this);
- btn.setText(getString(R.string.update_channels_button));
- btn.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (updateChannels() > 0) {
- setResult(Activity.RESULT_OK);
- } else {
- Context context = ExternalFileTvInputSettingsActivity.this;
- Toast.makeText(context, context.getString(R.string.failed_to_load_channels,
- ExternalFileTvInputService.CHANNEL_XML_PATH), Toast.LENGTH_SHORT)
- .show();
- }
- finish();
- }
- });
-
- layout.addView(btn);
- }
-
- private int updateChannels() {
- Uri uri = TvContract.buildChannelsUriForInput(Utils.getInputIdFromComponentName(this,
- new ComponentName(this, ExternalFileTvInputService.class)), false);
- getContentResolver().delete(uri, null, null);
- getContentResolver().delete(TvContract.Programs.CONTENT_URI, null, null);
- List<ChannelInfo> channels = ExternalFileTvInputService.parseSampleChannels();
- String inputId = getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
- ChannelUtils.populateChannels(this, inputId, channels);
- return channels.size();
- }
-}
diff --git a/sample/src/com/example/sampletvinput/HlsTvInputService.java b/sample/src/com/example/sampletvinput/HlsTvInputService.java
deleted file mode 100644
index 54cf4add..00000000
--- a/sample/src/com/example/sampletvinput/HlsTvInputService.java
+++ /dev/null
@@ -1,137 +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.example.sampletvinput;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Handler;
-import android.util.Log;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-public class HlsTvInputService extends BaseTvInputService {
- private static final String TAG = "HlsTvInputService";
-
- private static List<ChannelInfo> sSampleChannels = null;
-
- private NetworkStateReceiver mNetworkStateReceiver;
-
- @Override
- public void onCreate() {
- super.onCreate();
- mNetworkStateReceiver = new NetworkStateReceiver();
- // TODO: Uncomment or remove when a new API design is locked down.
- // setAvailable(mNetworkStateReceiver.isConnected());
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mNetworkStateReceiver != null) {
- mNetworkStateReceiver.unregister();
- }
- }
-
- public static List<ChannelInfo> createSampleChannelsStatic(Context context) {
- synchronized (HlsTvInputService.class) {
- if (sSampleChannels != null) {
- return sSampleChannels;
- }
- sSampleChannels = new ArrayList<ChannelInfo>();
- try {
- InputStream is = context.getResources().openRawResource(R.raw.hls_channels);
- sSampleChannels = ChannelXMLParser.parseChannelXML(is);
- } catch (XmlPullParserException | IOException e) {
- // TODO: Disable this service.
- Log.w(TAG, "failed to load channels.");
- }
- return sSampleChannels;
- }
- }
-
- @Override
- public List<ChannelInfo> createSampleChannels() {
- return createSampleChannelsStatic(this);
- }
-
- private class NetworkStateReceiver extends BroadcastReceiver {
- private static final int DURATION_MONITOR = 30000; // 30 seconds
-
- private final Handler mUnregisterHandler = new Handler();
-
- private final Runnable mUnregister = new Runnable() {
- @Override
- public void run() {
- unregister();
- }
- };
-
- private boolean mIsConnected;
- private boolean mIsRegistered;
-
- public NetworkStateReceiver() {
- // Cache the latest network status.
- update();
- }
-
- public boolean isConnected() {
- // Now that we know you're interested in, turn on the monitor for some time.
- if (mIsRegistered) {
- mUnregisterHandler.removeCallbacks(mUnregister);
- } else {
- IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
- registerReceiver(this, filter);
- mIsRegistered = true;
- }
- mUnregisterHandler.postDelayed(mUnregister, DURATION_MONITOR);
-
- update();
- return mIsConnected;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- update();
- }
-
- public void unregister() {
- if (mIsRegistered) {
- unregisterReceiver(this);
- mIsRegistered = false;
- }
- }
-
- private void update() {
- ConnectivityManager cm = (ConnectivityManager) getSystemService(
- Context.CONNECTIVITY_SERVICE);
- NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
- mIsConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();
- // TODO: Uncomment or remove when a new API design is locked down.
- // setAvailable(mIsConnected);
- }
- }
-}
diff --git a/sample/src/com/example/sampletvinput/LocalTvInputService.java b/sample/src/com/example/sampletvinput/LocalTvInputService.java
deleted file mode 100644
index 59bd35d8..00000000
--- a/sample/src/com/example/sampletvinput/LocalTvInputService.java
+++ /dev/null
@@ -1,162 +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.example.sampletvinput;
-
-import android.content.Context;
-import android.media.tv.TvInputService;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-import android.widget.Button;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class LocalTvInputService extends BaseTvInputService {
- private static final String TAG = "LocalTvInputService";
- private static final boolean DEBUG = true;
-
- private static List<ChannelInfo> sSampleChannels = null;
-
- private static final String CHANNEL_1_NUMBER = "1-1";
- private static final String CHANNEL_2_NUMBER = "1-2";
- private static final String CHANNEL_1_NAME = "BUNNY(SD)";
- private static final String CHANNEL_2_NAME = "BUNNY(HD)";
- private static final String PROGRAM_1_TITLE = "Big Buck Bunny";
- private static final String PROGRAM_2_TITLE = "Big Buck Bunny";
- private static final String PROGRAM_1_DESC = "Big Buck Bunny - Low resolution";
- private static final String PROGRAM_2_DESC = "Big Buck Bunny - High resolution";
- private static final int RESOURCE_1 =
- R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz;
- private static final int RESOURCE_2 =
- R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
-
- private static final float WEBVIEW_ALPHA = 0.7f;
- private static final String WEBVIEW_SITE = "http://www.android.com";
-
- @Override
- public Session onCreateSession(String inputId) {
- if (DEBUG) Log.d(TAG, "onCreateSession(inputId=" + inputId + ")");
- TvInputService.Session impl = new LocalTvInputSessionImpl(this);
- impl.setOverlayViewEnabled(true);
- return impl;
- }
-
- public static List<ChannelInfo> createSampleChannelsStatic() {
- synchronized (LocalTvInputService.class) {
- if (sSampleChannels != null) {
- return sSampleChannels;
- }
- sSampleChannels = new ArrayList<ChannelInfo>();
- sSampleChannels.add(
- new ChannelInfo(CHANNEL_1_NUMBER, CHANNEL_1_NAME, null, 640, 480, 2, false,
- new ProgramInfo(PROGRAM_1_TITLE, null, PROGRAM_1_DESC, 0, 3600,
- null, null, RESOURCE_1)));
-
- sSampleChannels.add(
- new ChannelInfo(CHANNEL_2_NUMBER, CHANNEL_2_NAME, null, 1280, 720, 6, true,
- new ProgramInfo(PROGRAM_2_TITLE, null, PROGRAM_2_DESC, 0, 3600,
- null, null, RESOURCE_2)));
- return sSampleChannels;
- }
- }
-
- @Override
- public List<ChannelInfo> createSampleChannels() {
- return createSampleChannelsStatic();
- }
-
- class LocalTvInputSessionImpl extends BaseTvInputSessionImpl {
- private WebView mWebView;
- private View mUiLayout;
- private boolean mUiVisible;
-
- protected LocalTvInputSessionImpl(Context context) {
- super(context);
- }
-
- public void setUiVisibility(boolean visible) {
- if (mUiVisible == visible) {
- return;
- }
- mUiVisible = visible;
- mUiLayout.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_U) {
- if (mUiVisible) {
- Log.d(TAG, "Abandon UI interactivity");
- setUiVisibility(false);
- } else {
- Log.d(TAG, "Request UI interactivity");
- setUiVisibility(true);
- }
- return true;
- }
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- if (mUiVisible) {
- Log.d(TAG, "Abandon UI interactivity");
- setUiVisibility(false);
- return true;
- }
- }
- return false;
- }
-
- @Override
- public View onCreateOverlayView() {
- LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
- View view = inflater.inflate(R.layout.overlay, null);
- Button closeButton = (Button) view.findViewById(R.id.close_overlay_view);
- closeButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (DEBUG) Log.d(TAG, "Click the close button");
- setUiVisibility(false);
- }
- });
- mUiLayout = view.findViewById(R.id.ui_layout);
- mWebView = (WebView) view.findViewById(R.id.webview);
- mWebView.getSettings().setJavaScriptEnabled(true);
- mWebView.setAlpha(WEBVIEW_ALPHA);
- mWebView.setWebViewClient(new WebViewClient() {
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- view.loadUrl(url);
- return true;
- }
- });
- mWebView.loadUrl(WEBVIEW_SITE);
- mWebView.setVisibility(View.INVISIBLE);
-
- Button webViewToggle = (Button) view.findViewById(R.id.toggle_webview);
- webViewToggle.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (DEBUG) Log.d(TAG, "Toggle WebView");
- mWebView.setVisibility(mWebView.isShown() ? View.INVISIBLE : View.VISIBLE);
- }
- });
- return view;
- }
- }
-}
diff --git a/sample/src/com/example/sampletvinput/SampleTvInputSettingsActivity.java b/sample/src/com/example/sampletvinput/SampleTvInputSettingsActivity.java
deleted file mode 100644
index c8655a36..00000000
--- a/sample/src/com/example/sampletvinput/SampleTvInputSettingsActivity.java
+++ /dev/null
@@ -1,53 +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.example.sampletvinput;
-
-import android.app.Activity;
-import android.media.tv.TvInputInfo;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.LinearLayout;
-
-/**
- * The settings activity for demonstrating TvInput app.
- */
-public class SampleTvInputSettingsActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- LinearLayout layout = new LinearLayout(this);
- layout.setOrientation(LinearLayout.VERTICAL);
- setContentView(layout);
-
- String inputId = getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
- String serviceName = Utils.getServiceNameFromInputId(this, inputId);
- Button btn = new Button(this);
- btn.setText("Settings of " + serviceName);
- btn.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- setResult(Activity.RESULT_OK);
- finish();
- }
- });
-
- layout.addView(btn);
- }
-}
diff --git a/sample/src/com/example/sampletvinput/SampleTvInputSetupActivity.java b/sample/src/com/example/sampletvinput/SampleTvInputSetupActivity.java
deleted file mode 100644
index bdc064f5..00000000
--- a/sample/src/com/example/sampletvinput/SampleTvInputSetupActivity.java
+++ /dev/null
@@ -1,91 +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.example.sampletvinput;
-
-import com.example.sampletvinput.BaseTvInputService.ChannelInfo;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.database.Cursor;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputInfo;
-import android.net.Uri;
-import android.os.Bundle;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * The setup activity for demonstrating TvInput app.
- */
-public class SampleTvInputSetupActivity extends Activity {
- private static final String TAG = SampleTvInputSetupActivity.class.getSimpleName();
- private static final boolean DEBUG = true;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- String inputId = getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
- createSampleChannels(inputId);
- setResult(Activity.RESULT_OK);
- finish();
- }
-
- private void createSampleChannels(String inputId) {
- List<ChannelInfo> channels = null;
- Class clazz = null;
-
- String serviceName = Utils.getServiceNameFromInputId(this, inputId);
-
- if (serviceName.equals(LocalTvInputService.class.getName())) {
- channels = LocalTvInputService.createSampleChannelsStatic();
- clazz = LocalTvInputService.class;
- } else if (serviceName.equals(HlsTvInputService.class.getName())) {
- channels = HlsTvInputService.createSampleChannelsStatic(this);
- clazz = HlsTvInputService.class;
- }
-
- if (channels == null || clazz == null) {
- return;
- }
-
- Uri uri = TvContract.buildChannelsUriForInput(inputId, false);
- String[] projection = { TvContract.Channels._ID };
-
- Cursor cursor = null;
- try {
- while (true) {
- cursor = getContentResolver().query(uri, projection, null, null, null);
- if (cursor != null && cursor.getCount() > 0) {
- return;
- }
- if (cursor != null) {
- cursor.close();
- }
- if (DEBUG) Log.d(TAG, "Couldn't find the channel list. Inserting new channels...");
- // Insert channels into the database. This needs to be done only for the first time.
- ChannelUtils.populateChannels(this, inputId, channels);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-}
diff --git a/src/com/android/tv/ChannelTuner.java b/src/com/android/tv/ChannelTuner.java
new file mode 100644
index 00000000..3b5b631d
--- /dev/null
+++ b/src/com/android/tv/ChannelTuner.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelDataManager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * It manages the current tuned channel among browsable channels. And it determines the next channel
+ * by channel up/down. But, it doesn't actually tune through TvView.
+ */
+public class ChannelTuner {
+ private static final String TAG = "ChannelTuner";
+
+ private static final int INVALID_INDEX = -1;
+
+ private final Context mContext;
+ private boolean mStarted;
+ private boolean mChannelDataManagerLoaded;
+ private final List<Channel> mChannels = new ArrayList<>();
+ private final List<Channel> mBrowsableChannels = new ArrayList<>();
+ private final Map<Long, Channel> mChannelMap = new HashMap<>();
+ // TODO: need to check that mChannelIndexMap can be removed, once mCurrentChannelIndex
+ // is changed to mCurrentChannel(Id).
+ private final Map<Long, Integer> mChannelIndexMap = new HashMap<>();
+
+ private final Handler mHandler = new Handler();
+ private final ChannelDataManager mChannelDataManager;
+ private final Set<Listener> mListeners = new HashSet<>();
+ private Channel mCurrentChannel;
+
+ private final ChannelDataManager.Listener mChannelDataManagerListener =
+ new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ mChannelDataManagerLoaded = true;
+ updateChannelData(mChannelDataManager.getChannelList());
+ for (Listener l : mListeners) {
+ l.onLoadFinished();
+ }
+ }
+
+ @Override
+ public void onChannelListUpdated() {
+ updateChannelData(mChannelDataManager.getChannelList());
+ }
+
+ @Override
+ public void onChannelBrowsableChanged() {
+ updateBrowsableChannels();
+ for (Listener l : mListeners) {
+ l.onBrowsableChannelListChanged();
+ }
+ }
+ };
+
+ public ChannelTuner(Context context, ChannelDataManager channelDataManager) {
+ mContext = context;
+ mChannelDataManager = channelDataManager;
+ }
+
+ /**
+ * Starts ChannelTuner. It cannot be called twice before calling {@link #stop}.
+ */
+ public void start() {
+ if (mStarted) {
+ throw new IllegalStateException("start is called twice");
+ }
+ mStarted = true;
+ mChannelDataManager.addListener(mChannelDataManagerListener);
+ if (mChannelDataManager.isDbLoadFinished()) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mChannelDataManagerListener.onLoadFinished();
+ }
+ });
+ }
+ }
+
+ /**
+ * Stops ChannelTuner.
+ */
+ public void stop() {
+ if (!mStarted) {
+ return;
+ }
+ mStarted = false;
+ mHandler.removeCallbacksAndMessages(null);
+ mChannelDataManager.removeListener(mChannelDataManagerListener);
+ mCurrentChannel = null;
+ mChannels.clear();
+ mBrowsableChannels.clear();
+ mChannelMap.clear();
+ mChannelIndexMap.clear();
+ mChannelDataManagerLoaded = false;
+ }
+
+ /**
+ * Returns true, if all the channels are loaded.
+ */
+ public boolean areAllChannelsLoaded() {
+ return mChannelDataManagerLoaded;
+ }
+
+ /**
+ * Returns browsable channel lists.
+ */
+ public List<Channel> getBrowsableChannelList() {
+ return Collections.unmodifiableList(mBrowsableChannels);
+ }
+
+ /**
+ * Returns the number of browsable channels.
+ */
+ public int getBrowsableChannelCount() {
+ return mBrowsableChannels.size();
+ }
+
+ /**
+ * Returns the current channel.
+ */
+ public Channel getCurrentChannel() {
+ return mCurrentChannel;
+ }
+
+ /**
+ * Sets the current channel. Call this method only when setting the current channel without
+ * actually tuning to it.
+ *
+ * @param currentChannel The new current channel to set to.
+ */
+ public void setCurrentChannel(Channel currentChannel) {
+ mCurrentChannel = currentChannel;
+ }
+
+ /**
+ * Returns the current channel's ID.
+ */
+ public long getCurrentChannelId() {
+ return mCurrentChannel != null ? mCurrentChannel.getId() : Channel.INVALID_ID;
+ }
+
+ /**
+ * Returns the current channel's URI
+ */
+ public Uri getCurrentChannelUri() {
+ if (mCurrentChannel == null) {
+ return null;
+ }
+ if (mCurrentChannel.isPassthrough()) {
+ return TvContract.buildChannelUriForPassthroughInput(mCurrentChannel.getInputId());
+ } else {
+ return TvContract.buildChannelUri(mCurrentChannel.getId());
+ }
+ }
+
+ /**
+ * Returns true, if the current channel is for a passthrough TV input.
+ */
+ public boolean isCurrentChannelPassthrough() {
+ return mCurrentChannel != null && mCurrentChannel.isPassthrough();
+ }
+
+ /**
+ * 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.
+ */
+ public boolean moveToAdjacentBrowsableChannel(boolean up) {
+ Channel channel = getAdjacentBrowsableChannel(up);
+ if (channel == null) {
+ return false;
+ }
+ setCurrentChannelAndNotify(mChannelMap.get(channel.getId()));
+ return true;
+ }
+
+ /**
+ * Returns a next browsable channel. It doesn't change the current channel unlike
+ * {@link #moveToAdjacentBrowsableChannel}.
+ */
+ public Channel getAdjacentBrowsableChannel(boolean up) {
+ if (isCurrentChannelPassthrough() || getBrowsableChannelCount() == 0) {
+ return null;
+ }
+ int channelIndex;
+ if (mCurrentChannel == null) {
+ channelIndex = 0;
+ Channel channel = mChannels.get(channelIndex);
+ if (channel.isBrowsable()) {
+ return channel;
+ }
+ } else {
+ channelIndex = mChannelIndexMap.get(mCurrentChannel.getId());
+ }
+ int size = mChannels.size();
+ for (int i = 0; i < size; ++i) {
+ int nextChannelIndex = up ? channelIndex + 1 + i
+ : channelIndex - 1 - i + size;
+ if (nextChannelIndex >= size) {
+ nextChannelIndex -= size;
+ }
+ Channel channel = mChannels.get(nextChannelIndex);
+ if (channel.isBrowsable()) {
+ return channel;
+ }
+ }
+ Log.e(TAG, "This code should not be reached");
+ return null;
+ }
+
+ /**
+ * Finds the nearest browsable channel from a channel with {@code channelId}. If the channel
+ * with {@code channelId} is browsable, the channel will be returned.
+ */
+ public Channel findNearestBrowsableChannel(long channelId) {
+ if (getBrowsableChannelCount() == 0) {
+ return null;
+ }
+ Channel channel = mChannelMap.get(channelId);
+ if (channel == null) {
+ return mBrowsableChannels.get(0);
+ } else if (channel.isBrowsable()) {
+ return channel;
+ }
+ int index = mChannelIndexMap.get(channelId);
+ int size = mChannels.size();
+ for (int i = 1; i <= size / 2; ++i) {
+ Channel upChannel = mChannels.get((index + i) % size);
+ if (upChannel.isBrowsable()) {
+ return upChannel;
+ }
+ Channel downChannel = mChannels.get((index - i + size) % size);
+ if (downChannel.isBrowsable()) {
+ return downChannel;
+ }
+ }
+ throw new IllegalStateException(
+ "This code should be unreachable in findNearestBrowsableChannel");
+ }
+
+ /**
+ * Moves the current channel to {@code channel}. It can move to a non-browsable channel as well
+ * 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.
+ */
+ public boolean moveToChannel(Channel channel) {
+ if (channel == null) {
+ return false;
+ }
+ if (channel.isPassthrough()) {
+ setCurrentChannelAndNotify(channel);
+ return true;
+ }
+ Channel newChannel = mChannelMap.get(channel.getId());
+ if (newChannel != null) {
+ setCurrentChannelAndNotify(newChannel);
+ return true;
+ } else if (!mChannelDataManagerLoaded) {
+ return loadChannel(channel.getId()) != null;
+ }
+ return false;
+ }
+
+ /**
+ * Resets the current channel to {@code null}.
+ */
+ public void resetCurrentChannel() {
+ setCurrentChannelAndNotify(null);
+ }
+
+ /**
+ * Adds {@link Listener}.
+ */
+ public void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes {@link Listener}.
+ */
+ public void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ public interface Listener {
+ /**
+ * Called when all the channels are loaded.
+ */
+ void onLoadFinished();
+ /**
+ * Called when the browsable channel list is changed.
+ */
+ void onBrowsableChannelListChanged();
+ /**
+ * Called when the current channel is removed.
+ */
+ void onCurrentChannelUnavailable(Channel channel);
+ /**
+ * Called when the current channel is changed.
+ */
+ void onChannelChanged(Channel previousChannel, Channel currentChannel);
+ }
+
+ private void setCurrentChannelAndNotify(Channel channel) {
+ if (mCurrentChannel == channel
+ || (channel != null && channel.hasSameReadOnlyInfo(mCurrentChannel))) {
+ return;
+ }
+ Channel previousChannel = mCurrentChannel;
+ mCurrentChannel = channel;
+ for (Listener l : mListeners) {
+ l.onChannelChanged(previousChannel, mCurrentChannel);
+ }
+ }
+
+ private void updateChannelData(List<Channel> channels) {
+ mChannels.clear();
+ mChannels.addAll(channels);
+
+ mChannelMap.clear();
+ mChannelIndexMap.clear();
+ for (int i = 0; i < channels.size(); ++i) {
+ Channel channel = channels.get(i);
+ long channelId = channel.getId();
+ mChannelMap.put(channelId, channel);
+ mChannelIndexMap.put(channelId, i);
+ }
+ updateBrowsableChannels();
+
+ if (mCurrentChannel != null && !mCurrentChannel.isPassthrough()) {
+ Channel prevChannel = mCurrentChannel;
+ setCurrentChannelAndNotify(mChannelMap.get(mCurrentChannel.getId()));
+ if (mCurrentChannel == null) {
+ for (Listener l : mListeners) {
+ l.onCurrentChannelUnavailable(prevChannel);
+ }
+ }
+ }
+ // TODO: Do not call onBrowsableChannelListChanged, when only non-browsable
+ // channels are changed.
+ for (Listener l : mListeners) {
+ l.onBrowsableChannelListChanged();
+ }
+ }
+
+ private void updateBrowsableChannels() {
+ mBrowsableChannels.clear();
+ for (Channel channel : mChannels) {
+ if (channel.isBrowsable()) {
+ mBrowsableChannels.add(channel);
+ }
+ }
+ }
+
+ /**
+ * Loads and returns a channel which has the given channel ID.
+ *
+ * @param channelId The ID of the channel to be loaded.
+ * @return a channel if it has been loaded. {@code null} if the channel is not found.
+ */
+ public Channel loadChannel(long channelId) {
+ if (channelId < 0) {
+ return null;
+ }
+ if (mChannelDataManagerLoaded) {
+ return mChannelMap.get(channelId);
+ }
+ Channel channel = mChannelMap.get(channelId);
+ if (channel != null) {
+ return channel;
+ }
+
+ Uri uri = TvContract.buildChannelUri(channelId);
+ try (Cursor c = mContext.getContentResolver().query(uri, Channel.PROJECTION,
+ null, null, null)) {
+ if (c != null && c.moveToNext()) {
+ channel = Channel.fromCursor(c);
+ List<Channel> channels = new ArrayList<>(mChannels);
+ channels.add(channel);
+ updateChannelData(channels);
+ return channel;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/tv/LauncherActivity.java b/src/com/android/tv/LauncherActivity.java
new file mode 100644
index 00000000..96742485
--- /dev/null
+++ b/src/com/android/tv/LauncherActivity.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;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * An activity to launch a new activity.
+ *
+ * <p>In the case when {@link MainActivity} starts a new activity using
+ * {@link Activity#startActivity} or {@link Activity#startActivityForResult}, Live Channels 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";
+
+ private static final int REQUEST_CODE_DEFAULT = 0;
+ private static final int REQUEST_START_ACTIVITY = 100;
+
+ private static final String EXTRA_INTENT = "com.android.tv.LauncherActivity.INTENT";
+ private static final String EXTRA_REQUEST_RESULT =
+ "com.android.tv.LauncherActivity.REQUEST_RESULT";
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Starts an activity by calling {@link Activity#startActivityForResult}.
+ *
+ * <p>Note: {@code requestCode} should not be 0. The value is reserved for internal use.
+ */
+ public static void startActivityForResultSafe(Activity baseActivity, Intent intentToLaunch,
+ int requestCode) {
+ if (requestCode == REQUEST_CODE_DEFAULT) {
+ throw new IllegalArgumentException("requestCode should not be 0.");
+ }
+ // To avoid the app termination when the new activity crashes, LauncherActivity should be
+ // started by calling startActivityForResult().
+ baseActivity.startActivityForResult(createIntent(baseActivity, intentToLaunch, true),
+ requestCode);
+ }
+
+ private static Intent createIntent(Context context, Intent intentToLaunch,
+ boolean requestResult) {
+ Intent intent = new Intent(context, LauncherActivity.class);
+ intent.putExtra(EXTRA_INTENT, intentToLaunch);
+ if (requestResult) {
+ intent.putExtra(EXTRA_REQUEST_RESULT, true);
+ }
+ return intent;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // We should launch the new activity in onCreate rather than in onStart.
+ // That's because it is not guaranteed that onStart is called only once.
+ Intent intent = getIntent().getParcelableExtra(EXTRA_INTENT);
+ boolean requstResult = getIntent().getBooleanExtra(EXTRA_REQUEST_RESULT, false);
+ try {
+ if (requstResult) {
+ startActivityForResult(intent, REQUEST_START_ACTIVITY);
+ } else {
+ startActivity(intent);
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Activity not found for " + intent);
+ intent.putExtra(ERROR_MESSAGE,
+ getResources().getString(R.string.msg_missing_app));
+ setResult(Activity.RESULT_CANCELED, intent);
+ finish();
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ setResult(resultCode, data);
+ finish();
+ }
+}
diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java
new file mode 100644
index 00000000..b2fed7aa
--- /dev/null
+++ b/src/com/android/tv/MainActivity.java
@@ -0,0 +1,2757 @@
+/*
+ * 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.app.Activity;
+import android.app.ActivityManager;
+import android.app.FragmentTransaction;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.media.AudioManager;
+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;
+import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
+import android.media.tv.TvView.OnUnhandledInputEventListener;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Settings;
+import android.support.annotation.IntDef;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+
+import com.android.tv.analytics.DurationTimer;
+import com.android.tv.analytics.Tracker;
+import com.android.tv.customization.TvCustomizationManager;
+import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.OnCurrentProgramUpdatedListener;
+import com.android.tv.data.Program;
+import com.android.tv.data.ProgramDataManager;
+import com.android.tv.data.StreamInfo;
+import com.android.tv.dialog.PinDialogFragment;
+import com.android.tv.dialog.SafeDismissDialogFragment;
+import com.android.tv.menu.MenuView;
+import com.android.tv.parental.ContentRatingsManager;
+import com.android.tv.parental.ParentalControlSettings;
+import com.android.tv.receiver.AudioCapabilitiesReceiver;
+import com.android.tv.recommendation.NotificationService;
+import com.android.tv.search.ProgramGuideSearchFragment;
+import com.android.tv.ui.AppLayerTvView;
+import com.android.tv.ui.ChannelBannerView;
+import com.android.tv.ui.InputBannerView;
+import com.android.tv.ui.KeypadChannelSwitchView;
+import com.android.tv.ui.OverlayRootView;
+import com.android.tv.ui.SelectInputView;
+import com.android.tv.ui.TunableTvView;
+import com.android.tv.ui.TunableTvView.OnTuneListener;
+import com.android.tv.ui.TvOverlayManager;
+import com.android.tv.ui.TvTransitionManager;
+import com.android.tv.ui.TvViewUiManager;
+import com.android.tv.ui.sidepanel.ChannelSourcesFragment;
+import com.android.tv.ui.sidepanel.ClosedCaptionFragment;
+import com.android.tv.ui.sidepanel.CustomizeChannelListFragment;
+import com.android.tv.ui.sidepanel.DebugOptionFragment;
+import com.android.tv.ui.sidepanel.DisplayModeFragment;
+import com.android.tv.ui.sidepanel.MultiAudioFragment;
+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.ImageCache;
+import com.android.tv.util.PipInputManager;
+import com.android.tv.util.PipInputManager.PipInput;
+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 java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The main activity for the Live Channels app.
+ */
+public class MainActivity extends Activity implements AudioManager.OnAudioFocusChangeListener {
+ 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})
+ 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;
+ public static final int KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY = 3;
+
+ 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;
+
+ // Tracker screen names.
+ public static final String SCREEN_NAME = "Main";
+ private static final String SCREEN_BEHIND_NAME = "Behind";
+
+ private static final float REFRESH_RATE_EPSILON = 0.01f;
+ private static final HashSet<Integer> BLACKLIST_KEYCODE_TO_TIS;
+ // These keys won't be passed to TIS in addition to gamepad buttons.
+ static {
+ BLACKLIST_KEYCODE_TO_TIS = new HashSet<>();
+ BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_TV_INPUT);
+ BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MENU);
+ BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_UP);
+ BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_DOWN);
+ BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_UP);
+ BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_DOWN);
+ BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_MUTE);
+ BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MUTE);
+ BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_SEARCH);
+ }
+
+ 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 boolean USE_ACCELERATION_IN_CHANNEL_CHANGE = true;
+ 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 {}
+ private static final int UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW = 1;
+ private static final int UPDATE_CHANNEL_BANNER_REASON_TUNE = 2;
+ private static final int UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST = 3;
+ private static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO = 4;
+ private static final int UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK = 5;
+
+ private static final int TVVIEW_SET_MAIN_TIMEOUT_MS = 3000;
+
+ // Lazy initialization.
+ // Delay 1 second in order not to interrupt the first tune.
+ private static final long LAZY_INITIALIZATION_DELAY = TimeUnit.SECONDS.toMillis(1);
+
+ 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;
+ private Tracker mTracker;
+ private final DurationTimer mMainDurationTimer = new DurationTimer();
+ private final DurationTimer mTuneDurationTimer = new DurationTimer();
+
+ private TunableTvView mTvView;
+ private TunableTvView mPipView;
+ private OverlayRootView mOverlayRootView;
+ private Bundle mTuneParams;
+ private TvCustomizationManager mTvCustomizationManager;
+ private boolean mChannelBannerHiddenBySideFragment;
+ // TODO: Move the scene views into TvTransitionManager or TvOverlayManager.
+ private ChannelBannerView mChannelBannerView;
+ private KeypadChannelSwitchView mKeypadChannelSwitchView;
+ private Uri mInitChannelUri;
+ private boolean mShowProgramGuide;
+ private boolean mShowSelectInputView;
+ private TvInputInfo mInputToSetUp;
+ private final List<MemoryManageable> mMemoryManageables = new ArrayList<>();
+ private MediaSession mMediaSession;
+
+ private String mInputIdUnderSetup;
+ private boolean mIsSetupActivityCalledByDialog;
+ private AudioManager mAudioManager;
+ private int mAudioFocusStatus;
+ 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;
+ private boolean mLaunchedByLauncher;
+ private boolean mUseKeycodeBlacklist;
+ private boolean mShowLockedChannelsTemporarily;
+ private boolean mBackKeyPressed;
+ private boolean mNeedShowBackKeyGuide;
+ private boolean mVisibleBehind;
+
+ private boolean mIsFilmModeSet;
+ private float mDefaultRefreshRate;
+
+ // TODO: Merge the TvTransitionManager into TvOverlayManager because the scene is a kind of
+ // overlay.
+ private TvTransitionManager mTransitionManager;
+ private TvOverlayManager mOverlayManager;
+
+ // mIsCurrentChannelUnblockedByUser and mWasChannelUnblockedBeforeShrunkenByUser are used for
+ // keeping the channel unblocking status while TV view is shrunken.
+ 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;
+
+ private CaptionSettings mCaptionSettings;
+ // Lazy initialization
+ private boolean mLazyInitialized;
+
+ private static final int MAX_RECENT_CHANNELS = 5;
+ private final ArrayDeque<Long> mRecentChannels = new ArrayDeque<>(MAX_RECENT_CHANNELS);
+
+ private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver;
+
+ // A caller which started this activity. (e.g. TvSearch)
+ private String mSource;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CHANNEL_DOWN_PRESSED:
+ long startTime = (Long) msg.obj;
+ moveToAdjacentChannel(false, true);
+ mHandler.sendMessageDelayed(Message.obtain(msg), getDelay(startTime));
+ break;
+ case MSG_CHANNEL_UP_PRESSED:
+ startTime = (Long) msg.obj;
+ moveToAdjacentChannel(true, true);
+ mHandler.sendMessageDelayed(Message.obtain(msg), getDelay(startTime));
+ break;
+ case MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE:
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
+ break;
+ }
+ }
+
+ private long getDelay(long startTime) {
+ if (USE_ACCELERATION_IN_CHANNEL_CHANGE && System.currentTimeMillis() - startTime >
+ CHANNEL_CHANGE_NORMAL_SPEED_DURATION_MS) {
+ return CHANNEL_CHANGE_DELAY_MS_IN_MAX_SPEED;
+ }
+ return CHANNEL_CHANGE_DELAY_MS_IN_NORMAL_SPEED;
+ }
+ };
+
+ 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.
+ mInitChannelUri = mChannelTuner.getCurrentChannelUri();
+ 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();
+ }
+ }
+ };
+
+ 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) {
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
+ }
+ }
+ };
+
+ private final ChannelTuner.Listener mChannelTunerListener =
+ new ChannelTuner.Listener() {
+ @Override
+ public void onLoadFinished() {
+ if (mActivityResumed) {
+ resumeTvIfNeeded();
+ resumePipIfNeeded();
+ }
+ mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList());
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mOverlayManager.getMenuView().setChannelTuner(mChannelTuner);
+ }
+ });
+ }
+
+ @Override
+ public void onBrowsableChannelListChanged() {
+ mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList());
+ }
+
+ @Override
+ public void onCurrentChannelUnavailable(Channel channel) {
+ // TODO: handle the case that a channel is suddenly removed from DB.
+ }
+
+ @Override
+ public void onChannelChanged(Channel previousChannel, Channel currentChannel) {
+ }
+ };
+
+ private final Runnable mRestoreMainViewRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ restoreMainTvView();
+ }
+ };
+ private ProgramGuideSearchFragment mSearchFragment;
+
+ private void applyParentalControlSettings() {
+ boolean parentalControlEnabled = mTvInputManagerHelper.getParentalControlSettings()
+ .isParentalControlsEnabled();
+ mTvView.onParentalControlChanged(parentalControlEnabled);
+ mPipView.onParentalControlChanged(parentalControlEnabled);
+ mTvOptionsManager.onParentalControlChanged(parentalControlEnabled);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ if (DEBUG) Log.d(TAG,"onCreate");
+ super.onCreate(savedInstanceState);
+
+ TvApplication tvApplication = (TvApplication) getApplication();
+ tvApplication.setMainActivity(this);
+ mTracker = tvApplication.getTracker();
+ mTvInputManagerHelper = new TvInputManagerHelper(this);
+ mTvInputManagerHelper.start();
+ mChannelDataManager = new ChannelDataManager(this, mTvInputManagerHelper);
+ mProgramDataManager = new ProgramDataManager(this);
+ mProgramDataManager.addOnCurrentProgramUpdatedListener(Channel.INVALID_ID,
+ mOnCurrentProgramUpdatedListener);
+ mChannelTuner = new ChannelTuner(this, mChannelDataManager);
+ mChannelTuner.addListener(mChannelTunerListener);
+ mChannelTuner.start();
+ mPipInputManager = new PipInputManager(this, mTvInputManagerHelper, mChannelTuner);
+ mPipInputManager.start();
+ mMemoryManageables.add(mProgramDataManager);
+ mMemoryManageables.add(ImageCache.getInstance());
+
+ 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();
+
+ mOverlayRootView = (OverlayRootView) getLayoutInflater().inflate(
+ R.layout.overlay_root_view, null, false);
+ setContentView(R.layout.activity_tv);
+ 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.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;
+ }
+ });
+ mTimeShiftManager = new TimeShiftManager(this, mTvView, mProgramDataManager,
+ new OnCurrentProgramUpdatedListener() {
+ @Override
+ public void onCurrentProgramUpdated(long channelId, Program 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);
+ break;
+ default:
+ updateChannelBannerAndShowIfNeeded(
+ 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);
+
+ mTvViewUiManager = new TvViewUiManager(this, mTvView, mPipView,
+ (FrameLayout) findViewById(android.R.id.content), mTvOptionsManager);
+
+ mPipView.setFixedSurfaceSize(screenWidth / 2, screenHeight / 2);
+ mPipView.setBlockScreenType(TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW);
+
+ ViewGroup sceneContainer = (ViewGroup) findViewById(R.id.scene_container);
+ mChannelBannerView = (ChannelBannerView) getLayoutInflater().inflate(
+ R.layout.channel_banner, sceneContainer, false);
+ mKeypadChannelSwitchView = (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);
+ mTransitionManager = new TvTransitionManager(this, sceneContainer, mChannelBannerView,
+ inputBannerView, mKeypadChannelSwitchView, selectInputView);
+ mSearchFragment = new ProgramGuideSearchFragment();
+ mOverlayManager = new TvOverlayManager(this, mChannelTuner, mTransitionManager,
+ mKeypadChannelSwitchView, selectInputView, mSearchFragment);
+
+ mTvCustomizationManager = new TvCustomizationManager(this);
+ mTvCustomizationManager.initialize();
+
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
+
+ mTvViewUiManager.restoreDisplayMode(false);
+ if (!handleIntent(getIntent())) {
+ finish();
+ return;
+ }
+ if (!TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) {
+ // Preloads the initial channel to reduce the initial tuning time.
+ Channel channel = loadInitialChannel(mInitChannelUri);
+ mInitChannelUri = channel == null ? null : channel.getUri();
+ }
+ // mChannelDataManager.start() and mProgramDataManager.start() are called
+ // after loadInitialChannel. Unless, loadInitialChannel can be blocked
+ // by channel data and program data loading.
+ mChannelDataManager.start();
+ mProgramDataManager.start();
+
+ mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(this, mTracker);
+ mAudioCapabilitiesReceiver.register();
+
+ mAccessibilityManager =
+ (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+ initForTest();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, 0, PixelFormat.TRANSPARENT);
+ windowParams.token = getWindow().getDecorView().getWindowToken();
+ ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).addView(mOverlayRootView,
+ windowParams);
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).removeView(mOverlayRootView);
+ }
+
+ private int getDesiredBlockScreenType() {
+ if (!mActivityResumed) {
+ return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
+ }
+ if (isUnderShrunkenTvView()) {
+ return TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW;
+ }
+ if (mOverlayManager.getSideFragmentManager().isActive()
+ || mOverlayManager.getMenuView().isActive()
+ || mTransitionManager.isKeypadChannelSwitchActive()
+ || mTransitionManager.isSelectInputActive()) {
+ return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
+ }
+ SafeDismissDialogFragment currentDialog = mOverlayManager.getCurrentDialog();
+ if (currentDialog != null) {
+ // If PIN dialog is shown for unblocking the channel lock or content ratings lock,
+ // keeping the unlocking message is more natural instead of changing it.
+ if (currentDialog instanceof PinDialogFragment) {
+ int type = ((PinDialogFragment) currentDialog).getType();
+ if (type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_CHANNEL
+ || type == PinDialogFragment.PIN_DIALOG_TYPE_UNLOCK_PROGRAM) {
+ return TunableTvView.BLOCK_SCREEN_TYPE_NORMAL;
+ }
+ }
+ return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
+ }
+ return TunableTvView.BLOCK_SCREEN_TYPE_NORMAL;
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ mOverlayManager.getSideFragmentManager().hideAll(false);
+ if (!handleIntent(intent) && !mActivityStarted) {
+ // If the activity is stopped and not destroyed, finish the activity.
+ // Otherwise, just ignore the intent.
+ finish();
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mActivityStarted = true;
+ mTracker.sendMainStart();
+ 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);
+
+ Intent notificationIntent = new Intent(this, NotificationService.class);
+ notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION);
+ startService(notificationIntent);
+
+ mOverlayManager.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (DEBUG) Log.d(TAG, "onResume()");
+ mTracker.sendScreenView(SCREEN_NAME);
+
+ SystemProperties.updateSystemProperties();
+ mNeedShowBackKeyGuide = true;
+ mActivityResumed = true;
+ 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();
+
+ if (mTvView.isPlaying()) {
+ // Every time onResume() is called the activity will be assumed to not have requested
+ // visible behind.
+ requestVisibleBehind(true);
+ }
+ 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.
+ if (mInputToSetUp != null) {
+ startSetupActivity(mInputToSetUp, false);
+ 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();
+ }
+ });
+ } 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() {
+ showSelectInputView();
+ }
+ });
+ }
+ if (mMediaSession == null) {
+ mMediaSession = new MediaSession(this, MEDIA_SESSION_TAG);
+ PlaybackState playbackState = new PlaybackState.Builder()
+ .setActions(PlaybackState.ACTION_PAUSE
+ | PlaybackState.ACTION_PLAY
+ | PlaybackState.ACTION_PLAY_PAUSE
+ | PlaybackState.ACTION_FAST_FORWARD
+ | PlaybackState.ACTION_REWIND
+ | PlaybackState.ACTION_SKIP_TO_NEXT
+ | PlaybackState.ACTION_SKIP_TO_PREVIOUS
+ | PlaybackState.ACTION_STOP)
+ .setState(PlaybackState.STATE_PLAYING, 0, 1)
+ .build();
+ mMediaSession.setPlaybackState(playbackState);
+ mMediaSession.setCallback(new MediaSession.Callback() {
+ @Override
+ public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+ // Consume media button events to avoid to send the events to another app.
+ return true;
+ }
+ });
+ mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
+ mMediaSession.setActive(true);
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ if (DEBUG) Log.d(TAG, "onPause()");
+ finishChannelChangeIfNeeded();
+ 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;
+ mLaunchedByLauncher = false;
+ if (!mVisibleBehind) {
+ mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
+ mAudioManager.abandonAudioFocus(this);
+ mTracker.sendScreenView("");
+ } else {
+ mTracker.sendScreenView(SCREEN_BEHIND_NAME);
+ }
+ super.onPause();
+ }
+
+ /**
+ * Returns true if {@link #onResume} is called and {@link #onPause} is not called yet.
+ */
+ public boolean isActivityResumed() {
+ return mActivityResumed;
+ }
+
+ @Override
+ public boolean requestVisibleBehind(boolean enable) {
+ boolean state = super.requestVisibleBehind(enable);
+ mVisibleBehind = state;
+ return state;
+ }
+
+ private void resumeTvIfNeeded() {
+ if (DEBUG) Log.d(TAG, "resumeTvIfNeeded()");
+ if (!mTvView.isPlaying() || mInitChannelUri != null
+ || (mLaunchedByLauncher && mChannelTuner.isCurrentChannelPassthrough())) {
+ startTv(mInitChannelUri);
+ mInitChannelUri = null;
+ }
+ // Make sure TV app has the main TV view to handle the case that TvView is used in other
+ // application.
+ restoreMainTvView();
+ 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 Channel loadInitialChannel(Uri channelUri) {
+ if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
+ throw new IllegalArgumentException("channelUri should be null or tuner input channel");
+ }
+ if (channelUri == null) {
+ // If any initial channel id is not given, remember the last channel the user watched.
+ long channelId = Utils.getLastWatchedChannelId(this);
+ if (channelId != Channel.INVALID_ID) {
+ channelUri = TvContract.buildChannelUri(channelId);
+ }
+ }
+ if (channelUri == null) {
+ return null;
+ }
+ long channelId = ContentUris.parseId(channelUri);
+ Channel channel = mChannelTuner.loadChannel(channelId);
+ if (channel == null) {
+ // If the requested channel doesn't exist, it's better to tune to the
+ // last watched channel.
+ channel = mChannelTuner.loadChannel(Utils.getLastWatchedChannelId(this));
+ Log.w(TAG, "The requested channel (id=" + channelId + ") doesn't exist. "
+ + "The last watched channel will be tuned to.");
+ }
+ return channel;
+ }
+
+ private void startTv(Uri channelUri) {
+ if (DEBUG) Log.d(TAG, "startTv Uri=" + channelUri);
+ if ((channelUri == null || !TvContract.isChannelUriForPassthroughInput(channelUri))
+ && mChannelTuner.isCurrentChannelPassthrough()) {
+ // For passthrough TV input, channelUri is always given. If TV app is launched
+ // by TV app icon in a launcher, channelUri is null. So if passthrough TV input
+ // is playing, we stop the passthrough TV input.
+ stopTv();
+ }
+ if (mTvView.isPlaying()) {
+ // TV has already started.
+ if (channelUri == null) {
+ // Simply adjust the volume without tune.
+ setVolumeByAudioFocusStatus();
+ return;
+ }
+ if (channelUri.equals(mChannelTuner.getCurrentChannelUri())) {
+ // The requested channel is already tuned.
+ setVolumeByAudioFocusStatus();
+ return;
+ }
+ stopTv();
+ }
+ if (mChannelTuner.getCurrentChannel() != null) {
+ Log.w(TAG, "The current channel should be reset before");
+ mChannelTuner.resetCurrentChannel();
+ }
+ if (channelUri == null) {
+ // If any initial channel id is not given, remember the last channel the user watched.
+ long channelId = Utils.getLastWatchedChannelId(this);
+ if (channelId != Channel.INVALID_ID) {
+ channelUri = TvContract.buildChannelUri(channelId);
+ }
+ }
+
+ if (channelUri == null) {
+ mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0));
+ } else {
+ if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
+ Channel channel = Channel.createPassthroughChannel(channelUri);
+ mChannelTuner.moveToChannel(channel);
+ } else {
+ Channel channel = loadInitialChannel(channelUri);
+ if (channel == null || !mChannelTuner.moveToChannel(channel)) {
+ mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0));
+ long channelId = (channel == null) ? ContentUris.parseId(channelUri)
+ : channel.getId();
+ Log.w(TAG, "The requested channel (id=" + channelId + ") doesn't exist. "
+ + "The first channel will be tuned to.");
+ }
+ }
+ }
+
+ mTvView.start(mTvInputManagerHelper);
+ setVolumeByAudioFocusStatus();
+ tune();
+ }
+
+ @Override
+ protected void onStop() {
+ if (DEBUG) Log.d(TAG, "onStop()");
+ if (mMediaSession != null) {
+ mMediaSession.release();
+ mMediaSession = null;
+ }
+ mActivityStarted = false;
+ mOverlayManager.onStop();
+ stopAll(false);
+ unregisterReceiver(mBroadcastReceiver);
+ mTracker.sendMainStop(mMainDurationTimer.reset());
+ super.onStop();
+ }
+
+ private void stopAll(boolean keepVisibleBehind) {
+ mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION);
+ stopTv("stopAll()", keepVisibleBehind);
+ stopPip();
+ }
+
+ public TvInputManagerHelper getTvInputManagerHelper() {
+ return mTvInputManagerHelper;
+ }
+
+ /**
+ * Starts setup activity for the given input {@code input}.
+ *
+ * @param calledByDialog If true, startSetupActivity is invoked from the setup dialog.
+ */
+ public boolean startSetupActivity(TvInputInfo input, boolean calledByDialog) {
+ Intent intent = input.createSetupIntent();
+ if (intent == null) {
+ Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ 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);
+
+ mInputIdUnderSetup = input.getId();
+ mIsSetupActivityCalledByDialog = calledByDialog;
+ startActivityForResultSafe(intent, REQUEST_CODE_START_SETUP_ACTIVITY);
+ SetupUtils.getInstance(this).onSetupLaunched(input.getId());
+ } catch (ActivityNotFoundException e) {
+ mInputIdUnderSetup = null;
+ Toast.makeText(this, getString(R.string.msg_unable_to_start_setup_activity,
+ input.loadLabel(this)), Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ if (calledByDialog) {
+ mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG);
+ } else {
+ mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY);
+ }
+ stopTv("startSetupActivity()", false);
+ return true;
+ }
+
+ public boolean hasCaptioningSettingsActivity() {
+ return Utils.isIntentAvailable(this, new Intent(Settings.ACTION_CAPTIONING_SETTINGS));
+ }
+
+ 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);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, getString(R.string.msg_unable_to_start_system_captioning_settings),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ public ChannelDataManager getChannelDataManager() {
+ return mChannelDataManager;
+ }
+
+ public ProgramDataManager getProgramDataManager() {
+ return mProgramDataManager;
+ }
+
+ public PipInputManager getPipInputManager() {
+ return mPipInputManager;
+ }
+
+ public TvOptionsManager getTvOptionsManager() {
+ return mTvOptionsManager;
+ }
+
+ public TvViewUiManager getTvViewUiManager() {
+ return mTvViewUiManager;
+ }
+
+ public TimeShiftManager getTimeShiftManager() {
+ return mTimeShiftManager;
+ }
+
+ /**
+ * Returns the instance of {@link TvOverlayManager}.
+ */
+ public TvOverlayManager getOverlayManager() {
+ return mOverlayManager;
+ }
+
+ public Channel getCurrentChannel() {
+ return mChannelTuner.getCurrentChannel();
+ }
+
+ public long getCurrentChannelId() {
+ return mChannelTuner.getCurrentChannelId();
+ }
+
+ /**
+ * Returns the current program which the user is watching right now.<p>
+ *
+ * If the time shifting is available, it can be a past program.
+ */
+ public Program getCurrentProgram() {
+ if (mTimeShiftManager.isAvailable()) {
+ return mTimeShiftManager.getCurrentProgram();
+ }
+ return mProgramDataManager.getCurrentProgram(getCurrentChannelId());
+ }
+
+ /**
+ * Returns the current playing time in milliseconds.<p>
+ *
+ * If the time shifting is available, the time is the playing position of the program,
+ * otherwise, the system current time.
+ */
+ public long getCurrentPlayingPosition() {
+ if (mTimeShiftManager.isAvailable()) {
+ return mTimeShiftManager.getCurrentPositionMs();
+ }
+ return System.currentTimeMillis();
+ }
+
+ public 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;
+ } else {
+ return mChannelTuner.getAdjacentBrowsableChannel(true);
+ }
+ }
+
+ /**
+ * Call {@link Activity#startActivity} in a safe way.
+ *
+ * @see LauncherActivity
+ */
+ public void startActivitySafe(Intent intent) {
+ 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 channel sources fragment.
+ */
+ public void showChannelSourcesFragment() {
+ if (!mChannelTuner.areAllChannelsLoaded()) {
+ // 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 ChannelSourcesFragment(channelId));
+ }
+
+ // TODO: Refactor this.
+ public void showParentalControlFragment() {
+ mOverlayManager.showDialogFragment(PinDialogFragment.DIALOG_TAG,
+ new PinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN,
+ new PinDialogFragment.ResultListener() {
+ @Override
+ public void done(boolean success) {
+ if (success) {
+ mOverlayManager.getSideFragmentManager()
+ .show(new ParentalControlsFragment());
+ }
+ }
+ }), false);
+ }
+
+ /**
+ * It is called when shrunken TvView is desired, such as EditChannelFragment and
+ * ChannelsLockedFragment.
+ */
+ public void startShrunkenTvView(boolean showLockedChannelsTemporarily,
+ boolean willMainViewBeTunerInput) {
+ 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);
+ }
+
+ mTvView.setBlockScreenType(getDesiredBlockScreenType());
+ }
+
+ /**
+ * It is called when shrunken TvView is no longer desired, such as EditChannelFragment and
+ * ChannelsLockedFragment.
+ */
+ public void endShrunkenTvView() {
+ mTvViewUiManager.endShrunkenTvView();
+ mIsCompletingShrunkenTvView = true;
+
+ Channel returnChannel = mChannelBeforeShrunkenTvView;
+ if (returnChannel == null
+ || (!returnChannel.isPassthrough() && !returnChannel.isBrowsable())) {
+ // Try to tune to the next best channel instead.
+ returnChannel = getBrowsableChannel();
+ }
+ mShowLockedChannelsTemporarily = false;
+
+ // 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());
+ if (mPipChannelBeforeShrunkenTvView != null) {
+ enablePipView(true, false);
+ mPipChannelBeforeShrunkenTvView = null;
+ }
+ }
+ };
+ mTvViewUiManager.fadeOutTvView(tuneAction);
+ // Will automatically fade-in when video becomes available.
+ } else {
+ checkChannelLockNeeded(mTvView);
+ mIsCompletingShrunkenTvView = false;
+ mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser;
+ mTvView.setBlockScreenType(getDesiredBlockScreenType());
+ if (mPipChannelBeforeShrunkenTvView != null) {
+ enablePipView(true, false);
+ mPipChannelBeforeShrunkenTvView = null;
+ }
+ }
+ }
+
+ private boolean isUnderShrunkenTvView() {
+ return mTvViewUiManager.isUnderShrunkenTvView() || mIsCompletingShrunkenTvView;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQUEST_CODE_START_SETUP_ACTIVITY:
+ if (resultCode == Activity.RESULT_OK) {
+ final String inputId = mInputIdUnderSetup;
+ // 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
+ // list again and make all channels of {@code inputId} browsable.
+ mChannelDataManager.updateChannels(new Runnable() {
+ @Override
+ public void run() {
+ int count = 0;
+ boolean browsableChanged = false;
+ for (Channel channel : mChannelDataManager.getChannelList()) {
+ if (channel.getInputId().equals(inputId)) {
+ if (!channel.isBrowsable()) {
+ mChannelDataManager.updateBrowsable(channel.getId(), true,
+ true);
+ browsableChanged = true;
+ }
+ ++count;
+ }
+ }
+ if (browsableChanged) {
+ mChannelDataManager.notifyChannelBrowsableChanged();
+ mChannelDataManager.applyUpdatedValuesToDb();
+ }
+ 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();
+ }
+ }
+ });
+ } else {
+ mInputIdUnderSetup = null;
+ }
+ if (!mIsSetupActivityCalledByDialog) {
+ mOverlayManager.getSideFragmentManager().showSidePanel(false);
+ }
+ break;
+ case REQUEST_CODE_START_SYSTEM_CAPTIONING_SETTINGS:
+ mOverlayManager.getSideFragmentManager().showSidePanel(false);
+ break;
+ }
+ if (data != null) {
+ String errorMessage = data.getStringExtra(LauncherActivity.ERROR_MESSAGE);
+ if (!TextUtils.isEmpty(errorMessage)) {
+ Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ @Override
+ public View findViewById(int id) {
+ // In order to locate fragments in non-application window, we should override findViewById.
+ // Internally, Activity.findViewById is called to attach a view of a fragment into its
+ // container. Without the override, we'll get crash during the fragment attachment.
+ View v = mOverlayRootView != null ? mOverlayRootView.findViewById(id) : null;
+ return v == null ? super.findViewById(id) : v;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (SystemProperties.LOG_KEYEVENT.getValue()) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
+ // If an activity is closed on a back key down event, back key down events with none zero
+ // repeat count or a back key up event can be happened without the first back key down
+ // event which should be ignored in this activity.
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+ mBackKeyPressed = true;
+ }
+ if (!mBackKeyPressed) {
+ return true;
+ }
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ mBackKeyPressed = false;
+ }
+ }
+
+ // When side panel is closing, it has the focus.
+ // Keep the focus, but just don't deliver the key events.
+ if ((mOverlayRootView.hasFocusable()
+ && !mOverlayManager.getSideFragmentManager().isHiding())
+ || mOverlayManager.getSideFragmentManager().isActive()) {
+ return super.dispatchKeyEvent(event);
+ }
+ if (BLACKLIST_KEYCODE_TO_TIS.contains(event.getKeyCode())
+ || KeyEvent.isGamepadButton(event.getKeyCode())) {
+ // If the event is in blacklisted or gamepad key, do not pass it to session.
+ // Gamepad keys are blacklisted to support TV UIs and here's the detail.
+ // If there's a TIS granted RECEIVE_INPUT_EVENT, TIF sends key events to TIS
+ // and return immediately saying that the event is handled.
+ // In this case, fallback key will be injected but with FLAG_CANCELED
+ // while gamepads support DPAD_CENTER and BACK by fallback.
+ // Since we don't expect that TIS want to handle gamepad buttons now,
+ // blacklist gamepad buttons and wait for next fallback keys.
+ // 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.
+ */
+ public void updateKeyInputFocus() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mTvView.setBlockScreenType(getDesiredBlockScreenType());
+ }
+ });
+ }
+
+ // It should be called before onResume.
+ private boolean handleIntent(Intent intent) {
+ // Reset the closed caption settings when the activity is 1)created or 2) restarted.
+ // And do not reset while TvView is playing.
+ 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;
+ }
+ mLaunchedByLauncher = intent.getBooleanExtra(Utils.EXTRA_KEY_FROM_LAUNCHER, false);
+ mInitChannelUri = null;
+
+ String extraAction = intent.getStringExtra(Utils.EXTRA_KEY_ACTION);
+ if (!TextUtils.isEmpty(extraAction)) {
+ if (DEBUG) Log.d(TAG, "Got an extra action: " + extraAction);
+ if (Utils.EXTRA_ACTION_SHOW_TV_INPUT.equals(extraAction)) {
+ String lastWatchedChannelUri = Utils.getLastWatchedChannelUri(this);
+ if (lastWatchedChannelUri != null) {
+ mInitChannelUri = Uri.parse(lastWatchedChannelUri);
+ }
+ mShowSelectInputView = true;
+ }
+ }
+
+ 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.
+ mShowProgramGuide = true;
+ return true;
+ }
+ // In case the channel is given explicitly, use it.
+ mInitChannelUri = uri;
+ if (DEBUG) Log.d(TAG, "ACTION_VIEW with " + mInitChannelUri);
+ if (Channels.CONTENT_URI.equals(mInitChannelUri)) {
+ // Tune to default channel.
+ mInitChannelUri = null;
+ return true;
+ }
+ if ((!Utils.isChannelUriForOneChannel(mInitChannelUri)
+ && !Utils.isChannelUriForInput(mInitChannelUri))) {
+ Log.w(TAG, "Malformed channel uri " + mInitChannelUri
+ + " tuning to default instead");
+ mInitChannelUri = null;
+ return true;
+ }
+ mTuneParams = intent.getExtras();
+ if (mTuneParams == null) {
+ mTuneParams = new Bundle();
+ }
+ if (Utils.isChannelUriForTunerInput(mInitChannelUri)) {
+ long channelId = ContentUris.parseId(mInitChannelUri);
+ mTuneParams.putLong(KEY_INIT_CHANNEL_ID, channelId);
+ } else if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) {
+ // If mInitChannelUri is for a passthrough TV input.
+ String inputId = mInitChannelUri.getPathSegments().get(1);
+ TvInputInfo input = mTvInputManagerHelper.getTvInputInfo(inputId);
+ if (input == null) {
+ mInitChannelUri = null;
+ Toast.makeText(this, R.string.msg_no_specific_input, Toast.LENGTH_SHORT).show();
+ return false;
+ } else if (!input.isPassthroughInput()) {
+ mInitChannelUri = null;
+ Toast.makeText(this, R.string.msg_not_passthrough_input, Toast.LENGTH_SHORT)
+ .show();
+ return false;
+ }
+ } 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 };
+ try (Cursor cursor = getContentResolver().query(uri, projection,
+ null, null, null)) {
+ if (cursor != null && cursor.moveToNext()) {
+ channelId = cursor.getLong(0);
+ }
+ }
+ }
+ if (channelId == Channel.INVALID_ID) {
+ // Couldn't find any channel probably because the input hasn't been set up.
+ // Try to set it up.
+ mInitChannelUri = null;
+ mInputToSetUp = mTvInputManagerHelper.getTvInputInfo(inputId);
+ } else {
+ mInitChannelUri = TvContract.buildChannelUri(channelId);
+ mTuneParams.putLong(KEY_INIT_CHANNEL_ID, channelId);
+ }
+ }
+ }
+ 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) {
+ 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;
+ }
+ }
+ }
+
+ private void stopTv() {
+ stopTv(null, false);
+ }
+
+ private void stopTv(String logForCaller, boolean keepVisibleBehind) {
+ if (logForCaller != null) {
+ Log.i(TAG, "stopTv is called at " + logForCaller + ".");
+ } else {
+ if (DEBUG) Log.d(TAG, "stopTv()");
+ }
+ if (mTvView.isPlaying()) {
+ mTvView.stop();
+ if (!keepVisibleBehind) {
+ requestVisibleBehind(false);
+ }
+ mAudioManager.abandonAudioFocus(this);
+ }
+ mChannelTuner.resetCurrentChannel();
+ 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;
+ }
+ mPipChannel = currentChannel;
+ mPipView.setCurrentChannel(mPipChannel);
+ }
+
+ @Override
+ public void onContentBlocked() {
+ // Do nothing.
+ }
+
+ @Override
+ public void onContentAllowed() {
+ // Do nothing.
+ }
+ });
+ 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();
+ mPipView.setStreamVolume(AUDIO_MIN_VOLUME);
+ }
+
+ 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();
+ }
+ }
+
+ private void tune() {
+ if (DEBUG) Log.d(TAG, "tune()");
+ mTuneDurationTimer.start();
+
+ lazyInitializeIfNeeded(LAZY_INITIALIZATION_DELAY);
+
+ // Prerequisites to be able to tune.
+ if (mInputIdUnderSetup != null) {
+ mTunePending = true;
+ return;
+ }
+ mTunePending = false;
+ if (!mChannelTuner.isCurrentChannelPassthrough()
+ && mTvInputManagerHelper.getTunerTvInputSize() == 0) {
+ Toast.makeText(this, R.string.msg_no_input, Toast.LENGTH_SHORT).show();
+ // TODO: Direct the user to a Play Store landing page for TvInputService apps.
+ finish();
+ return;
+ }
+ SetupUtils setupUtils = SetupUtils.getInstance(this);
+ if (!mChannelTuner.isCurrentChannelPassthrough() && setupUtils.isFirstTune()) {
+ if (!mChannelTuner.areAllChannelsLoaded()) {
+ // tune() will be called, once all channels are loaded.
+ stopTv("tune()", false);
+ return;
+ }
+ if (mChannelDataManager.getChannelCount() > 0) {
+ Set<String> inputIds = new HashSet<>();
+ // Enable all channels.
+ for (Channel channel : mChannelDataManager.getChannelList()) {
+ mChannelDataManager.updateBrowsable(channel.getId(), true);
+ inputIds.add(channel.getInputId());
+ }
+ mChannelDataManager.applyUpdatedValuesToDb();
+ // Move to a first channel
+ mChannelTuner.moveToAdjacentBrowsableChannel(true);
+ for (String inputId : inputIds) {
+ setupUtils.onSetupLaunched(inputId);
+ setupUtils.markAsKnownInput(inputId);
+ }
+ mOverlayManager.showIntroDialog();
+ } else {
+ mOverlayManager.showSetupDialog();
+ return;
+ }
+ }
+ if (!mChannelTuner.isCurrentChannelPassthrough()
+ && mChannelTuner.getBrowsableChannelCount() == 0
+ && mChannelDataManager.getChannelCount() > 0
+ && !mOverlayManager.getSideFragmentManager().isActive()) {
+ if (!mChannelTuner.areAllChannelsLoaded()) {
+ return;
+ }
+ if (mTvInputManagerHelper.getTunerTvInputSize() == 1) {
+ mOverlayManager.getSideFragmentManager().show(new CustomizeChannelListFragment());
+ } else {
+ showChannelSourcesFragment();
+ }
+ return;
+ }
+ // TODO: need to refactor the following code to put in startTv.
+ final Channel channel = mChannelTuner.getCurrentChannel();
+ 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.showSetupDialog();
+ return;
+ }
+
+ if (!channel.isPassthrough()) {
+ setupUtils.onTuned();
+ if (mTuneParams != null) {
+ Long initChannelId = mTuneParams.getLong(KEY_INIT_CHANNEL_ID);
+ if (initChannelId == channel.getId()) {
+ mTuneParams.remove(KEY_INIT_CHANNEL_ID);
+ } else {
+ mTuneParams = null;
+ }
+ }
+ }
+
+ mIsCurrentChannelUnblockedByUser = false;
+ if (!isUnderShrunkenTvView()) {
+ mLastAllowedRatingForCurrentChannel = null;
+ }
+ final boolean wasUnderShrunkenTvView = isUnderShrunkenTvView();
+ final long streamInfoUpdateTimeThresholdMs =
+ System.currentTimeMillis() + FIRST_STREAM_INFO_UPDATE_DELAY_MILLIS;
+ mHandler.removeMessages(MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE);
+ if (mAccessibilityManager.isEnabled()) {
+ // For every tune, we need to inform the tuned channel or input to a user,
+ // if Talkback is turned on.
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setClassName(getClass().getName());
+ event.setPackageName(getPackageName());
+ event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
+ if (TvContract.isChannelUriForPassthroughInput(channel.getUri())) {
+ TvInputInfo input = mTvInputManagerHelper.getTvInputInfo(channel.getInputId());
+ event.getText().add(Utils.loadLabel(this, input));
+ } else {
+ event.getText().add(channel.getDisplayNumber() + " " + channel.getDisplayName());
+ }
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+
+ boolean success = mTvView.tuneTo(channel, mTuneParams, new OnTuneListener() {
+ boolean mUnlockAllowedRatingBeforeShrunken = true;
+
+ @Override
+ public void onUnexpectedStop(Channel channel) {
+ stopTv();
+ startTv(null);
+ }
+
+ @Override
+ public void onTuneFailed(Channel channel) {
+ Log.w(TAG, "Failed to tune to channel " + channel.getId()
+ + "@" + channel.getInputId());
+ if (mTvView.isFadedOut()) {
+ mTvView.removeFadeEffect();
+ }
+ // TODO: show something to user about this error.
+ }
+
+ @Override
+ public void onStreamInfoChanged(StreamInfo info) {
+ if (info.isVideoAvailable() && mTuneDurationTimer.isRunning()) {
+ mTracker.sendChannelTuneTime(info.getCurrentChannel(),
+ mTuneDurationTimer.reset());
+ }
+ // If updatChannelBanner() 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() > streamInfoUpdateTimeThresholdMs) {
+ updateChannelBannerAndShowIfNeeded(
+ UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
+ } else {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MSG_UPDATE_CHANNEL_BANNER_BY_INFO_UPDATE),
+ streamInfoUpdateTimeThresholdMs - System.currentTimeMillis());
+ }
+ }
+
+ applyDisplayRefreshRate(info.getVideoFrameRate());
+ mTvViewUiManager.updateTvView();
+ applyMultiAudio();
+ applyClosedCaption();
+ // TODO: Send command to TIS with checking the settings in TV and CaptionManager.
+ mOverlayManager.getMenuView().updateOptionsRow();
+ if (mTvView.isVideoAvailable()) {
+ mTvViewUiManager.fadeInTvView();
+ }
+ 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 but can't find a channel with the URI "
+ + channel);
+ return;
+ }
+ mChannelTuner.setCurrentChannel(currentChannel);
+ mTvView.setCurrentChannel(currentChannel);
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_TUNE);
+ }
+
+ @Override
+ public void onContentBlocked() {
+ 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 (wasUnderShrunkenTvView && mUnlockAllowedRatingBeforeShrunken
+ && mChannelBeforeShrunkenTvView.equals(channel)
+ && rating.equals(mAllowedRatingBeforeShrunken)) {
+ mUnlockAllowedRatingBeforeShrunken = isUnderShrunkenTvView();
+ mTvView.requestUnblockContent(rating);
+ }
+
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
+ mTvViewUiManager.fadeInTvView();
+ }
+
+ @Override
+ public void onContentAllowed() {
+ if (!isUnderShrunkenTvView()) {
+ mUnlockAllowedRatingBeforeShrunken = false;
+ }
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
+ }
+ });
+ mTuneParams = null;
+ if (!success) {
+ Toast.makeText(this, R.string.msg_tune_failed, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // Explicitly make the TV view main to make the selected input an HDMI-CEC active source.
+ mTvView.setMain();
+ scheduleRestoreMainTvView();
+ if (!isUnderShrunkenTvView()) {
+ if (!channel.isPassthrough()) {
+ addToRecentChannels(channel.getId());
+ }
+ Utils.setLastWatchedChannel(this, channel);
+ }
+ checkChannelLockNeeded(mTvView);
+ updateChannelBannerAndShowIfNeeded(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);
+ }
+ }
+
+ private void addToRecentChannels(long channelId) {
+ if (!mRecentChannels.remove(channelId)) {
+ if (mRecentChannels.size() >= MAX_RECENT_CHANNELS) {
+ mRecentChannels.removeLast();
+ }
+ }
+ mRecentChannels.addFirst(channelId);
+ mOverlayManager.getMenuView().onRecentChannelUpdated();
+ }
+
+ /**
+ * Returns the recently tuned channels.
+ */
+ public ArrayDeque<Long> getRecentChannels() {
+ return mRecentChannels;
+ }
+
+ private void checkChannelLockNeeded(TunableTvView tvView) {
+ Channel channel = tvView.getCurrentChannel();
+ if (tvView.isPlaying() && channel != null) {
+ if (getParentalControlSettings().isParentalControlsEnabled()
+ && channel.isLocked()
+ && !mShowLockedChannelsTemporarily
+ && !(isUnderShrunkenTvView()
+ && channel.equals(mChannelBeforeShrunkenTvView)
+ && mWasChannelUnblockedBeforeShrunkenByUser)) {
+ if (DEBUG) Log.d(TAG, "Channel " + channel.getId() + " is locked");
+ blockScreen(tvView);
+ } else {
+ unblockScreen(tvView);
+ }
+ }
+ }
+
+ private void blockScreen(TunableTvView tvView) {
+ tvView.blockScreen();
+ if (tvView == mTvView) {
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
+ }
+ }
+
+ private void unblockScreen(TunableTvView tvView) {
+ tvView.unblockScreen();
+ if (tvView == mTvView) {
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
+ }
+ }
+
+ /**
+ * Shows the channel banner if it was hidden from the side fragment.
+ *
+ * <p>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) {
+ if (mChannelTuner.getCurrentChannel() == null) {
+ mChannelBannerHiddenBySideFragment = false;
+ } else if (mOverlayManager.getSideFragmentManager().isActive()) {
+ mChannelBannerHiddenBySideFragment = true;
+ } else {
+ mChannelBannerHiddenBySideFragment = false;
+ mTransitionManager.goToChannelBannerScene();
+ }
+ }
+ updateAvailabilityToast();
+ }
+
+ /**
+ * 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);
+ }
+
+ public boolean needToKeepDialogWhenHidingOverlay() {
+ return mInputIdUnderSetup != null && mIsSetupActivityCalledByDialog;
+ }
+
+ // For now, this only takes care of 24fps.
+ private void applyDisplayRefreshRate(float videoFrameRate) {
+ boolean is24Fps = Math.abs(videoFrameRate - FRAME_RATE_FOR_FILM) < FRAME_RATE_EPSILON;
+ if (mIsFilmModeSet && !is24Fps) {
+ setPreferredRefreshRate(mDefaultRefreshRate);
+ mIsFilmModeSet = false;
+ } else if (!mIsFilmModeSet && is24Fps) {
+ DisplayManager displayManager = (DisplayManager) getSystemService(
+ Context.DISPLAY_SERVICE);
+ Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+
+ float[] refreshRates = display.getSupportedRefreshRates();
+ for (float refreshRate : refreshRates) {
+ // Be conservative and set only when the display refresh rate supports 24fps.
+ if (Math.abs(videoFrameRate - refreshRate) < REFRESH_RATE_EPSILON) {
+ setPreferredRefreshRate(refreshRate);
+ mIsFilmModeSet = true;
+ return;
+ }
+ }
+ }
+ }
+
+ private void setPreferredRefreshRate(float refreshRate) {
+ Window window = getWindow();
+ WindowManager.LayoutParams layoutParams = window.getAttributes();
+ layoutParams.preferredRefreshRate = refreshRate;
+ window.setAttributes(layoutParams);
+ }
+
+ private void applyMultiAudio() {
+ List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_AUDIO);
+ if (tracks == null) {
+ mTvOptionsManager.onMultiAudioChanged(null);
+ return;
+ }
+
+ String id = TvSettings.getMultiAudioId(this);
+ String language = TvSettings.getMultiAudioLanguage(this);
+ int channelCount = TvSettings.getMultiAudioChannelCount(this);
+ TvTrackInfo bestTrack = TvTrackInfoUtils
+ .getBestTrackInfo(tracks, id, language, channelCount);
+ if (bestTrack != null) {
+ String selectedTrack = getSelectedTrack(TvTrackInfo.TYPE_AUDIO);
+ if (!bestTrack.getId().equals(selectedTrack)) {
+ selectTrack(TvTrackInfo.TYPE_AUDIO, bestTrack);
+ } else {
+ mTvOptionsManager.onMultiAudioChanged(
+ Utils.getMultiAudioString(this, bestTrack, false));
+ }
+ return;
+ }
+ mTvOptionsManager.onMultiAudioChanged(null);
+ }
+
+ private void applyClosedCaption() {
+ List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_SUBTITLE);
+ if (tracks == null) {
+ mTvOptionsManager.onClosedCaptionsChanged(null);
+ return;
+ }
+
+ boolean enabled = mCaptionSettings.isEnabled();
+ mTvView.setClosedCaptionEnabled(enabled);
+
+ String selectedTrackId = getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE);
+ TvTrackInfo alternativeTrack = null;
+ if (enabled) {
+ String language = mCaptionSettings.getLanguage();
+ String trackId = mCaptionSettings.getTrackId();
+ for (TvTrackInfo track : tracks) {
+ if (Utils.isEqualLanguage(track.getLanguage(), language)) {
+ if (track.getId().equals(trackId)) {
+ if (!track.getId().equals(selectedTrackId)) {
+ selectTrack(TvTrackInfo.TYPE_SUBTITLE, track);
+ } else {
+ // Already selected. Update the option string only.
+ mTvOptionsManager.onClosedCaptionsChanged(track);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Subtitle Track Selected {id=" + track.getId()
+ + ", language=" + track.getLanguage() + "}");
+ }
+ return;
+ } else if (alternativeTrack == null) {
+ alternativeTrack = track;
+ }
+ }
+ }
+ if (alternativeTrack != null) {
+ if (!alternativeTrack.getId().equals(selectedTrackId)) {
+ selectTrack(TvTrackInfo.TYPE_SUBTITLE, alternativeTrack);
+ } else {
+ mTvOptionsManager.onClosedCaptionsChanged(alternativeTrack);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Subtitle Track Selected {id=" + alternativeTrack.getId()
+ + ", language=" + alternativeTrack.getLanguage() + "}");
+ }
+ return;
+ }
+ }
+ if (selectedTrackId != null) {
+ selectTrack(TvTrackInfo.TYPE_SUBTITLE, null);
+ 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()) {
+ // Show KeypadChannelSwitchView only if all the channels are loaded.
+ mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG);
+ mTransitionManager.goToKeypadChannelSwitchScene();
+ mKeypadChannelSwitchView.onNumberKeyUp(keyCode - KeyEvent.KEYCODE_0);
+ }
+ }
+
+ public void showSelectInputView() {
+ mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE);
+ mTransitionManager.goToSelectInputScene();
+ }
+
+ 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();
+ }
+
+ public void showProgramGuideSearchFragment() {
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ ft.replace(R.id.search, mSearchFragment);
+ ft.addToBackStack(null);
+ ft.commit();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ // Do not save instance state because restoring instance state when TV app died
+ // unexpectedly can cause some problems like initializing fragments duplicately and
+ // accessing resource before it is initialized.
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (DEBUG) Log.d(TAG, "onDestroy()");
+ mChannelTuner.removeListener(mChannelTunerListener);
+ mProgramDataManager.removeOnCurrentProgramUpdatedListener(
+ Channel.INVALID_ID, mOnCurrentProgramUpdatedListener);
+ mProgramDataManager.stop();
+ mChannelDataManager.stop();
+ mTvInputManagerHelper.stop();
+ mPipInputManager.stop();
+ mOverlayManager.getMenuView().setChannelTuner(null);
+ mChannelTuner.stop();
+ mKeypadChannelSwitchView.setChannels(null);
+ mMemoryManageables.clear();
+ ((TvApplication) getApplication()).setMainActivity(null);
+ mAudioCapabilitiesReceiver.unregister();
+ super.onDestroy();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (SystemProperties.LOG_KEYEVENT.getValue()) {
+ Log.d(TAG, "onKeyDown(" + keyCode + ", " + event + ")");
+ }
+ switch (mOverlayManager.onKeyDown(keyCode, event)) {
+ case KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY:
+ return super.onKeyDown(keyCode, event);
+ case KEY_EVENT_HANDLER_RESULT_HANDLED:
+ return true;
+ case KEY_EVENT_HANDLER_RESULT_NOT_HANDLED:
+ return false;
+ case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH:
+ default:
+ // pass through
+ }
+ if (mSearchFragment.isVisible()) {
+ return super.onKeyDown(keyCode, event);
+ }
+ if (!mChannelTuner.areAllChannelsLoaded()) {
+ return false;
+ }
+ if (!mChannelTuner.isCurrentChannelPassthrough()) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_CHANNEL_UP:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (event.getRepeatCount() == 0
+ && mChannelTuner.getBrowsableChannelCount() > 0) {
+ moveToAdjacentChannel(true, false);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_UP_PRESSED,
+ System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
+ mTracker.sendChannelUp();
+ }
+ return true;
+ case KeyEvent.KEYCODE_CHANNEL_DOWN:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (event.getRepeatCount() == 0
+ && mChannelTuner.getBrowsableChannelCount() > 0) {
+ moveToAdjacentChannel(false, false);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHANNEL_DOWN_PRESSED,
+ System.currentTimeMillis()), CHANNEL_CHANGE_INITIAL_DELAY_MILLIS);
+ mTracker.sendChannelDown();
+ }
+ return true;
+ }
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ /*
+ * The following keyboard keys map to these remote keys or "debug actions"
+ * - --------
+ * A KEYCODE_MEDIA_AUDIO_TRACK
+ * D debug: show debug options
+ * E updateChannelBannerAndShowIfNeeded
+ * I KEYCODE_TV_INPUT
+ * O debug: show display mode option
+ * P debug: togglePipView
+ * S KEYCODE_CAPTIONS: select subtitle
+ * W debug: toggle screen size
+ */
+ if (SystemProperties.LOG_KEYEVENT.getValue()) {
+ Log.d(TAG, "onKeyUp(" + keyCode + ", " + event + ")");
+ }
+ // If we are in the middle of channel change, finish it before showing overlays.
+ finishChannelChangeIfNeeded();
+
+ if (event.getKeyCode() == KeyEvent.KEYCODE_SEARCH) {
+ showSearchActivity();
+ return true;
+ }
+ switch (mOverlayManager.onKeyUp(keyCode, event)) {
+ case KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY:
+ return super.onKeyUp(keyCode, event);
+ case KEY_EVENT_HANDLER_RESULT_HANDLED:
+ return true;
+ case KEY_EVENT_HANDLER_RESULT_NOT_HANDLED:
+ return false;
+ case KEY_EVENT_HANDLER_RESULT_PASSTHROUGH:
+ default:
+ // pass through
+ }
+ if (mSearchFragment.isVisible()) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ getFragmentManager().popBackStack();
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // When the event is from onUnhandledInputEvent, onBackPressed is not automatically
+ // called. Therefore, we need to explicitly call onBackPressed().
+ onBackPressed();
+ return true;
+ }
+
+ if (!mChannelTuner.areAllChannelsLoaded()) {
+ // Now channel map is under loading.
+ } else if (mChannelTuner.getBrowsableChannelCount() == 0) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_CHANNEL_UP:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_CHANNEL_DOWN:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_NUMPAD_ENTER:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_E:
+ case KeyEvent.KEYCODE_MENU:
+ showChannelSourcesFragment();
+ return true;
+ }
+ } else {
+ if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) {
+ showKeypadChannelSwitchView(keyCode);
+ return true;
+ }
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ 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.requestUnblockContent(rating);
+ }
+ }
+ });
+ }
+ if (dialog != null) {
+ mOverlayManager.showDialogFragment(PinDialogFragment.DIALOG_TAG, dialog,
+ false);
+ }
+ return true;
+
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_NUMPAD_ENTER:
+ case KeyEvent.KEYCODE_E:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_MENU:
+ if (event.isCanceled()) {
+ // Ignore canceled key.
+ // Note that if there's a TIS granted RECEIVE_INPUT_EVENT,
+ // fallback keys not blacklisted will have FLAG_CANCELED.
+ // See dispatchKeyEvent() for detail.
+ return true;
+ }
+ if (keyCode != KeyEvent.KEYCODE_MENU) {
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW);
+ }
+ if (keyCode != KeyEvent.KEYCODE_E) {
+ mOverlayManager.showMenu(MenuView.REASON_NONE);
+ }
+ return true;
+ case KeyEvent.KEYCODE_CHANNEL_UP:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_CHANNEL_DOWN:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ // Channel change is already done in the head of this method.
+ return true;
+ case KeyEvent.KEYCODE_S:
+ if (!SystemProperties.USE_DEBUG_KEYS.getValue()) {
+ break;
+ }
+ case KeyEvent.KEYCODE_CAPTIONS: {
+ mOverlayManager.getSideFragmentManager().show(new ClosedCaptionFragment());
+ return true;
+ }
+ case KeyEvent.KEYCODE_A:
+ if (!SystemProperties.USE_DEBUG_KEYS.getValue()) {
+ break;
+ }
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
+ mOverlayManager.getSideFragmentManager().show(new MultiAudioFragment());
+ return true;
+ }
+ }
+ }
+ if (SystemProperties.USE_DEBUG_KEYS.getValue()) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_W: {
+ mDebugNonFullSizeScreen = !mDebugNonFullSizeScreen;
+ if (mDebugNonFullSizeScreen) {
+ FrameLayout.LayoutParams params =
+ (FrameLayout.LayoutParams) mTvView.getLayoutParams();
+ params.width = 960;
+ params.height = 540;
+ params.gravity = Gravity.START;
+ mTvView.setLayoutParams(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);
+ }
+ return true;
+ }
+ case KeyEvent.KEYCODE_P: {
+ togglePipView();
+ return true;
+ }
+ case KeyEvent.KEYCODE_CTRL_LEFT:
+ case KeyEvent.KEYCODE_CTRL_RIGHT: {
+ mUseKeycodeBlacklist = !mUseKeycodeBlacklist;
+ return true;
+ }
+ case KeyEvent.KEYCODE_O: {
+ mOverlayManager.getSideFragmentManager().show(new DisplayModeFragment());
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_D:
+ mOverlayManager.getSideFragmentManager().show(new DebugOptionFragment());
+ return true;
+ }
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ if (SystemProperties.LOG_KEYEVENT.getValue()) Log.d(TAG, "onKeyLongPress(" + event);
+ if (USE_BACK_KEY_LONG_PRESS) {
+ // Treat the BACK key long press as the normal press since we changed the behavior in
+ // onBackPressed().
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // It takes long time for TV app to finish, so stop TV first.
+ stopAll(false);
+ super.onBackPressed();
+ return true;
+ }
+ }
+ 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();
+ mOverlayManager.onUserInteraction();
+ }
+
+ /**
+ * Returns TvCustomizationManager.
+ */
+ public TvCustomizationManager getTvCustomizationManager() {
+ return mTvCustomizationManager;
+ }
+
+ public void togglePipView() {
+ enablePipView(!mPipEnabled, true);
+ mOverlayManager.getMenuView().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);
+ }
+
+ public void enablePipView(boolean enable, boolean fromUserInteraction) {
+ if (enable == mPipEnabled) {
+ return;
+ }
+ if (enable) {
+ List<PipInput> 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);
+ mPipView.setStreamVolume(AUDIO_MIN_VOLUME);
+ mPipSound = TvSettings.PIP_SOUND_MAIN;
+ mTvOptionsManager.onPipSoundChanged(mPipSound);
+ }
+ stopPip();
+ mTvViewUiManager.restoreDisplayMode(false);
+ mTvOptionsManager.onPipChanged(mPipEnabled);
+ }
+ }
+
+ private void finishChannelChangeIfNeeded() {
+ if (!mHandler.hasMessages(MSG_CHANNEL_UP_PRESSED) && !mHandler.hasMessages(
+ MSG_CHANNEL_DOWN_PRESSED)) {
+ return;
+ }
+ mHandler.removeMessages(MSG_CHANNEL_UP_PRESSED);
+ mHandler.removeMessages(MSG_CHANNEL_DOWN_PRESSED);
+ if (mChannelTuner.getBrowsableChannelCount() > 0) {
+ if (!mTvView.isPlaying()) {
+ // We expect that mTvView is already played. But, it is sometimes not.
+ // TODO: we figure out the reason when mTvView is not played.
+ Log.w(TAG, "TV view isn't played in finishChannelChangeIfNeeded");
+ }
+ tuneToChannel(mChannelTuner.getCurrentChannel());
+ } else {
+ showChannelSourcesFragment();
+ }
+ }
+
+ private boolean dispatchKeyEventToSession(final KeyEvent event) {
+ 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);
+ }
+ if (isKeyEventBlocked()) {
+ if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK
+ || 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;
+ }
+ return true;
+ }
+ return handled;
+ }
+
+ 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;
+ }
+
+ public void tuneToLastWatchedChannelForTunerInput() {
+ if (!mChannelTuner.isCurrentChannelPassthrough()) {
+ return;
+ }
+ if (mPipEnabled) {
+ if (!mPipChannel.isPassthrough()) {
+ enablePipView(false, true);
+ }
+ }
+ stopTv();
+ startTv(null);
+ }
+
+ public void tuneToChannel(Channel channel) {
+ if (channel == null) {
+ if (mTvView.isPlaying()) {
+ 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);
+ } else if (mChannelTuner.moveToChannel(channel)) {
+ // Channel banner would be updated inside of tune.
+ tune();
+ } else {
+ showChannelSourcesFragment();
+ }
+ }
+ }
+
+ /**
+ * 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 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)) {
+ 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();
+ }
+
+ 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);
+ mPipView.setStreamVolume(AUDIO_MIN_VOLUME);
+ } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW
+ setVolumeByAudioFocusStatus(mPipView);
+ mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
+ }
+ 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);
+ mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
+ mPipSound = TvSettings.PIP_SOUND_PIP_WINDOW;
+ } else { // mPipSound == TvSettings.PIP_SOUND_PIP_WINDOW
+ setVolumeByAudioFocusStatus(mTvView);
+ mPipView.setStreamVolume(AUDIO_MIN_VOLUME);
+ 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();
+ }
+ }
+
+ @Override
+ public void onVisibleBehindCanceled() {
+ stopTv("onVisibleBehindCanceled()", false);
+ mTracker.sendScreenView("");
+ if (mMediaSession != null) {
+ mMediaSession.release();
+ mMediaSession = null;
+ }
+ mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
+ mAudioManager.abandonAudioFocus(this);
+ stopPip();
+ mVisibleBehind = false;
+ super.onVisibleBehindCanceled();
+ }
+
+ public List<TvTrackInfo> getTracks(int type) {
+ return mTvView.getTracks(type);
+ }
+
+ public String getSelectedTrack(int type) {
+ return mTvView.getSelectedTrack(type);
+ }
+
+ public void selectTrack(int type, TvTrackInfo track) {
+ 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);
+ }
+ }
+
+ public void selectAudioTrack(String trackId) {
+ saveMultiAudioSetting(trackId);
+ applyMultiAudio();
+ }
+
+ private void saveMultiAudioSetting(String trackId) {
+ List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_AUDIO);
+ if (tracks != null) {
+ for (TvTrackInfo track : tracks) {
+ if (track.getId().equals(trackId)) {
+ TvSettings.setMultiAudioId(this, track.getId());
+ TvSettings.setMultiAudioLanguage(this, track.getLanguage());
+ TvSettings.setMultiAudioChannelCount(this, track.getAudioChannelCount());
+ return;
+ }
+ }
+ }
+ TvSettings.setMultiAudioId(this, null);
+ TvSettings.setMultiAudioLanguage(this, null);
+ TvSettings.setMultiAudioChannelCount(this, 0);
+ }
+
+ public void selectSubtitleTrack(int option, String trackId) {
+ saveClosedCaptionSetting(option, trackId);
+ applyClosedCaption();
+ }
+
+ public void selectSubtitleLanguage(int option, String language, String trackId) {
+ mCaptionSettings.setEnableOption(option);
+ mCaptionSettings.setLanguage(language);
+ mCaptionSettings.setTrackId(trackId);
+ applyClosedCaption();
+ }
+
+ private void saveClosedCaptionSetting(int option, String trackId) {
+ mCaptionSettings.setEnableOption(option);
+ if (option == CaptionSettings.OPTION_ON) {
+ List<TvTrackInfo> tracks = getTracks(TvTrackInfo.TYPE_SUBTITLE);
+ if (tracks != null) {
+ for (TvTrackInfo track : tracks) {
+ if (track.getId().equals(trackId)) {
+ mCaptionSettings.setLanguage(track.getLanguage());
+ mCaptionSettings.setTrackId(trackId);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ private void updateAvailabilityToast() {
+ updateAvailabilityToast(mTvView);
+ }
+
+ private void updateAvailabilityToast(StreamInfo info) {
+ if (mTransitionManager.isSceneActive() || info.isVideoAvailable()) {
+ return;
+ }
+
+ int stringId;
+ switch (info.getVideoUnavailableReason()) {
+ case TunableTvView.VIDEO_UNAVAILABLE_REASON_NOT_TUNED:
+ return;
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING:
+ return;
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
+ stringId = R.string.msg_channel_unavailable_weak_signal;
+ break;
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
+ return;
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
+ default:
+ stringId = R.string.msg_channel_unavailable_unknown;
+ break;
+ }
+
+ Toast.makeText(this, stringId, Toast.LENGTH_SHORT).show();
+ }
+
+ public ParentalControlSettings getParentalControlSettings() {
+ return mTvInputManagerHelper.getParentalControlSettings();
+ }
+
+ /**
+ * Returns a ContentRatingsManager instance.
+ */
+ public ContentRatingsManager getContentRatingsManager() {
+ return mTvInputManagerHelper.getContentRatingsManager();
+ }
+
+ public CaptionSettings getCaptionSettings() {
+ return mCaptionSettings;
+ }
+
+ public void goToEmptyScene(boolean withAnimation) {
+ mTransitionManager.goToEmptyScene(withAnimation);
+ }
+
+ // 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 (!ActivityManager.isRunningInTestHarness()) {
+ return;
+ }
+
+ Utils.enableAllChannels(this);
+ }
+
+ // Lazy initialization
+ private void lazyInitializeIfNeeded(long delay) {
+ // Already initialized.
+ if (mLazyInitialized) {
+ return;
+ }
+ mLazyInitialized = true;
+ // Running initialization.
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ initAnimations();
+ initSideFragments();
+ }
+ }, delay);
+ }
+
+ private void initAnimations() {
+ mTvViewUiManager.initAnimatorIfNeeded();
+ mTransitionManager.initIfNeeded();
+ }
+
+ private void initSideFragments() {
+ SideFragment.preloadRecycledViews(this);
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ super.onTrimMemory(level);
+ for (MemoryManageable memoryManageable : mMemoryManageables) {
+ memoryManageable.performTrimMemory(level);
+ }
+ }
+
+ /**
+ * 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 {
+ /**
+ * This method is called when the {@link MainActivity#onTrimMemory} is called.
+ * For more information, see {@link android.content.ComponentCallbacks2#onTrimMemory}.
+ */
+ void performTrimMemory(int level);
+ }
+}
diff --git a/src/com/android/tv/PassthroughTvActivity.java b/src/com/android/tv/PassthroughTvActivity.java
deleted file mode 100644
index 669d91a3..00000000
--- a/src/com/android/tv/PassthroughTvActivity.java
+++ /dev/null
@@ -1,143 +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;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputInfo;
-import android.media.tv.TvInputManager;
-import android.media.tv.TvView;
-import android.net.Uri;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.widget.Toast;
-
-import java.util.List;
-
-/**
- * The activity for passthrough TV inputs.
- */
-public class PassthroughTvActivity extends Activity {
- // STOPSHIP: Turn debugging off
- private static final boolean DEBUG = true;
- private static final String TAG = "PassthroughTvActivity";
-
- TvView mTvView;
- String mTvInputId;
- TvInputManager mTvInputManager;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_passthrough_tv);
-
- mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
- mTvView = (TvView) findViewById(R.id.tv_view);
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- startPassthroughTvInput(intent);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- startPassthroughTvInput(getIntent());
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- setMediaPlaying(true);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mTvView.reset();
- mTvView.setStreamVolume(0f);
- setMediaPlaying(false);
- }
-
- @Override
- public void onStopMediaPlaying() {
- mTvView.reset();
- mTvView.setStreamVolume(0f);
- setMediaPlaying(false);
- super.onStopMediaPlaying();
- }
-
- @Override
- public boolean onKeyLongPress(int keyCode, KeyEvent event) {
- if (DEBUG) Log.d(TAG, "onKeyLongPress(" + event);
- // Treat the BACK key long press as the normal press since we changed the behavior in
- // onBackPressed().
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- super.onBackPressed();
- return true;
- }
- return false;
- }
-
- @Override
- public void onBackPressed() {
- Intent startMain = new Intent(Intent.ACTION_MAIN);
- startMain.addCategory(Intent.CATEGORY_HOME);
- startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(startMain);
- }
-
- private void startPassthroughTvInput(Intent intent) {
- if (Intent.ACTION_VIEW.equals(intent.getAction())) {
- mTvInputId = null;
- if (intent.getData().getHost().equals("android.media.tv")) {
- String uriString = intent.getData().toString();
- if (DEBUG) Log.d(TAG, "Intent: " + uriString);
- Uri uri = Uri.parse(uriString);
- if (!TvContract.isChannelUriForPassthroughTvInput(uri)) {
- Toast.makeText(this, R.string.not_passthrough_input, Toast.LENGTH_SHORT)
- .show();
- finish();
- return;
- }
- mTvInputId = uri.getPathSegments().get(1);
- }
- }
- if (mTvInputId != null) {
- TvInputInfo info = mTvInputManager.getTvInputInfo(mTvInputId);
- if (info == null) {
- Toast.makeText(this, R.string.input_is_not_available, Toast.LENGTH_SHORT).show();
- finish();
- return;
- } else if (!info.isPassthroughInputType()) {
- Toast.makeText(this, R.string.not_passthrough_input, Toast.LENGTH_SHORT).show();
- finish();
- return;
- }
- } else {
- Toast.makeText(this, R.string.not_passthrough_input, Toast.LENGTH_SHORT).show();
- finish();
- return;
- }
- mTvView.tune(mTvInputId, TvContract.buildChannelUriForPassthroughTvInput(mTvInputId));
- mTvView.setStreamVolume(1.0f);
- }
-}
diff --git a/src/com/android/tv/TimeShiftManager.java b/src/com/android/tv/TimeShiftManager.java
new file mode 100644
index 00000000..5b998280
--- /dev/null
+++ b/src/com/android/tv/TimeShiftManager.java
@@ -0,0 +1,1295 @@
+/*
+ * 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.annotation.SuppressLint;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+import android.util.Range;
+
+import com.android.tv.data.Channel;
+import com.android.tv.data.OnCurrentProgramUpdatedListener;
+import com.android.tv.data.Program;
+import com.android.tv.data.ProgramDataManager;
+import com.android.tv.ui.TunableTvView;
+import com.android.tv.ui.TunableTvView.TimeShiftListener;
+import com.android.tv.util.AsyncDbTask;
+import com.android.tv.util.Utils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class which manages the time shift feature in Live Channels. 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";
+ private static final boolean DEBUG = false;
+
+ @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_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 static final int PLAY_SPEED_1X = 1;
+ public static final int PLAY_SPEED_2X = 2;
+ public static final int PLAY_SPEED_3X = 3;
+ public static final int PLAY_SPEED_4X = 4;
+ public static final int PLAY_SPEED_5X = 5;
+
+ private static final int SHORT_PROGRAM_THRESHOLD_MILLIS = 46 * 60 * 1000; // 46 mins.
+ private static final int[] SHORT_PROGRAM_SPEED_FACTORS = new int[] {2, 4, 12, 48};
+ private static final int[] LONG_PROGRAM_SPEED_FACTORS = new int[] {2, 8, 32, 128};
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({PLAY_DIRECTION_FORWARD, PLAY_DIRECTION_BACKWARD})
+ public @interface PlayDirection{}
+ public static final int PLAY_DIRECTION_FORWARD = 0;
+ public static final int PLAY_DIRECTION_BACKWARD = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({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;
+ public static final int TIME_SHIFT_ACTION_ID_FAST_FORWARD = 1 << 3;
+ public static final int TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS = 1 << 4;
+ public static final int TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT = 1 << 5;
+
+ private static final int MSG_GET_CURRENT_POSITION = 1000;
+ private static final int MSG_PREFETCH_PROGRAM = 1001;
+ private static final long REQUEST_CURRENT_POSITION_INTERVAL = TimeUnit.SECONDS.toMillis(1);
+ private static final long MAX_DUMMY_PROGRAM_DURATION = TimeUnit.MINUTES.toMillis(30);
+ @VisibleForTesting
+ static final long INVALID_TIME = -1;
+ 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);
+
+ @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.
+ * 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.
+ */
+ 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.
+ */
+ 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 Channels.
+ * So the boundary threshold is necessary.
+ * The same goes for the recording start time.
+ * It must be three times longer than {@link #REQUEST_CURRENT_POSITION_INTERVAL} at least.
+ */
+ private static final long RECORDING_BOUNDARY_THRESHOLD = 3 * REQUEST_CURRENT_POSITION_INTERVAL;
+
+ private final PlayController mPlayController;
+ private final ProgramManager mProgramManager;
+ @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;
+ private int mLastActionId = 0;
+
+ // TODO: Remove these variables once API level 23 is available.
+ private final Context mContext;
+
+ private Program mCurrentProgram;
+ // This variable is used to block notification while changing the availability status.
+ private boolean mNotificationEnabled;
+
+ @SuppressLint("HandlerLeak")
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_GET_CURRENT_POSITION:
+ mPlayController.handleGetCurrentPosition();
+ break;
+ case MSG_PREFETCH_PROGRAM:
+ mProgramManager.prefetchPrograms();
+ break;
+ }
+ }
+ };
+
+ public TimeShiftManager(Context context, TunableTvView tvView,
+ ProgramDataManager programDataManager,
+ OnCurrentProgramUpdatedListener onCurrentProgramUpdatedListener) {
+ mContext = context;
+ mPlayController = new PlayController(tvView);
+ mProgramManager = new ProgramManager(programDataManager);
+ mOnCurrentProgramUpdatedListener = onCurrentProgramUpdatedListener;
+ tvView.setOnScreenBlockedListener(new TunableTvView.OnScreenBlockingChangedListener() {
+ @Override
+ public void onScreenBlockingChanged(boolean blocked) {
+ onAvailabilityChanged();
+ }
+ });
+ }
+
+ /**
+ * 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.
+ */
+ public boolean isAvailable() {
+ return mPlayController.isAvailable();
+ }
+
+ /**
+ * Returns the current time position in milliseconds.
+ */
+ public long getCurrentPositionMs() {
+ return mCurrentPositionMediator.mCurrentPositionMs;
+ }
+
+ void setCurrentPositionMs(long currentTimeMs) {
+ mCurrentPositionMediator.onCurrentPositionChanged(currentTimeMs);
+ }
+
+ /**
+ * Returns the start time of the recording in milliseconds.
+ */
+ public long getRecordStartTimeMs() {
+ long oldestProgramStartTime = mProgramManager.getOldestProgramStartTime();
+ return oldestProgramStartTime == INVALID_TIME ? INVALID_TIME
+ : mPlayController.mRecordStartTimeMs;
+ }
+
+ /**
+ * Plays the media.
+ *
+ * @throws IllegalStateException if the trick play is not available.
+ */
+ public void play() {
+ if (!isActionEnabled(TIME_SHIFT_ACTION_ID_PLAY)) {
+ return;
+ }
+ mLastActionId = TIME_SHIFT_ACTION_ID_PLAY;
+ mPlayController.play();
+ updateActions();
+ }
+
+ /**
+ * Pauses the playback.
+ *
+ * @throws IllegalStateException if the trick play is not available.
+ */
+ public void pause() {
+ if (!isActionEnabled(TIME_SHIFT_ACTION_ID_PAUSE)) {
+ return;
+ }
+ mLastActionId = TIME_SHIFT_ACTION_ID_PAUSE;
+ mPlayController.pause();
+ updateActions();
+ }
+
+ /**
+ * Toggles the playing and paused state.
+ *
+ * @throws IllegalStateException if the trick play is not available.
+ */
+ public void togglePlayPause() {
+ mPlayController.togglePlayPause();
+ }
+
+ /**
+ * 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.
+ */
+ public void rewind() {
+ if (!isActionEnabled(TIME_SHIFT_ACTION_ID_REWIND)) {
+ return;
+ }
+ mLastActionId = TIME_SHIFT_ACTION_ID_REWIND;
+ mPlayController.rewind();
+ updateActions();
+ }
+
+ /**
+ * 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.
+ */
+ public void fastForward() {
+ if (!isActionEnabled(TIME_SHIFT_ACTION_ID_FAST_FORWARD)) {
+ return;
+ }
+ mLastActionId = TIME_SHIFT_ACTION_ID_FAST_FORWARD;
+ mPlayController.fastForward();
+ updateActions();
+ }
+
+ /**
+ * 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.
+ */
+ public void jumpToPrevious() {
+ if (!isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS)) {
+ return;
+ }
+ Program program = mProgramManager.getProgramAt(
+ mCurrentPositionMediator.mCurrentPositionMs - PROGRAM_START_TIME_THRESHOLD);
+ if (program == null) {
+ return;
+ }
+ long seekPosition =
+ Math.max(program.getStartTimeUtcMillis(), mPlayController.mRecordStartTimeMs);
+ mLastActionId = TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS;
+ mPlayController.seekTo(seekPosition);
+ mCurrentPositionMediator.onSeekRequested(seekPosition);
+ updateActions();
+ }
+
+ /**
+ * 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.
+ */
+ public void jumpToNext() {
+ if (!isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT)) {
+ return;
+ }
+ Program currentProgram = mProgramManager.getProgramAt(
+ mCurrentPositionMediator.mCurrentPositionMs);
+ if (currentProgram == null) {
+ return;
+ }
+ Program nextProgram = mProgramManager.getProgramAt(currentProgram.getEndTimeUtcMillis());
+ long currentTimeMs = System.currentTimeMillis();
+ mLastActionId = TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT;
+ if (nextProgram == null || nextProgram.getStartTimeUtcMillis() > currentTimeMs) {
+ mPlayController.seekTo(currentTimeMs);
+ if (mPlayController.isForwarding()) {
+ // The current position will be the current system time from now.
+ mPlayController.mIsPlayOffsetChanged = false;
+ mCurrentPositionMediator.initialize(currentTimeMs);
+ } else {
+ // The current position would not be the current system time.
+ // So need to wait for the correct time from TIS.
+ mCurrentPositionMediator.onSeekRequested(currentTimeMs);
+ }
+ } else {
+ mPlayController.seekTo(nextProgram.getStartTimeUtcMillis());
+ mCurrentPositionMediator.onSeekRequested(nextProgram.getStartTimeUtcMillis());
+ }
+ updateActions();
+ }
+
+ /**
+ * Returns the playback status. The value is PLAY_STATUS_PAUSED or PLAY_STATUS_PLAYING.
+ */
+ @PlayStatus public int getPlayStatus() {
+ return mPlayController.mPlayStatus;
+ }
+
+ /**
+ * 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() {
+ return mPlayController.mDisplayedPlaySpeed;
+ }
+
+ /**
+ * Returns the playback speed. The value is PLAY_DIRECTION_FORWARD or PLAY_DIRECTION_BACKWARD.
+ */
+ @PlayDirection public int getPlayDirection() {
+ return mPlayController.mPlayDirection;
+ }
+
+ /**
+ * Returns the ID of the last action..
+ */
+ @TimeShiftActionId public int getLastActionId() {
+ return mLastActionId;
+ }
+
+ /**
+ * Enables or disables the time-shift actions.
+ */
+ @VisibleForTesting
+ void enableAction(@TimeShiftActionId int actionId, boolean enable) {
+ int oldEnabledActionIds = mEnabledActionIds;
+ if (enable) {
+ mEnabledActionIds |= actionId;
+ } else {
+ mEnabledActionIds &= ~actionId;
+ }
+ if (mNotificationEnabled && mListener != null
+ && oldEnabledActionIds != mEnabledActionIds) {
+ mListener.onActionEnabledChanged(actionId, enable);
+ }
+ }
+
+ public boolean isActionEnabled(@TimeShiftActionId int actionId) {
+ return (mEnabledActionIds & actionId) == actionId;
+ }
+
+ private void updateActions() {
+ if (isAvailable()) {
+ 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;
+ 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 = System.currentTimeMillis() - mCurrentPositionMediator.mCurrentPositionMs
+ > threshold;
+ enableAction(TIME_SHIFT_ACTION_ID_FAST_FORWARD, enabled);
+ enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT, enabled);
+ } else {
+ enableAction(TIME_SHIFT_ACTION_ID_PLAY, false);
+ enableAction(TIME_SHIFT_ACTION_ID_PAUSE, false);
+ enableAction(TIME_SHIFT_ACTION_ID_REWIND, false);
+ enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, false);
+ enableAction(TIME_SHIFT_ACTION_ID_FAST_FORWARD, false);
+ enableAction(TIME_SHIFT_ACTION_ID_PLAY, false);
+ }
+ }
+
+ private void updateCurrentProgram() {
+ Program currentProgram = getProgramAt(mCurrentPositionMediator.mCurrentPositionMs);
+ if (!Program.isValid(currentProgram)) {
+ currentProgram = null;
+ }
+ if (!Objects.equals(mCurrentProgram, currentProgram)) {
+ if (DEBUG) Log.d(TAG, "Current program has been updated. " + currentProgram);
+ mCurrentProgram = currentProgram;
+ if (mNotificationEnabled && mOnCurrentProgramUpdatedListener != null) {
+ Channel channel = mPlayController.getCurrentChannel();
+ if (channel != null) {
+ mOnCurrentProgramUpdatedListener.onCurrentProgramUpdated(channel.getId(),
+ mCurrentProgram);
+ mPlayController.onCurrentProgramChanged();
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns {@code true} if the trick play is available and it's playing to the forward direction
+ * with normal speed, otherwise {@code false}.
+ */
+ public boolean isNormalPlaying() {
+ return mPlayController.isAvailable()
+ && mPlayController.mPlayStatus == PLAY_STATUS_PLAYING
+ && mPlayController.mPlayDirection == PLAY_DIRECTION_FORWARD
+ && mPlayController.mDisplayedPlaySpeed == PLAY_SPEED_1X;
+ }
+
+ /**
+ * Checks if the trick play is available and it's playback status is paused.
+ */
+ public boolean isPaused() {
+ return mPlayController.isAvailable() && mPlayController.mPlayStatus == PLAY_STATUS_PAUSED;
+ }
+
+ /**
+ * Returns the program which airs at the given time.
+ */
+ @NonNull
+ public Program getProgramAt(long timeMs) {
+ Program program = mProgramManager.getProgramAt(timeMs);
+ if (program == null) {
+ // Guard just in case when the program prefetch handler doesn't work on time.
+ mProgramManager.addDummyProgramsAt(timeMs);
+ program = mProgramManager.getProgramAt(timeMs);
+ }
+ return program;
+ }
+
+ void onAvailabilityChanged() {
+ mProgramManager.onAvailabilityChanged(mPlayController.isAvailable(),
+ mPlayController.getCurrentChannel(), mPlayController.mRecordStartTimeMs);
+ updateActions();
+ // Availability change notification should be always sent
+ // even if mNotificationEnabled is false.
+ if (mListener != null) {
+ mListener.onAvailabilityChanged();
+ }
+ }
+
+ void onRecordStartTimeChanged() {
+ if (mPlayController.isAvailable()) {
+ mProgramManager.onRecordStartTimeChanged(mPlayController.mRecordStartTimeMs);
+ }
+ updateActions();
+ if (mNotificationEnabled && mListener != null) {
+ mListener.onRecordStartTimeChanged();
+ }
+ }
+
+ void onCurrentPositionChanged() {
+ updateActions();
+ updateCurrentProgram();
+ if (mNotificationEnabled && mListener != null) {
+ mListener.onCurrentPositionChanged();
+ }
+ }
+
+ void onPlayStatusChanged(@PlayStatus int status) {
+ if (mNotificationEnabled && mListener != null) {
+ mListener.onPlayStatusChanged(status);
+ }
+ }
+
+ void onProgramInfoChanged() {
+ updateCurrentProgram();
+ if (mNotificationEnabled && mListener != null) {
+ mListener.onProgramInfoChanged();
+ }
+ }
+
+ /**
+ * Returns the current program which airs right now.<p>
+ *
+ * If the program is a dummy program, which means there's no program information,
+ * returns {@code null}.
+ */
+ @Nullable
+ public Program getCurrentProgram() {
+ if (isAvailable()) {
+ return mCurrentProgram;
+ }
+ return null;
+ }
+
+ private int getPlaybackSpeed() {
+ int[] playbackSpeedList;
+ if (getCurrentProgram() == null || getCurrentProgram().getEndTimeUtcMillis()
+ - getCurrentProgram().getStartTimeUtcMillis() > SHORT_PROGRAM_THRESHOLD_MILLIS) {
+ playbackSpeedList = LONG_PROGRAM_SPEED_FACTORS;
+ } else {
+ playbackSpeedList = SHORT_PROGRAM_SPEED_FACTORS;
+ }
+ switch (mPlayController.mDisplayedPlaySpeed) {
+ case PLAY_SPEED_1X:
+ return 1;
+ case PLAY_SPEED_2X:
+ return playbackSpeedList[0];
+ case PLAY_SPEED_3X:
+ return playbackSpeedList[1];
+ case PLAY_SPEED_4X:
+ return playbackSpeedList[2];
+ case PLAY_SPEED_5X:
+ return playbackSpeedList[3];
+ default:
+ Log.w(TAG, "Unknown displayed play speed is chosen : "
+ + mPlayController.mDisplayedPlaySpeed);
+ return 1;
+ }
+ }
+
+ /**
+ * A class which controls the trick play.
+ */
+ private class PlayController {
+ private final TunableTvView mTvView;
+
+ private long mPossibleStartTimeMs;
+ private long mRecordStartTimeMs;
+
+ @PlayStatus private int mPlayStatus = PLAY_STATUS_PAUSED;
+ @PlaySpeed private int mDisplayedPlaySpeed = PLAY_SPEED_1X;
+ @PlayDirection private int mPlayDirection = PLAY_DIRECTION_FORWARD;
+ private int mPlaybackSpeed;
+
+ /**
+ * 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() {
+ // Do not send the notifications while the availability is changing,
+ // because the variables are in the intermediate state.
+ // For example, the current program can be null.
+ mNotificationEnabled = false;
+ mDisplayedPlaySpeed = PLAY_SPEED_1X;
+ mPlaybackSpeed = 1;
+ mPlayDirection = PLAY_DIRECTION_FORWARD;
+ mIsPlayOffsetChanged = false;
+ mPossibleStartTimeMs = System.currentTimeMillis();
+ mRecordStartTimeMs = mPossibleStartTimeMs;
+ mCurrentPositionMediator.initialize(mPossibleStartTimeMs);
+ mHandler.removeMessages(MSG_GET_CURRENT_POSITION);
+
+ if (isAvailable()) {
+ // When the media availability message has come.
+ mPlayController.setPlayStatus(PLAY_STATUS_PLAYING);
+ mHandler.sendEmptyMessageDelayed(MSG_GET_CURRENT_POSITION,
+ REQUEST_CURRENT_POSITION_INTERVAL);
+ } else {
+ // When the tune command is sent.
+ mPlayController.setPlayStatus(PLAY_STATUS_PAUSED);
+ }
+ TimeShiftManager.this.onAvailabilityChanged();
+ mNotificationEnabled = true;
+ }
+
+ @Override
+ public void onRecordStartTimeChanged(long recordStartTimeMs) {
+ if (recordStartTimeMs < mPossibleStartTimeMs) {
+ // Do not warn in this case because it can happen in normal cases.
+ if (DEBUG) {
+ Log.d(TAG, "Record start time is less then the time when it became "
+ + "available. {availableStartTime="
+ + Utils.toTimeString(mPossibleStartTimeMs)
+ + ", recordStartTimeMs=" + Utils.toTimeString(recordStartTimeMs)
+ + "}");
+ }
+ recordStartTimeMs = mPossibleStartTimeMs;
+ }
+ if (mRecordStartTimeMs == recordStartTimeMs) {
+ return;
+ }
+ mRecordStartTimeMs = recordStartTimeMs;
+ TimeShiftManager.this.onRecordStartTimeChanged();
+
+ // 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();
+ }
+ }
+ });
+ }
+
+ boolean isAvailable() {
+ return mTvView.isTimeShiftAvailable() && !mTvView.isScreenBlocked();
+ }
+
+ void handleGetCurrentPosition() {
+ if (mIsPlayOffsetChanged) {
+ long currentTimeMs = System.currentTimeMillis();
+ long currentPositionMs = Math.max(
+ Math.min(mTvView.timeshiftGetCurrentPositionMs(), currentTimeMs),
+ mRecordStartTimeMs);
+ boolean isCurrentTime =
+ currentTimeMs - currentPositionMs < RECORDING_BOUNDARY_THRESHOLD;
+ long newCurrentPositionMs;
+ if (isCurrentTime && isForwarding()) {
+ // It's playing forward and the current playing position reached
+ // the current system time. i.e. The live stream is played.
+ // Therefore no need to call TvView.timeshiftGetCurrentPositionMs
+ // any more.
+ newCurrentPositionMs = currentTimeMs;
+ mIsPlayOffsetChanged = false;
+ if (mDisplayedPlaySpeed > PLAY_SPEED_1X) {
+ TimeShiftManager.this.play();
+ }
+ } else {
+ newCurrentPositionMs = currentPositionMs;
+ boolean isRecordStartTime = currentPositionMs - mRecordStartTimeMs
+ < RECORDING_BOUNDARY_THRESHOLD;
+ if (isRecordStartTime && isRewinding()) {
+ TimeShiftManager.this.play();
+ }
+ }
+ setCurrentPositionMs(newCurrentPositionMs);
+ } else {
+ setCurrentPositionMs(System.currentTimeMillis());
+ TimeShiftManager.this.onCurrentPositionChanged();
+ }
+ // 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);
+ }
+
+ void play() {
+ mDisplayedPlaySpeed = PLAY_SPEED_1X;
+ mPlaybackSpeed = 1;
+ mPlayDirection = PLAY_DIRECTION_FORWARD;
+ mTvView.timeshiftPlay();
+ setPlayStatus(PLAY_STATUS_PLAYING);
+ }
+
+ void pause() {
+ mDisplayedPlaySpeed = PLAY_SPEED_1X;
+ mPlaybackSpeed = 1;
+ mTvView.timeshiftPause();
+ setPlayStatus(PLAY_STATUS_PAUSED);
+ mIsPlayOffsetChanged = true;
+ }
+
+ void togglePlayPause() {
+ if (mPlayStatus == PLAY_STATUS_PAUSED) {
+ play();
+ } else {
+ pause();
+ }
+ }
+
+ void rewind() {
+ if (mPlayDirection == PLAY_DIRECTION_BACKWARD) {
+ increaseDisplayedPlaySpeed();
+ } else {
+ mDisplayedPlaySpeed = PLAY_SPEED_2X;
+ }
+ mPlayDirection = PLAY_DIRECTION_BACKWARD;
+ mPlaybackSpeed = getPlaybackSpeed();
+ mTvView.timeshiftRewind(mPlaybackSpeed);
+ setPlayStatus(PLAY_STATUS_PLAYING);
+ mIsPlayOffsetChanged = true;
+ }
+
+ void fastForward() {
+ if (mPlayDirection == PLAY_DIRECTION_FORWARD) {
+ increaseDisplayedPlaySpeed();
+ } else {
+ mDisplayedPlaySpeed = PLAY_SPEED_2X;
+ }
+ mPlayDirection = PLAY_DIRECTION_FORWARD;
+ mPlaybackSpeed = getPlaybackSpeed();
+ mTvView.timeshiftFastForward(mPlaybackSpeed);
+ setPlayStatus(PLAY_STATUS_PLAYING);
+ mIsPlayOffsetChanged = true;
+ }
+
+ /**
+ * Moves to the specified time.
+ */
+ void seekTo(long timeMs) {
+ mTvView.timeshiftSeekTo(Math.min(System.currentTimeMillis(),
+ Math.max(mRecordStartTimeMs, timeMs)));
+ mIsPlayOffsetChanged = true;
+ }
+
+ void onCurrentProgramChanged() {
+ // Update playback speed
+ if (mDisplayedPlaySpeed == PLAY_SPEED_1X) {
+ return;
+ }
+ int playbackSpeed = getPlaybackSpeed();
+ if (playbackSpeed != mPlaybackSpeed) {
+ mPlaybackSpeed = playbackSpeed;
+ if (mPlayDirection == PLAY_DIRECTION_FORWARD) {
+ mTvView.timeshiftFastForward(mPlaybackSpeed);
+ } else {
+ mTvView.timeshiftRewind(mPlaybackSpeed);
+ }
+ }
+ }
+
+ private void increaseDisplayedPlaySpeed() {
+ switch (mDisplayedPlaySpeed) {
+ case PLAY_SPEED_1X:
+ mDisplayedPlaySpeed = PLAY_SPEED_2X;
+ break;
+ case PLAY_SPEED_2X:
+ mDisplayedPlaySpeed = PLAY_SPEED_3X;
+ break;
+ case PLAY_SPEED_3X:
+ mDisplayedPlaySpeed = PLAY_SPEED_4X;
+ break;
+ case PLAY_SPEED_4X:
+ mDisplayedPlaySpeed = PLAY_SPEED_5X;
+ break;
+ }
+ }
+
+ private void setPlayStatus(@PlayStatus int status) {
+ mPlayStatus = status;
+ TimeShiftManager.this.onPlayStatusChanged(status);
+ }
+
+ boolean isForwarding() {
+ return mPlayStatus == PLAY_STATUS_PLAYING && mPlayDirection == PLAY_DIRECTION_FORWARD;
+ }
+
+ private boolean isRewinding() {
+ return mPlayStatus == PLAY_STATUS_PLAYING && mPlayDirection == PLAY_DIRECTION_BACKWARD;
+ }
+
+ Channel getCurrentChannel() {
+ return mTvView.getCurrentChannel();
+ }
+ }
+
+ private class ProgramManager {
+ private final ProgramDataManager mProgramDataManager;
+ private Channel mChannel;
+ private final List<Program> mPrograms = new ArrayList<>();
+ private final Queue<Range<Long>> mProgramLoadQueue = new LinkedList<>();
+ private LoadProgramsForCurrentChannelTask mProgramLoadTask = null;
+
+ ProgramManager(ProgramDataManager programDataManager) {
+ mProgramDataManager = programDataManager;
+ }
+
+ void onAvailabilityChanged(boolean available, Channel channel, long currentPositionMs) {
+ mProgramLoadQueue.clear();
+ if (mProgramLoadTask != null) {
+ mProgramLoadTask.cancel(true);
+ }
+ mHandler.removeMessages(MSG_PREFETCH_PROGRAM);
+ mPrograms.clear();
+ mChannel = channel;
+ if (channel == null || channel.isPassthrough()) {
+ return;
+ }
+ if (available) {
+ Program program = mProgramDataManager.getCurrentProgram(channel.getId());
+ long prefetchStartTimeMs;
+ if (program != null) {
+ mPrograms.add(program);
+ prefetchStartTimeMs = program.getEndTimeUtcMillis();
+ } else {
+ prefetchStartTimeMs = Utils.floorTime(currentPositionMs,
+ MAX_DUMMY_PROGRAM_DURATION);
+ }
+ // Create dummy program
+ mPrograms.addAll(createDummyPrograms(prefetchStartTimeMs,
+ currentPositionMs + PREFETCH_DURATION_FOR_NEXT));
+ schedulePrefetchPrograms();
+ TimeShiftManager.this.onProgramInfoChanged();
+ }
+ }
+
+ void onRecordStartTimeChanged(long startTimeMs) {
+ if (mChannel == null || mChannel.isPassthrough()) {
+ return;
+ }
+ long currentMs = System.currentTimeMillis();
+
+ long fetchStartTimeMs = Utils.floorTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION);
+ boolean needToLoad = addDummyPrograms(fetchStartTimeMs,
+ currentMs + PREFETCH_DURATION_FOR_NEXT);
+ if (needToLoad) {
+ Range<Long> period = Range.create(fetchStartTimeMs, currentMs);
+ mProgramLoadQueue.add(period);
+ startTaskIfNeeded();
+ }
+ }
+
+ private void startTaskIfNeeded() {
+ if (mProgramLoadQueue.isEmpty()) {
+ return;
+ }
+ if (mProgramLoadTask == null || mProgramLoadTask.isCancelled()) {
+ startNext();
+ } else {
+ switch (mProgramLoadTask.getStatus()) {
+ case PENDING:
+ if (mProgramLoadTask.overlaps(mProgramLoadQueue)) {
+ if (mProgramLoadTask.cancel(true)) {
+ mProgramLoadQueue.add(mProgramLoadTask.getPeriod());
+ mProgramLoadTask = null;
+ startNext();
+ }
+ }
+ break;
+ case RUNNING:
+ // Remove pending task fully satisfied by the current
+ Range<Long> current = mProgramLoadTask.getPeriod();
+ Iterator<Range<Long>> i = mProgramLoadQueue.iterator();
+ while (i.hasNext()) {
+ Range<Long> r = i.next();
+ if (current.contains(r)) {
+ i.remove();
+ }
+ }
+ break;
+ case FINISHED:
+ // The task should have already cleared it self, clear and restart anyways.
+ Log.w(TAG, mProgramLoadTask + " is finished, but was not cleared");
+ startNext();
+ break;
+ }
+ }
+ }
+
+ private void startNext() {
+ mProgramLoadTask = null;
+ if (mProgramLoadQueue.isEmpty()) {
+ return;
+ }
+
+ Range<Long> next = mProgramLoadQueue.poll();
+ // Extend next to include any overlapping Ranges.
+ Iterator<Range<Long>> i = mProgramLoadQueue.iterator();
+ while(i.hasNext()) {
+ Range<Long> r = i.next();
+ 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.executeOnDbThread();
+ }
+ }
+
+ void addDummyProgramsAt(long timeMs) {
+ addDummyPrograms(timeMs, timeMs + PREFETCH_DURATION_FOR_NEXT);
+ }
+
+ private boolean addDummyPrograms(Range<Long> period) {
+ return addDummyPrograms(period.getLower(), period.getUpper());
+ }
+
+ private boolean addDummyPrograms(long startTimeMs, long endTimeMs) {
+ boolean added = false;
+ if (mPrograms.isEmpty()) {
+ // Insert dummy program.
+ mPrograms.addAll(createDummyPrograms(startTimeMs, endTimeMs));
+ return true;
+ }
+ // Insert dummy program to the head of the list if needed.
+ Program firstProgram = mPrograms.get(0);
+ if (startTimeMs < firstProgram.getStartTimeUtcMillis()) {
+ if (!firstProgram.isValid()) {
+ // Already the firstProgram is dummy.
+ mPrograms.remove(0);
+ mPrograms.addAll(0,
+ createDummyPrograms(startTimeMs, firstProgram.getEndTimeUtcMillis()));
+ } else {
+ mPrograms.addAll(0,
+ createDummyPrograms(startTimeMs, firstProgram.getStartTimeUtcMillis()));
+ }
+ added = true;
+ }
+ // Insert dummy program to the tail of the list if needed.
+ Program lastProgram = mPrograms.get(mPrograms.size() - 1);
+ if (endTimeMs > lastProgram.getEndTimeUtcMillis()) {
+ if (!lastProgram.isValid()) {
+ // Already the lastProgram is dummy.
+ mPrograms.remove(mPrograms.size() - 1);
+ mPrograms.addAll(
+ createDummyPrograms(lastProgram.getStartTimeUtcMillis(), endTimeMs));
+ } else {
+ mPrograms.addAll(
+ createDummyPrograms(lastProgram.getEndTimeUtcMillis(), endTimeMs));
+ }
+ added = true;
+ }
+ // Insert dummy programs if the holes exist in the list.
+ for (int i = 1; i < mPrograms.size(); ++i) {
+ long endOfPrevious = mPrograms.get(i - 1).getEndTimeUtcMillis();
+ long startOfCurrent = mPrograms.get(i).getStartTimeUtcMillis();
+ if (startOfCurrent > endOfPrevious) {
+ List<Program> dummyPrograms =
+ createDummyPrograms(endOfPrevious, startOfCurrent);
+ mPrograms.addAll(i, dummyPrograms);
+ i += dummyPrograms.size();
+ added = true;
+ }
+ }
+ return added;
+ }
+
+ private void removeDummyPrograms() {
+ for (int i = 0; i < mPrograms.size(); ++i) {
+ Program program = mPrograms.get(i);
+ if (!program.isValid()) {
+ mPrograms.remove(i--);
+ }
+ }
+ }
+
+ private void removeOverlappedPrograms(List<Program> loadedPrograms) {
+ if (mPrograms.size() == 0) {
+ return;
+ }
+ Program program = mPrograms.get(0);
+ 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()) {
+ // Reached end of mPrograms.
+ if (++i == mPrograms.size()) {
+ return;
+ }
+ program = mPrograms.get(i);
+ }
+ // Remove overlapped programs.
+ while (program.getStartTimeUtcMillis() < loadedProgram.getEndTimeUtcMillis()
+ && program.getEndTimeUtcMillis() > loadedProgram.getStartTimeUtcMillis()) {
+ mPrograms.remove(i);
+ if (i >= mPrograms.size()) {
+ break;
+ }
+ program = mPrograms.get(i);
+ }
+ }
+ }
+
+ // Returns a list of dummy programs.
+ // The maximum duration of a dummy program is {@link MAX_DUMMY_PROGRAM_DURATION}.
+ // So if the duration ({@code endTimeMs}-{@code startTimeMs}) is greater than the duration,
+ // we need to create multiple dummy programs.
+ // The reason of the limitation of the duration is because we want the trick play viewer
+ // to show the time-line duration of {@link MAX_DUMMY_PROGRAM_DURATION} at most
+ // for a dummy program.
+ private List<Program> createDummyPrograms(long startTimeMs, long endTimeMs) {
+ if (startTimeMs >= endTimeMs) {
+ return Collections.emptyList();
+ }
+ List<Program> programs = new ArrayList<>();
+ 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());
+ start = end;
+ end += MAX_DUMMY_PROGRAM_DURATION;
+ }
+ programs.add(new Program.Builder()
+ .setStartTimeUtcMillis(start)
+ .setEndTimeUtcMillis(endTimeMs)
+ .build());
+ return programs;
+ }
+
+ Program getProgramAt(long timeMs) {
+ return getProgramAt(timeMs, 0, mPrograms.size() - 1);
+ }
+
+ private Program getProgramAt(long timeMs, int start, int end) {
+ if (start > end) {
+ return null;
+ }
+ int mid = (start + end) / 2;
+ Program program = mPrograms.get(mid);
+ if (program.getStartTimeUtcMillis() > timeMs) {
+ return getProgramAt(timeMs, start, mid - 1);
+ } else if (program.getEndTimeUtcMillis() <= timeMs) {
+ return getProgramAt(timeMs, mid+1, end);
+ } else {
+ return program;
+ }
+ }
+
+ private long getOldestProgramStartTime() {
+ if (mPrograms.isEmpty()) {
+ return INVALID_TIME;
+ }
+ return mPrograms.get(0).getStartTimeUtcMillis();
+ }
+
+ private Program getLastValidProgram() {
+ for (int i = mPrograms.size() - 1; i >= 0; --i) {
+ Program program = mPrograms.get(i);
+ if (program.isValid()) {
+ return program;
+ }
+ }
+ return null;
+ }
+
+ private void schedulePrefetchPrograms() {
+ if (DEBUG) Log.d(TAG, "Scheduling prefetching programs.");
+ if (mHandler.hasMessages(MSG_PREFETCH_PROGRAM)) {
+ return;
+ }
+ Program lastValidProgram = getLastValidProgram();
+ if (DEBUG) Log.d(TAG, "Last valid program = " + lastValidProgram);
+ if (lastValidProgram != null) {
+ long delay = lastValidProgram.getEndTimeUtcMillis()
+ - PREFETCH_TIME_OFFSET_FROM_PROGRAM_END - System.currentTimeMillis();
+ mHandler.sendEmptyMessageDelayed(MSG_PREFETCH_PROGRAM, delay);
+ if (DEBUG) Log.d(TAG, "Scheduling with " + delay + "(ms) delays.");
+ } else {
+ mHandler.sendEmptyMessage(MSG_PREFETCH_PROGRAM);
+ if (DEBUG) Log.d(TAG, "Scheduling promptly.");
+ }
+ }
+
+ private void prefetchPrograms() {
+ long startTimeMs;
+ Program lastValidProgram = getLastValidProgram();
+ if (lastValidProgram == null) {
+ startTimeMs = System.currentTimeMillis();
+ } else {
+ startTimeMs = lastValidProgram.getEndTimeUtcMillis();
+ }
+ long endTimeMs = System.currentTimeMillis() + PREFETCH_DURATION_FOR_NEXT;
+ if (DEBUG) {
+ Log.d(TAG, "Prefetch task starts: {startTime=" + Utils.toTimeString(startTimeMs)
+ + ", endTime=" + Utils.toTimeString(endTimeMs) + "}");
+ }
+ mProgramLoadQueue.add(Range.create(startTimeMs, endTimeMs));
+ startTaskIfNeeded();
+ }
+
+ private class LoadProgramsForCurrentChannelTask
+ extends AsyncDbTask.LoadProgramsForChannelTask {
+
+ public LoadProgramsForCurrentChannelTask(ContentResolver contentResolver,
+ Range<Long> period) {
+ super(contentResolver, mChannel.getId(), period);
+ }
+
+ @Override
+ protected void onPostExecute(List<Program> programs) {
+ if (DEBUG) {
+ 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.
+ Iterator<Range<Long>> it = mProgramLoadQueue.iterator();
+ while (it.hasNext()) {
+ Range<Long> r = it.next();
+ if (mPeriod.contains(r)) {
+ it.remove();
+ }
+ }
+ if (programs == null || programs.isEmpty() || mPrograms.isEmpty()) {
+ mPrograms.addAll(programs);
+ if (addDummyPrograms(mPeriod)) {
+ TimeShiftManager.this.onProgramInfoChanged();
+ }
+ schedulePrefetchPrograms();
+ startNextLoadingIfNeeded();
+ return;
+ }
+ 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()) {
+ mPrograms.add(i++, loadedProgram);
+ programs.remove(0);
+ if (programs.isEmpty()) {
+ break;
+ }
+ loadedProgram = programs.get(0);
+ }
+ }
+ mPrograms.addAll(programs);
+ addDummyPrograms(mPeriod);
+ TimeShiftManager.this.onProgramInfoChanged();
+ schedulePrefetchPrograms();
+ startNextLoadingIfNeeded();
+ }
+
+ @Override
+ protected void onCancelled(List<Program> 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()) + "}");
+ }
+ startNextLoadingIfNeeded();
+ }
+
+ private void startNextLoadingIfNeeded() {
+ mProgramLoadTask = null;
+ // Need to post to handler, because the task is still running.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ startTaskIfNeeded();
+ }
+ });
+ }
+
+ public boolean overlaps(Queue<Range<Long>> programLoadQueue) {
+ for (Range<Long> r : programLoadQueue) {
+ if (mPeriod.contains(r.getLower()) || mPeriod.contains(r.getUpper())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ final class CurrentPositionMediator {
+ long mCurrentPositionMs;
+ long mSeekRequestTimeMs;
+
+ void initialize(long timeMs) {
+ mSeekRequestTimeMs = INVALID_TIME;
+ mCurrentPositionMs = timeMs;
+ TimeShiftManager.this.onCurrentPositionChanged();
+ }
+
+ void onSeekRequested(long seekTimeMs) {
+ mSeekRequestTimeMs = System.currentTimeMillis();
+ mCurrentPositionMs = seekTimeMs;
+ TimeShiftManager.this.onCurrentPositionChanged();
+ }
+
+ void onCurrentPositionChanged(long currentPositionMs) {
+ if (mSeekRequestTimeMs == INVALID_TIME) {
+ mCurrentPositionMs = currentPositionMs;
+ TimeShiftManager.this.onCurrentPositionChanged();
+ return;
+ }
+ long currentTimeMs = System.currentTimeMillis();
+ boolean isValid = Math.abs(currentPositionMs - mCurrentPositionMs) < REQUEST_TIMEOUT_MS;
+ boolean isTimeout = currentTimeMs > mSeekRequestTimeMs + REQUEST_TIMEOUT_MS;
+ if (isValid || isTimeout) {
+ initialize(currentPositionMs);
+ } else {
+ if (getPlayStatus() == PLAY_STATUS_PLAYING) {
+ if (getPlayDirection() == PLAY_DIRECTION_FORWARD) {
+ mCurrentPositionMs += (currentTimeMs - mSeekRequestTimeMs)
+ * getPlaybackSpeed();
+ } else {
+ mCurrentPositionMs -= (currentTimeMs - mSeekRequestTimeMs)
+ * getPlaybackSpeed();
+ }
+ }
+ TimeShiftManager.this.onCurrentPositionChanged();
+ }
+ }
+ }
+
+ /**
+ * 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.
+ * If the time shift is available, {@link TimeShiftManager#getRecordStartTimeMs} should
+ * return the valid time.
+ */
+ void onAvailabilityChanged();
+
+ /**
+ * 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.
+ */
+ void onRecordStartTimeChanged();
+
+ /**
+ * Called when the current position is changed.
+ */
+ void onCurrentPositionChanged();
+
+ /**
+ * Called when the program information is updated.
+ */
+ void onProgramInfoChanged();
+
+ /**
+ * 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 18e78db6..9a1cea55 100644
--- a/src/com/android/tv/TvActivity.java
+++ b/src/com/android/tv/TvActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,1588 +16,19 @@
package com.android.tv;
-import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
-
import android.app.Activity;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.content.ContentUris;
-import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
-import android.graphics.Point;
-import android.media.AudioManager;
-import android.media.tv.TvInputInfo;
-import android.media.tv.TvInputManager;
-import android.media.tv.TvTrackInfo;
-import android.media.tv.TvView.OnUnhandledInputEventListener;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.preference.PreferenceManager;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Display;
-import android.view.GestureDetector;
-import android.view.GestureDetector.SimpleOnGestureListener;
-import android.view.Gravity;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.Toast;
-import com.android.tv.data.Channel;
-import com.android.tv.data.ChannelMap;
-import com.android.tv.data.DisplayMode;
-import com.android.tv.data.StreamInfo;
-import com.android.tv.dialog.EditInputDialogFragment;
-import com.android.tv.dialog.RecentlyWatchedDialogFragment;
-import com.android.tv.input.TisTvInput;
-import com.android.tv.input.TvInput;
-import com.android.tv.input.UnifiedTvInput;
-import com.android.tv.ui.ChannelBannerView;
-import com.android.tv.ui.ChannelNumberView;
-import com.android.tv.ui.KeypadView;
-import com.android.tv.ui.MainMenuView;
-import com.android.tv.ui.TunableTvView;
-import com.android.tv.ui.TunableTvView.OnTuneListener;
-import com.android.tv.ui.sidepanel.ActionItem;
-import com.android.tv.ui.sidepanel.BaseSideFragment;
-import com.android.tv.ui.sidepanel.ClosedCaptionFragment;
-import com.android.tv.ui.sidepanel.ClosedCaptionOptionFragment;
-import com.android.tv.ui.sidepanel.DisplayModeFragment;
-import com.android.tv.ui.sidepanel.DisplayModeOptionFragment;
-import com.android.tv.ui.sidepanel.EditChannelsFragment;
-import com.android.tv.ui.sidepanel.InputPickerFragment;
-import com.android.tv.ui.sidepanel.Item;
-import com.android.tv.ui.sidepanel.PipLocationFragment;
-import com.android.tv.ui.sidepanel.RadioButtonItem;
-import com.android.tv.ui.sidepanel.SideFragment;
-import com.android.tv.ui.sidepanel.SidePanelContainer;
-import com.android.tv.ui.sidepanel.SimpleGuideFragment;
-import com.android.tv.ui.sidepanel.SubMenuItem;
-import com.android.tv.util.TvInputManagerHelper;
-import com.android.tv.util.TvSettings;
import com.android.tv.util.Utils;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.List;
-
-/**
- * The main activity for demonstrating TV app.
- */
-public class TvActivity extends Activity implements AudioManager.OnAudioFocusChangeListener {
- // STOPSHIP: Turn debugging off
- private static final boolean DEBUG = true;
- private static final boolean USE_DEBUG_KEYS = true;
- private static final String TAG = "TvActivity";
-
- private static final int MSG_START_TV_RETRY = 1;
-
- private static final int DURATION_SHOW_CHANNEL_BANNER = 8000;
- private static final int DURATION_SHOW_CONTROL_GUIDE = 1000;
- private static final int DURATION_SHOW_MAIN_MENU = 5000;
- private static final int DURATION_SHOW_SIDE_FRAGMENT = 60000;
- 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;
- // Wait for 3 seconds
- private static final int START_TV_MAX_RETRY = 12;
- private static final int START_TV_RETRY_INTERVAL = 250;
-
- private static final int SIDE_FRAGMENT_TAG_SHOW = 0;
- private static final int SIDE_FRAGMENT_TAG_HIDE = 1;
- private static final int SIDE_FRAGMENT_TAG_RESET = 2;
-
- private static final int[] KEYCODE_BLACKLIST = {
- KeyEvent.KEYCODE_MENU, KeyEvent.KEYCODE_CHANNEL_UP, KeyEvent.KEYCODE_CHANNEL_DOWN,
- };
-
- private static final int REQUEST_START_SETUP_ACTIIVTY = 0;
- private static final int REQUEST_START_SETTINGS_ACTIIVTY = 1;
-
- private static final String KEY_INIT_CHANNEL_ID = "com.android.tv.init_channel_id";
-
- private static final HashSet<String> AVAILABLE_DIALOG_TAGS = new HashSet<String>();
-
- private View mContentView;
- private TunableTvView mTvView;
- private Bundle mTuneParams;
- private LinearLayout mControlGuide;
- private MainMenuView mMainMenuView;
- private ChannelBannerView mChannelBanner;
- private ChannelNumberView mChannelNumberView;
- private KeypadView mKeypadView;
- private SidePanelContainer mSidePanelContainer;
- private HideRunnable mHideChannelBanner;
- private HideRunnable mHideControlGuide;
- private HideRunnable mHideMainMenu;
- private HideRunnable mHideSideFragment;
- private int mShortAnimationDuration;
- private int mDisplayWidth;
- private GestureDetector mGestureDetector;
- private ChannelMap mChannelMap;
- private long mInitChannelId;
- private String mInitTvInputId;
- private boolean mChildActivityCanceled;
-
- private TvInput mTvInputForSetup;
- private TvInputManagerHelper mTvInputManagerHelper;
- private AudioManager mAudioManager;
- private int mAudioFocusStatus;
- private boolean mTunePendding;
- private boolean mPipEnabled;
- private long mPipChannelId;
- private boolean mDebugNonFullSizeScreen;
- private boolean mActivityResumed;
- private boolean mSilenceRequired;
- private boolean mUseKeycodeBlacklist;
- private boolean mIsShy = true;
-
- private boolean mIsClosedCaptionEnabled;
- private int mDisplayMode;
- private int mPipLocation;
- private SharedPreferences mSharedPreferences;
-
- private SimpleGuideFragment mSimpleGuideFragment;
-
- static {
- AVAILABLE_DIALOG_TAGS.add(RecentlyWatchedDialogFragment.DIALOG_TAG);
- AVAILABLE_DIALOG_TAGS.add(EditInputDialogFragment.DIALOG_TAG);
- }
-
- // PIP is used for debug/verification of multiple sessions rather than real PIP feature.
- // When PIP is enabled, the same channel as mTvView is tuned.
- private TunableTvView mPipView;
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_START_TV_RETRY) {
- Object[] arg = (Object[]) msg.obj;
- TvInput input = (TvInput) arg[0];
- long channelId = (Long) arg[1];
- int retryCount = msg.arg1;
- startTvIfAvailableOrRetry(input, channelId, retryCount);
- }
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.activity_tv);
- mContentView = findViewById(android.R.id.content);
- mTvView = (TunableTvView) findViewById(R.id.main_tv_view);
- mTvView.setOnUnhandledInputEventListener(new OnUnhandledInputEventListener() {
- @Override
- public boolean onUnhandledInputEvent(InputEvent event) {
- 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);
- }
- } else if (event instanceof MotionEvent) {
- MotionEvent motionEvent = (MotionEvent) event;
- if (motionEvent.isTouchEvent()) {
- return onTouchEvent(motionEvent);
- }
- }
- return false;
- }
- });
- mPipView = (TunableTvView) findViewById(R.id.pip_tv_view);
- mPipView.setPip(true);
-
- mControlGuide = (LinearLayout) findViewById(R.id.control_guide);
- mChannelBanner = (ChannelBannerView) findViewById(R.id.channel_banner);
- mMainMenuView = (MainMenuView) findViewById(R.id.main_menu);
- mSidePanelContainer = (SidePanelContainer) findViewById(R.id.right_panel);
- mMainMenuView.setTvActivity(this);
- mChannelNumberView = (ChannelNumberView) findViewById(R.id.channel_number_view);
- mChannelNumberView.setTvActivity(this);
- mKeypadView = (KeypadView) findViewById(R.id.keypad);
-
- // Initially hide the channel banner and the control guide.
- mChannelBanner.setVisibility(View.GONE);
- mMainMenuView.setVisibility(View.GONE);
- mControlGuide.setVisibility(View.GONE);
- mSidePanelContainer.setVisibility(View.GONE);
- mSidePanelContainer.setTag(SIDE_FRAGMENT_TAG_RESET);
-
- mHideControlGuide = new HideRunnable(mControlGuide, DURATION_SHOW_CONTROL_GUIDE);
- mHideChannelBanner = new HideRunnable(mChannelBanner, DURATION_SHOW_CHANNEL_BANNER);
- mHideMainMenu = new HideRunnable(mMainMenuView, DURATION_SHOW_MAIN_MENU,
- new Runnable() {
- @Override
- public void run() {
- if (mPipEnabled) {
- mPipView.setVisibility(View.GONE);
- }
- }
- },
- new Runnable() {
- @Override
- public void run() {
- if (mPipEnabled && mActivityResumed
- && getFragmentManager().getBackStackEntryCount() <= 0) {
- mPipView.setVisibility(View.VISIBLE);
- }
- }
- });
- mHideSideFragment = new HideRunnable(mSidePanelContainer, DURATION_SHOW_SIDE_FRAGMENT,
- new Runnable() {
- @Override
- public void run() {
- if (mPipEnabled) {
- mPipView.setVisibility(View.GONE);
- }
- }
- },
- new Runnable() {
- @Override
- public void run() {
- resetSideFragment();
- if (mPipEnabled && mActivityResumed && !mMainMenuView.isShown()) {
- mPipView.setVisibility(View.VISIBLE);
- }
- }
- });
-
- mShortAnimationDuration = getResources().getInteger(
- android.R.integer.config_shortAnimTime);
-
- mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
- Display display = getWindowManager().getDefaultDisplay();
- Point size = new Point();
- display.getSize(size);
- mDisplayWidth = size.x;
-
- mGestureDetector = new GestureDetector(this, new SimpleOnGestureListener() {
- static final float CONTROL_MARGIN = 0.2f;
- final float mLeftMargin = mDisplayWidth * CONTROL_MARGIN;
- final float mRightMargin = mDisplayWidth * (1 - CONTROL_MARGIN);
-
- @Override
- public boolean onDown(MotionEvent event) {
- if (DEBUG) Log.d(TAG, "onDown: " + event.toString());
- if (mChannelMap == null) {
- return false;
- }
-
- mHideControlGuide.showAndHide();
-
- if (event.getX() <= mLeftMargin) {
- channelDown();
- return true;
- } else if (event.getX() >= mRightMargin) {
- channelUp();
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onSingleTapUp(MotionEvent event) {
- if (mChannelMap == null) {
- showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN);
- return true;
- }
-
- if (event.getX() > mLeftMargin && event.getX() < mRightMargin) {
- displayMainMenu(true);
- return true;
- }
- return false;
- }
- });
-
- TvInputManager manager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
- mTvInputManagerHelper = new TvInputManagerHelper(manager);
- mTvInputManagerHelper.start();
-
- mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- restoreClosedCaptionEnabled();
- restoreDisplayMode();
- restorePipLocation();
- onNewIntent(getIntent());
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- // 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_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;
- }
-
- if (Intent.ACTION_VIEW.equals(intent.getAction())) {
- // In case the channel is given explicitly, use it.
- mInitChannelId = ContentUris.parseId(intent.getData());
- mTuneParams = intent.getExtras();
- if (mTuneParams == null) {
- mTuneParams = new Bundle();
- }
- mTuneParams.putLong(KEY_INIT_CHANNEL_ID, mInitChannelId);
- } else {
- mInitChannelId = Channel.INVALID_ID;
- }
- }
+public class TvActivity extends Activity {
@Override
protected void onStart() {
super.onStart();
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mActivityResumed = true;
- mSilenceRequired = false;
- mTvInputManagerHelper.update();
- if (mTvInputManagerHelper.getTvInputSize() == 0) {
- Toast.makeText(this, R.string.no_input_device_found, Toast.LENGTH_SHORT).show();
- // TODO: Direct the user to a Play Store landing page for TvInputService apps.
- return;
- }
- boolean tvStarted = false;
- if (mInitTvInputId != null) {
- TvInputInfo inputInfo = mTvInputManagerHelper.getTvInputInfo(mInitTvInputId);
- if (inputInfo != null) {
- startTvIfAvailableOrRetry(new TisTvInput(mTvInputManagerHelper, inputInfo, this),
- Channel.INVALID_ID, 0);
- tvStarted = true;
- }
- }
- if (!tvStarted) {
- startTv(mInitChannelId);
- }
- mInitChannelId = Channel.INVALID_ID;
- mInitTvInputId = null;
- if (mPipEnabled) {
- if (!mPipView.isPlaying()) {
- startPip();
- } else if (!mPipView.isShown()) {
- mPipView.setVisibility(View.VISIBLE);
- }
- }
- }
-
- @Override
- protected void onPause() {
- hideOverlays(true, true, true);
- if (mPipEnabled) {
- mPipView.setVisibility(View.GONE);
- }
- mActivityResumed = false;
- super.onPause();
- }
-
- private void startTv(long channelId) {
- if (mTvView.isPlaying()) {
- // TV has already started.
- if (channelId == Channel.INVALID_ID) {
- // Simply adjust the volume without tune.
- setVolumeByAudioFocusStatus();
- return;
- }
- Uri channelUri = mChannelMap.getCurrentChannelUri();
- if (channelUri != null && ContentUris.parseId(channelUri) == channelId) {
- // The requested channel is already tuned.
- setVolumeByAudioFocusStatus();
- return;
- }
- stopTv();
- }
-
- if (channelId == Channel.INVALID_ID) {
- // If any initial channel id is not given, remember the last channel the user watched.
- channelId = Utils.getLastWatchedChannelId(this);
- }
- if (channelId == Channel.INVALID_ID) {
- // If failed to pick a channel, try a different input.
- showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN);
- return;
- }
- String inputId = Utils.getInputIdForChannel(this, channelId);
- if (TextUtils.isEmpty(inputId)) {
- // If the channel is invalid, try to use the last selected physical tv input.
- inputId = Utils.getLastSelectedPhysInputId(this);
- channelId = Channel.INVALID_ID;
- if (TextUtils.isEmpty(inputId)) {
- // If failed to determine the input for that channel, try a different input.
- showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN);
- return;
- }
- }
- TvInputInfo inputInfo = mTvInputManagerHelper.getTvInputInfo(inputId);
- if (inputInfo == null) {
- // TODO: if the last selected TV input is uninstalled, getLastWatchedChannelId
- // should return Channel.INVALID_ID.
- Log.w(TAG, "Input (id=" + inputId + ") doesn't exist");
- showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN);
- return;
- }
- String lastSelectedInputId = Utils.getLastSelectedInputId(this);
- TvInput input;
- if (UnifiedTvInput.ID.equals(lastSelectedInputId)) {
- input = new UnifiedTvInput(mTvInputManagerHelper, this);
- } else {
- input = new TisTvInput(mTvInputManagerHelper, inputInfo, this);
- }
- startTvIfAvailableOrRetry(input, channelId, 0);
- }
-
- private void startTvIfAvailableOrRetry(TvInput input, long channelId, int retryCount) {
- if (input.getInputState() == INPUT_STATE_DISCONNECTED) {
- if (retryCount >= START_TV_MAX_RETRY) {
- showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN);
- return;
- }
- if (DEBUG) Log.d(TAG, "Retry start TV (retryCount=" + retryCount + ")");
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_TV_RETRY,
- retryCount + 1, 0, new Object[]{input, channelId}),
- START_TV_RETRY_INTERVAL);
- return;
- }
- startTv(input, channelId);
- }
-
- @Override
- protected void onStop() {
- if (DEBUG) Log.d(TAG, "onStop()");
- hideOverlays(true, true, true, false);
- mHandler.removeMessages(MSG_START_TV_RETRY);
- stopTv();
- stopPip();
- super.onStop();
- }
-
- public void onInputPicked(TvInput input) {
- if (input.equals(getSelectedTvInput())) {
- // Nothing has changed thus nothing to do.
- return;
- }
- if (input.getInputState() == INPUT_STATE_DISCONNECTED) {
- String message = getString(R.string.input_is_not_available);
- Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
- return;
- }
- if (!input.hasChannel(false)) {
- mTvInputForSetup = null;
- if (!startSetupActivity(input)) {
- String message = String.format(
- getString(R.string.empty_channel_tvinput_and_no_setup_activity),
- input.getDisplayName());
- Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
- showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN);
- }
- return;
- }
-
- stopTv();
- startTvWithLastWatchedChannel(input);
- }
-
- public TvInputManagerHelper getTvInputManagerHelper() {
- return mTvInputManagerHelper;
- }
-
- public TvInput getSelectedTvInput() {
- return mChannelMap == null ? null : mChannelMap.getTvInput();
- }
-
- public void showEditChannelsFragment(int initiator) {
- showSideFragment(new EditChannelsFragment(mChannelMap.getChannelList(false)), initiator);
- }
-
- public boolean startSetupActivity() {
- if (getSelectedTvInput() == null) {
- return false;
- }
- return startSetupActivity(getSelectedTvInput());
- }
-
- public boolean startSetupActivity(TvInput input) {
- Intent intent = input.getIntentForSetupActivity();
- if (intent == null) {
- return false;
- }
- startActivityForResult(intent, REQUEST_START_SETUP_ACTIIVTY);
- mTvInputForSetup = input;
- mInitTvInputId = null;
- stopTv();
- return true;
- }
-
- public boolean startSettingsActivity() {
- TvInput input = getSelectedTvInput();
- if (input == null) {
- Log.w(TAG, "There is no selected TV input during startSettingsActivity");
- return false;
- }
- Intent intent = input.getIntentForSettingsActivity();
- if (intent == null) {
- return false;
- }
- startActivityForResult(intent, REQUEST_START_SETTINGS_ACTIIVTY);
- return true;
- }
-
- public void showSimpleGuide(int initiator) {
- mSimpleGuideFragment = new SimpleGuideFragment(this, mChannelMap);
- showSideFragment(mSimpleGuideFragment, initiator);
- }
-
- public void showInputPicker(int initiator) {
- showSideFragment(new InputPickerFragment(), initiator);
- }
-
- public void showDisplayModeOption(int initiator) {
- showSideFragment(new DisplayModeOptionFragment(), initiator);
- }
-
- public void showClosedCaptionOption(int initiator) {
- showSideFragment(new ClosedCaptionOptionFragment(), initiator);
- }
-
- public void showPipLocationOption(int initiator) {
- showSideFragment(new PipLocationFragment(), initiator);
- }
-
- public void showDebugOptions(int initiator) {
- showSideFragment(new SideFragment() {
- @Override
- protected String getTitle() {
- return getString(R.string.menu_debug_options);
- }
-
- @Override
- protected List<Item> getItemList() {
- List<Item> items = new ArrayList<>();
- items.add(new SubMenuItem(getString(R.string.item_tv_input), getFragmentManager()) {
- @Override
- protected List<Item> getItemList() {
- return getTvInputMenu();
- }
- });
- items.add(new ActionItem(getString(R.string.item_watch_history)) {
- @Override
- protected void onSelected() {
- super.onSelected();
- showRecentlyWatchedDialog();
- }
- });
- return items;
- }
-
- private List<Item> getTvInputMenu() {
- ArrayList<Item> items = new ArrayList<>();
-
- items.add(new TvInputItem(
- new UnifiedTvInput(mTvInputManagerHelper, getActivity())));
-
- mTvInputManagerHelper.update();
- List<TvInputInfo> infos = new ArrayList<>(
- mTvInputManagerHelper.getTvInputInfos(false));
- Collections.sort(infos, new Comparator<TvInputInfo>() {
- @Override
- public int compare(TvInputInfo lhs, TvInputInfo rhs) {
- String a = Utils.getDisplayNameForInput(getActivity(), lhs);
- String b = Utils.getDisplayNameForInput(getActivity(), rhs);
- return a.compareTo(b);
- }
- });
- for (TvInputInfo inputInfo : infos) {
- if (inputInfo.getType() == TvInputInfo.TYPE_TUNER) {
- items.add(new TvInputItem(
- new TisTvInput(mTvInputManagerHelper, inputInfo, getActivity())));
- }
- }
-
- TvInput selected = getSelectedTvInput();
- if (selected == null) {
- ((TvInputItem) items.get(0)).setChecked(true);
- } else {
- for (Item item : items) {
- if (((TvInputItem) item).getTvInput().equals(selected)) {
- ((TvInputItem) item).setChecked(true);
- break;
- }
- }
- }
-
- return items;
- }
-
- class TvInputItem extends RadioButtonItem {
- private TvInput mTvInput;
-
- private TvInputItem(TvInput tvInput) {
- super(tvInput.getDisplayName());
- mTvInput = tvInput;
- }
-
- public TvInput getTvInput() {
- return mTvInput;
- }
-
- @Override
- protected void onSelected() {
- super.onSelected();
- onInputPicked(mTvInput);
- }
- }
- }, initiator);
- }
-
- public void showSideFragment(Fragment f, int initiator) {
- mSidePanelContainer.setTag(SIDE_FRAGMENT_TAG_SHOW);
- mSidePanelContainer.setKeyDispatchable(true);
-
- Bundle bundle = new Bundle();
- bundle.putInt(BaseSideFragment.KEY_INITIATOR, initiator);
- f.setArguments(bundle);
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.add(R.id.right_panel, f);
- ft.addToBackStack(null);
- ft.commit();
-
- mHideSideFragment.showAndHide();
- }
-
- public void popFragmentBackStack() {
- if (getFragmentManager().getBackStackEntryCount() > 1) {
- getFragmentManager().popBackStack();
- } else if (getFragmentManager().getBackStackEntryCount() == 1
- && (Integer) mSidePanelContainer.getTag() != SIDE_FRAGMENT_TAG_RESET) {
- if ((Integer) mSidePanelContainer.getTag() == SIDE_FRAGMENT_TAG_SHOW) {
- mSidePanelContainer.setKeyDispatchable(false);
- mSidePanelContainer.setTag(SIDE_FRAGMENT_TAG_HIDE);
- mHideSideFragment.hideImmediately(true);
- } else {
- // It is during fade-out animation.
- }
- } else {
- getFragmentManager().popBackStack();
- }
- }
-
- public void onSideFragmentCanceled(int initiator) {
- if ((Integer) mSidePanelContainer.getTag() == SIDE_FRAGMENT_TAG_RESET) {
- return;
- }
- if (initiator == BaseSideFragment.INITIATOR_MENU) {
- displayMainMenu(false);
- }
- }
-
- private void resetSideFragment() {
- while (true) {
- if (!getFragmentManager().popBackStackImmediate()) {
- break;
- }
- }
- mSidePanelContainer.setTag(SIDE_FRAGMENT_TAG_RESET);
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode == Activity.RESULT_OK) {
- switch (requestCode) {
- case REQUEST_START_SETUP_ACTIIVTY:
- if (mTvInputForSetup != null) {
- mInitTvInputId = mTvInputForSetup.getId();
- }
- break;
-
- default:
- //TODO: Handle failure of setup.
- }
- } else {
- mChildActivityCanceled = true;
- }
- mTvInputForSetup = null;
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
- if (mContentView.hasFocusable() || getFragmentManager().getBackStackEntryCount() > 0) {
- return super.dispatchKeyEvent(event);
- }
- int eventKeyCode = event.getKeyCode();
- for (int keycode : KEYCODE_BLACKLIST) {
- if (keycode == eventKeyCode) {
- return super.dispatchKeyEvent(event);
- }
- }
- return dispatchKeyEventToSession(event) || super.dispatchKeyEvent(event);
- }
-
- @Override
- public void onAudioFocusChange(int focusChange) {
- mAudioFocusStatus = focusChange;
- setVolumeByAudioFocusStatus();
- }
-
- private void setVolumeByAudioFocusStatus() {
- if (mTvView.isPlaying()) {
- switch (mAudioFocusStatus) {
- case AudioManager.AUDIOFOCUS_GAIN:
- if (!mSilenceRequired) {
- mTvView.setStreamVolume(AUDIO_MAX_VOLUME);
- if (isShyModeSet()) {
- setShynessMode(false);
- }
- } else {
- mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
- }
- break;
- case AudioManager.AUDIOFOCUS_LOSS:
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
- if (!mActivityResumed) {
- mSilenceRequired = true;
- }
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- if (!mSilenceRequired) {
- mTvView.setStreamVolume(AUDIO_DUCKING_VOLUME);
- } else {
- mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
- }
- break;
- }
- }
- // When the activity loses the audio focus, set the Shy mode regardless of the play status.
- if (mAudioFocusStatus == AudioManager.AUDIOFOCUS_LOSS ||
- mAudioFocusStatus == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
- if (!isShyModeSet()) {
- setShynessMode(true);
- }
- }
- }
-
- private void startTvWithLastWatchedChannel(TvInput input) {
- long channelId = Utils.getLastWatchedChannelId(TvActivity.this, input.getId());
- startTv(input, channelId);
- }
-
- private void startTv(TvInput input, long channelId) {
- if (mChannelMap != null) {
- // TODO: when this case occurs, we should remove the case.
- Log.w(TAG, "The previous variables are not released in startTv");
- stopTv();
- }
-
- mMainMenuView.setChannelMap(null);
- mChannelNumberView.setChannels(null);
- int result = mAudioManager.requestAudioFocus(TvActivity.this,
- AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
- mAudioFocusStatus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) ?
- AudioManager.AUDIOFOCUS_GAIN : AudioManager.AUDIOFOCUS_LOSS;
-
- // Prepare a new channel map for the current input.
- mChannelMap = input.buildChannelMap(this, channelId, mOnChannelsLoadFinished);
- mTvView.start(mTvInputManagerHelper);
- setVolumeByAudioFocusStatus();
- tune();
- }
-
- private void stopTv() {
- if (mTvView.isPlaying()) {
- mTvView.stop();
- mAudioManager.abandonAudioFocus(this);
- }
- if (mChannelMap != null) {
- mMainMenuView.setChannelMap(null);
- mChannelNumberView.setChannels(null);
- mChannelMap.close();
- mChannelMap = null;
- }
- mTunePendding = false;
-
- if (!isShyModeSet()) {
- setShynessMode(true);
- }
- }
-
- private boolean isPlaying() {
- return mTvView.isPlaying() && mTvView.getCurrentChannelId() != Channel.INVALID_ID;
- }
-
- private void startPip() {
- if (mPipChannelId == Channel.INVALID_ID) {
- Log.w(TAG, "PIP channel id is an invalid id.");
- return;
- }
- if (DEBUG) Log.d(TAG, "startPip()");
- mPipView.start(mTvInputManagerHelper);
- boolean success = mPipView.tuneTo(mPipChannelId, null, new OnTuneListener() {
- @Override
- public void onUnexpectedStop(long channelId) {
- Log.w(TAG, "The PIP is Unexpectedly stopped");
- enablePipView(false);
- }
-
- @Override
- public void onTuned(boolean success, long channelId) {
- if (!success) {
- Log.w(TAG, "Fail to start the PIP during channel tunning");
- enablePipView(false);
- } else {
- mPipView.setVisibility(View.VISIBLE);
- }
- }
-
- @Override
- public void onStreamInfoChanged(StreamInfo info) {
- // Do nothing.
- }
-
- @Override
- public void onChannelChanged(Uri channel) {
- // Do nothing.
- }
- });
- if (!success) {
- Log.w(TAG, "Fail to start the PIP");
- return;
- }
- mPipView.setStreamVolume(AUDIO_MIN_VOLUME);
- }
-
- private void stopPip() {
- if (DEBUG) Log.d(TAG, "stopPip");
- if (mPipView.isPlaying()) {
- mPipView.setVisibility(View.GONE);
- mPipView.stop();
- }
- }
-
- private final Runnable mOnChannelsLoadFinished = new Runnable() {
- @Override
- public void run() {
- if (mTunePendding) {
- tune();
- }
-
- mChannelNumberView.setChannels(
- (mChannelMap == null) ? null : mChannelMap.getChannelList(true));
- mMainMenuView.setChannelMap(mChannelMap);
- }
- };
-
- private void tune() {
- if (DEBUG) Log.d(TAG, "tune()");
- // Prerequisites to be able to tune.
- if (mChannelMap == null || !mChannelMap.isLoadFinished()) {
- if (DEBUG) Log.d(TAG, "Channel map not ready");
- mTunePendding = true;
- return;
- }
- mTunePendding = false;
- long channelId = mChannelMap.getCurrentChannelId();
- final String inputId = mChannelMap.getTvInput().getId();
- if (channelId == Channel.INVALID_ID) {
- stopTv();
- Toast.makeText(this, R.string.input_is_not_available, Toast.LENGTH_SHORT).show();
- return;
- }
-
- if (mTuneParams != null) {
- if (channelId == mTuneParams.getLong(KEY_INIT_CHANNEL_ID)) {
- mTuneParams.remove(KEY_INIT_CHANNEL_ID);
- } else {
- mTuneParams = null;
- }
- }
- mTvView.tuneTo(channelId, mTuneParams, new OnTuneListener() {
- @Override
- public void onUnexpectedStop(long channelId) {
- stopTv();
- startTv(Channel.INVALID_ID);
- }
-
- @Override
- public void onTuned(boolean success, long channelId) {
- if (!success) {
- Log.w(TAG, "Failed to tune to channel " + channelId);
- // TODO: show something to user about this error.
- } else {
- Utils.setLastWatchedChannelId(TvActivity.this, inputId,
- mTvView.getCurrentTvInputInfo().getId(), channelId);
- }
- }
-
- @Override
- public void onStreamInfoChanged(StreamInfo info) {
- updateChannelBanner(false);
- applyDisplayMode(info.getVideoWidth(), info.getVideoHeight());
- }
-
- @Override
- public void onChannelChanged(Uri channel) {
- // TODO: update {@code mChannelMap} and the banner.
- }
- });
- mTuneParams = null;
- updateChannelBanner(true);
- if (mChildActivityCanceled) {
- displayMainMenu(false);
- mChildActivityCanceled = false;
- }
- if (isShyModeSet()) {
- setShynessMode(false);
- // TODO: Set the shy mode to true when tune() fails.
- }
- }
-
- public void hideOverlays(boolean hideMainMenu, boolean hideChannelBanner,
- boolean hideSidePanel) {
- hideOverlays(hideMainMenu, hideChannelBanner, hideSidePanel, true);
- }
-
- public void hideOverlays(boolean hideMainMenu, boolean hideChannelBanner,
- boolean hideSidePanel, boolean withAnimation) {
- if (hideMainMenu) {
- mHideMainMenu.hideImmediately(withAnimation);
- }
- if (hideChannelBanner) {
- mHideChannelBanner.hideImmediately(withAnimation);
- }
- if (hideSidePanel) {
- if ((Integer) mSidePanelContainer.getTag() != SIDE_FRAGMENT_TAG_SHOW) {
- return;
- }
- mSidePanelContainer.setTag(SIDE_FRAGMENT_TAG_HIDE);
- mHideSideFragment.hideImmediately(withAnimation);
- }
- }
-
- private void applyDisplayMode(int videoWidth, int videoHeight) {
- int decorViewWidth = getWindow().getDecorView().getWidth();
- int decorViewHeight = getWindow().getDecorView().getHeight();
- ViewGroup.LayoutParams layoutParams = mTvView.getLayoutParams();
- int displayMode = mDisplayMode;
- double decorViewRatio = 0;
- double videoRatio = 0;
- if (decorViewWidth <= 0 || decorViewHeight <= 0 || videoWidth <= 0 || videoHeight <=0) {
- displayMode = DisplayMode.MODE_FULL;
- Log.w(TAG, "Some resolution info is missing during applyDisplayMode. ("
- + "decorViewWidth=" + decorViewWidth + ", decorViewHeight=" + decorViewHeight
- + ", width=" + videoWidth + ", height=" + videoHeight + ")");
- } else {
- decorViewRatio = (double) decorViewWidth / decorViewHeight;
- videoRatio = (double) videoWidth / videoHeight;
- }
- if (displayMode == DisplayMode.MODE_FULL) {
- layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
- layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
- } else if (displayMode == DisplayMode.MODE_ZOOM) {
- if (videoRatio < decorViewRatio) {
- // Y axis will be clipped.
- layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
- layoutParams.height = (int) (decorViewWidth / videoRatio);
- } else {
- // X axis will be clipped.
- layoutParams.width = (int) (decorViewHeight * videoRatio);
- layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
- }
- } else {
- // MODE_NORMAL (default value)
- if (videoRatio < decorViewRatio) {
- // X axis has black area.
- layoutParams.width = (int) (decorViewHeight * videoRatio);
- layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
- } else {
- // Y axis has black area.
- layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
- layoutParams.height = (int) (decorViewWidth / videoRatio);
- }
- }
- mTvView.setLayoutParams(layoutParams);
- }
-
- private void updateChannelBanner(final boolean showBanner) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (mChannelMap == null || !mChannelMap.isLoadFinished()) {
- return;
- }
-
- mChannelBanner.updateViews(mChannelMap, mTvView);
- if (showBanner) {
- mHideChannelBanner.showAndHide();
- }
- }
- });
- }
-
- private void displayMainMenu(final boolean resetSelectedItemPosition) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (mChannelMap == null || !mChannelMap.isLoadFinished()) {
- return;
- }
-
- if (!mMainMenuView.isShown() && resetSelectedItemPosition) {
- mMainMenuView.resetSelectedItemPosition();
- }
- mHideMainMenu.showAndHide();
- }
- });
- }
-
- public void showRecentlyWatchedDialog() {
- showDialogFragment(RecentlyWatchedDialogFragment.DIALOG_TAG,
- new RecentlyWatchedDialogFragment());
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- // Do not save instance state because restoring instance state when TV app died
- // unexpectedly can cause some problems like initializing fragments duplicately and
- // accessing resource before it is initialzed.
- }
-
- @Override
- protected void onDestroy() {
- if (DEBUG) Log.d(TAG, "onDestroy()");
- mTvInputManagerHelper.stop();
-
- super.onDestroy();
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (mMainMenuView.getVisibility() == View.VISIBLE || mKeypadView.wantKeys()
- || getFragmentManager().getBackStackEntryCount() > 0) {
- return super.onKeyDown(keyCode, event);
- }
- if (mChannelNumberView.isShown()) {
- return mChannelNumberView.onKeyDown(keyCode, event);
- }
- if (mChannelMap == null) {
- return false;
- }
- switch (keyCode) {
- case KeyEvent.KEYCODE_CHANNEL_UP:
- case KeyEvent.KEYCODE_DPAD_UP:
- channelUp();
- return true;
-
- case KeyEvent.KEYCODE_CHANNEL_DOWN:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- channelDown();
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (getFragmentManager().getBackStackEntryCount() > 0) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- popFragmentBackStack();
- return true;
- }
- return super.onKeyUp(keyCode, event);
- }
- if (mMainMenuView.isShown() || mChannelBanner.isShown()) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- hideOverlays(true, true, false);
- return true;
- }
- if (mMainMenuView.isShown()) {
- return super.onKeyUp(keyCode, event);
- }
- }
- if (mKeypadView.wantKeys()) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- mKeypadView.setVisibility(View.GONE);
- return true;
- }
- return super.onKeyUp(keyCode, event);
- }
- if (mChannelNumberView.isShown()) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- mChannelNumberView.hide();
- return true;
- }
- return mChannelNumberView.onKeyUp(keyCode, event);
- }
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- // When the event is from onUnhandledInputEvent, onBackPressed is not automatically
- // called. Therefore, we need to explicitly call onBackPressed().
- onBackPressed();
- return true;
- }
-
- if (mHandler.hasMessages(MSG_START_TV_RETRY)) {
- // Ignore key events during startTv retry.
- return true;
- }
- if (mChannelMap == null) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_TV_INPUT:
- case KeyEvent.KEYCODE_I:
- case KeyEvent.KEYCODE_CHANNEL_UP:
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_CHANNEL_DOWN:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- case KeyEvent.KEYCODE_NUMPAD_ENTER:
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_E:
- case KeyEvent.KEYCODE_MENU:
- showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN);
- return true;
- }
- } else {
- if (ChannelNumberView.isChannelNumberKey(keyCode)
- && keyCode != KeyEvent.KEYCODE_MINUS) {
- mChannelNumberView.show();
- mHideChannelBanner.hideImmediately(true);
- return mChannelNumberView.onKeyUp(keyCode, event);
- }
- switch (keyCode) {
- case KeyEvent.KEYCODE_TV_INPUT:
- case KeyEvent.KEYCODE_I:
- showInputPicker(BaseSideFragment.INITIATOR_UNKNOWN);
- return true;
-
- case KeyEvent.KEYCODE_DPAD_LEFT:
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- displayMainMenu(true);
- return true;
-
- case KeyEvent.KEYCODE_ENTER:
- case KeyEvent.KEYCODE_NUMPAD_ENTER:
- case KeyEvent.KEYCODE_E:
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_MENU:
- if (event.isCanceled()) {
- return true;
- }
- if (keyCode != KeyEvent.KEYCODE_MENU) {
- updateChannelBanner(true);
- }
- if (keyCode != KeyEvent.KEYCODE_E) {
- displayMainMenu(true);
- }
- return true;
- }
- }
- if (USE_DEBUG_KEYS) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_W: {
- mDebugNonFullSizeScreen = !mDebugNonFullSizeScreen;
- if (mDebugNonFullSizeScreen) {
- mTvView.layout(100, 100, 400, 300);
- } else {
- ViewGroup.LayoutParams params = mTvView.getLayoutParams();
- params.width = ViewGroup.LayoutParams.MATCH_PARENT;
- params.height = ViewGroup.LayoutParams.MATCH_PARENT;
- mTvView.setLayoutParams(params);
- }
- return true;
- }
- case KeyEvent.KEYCODE_P: {
- togglePipView();
- return true;
- }
- case KeyEvent.KEYCODE_S: {
- // For now, we just select the first subtitle track.
- // TODO: show audio/subtitle language options to user and handle the user's
- // selection.
- List<TvTrackInfo> tracks = mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE);
- if (!tracks.isEmpty()) {
- Log.d(TAG, "lang - " + tracks.get(0).getLanguage());
- Log.d(TAG, "selectTrack " + tracks.get(0));
- mTvView.selectTrack(TvTrackInfo.TYPE_SUBTITLE, tracks.get(0).getId());
- }
- return true;
- }
- case KeyEvent.KEYCODE_CTRL_LEFT:
- case KeyEvent.KEYCODE_CTRL_RIGHT: {
- mUseKeycodeBlacklist = !mUseKeycodeBlacklist;
- return true;
- }
- case KeyEvent.KEYCODE_O: {
- showDisplayModeOption(BaseSideFragment.INITIATOR_SHORTCUT_KEY);
- return true;
- }
-
- case KeyEvent.KEYCODE_D:
- showDebugOptions(BaseSideFragment.INITIATOR_SHORTCUT_KEY);
- return true;
-
- case KeyEvent.KEYCODE_K:
- mKeypadView.setVisibility(View.VISIBLE);
- mKeypadView.requestFocus();
- return true;
- }
- }
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public boolean onKeyLongPress(int keyCode, KeyEvent event) {
- if (DEBUG) Log.d(TAG, "onKeyLongPress(" + event);
- // Treat the BACK key long press as the normal press since we changed the behavior in
- // onBackPressed().
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- super.onBackPressed();
- return true;
- }
- return false;
- }
-
- @Override
- public void onBackPressed() {
- if (getFragmentManager().getBackStackEntryCount() <= 0 && isPlaying()) {
- // TODO: show the following toast message in the future.
-// Toast.makeText(getApplicationContext(), getResources().getString(
-// R.string.long_press_back), Toast.LENGTH_SHORT).show();
-
- // 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);
- startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(startMain);
- } else {
- super.onBackPressed();
- }
- }
-
- @Override
- public void onUserInteraction() {
- super.onUserInteraction();
- if (mHideMainMenu.hasFocus()
- && (Integer) mSidePanelContainer.getTag() != SIDE_FRAGMENT_TAG_SHOW) {
- mHideMainMenu.showAndHide();
- }
- if ((Integer) mSidePanelContainer.getTag() == SIDE_FRAGMENT_TAG_SHOW) {
- mHideSideFragment.showAndHide();
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (mMainMenuView.getVisibility() != View.VISIBLE && !mKeypadView.wantKeys()) {
- mGestureDetector.onTouchEvent(event);
- }
- return super.onTouchEvent(event);
- }
-
- public void togglePipView() {
- enablePipView(!mPipEnabled);
- }
-
- public void enablePipView(boolean enable) {
- if (enable == mPipEnabled) {
- return;
- }
- if (enable) {
- long pipChannelId = mTvView.getCurrentChannelId();
- if (pipChannelId != Channel.INVALID_ID) {
- mPipEnabled = true;
- mPipChannelId = pipChannelId;
- startPip();
- }
- } else {
- mPipEnabled = false;
- mPipChannelId = Channel.INVALID_ID;
- stopPip();
- }
- }
-
- private boolean dispatchKeyEventToSession(final KeyEvent event) {
- if (DEBUG) Log.d(TAG, "dispatchKeyEventToSession(" + event + ")");
- if (mTvView != null) {
- return mTvView.dispatchKeyEvent(event);
- }
- return false;
- }
-
- public void moveToChannel(long id) {
- if (mChannelMap != null && mChannelMap.isLoadFinished()
- && id != mChannelMap.getCurrentChannelId()) {
- if (mChannelMap.moveToChannel(id)) {
- tune();
- } else if (!TextUtils.isEmpty(Utils.getInputIdForChannel(this, id))) {
- startTv(id);
- } else {
- Toast.makeText(this, R.string.input_is_not_available, Toast.LENGTH_SHORT).show();
- }
- }
- }
-
- private void channelUp() {
- if (mChannelMap != null && mChannelMap.isLoadFinished()) {
- if (mChannelMap.moveToNextChannel()) {
- tune();
- } else {
- Toast.makeText(this, R.string.input_is_not_available, Toast.LENGTH_SHORT).show();
- }
- }
- }
-
- private void channelDown() {
- if (mChannelMap != null && mChannelMap.isLoadFinished()) {
- if (mChannelMap.moveToPreviousChannel()) {
- tune();
- } else {
- Toast.makeText(this, R.string.input_is_not_available, Toast.LENGTH_SHORT).show();
- }
- }
- }
-
- public void showDialogFragment(final String tag, final DialogFragment dialog) {
- // A tag for dialog must be added to AVAILABLE_DIALOG_TAGS to make it launchable from TV.
- if (!AVAILABLE_DIALOG_TAGS.contains(tag)) {
- return;
- }
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- FragmentManager fm = getFragmentManager();
- fm.executePendingTransactions();
-
- for (String availableTag : AVAILABLE_DIALOG_TAGS) {
- if (fm.findFragmentByTag(availableTag) != null) {
- return;
- }
- }
-
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.addToBackStack(null);
- dialog.show(ft, tag);
- }
- });
- }
-
- public boolean isClosedCaptionEnabled() {
- return mIsClosedCaptionEnabled;
- }
-
- public void setClosedCaptionEnabled(boolean enable, boolean storeInPreference) {
- mIsClosedCaptionEnabled = enable;
- if (storeInPreference) {
- mSharedPreferences.edit().putBoolean(TvSettings.PREF_CLOSED_CAPTION_ENABLED, enable)
- .apply();
- }
- // TODO: send the change to TIS
- }
-
- public void restoreClosedCaptionEnabled() {
- setClosedCaptionEnabled(mSharedPreferences.getBoolean(
- TvSettings.PREF_CLOSED_CAPTION_ENABLED, false), false);
- }
-
- // Returns a constant defined in DisplayMode.
- public int getDisplayMode() {
- return mDisplayMode;
- }
-
- public void setDisplayMode(int displayMode, boolean storeInPreference) {
- mDisplayMode = displayMode;
- if (storeInPreference) {
- mSharedPreferences.edit().putInt(TvSettings.PREF_DISPLAY_MODE, displayMode).apply();
- }
- applyDisplayMode(mTvView.getVideoWidth(), mTvView.getVideoHeight());
- }
-
- public void restoreDisplayMode() {
- setDisplayMode(mSharedPreferences.getInt(TvSettings.PREF_DISPLAY_MODE,
- DisplayMode.MODE_NORMAL), false);
- }
-
- // Returns a constant defined in TvSettings.
- public int getPipLocation() {
- return mPipLocation;
- }
-
- public void setPipLocation(int pipLocation, boolean storeInPreference) {
- mPipLocation = pipLocation;
- if (storeInPreference) {
- mSharedPreferences.edit().putInt(TvSettings.PREF_PIP_LOCATION, pipLocation).apply();
- }
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPipView.getLayoutParams();
- if (mPipLocation == TvSettings.PIP_LOCATION_TOP_LEFT) {
- lp.gravity = Gravity.TOP | Gravity.LEFT;
- } else if (mPipLocation == TvSettings.PIP_LOCATION_TOP_RIGHT) {
- lp.gravity = Gravity.TOP | Gravity.RIGHT;
- } else if (mPipLocation == TvSettings.PIP_LOCATION_BOTTOM_LEFT) {
- lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
- } else if (mPipLocation == TvSettings.PIP_LOCATION_BOTTOM_RIGHT) {
- lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
- } else {
- throw new IllegalArgumentException("Invaild PIP location: " + pipLocation);
- }
- mPipView.setLayoutParams(lp);
- }
-
- public void restorePipLocation() {
- setPipLocation(mSharedPreferences.getInt(TvSettings.PREF_PIP_LOCATION,
- TvSettings.PIP_LOCATION_BOTTOM_RIGHT), false);
- }
-
- private class HideRunnable implements Runnable {
- private final View mView;
- private final long mWaitingTime;
- private boolean mOnHideAnimation;
- private final Runnable mPreShowListener;
- private final Runnable mPostHideListener;
- private boolean mHasFocusDuringHideAnimation;
-
- private HideRunnable(View view, long waitingTime) {
- this(view, waitingTime, null, null);
- }
-
- private HideRunnable(View view, long waitingTime, Runnable preShowListener,
- Runnable postHideListener) {
- mView = view;
- mWaitingTime = waitingTime;
- mPreShowListener = preShowListener;
- mPostHideListener = postHideListener;
- }
-
- @Override
- public void run() {
- startHideAnimation(false);
- }
-
- private boolean hasFocus() {
- return mView.getVisibility() == View.VISIBLE
- && (!mOnHideAnimation || mHasFocusDuringHideAnimation);
- }
-
- private void startHideAnimation(boolean fastFadeOutRequired) {
- mOnHideAnimation = true;
- mHasFocusDuringHideAnimation = !fastFadeOutRequired;
- Animation anim = AnimationUtils.loadAnimation(TvActivity.this,
- android.R.anim.fade_out);
- anim.setInterpolator(AnimationUtils.loadInterpolator(TvActivity.this,
- android.R.interpolator.fast_out_linear_in));
- if (fastFadeOutRequired) {
- anim.setDuration(mShortAnimationDuration);
- }
- anim.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- if (mOnHideAnimation) {
- hideView();
- }
- }
- });
-
- mView.clearAnimation();
- mView.startAnimation(anim);
- }
-
- private void hideView() {
- mOnHideAnimation = false;
- mHasFocusDuringHideAnimation = false;
- mView.setVisibility(View.GONE);
- if (mPostHideListener != null) {
- mPostHideListener.run();
- }
- }
-
- private void hideImmediately(boolean withAnimation) {
- if (mView.getVisibility() != View.VISIBLE) {
- return;
- }
- if (!withAnimation) {
- mHandler.removeCallbacks(this);
- hideView();
- mView.clearAnimation();
- return;
- }
- if (!mOnHideAnimation) {
- mHandler.removeCallbacks(this);
- startHideAnimation(true);
- }
- }
-
- private void showAndHide() {
- if (mView.getVisibility() != View.VISIBLE) {
- if (mPreShowListener != null) {
- mPreShowListener.run();
- }
- mView.setVisibility(View.VISIBLE);
- Animation anim = AnimationUtils.loadAnimation(TvActivity.this,
- android.R.anim.fade_in);
- anim.setInterpolator(AnimationUtils.loadInterpolator(TvActivity.this,
- android.R.interpolator.linear_out_slow_in));
- mView.clearAnimation();
- mView.startAnimation(anim);
- }
- // Schedule the hide animation after a few seconds.
- mHandler.removeCallbacks(this);
- if (mOnHideAnimation) {
- mOnHideAnimation = false;
- mView.clearAnimation();
- mView.setAlpha(1f);
- }
- mHandler.postDelayed(this, mWaitingTime);
- }
- }
-
- private void setShynessMode(boolean shyMode) {
- mIsShy = shyMode;
- setMediaPlaying(!shyMode);
- }
-
- private boolean isShyModeSet() {
- return mIsShy;
- }
-
- @Override
- public void onStopMediaPlaying() {
- mHandler.removeMessages(MSG_START_TV_RETRY);
- stopTv();
- stopPip();
- super.onStopMediaPlaying();
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.putExtra(Utils.EXTRA_KEY_FROM_LAUNCHER, true);
+ startActivity(intent);
+ finish();
}
}
diff --git a/src/com/android/tv/TvApplication.java b/src/com/android/tv/TvApplication.java
new file mode 100644
index 00000000..308e0135
--- /dev/null
+++ b/src/com/android/tv/TvApplication.java
@@ -0,0 +1,135 @@
+/*
+ * 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.app.Application;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputManager;
+import android.os.Bundle;
+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.util.Utils;
+
+import java.util.List;
+
+public class TvApplication extends Application {
+ private static final String TAG = "TvApplication";
+ private static final boolean DEBUG = false;
+ private static String versionName = "";
+
+ private MainActivity mActivity;
+ private Tracker mTracker;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Analytics analytics = StubAnalytics.getInstance(this);
+ mTracker = analytics.getDefaultTracker();
+ try {
+ PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
+ versionName = pInfo.versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Unable to get version name.", e);
+ versionName = "";
+ }
+ if (DEBUG) Log.d(TAG, "Starting Live Channels " + versionName);
+ }
+
+ public Tracker getTracker() {
+ return mTracker;
+ }
+
+ /**
+ * MainActivity is set in {@link MainActivity#onCreate} and cleared in
+ * {@link MainActivity#onDestroy}.
+ */
+ public void setMainActivity(MainActivity activity) {
+ mActivity = activity;
+ }
+
+ /**
+ * Checks if MainActivity is set or not.
+ */
+ public boolean hasMainActivity() {
+ return (mActivity != null);
+ }
+
+ /**
+ * Handles the global key KEYCODE_TV.
+ */
+ public void handleTvKey() {
+ if (mActivity == null || !mActivity.isActivityResumed()) {
+ startMainActivity(null);
+ }
+ }
+
+ /**
+ * Handles the global key KEYCODE_TV_INPUT.
+ */
+ public void handleTvInputKey() {
+ TvInputManager tvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
+ List<TvInputInfo> tvInputs = tvInputManager.getTvInputList();
+ int inputCount = 0;
+ boolean hasTunerInput = false;
+ for (TvInputInfo input : tvInputs) {
+ if (input.isPassthroughInput()) {
+ ++inputCount;
+ } else if (!hasTunerInput) {
+ hasTunerInput = true;
+ ++inputCount;
+ }
+ }
+ if (inputCount < 2) {
+ return;
+ }
+ if (mActivity != null && mActivity.isActivityResumed()) {
+ // If startActivity is called, MainActivity.onPause is unnecessarily called. To
+ // prevent it, MainActivity.dispatchKeyEvent is directly called.
+ mActivity.dispatchKeyEvent(
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TV_INPUT));
+ mActivity.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_TV_INPUT));
+ } else {
+ Bundle extras = new Bundle();
+ extras.putString(Utils.EXTRA_KEY_ACTION, Utils.EXTRA_ACTION_SHOW_TV_INPUT);
+ startMainActivity(extras);
+ }
+ }
+
+ private void startMainActivity(Bundle extras) {
+ // 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);
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ startActivity(intent);
+ }
+
+ public static String getVersionName() {
+ return versionName;
+ }
+}
diff --git a/src/com/android/tv/TvOptionsManager.java b/src/com/android/tv/TvOptionsManager.java
new file mode 100644
index 00000000..8af35fae
--- /dev/null
+++ b/src/com/android/tv/TvOptionsManager.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv;
+
+import android.content.Context;
+import android.media.tv.TvTrackInfo;
+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.util.Locale;
+
+/**
+ * The TvOptionsManager is responsible for keeping track of current TV options such as closed
+ * captions and display mode. Can be also used to create MenuAction items to control such options.
+ */
+public class TvOptionsManager {
+ public static final int OPTION_CLOSED_CAPTIONS = 0;
+ public static final int OPTION_DISPLAY_MODE = 1;
+ public static final int OPTION_PIP = 2;
+ public static final int OPTION_MULTI_AUDIO = 3;
+ public static final int OPTION_CHANNEL_SOURCES = 4;
+ public static final int OPTION_PARENTAL_CONTROLS = 5;
+ public static final int OPTION_ABOUT = 6;
+
+ 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;
+
+ private final Context mContext;
+ private final SparseArray<OptionChangedListener> mOptionChangedListeners = new SparseArray<>();
+
+ private String mClosedCaptionsLanguage;
+ private int mDisplayMode;
+ private boolean mPip;
+ private String mMultiAudio;
+ private boolean mIsParentalControlEnabled;
+ 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) {
+ switch (option) {
+ case OPTION_CLOSED_CAPTIONS:
+ if (mClosedCaptionsLanguage == null) {
+ return mContext.getString(R.string.closed_caption_option_item_off);
+ }
+ return new Locale(mClosedCaptionsLanguage).getDisplayName();
+ case OPTION_DISPLAY_MODE:
+ return ((MainActivity) mContext).getTvViewUiManager()
+ .isDisplayModeAvailable(mDisplayMode)
+ ? DisplayMode.getLabel(mDisplayMode, mContext)
+ : DisplayMode.getLabel(DisplayMode.MODE_NORMAL, mContext);
+ case OPTION_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_PARENTAL_CONTROLS:
+ return mContext.getString(
+ mIsParentalControlEnabled ? R.string.option_toggle_parental_controls_on
+ : R.string.option_toggle_parental_controls_off);
+ 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);
+ notifyOptionChanged(OPTION_CLOSED_CAPTIONS);
+ }
+
+ public void onDisplayModeChanged(int displayMode) {
+ mDisplayMode = displayMode;
+ notifyOptionChanged(OPTION_DISPLAY_MODE);
+ }
+
+ public void onPipChanged(boolean pip) {
+ mPip = pip;
+ notifyOptionChanged(OPTION_PIP);
+ }
+
+ public void onMultiAudioChanged(String multiAudio) {
+ mMultiAudio = multiAudio;
+ notifyOptionChanged(OPTION_MULTI_AUDIO);
+ }
+
+ public void onParentalControlChanged(boolean isParentalControlEnabled) {
+ mIsParentalControlEnabled = isParentalControlEnabled;
+ notifyOptionChanged(OPTION_PARENTAL_CONTROLS);
+ }
+
+ 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) {
+ OptionChangedListener listener = mOptionChangedListeners.get(option);
+ if (listener != null) {
+ listener.onOptionChanged(getOptionString(option));
+ }
+ }
+
+ public void setOptionChangedListener(int option, OptionChangedListener listener) {
+ mOptionChangedListeners.put(option, listener);
+ }
+
+ /**
+ * An interface used to monitor option changes.
+ */
+ public interface OptionChangedListener {
+ void onOptionChanged(String newOption);
+ }
+}
diff --git a/sample/res/xml/sampleproxyservice.xml b/src/com/android/tv/analytics/Analytics.java
index c728426b..d5b99247 100644
--- a/sample/res/xml/sampleproxyservice.xml
+++ b/src/com/android/tv/analytics/Analytics.java
@@ -1,13 +1,11 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-/**
- * Copyright (c) 2014, The Android Open Source Project
+/*
+ * 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
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,7 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
--->
-<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
- android:setupActivity="com.example.android.sampleproxyservice.SampleProxySetupActivity" />
+package com.android.tv.analytics;
+
+/**
+ * Provides a Tracker used for user activity analysis.
+ */
+public interface Analytics {
+ Tracker getDefaultTracker();
+}
diff --git a/src/com/android/tv/analytics/DurationTimer.java b/src/com/android/tv/analytics/DurationTimer.java
new file mode 100644
index 00000000..ad2d91f8
--- /dev/null
+++ b/src/com/android/tv/analytics/DurationTimer.java
@@ -0,0 +1,62 @@
+/*
+ * 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/sample/res/xml/tvinputservice.xml b/src/com/android/tv/analytics/HasTrackerLabel.java
index 27b8948e..63466ad5 100644
--- a/sample/res/xml/tvinputservice.xml
+++ b/src/com/android/tv/analytics/HasTrackerLabel.java
@@ -1,13 +1,11 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-/**
- * Copyright (c) 2014, The Android Open Source Project
+/*
+ * 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
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,8 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
--->
-<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
- android:setupActivity="com.example.sampletvinput.SampleTvInputSetupActivity"
- android:settingsActivity="com.example.sampletvinput.SampleTvInputSettingsActivity" />
+package com.android.tv.analytics;
+
+/**
+ * Provides a label suitable for Analytics.
+ *
+ * <p><strong>WARNING</strong> implementers must ensure no PII is included in the label.
+ */
+public interface HasTrackerLabel {
+
+ /**
+ * Returns the label.
+ */
+ public String getTrackerLabel();
+}
diff --git a/src/com/android/tv/analytics/StubAnalytics.java b/src/com/android/tv/analytics/StubAnalytics.java
new file mode 100644
index 00000000..9b54e7d2
--- /dev/null
+++ b/src/com/android/tv/analytics/StubAnalytics.java
@@ -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.
+ */
+
+package com.android.tv.analytics;
+
+import android.app.Application;
+
+/**
+ * An implementation of {@link Analytics} that returns a {@link StubTracker}.
+ */
+public final class StubAnalytics implements Analytics {
+ private static final StubAnalytics INSTANCE = new StubAnalytics();
+
+ public static StubAnalytics getInstance(Application application) {
+ return INSTANCE;
+ }
+
+ private final Tracker mTracker = new StubTracker();
+
+ private StubAnalytics() { }
+
+ @Override
+ public Tracker getDefaultTracker() {
+ return mTracker;
+ }
+}
diff --git a/src/com/android/tv/analytics/StubTracker.java b/src/com/android/tv/analytics/StubTracker.java
new file mode 100644
index 00000000..d271ae83
--- /dev/null
+++ b/src/com/android/tv/analytics/StubTracker.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.analytics;
+
+import com.android.tv.data.Channel;
+
+/**
+ * A implementation of Tracker that does nothing.
+ */
+public class StubTracker implements Tracker {
+ @Override
+ public void sendMainStart() { }
+
+ @Override
+ public void sendMainStop(long durationMs) { }
+
+ @Override
+ public void sendScreenView(String screenName) { }
+
+ @Override
+ public void sendChannelViewStart(Channel channel) { }
+
+ @Override
+ public void sendChannelTuneTime(Channel channel, long durationMs) { }
+
+ @Override
+ public void sendChannelViewStop(Channel channel, long durationMs) { }
+
+ @Override
+ public void sendChannelUp() { }
+
+ @Override
+ public void sendChannelDown() { }
+
+ @Override
+ public void sendShowMenu() { }
+
+ @Override
+ public void sendHideMenu(long durationMs) { }
+
+ @Override
+ public void sendMenuClicked(String label) { }
+
+ @Override
+ public void sendMenuClicked(int labelResId) { }
+
+ @Override
+ public void sendShowEpg() { }
+
+ @Override
+ public void sendEpgItemClicked() { }
+
+ @Override
+ public void sendHideEpg(long durationMs) { }
+
+ @Override
+ public void sendShowChannelSwitch() { }
+
+ @Override
+ public void sendHideChannelSwitch(long durationMs) { }
+
+ @Override
+ public void sendChannelNumberInput() { }
+
+ @Override
+ public void sendChannelInputNavigated() { }
+
+ @Override
+ public void sendChannelNumberItemClicked() { }
+
+ @Override
+ public void sendChannelNumberItemChosenByTimeout() { }
+
+ @Override
+ public void sendAc3PassthroughCapabilities(boolean isSupported) { }
+
+ @Override
+ public void sendShowInputSelection() { }
+
+ @Override
+ public void sendHideInputSelection(long durationMs) { }
+
+ @Override
+ public void sendInputSelected(String inputLabel) { }
+
+ @Override
+ public void sendShowSidePanel(HasTrackerLabel trackerLabel) { }
+
+ @Override
+ public void sendHideSidePanel(HasTrackerLabel trackerLabel, long durationMs) { }
+}
diff --git a/src/com/android/tv/analytics/Tracker.java b/src/com/android/tv/analytics/Tracker.java
new file mode 100644
index 00000000..e2160e86
--- /dev/null
+++ b/src/com/android/tv/analytics/Tracker.java
@@ -0,0 +1,198 @@
+/*
+ * 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 com.android.tv.data.Channel;
+
+/**
+ * Interface for sending user activity for analysis.
+ */
+public interface Tracker {
+ /**
+ * Sends tracking information for starting the MainActivity.
+ */
+ void sendMainStart();
+
+ /**
+ * Sends tracking for stopping MainActivity.
+ *
+ * @param durationMs The time main activity was "started" in milliseconds.
+ */
+ void sendMainStop( long durationMs);
+
+ /**
+ * Sets the screen name and sends a ScreenView hit.
+ */
+ void sendScreenView(String screenName);
+
+ /**
+ * Sends tracking information for starting to view a channel.
+ *
+ * @param channel the current channel
+ */
+ void sendChannelViewStart(Channel channel);
+
+ /**
+ * Sends tracking information for tuning to a channel.
+ *
+ * @param channel The channel that was being tuned.
+ * @param durationMs The time the channel took to tune in milliseconds.
+ */
+ void sendChannelTuneTime(Channel channel, long durationMs);
+
+ /**
+ * Sends tracking information for stopping viewing a channel.
+ *
+ * @param channel The channel that was being watched.
+ * @param durationMs The time the channel was watched in milliseconds.
+ */
+ void sendChannelViewStop(Channel channel, long durationMs);
+
+ /**
+ * Sends tracking information for pressing channel up.
+ */
+ void sendChannelUp();
+
+ /**
+ * Sends tracking information for pressing channel down.
+ */
+ void sendChannelDown();
+
+ /**
+ * Sends tracking information for showing the main menu.
+ */
+ void sendShowMenu();
+
+ /**
+ * Sends tracking for hiding the main menu.
+ *
+ * @param durationMs The duration the menu was shown in milliseconds.
+ */
+ void sendHideMenu(long durationMs);
+
+ /**
+ * Sends tracking for clicking a menu item.
+ *
+ * <p><strong>WARNING</strong> callers must ensure no PII is included in the label.
+ *
+ * @param label The label of the item clicked.
+ */
+ void sendMenuClicked(String label);
+
+ /**
+ * Sends tracking for clicking a menu item.
+ *
+ * <p>NOTE: the tracker will use the english version of the label.
+ *
+ * @param labelResId The resource Id of the label for the menu item.
+ */
+ void sendMenuClicked(int labelResId);
+
+ /**
+ * Sends tracking information for showing the Enhanced Program Guide (EPG).
+ */
+ void sendShowEpg();
+
+ /**
+ * Sends tracking information for clicking an Enhanced Program Guide (EPG) item.
+ */
+ void sendEpgItemClicked();
+
+ /**
+ * Sends tracking for hiding the Enhanced Program Guide (EPG).
+ *
+ * @param durationMs The duration the EPG was shown in milliseconds.
+ */
+ void sendHideEpg(long durationMs);
+
+ /**
+ * Sends tracking information for showing the channel switch view.
+ */
+ void sendShowChannelSwitch();
+
+ /**
+ * Sends tracking for hiding the channel switch view.
+ *
+ * @param durationMs The duration the channel switch view was shown in milliseconds.
+ */
+ void sendHideChannelSwitch(long durationMs);
+
+ /**
+ * Sends tracking for each channel number or delimiter pressed.
+ */
+ void sendChannelNumberInput();
+
+ /**
+ * Sends tracking for navigating during channel number input.
+ *
+ * <p>This is sent once per channel input viewing.
+ */
+ void sendChannelInputNavigated();
+
+ /**
+ * Sends tracking for channel clicked.
+ */
+ void sendChannelNumberItemClicked();
+
+ /**
+ * Sends tracking for channel chosen (tuned) because the channel switch view timed out.
+ */
+ void sendChannelNumberItemChosenByTimeout();
+
+ /**
+ * Sends HDMI AC3 passthrough capablities.
+ *
+ * @param isSupported {@code true} if the feature is supported; otherwise {@code false}.
+ */
+ void sendAc3PassthroughCapabilities(boolean isSupported);
+
+ /**
+ * Sends tracking information for showing the input selection view.
+ */
+ void sendShowInputSelection();
+
+ /**
+ * Sends tracking for hiding the input selection view.
+ *
+ * @param durationMs The duration the input selection view was shown in milliseconds.
+ */
+ void sendHideInputSelection(long durationMs);
+
+ /**
+ * Sends tracking for input selected by the selection view.
+ *
+ * <p><strong>WARNING</strong> callers must ensure no PII is included in the label.
+ *
+ * @param inputLabel the label of the TV input selected
+ */
+ void sendInputSelected(String inputLabel);
+
+ /**
+ * Sends tracking information for showing a side panel.
+ *
+ * @param trackerLabel the label of the side panel.
+ */
+ void sendShowSidePanel(HasTrackerLabel trackerLabel);
+
+ /**
+ * Sends tracking for hiding a side panel.
+ *
+ * @param trackerLabel The label of the side panel
+ * @param durationMs The duration the side panel was shown in milliseconds.
+ */
+ void sendHideSidePanel(HasTrackerLabel trackerLabel, long durationMs);
+}
diff --git a/src/com/android/tv/customization/CustomAction.java b/src/com/android/tv/customization/CustomAction.java
new file mode 100644
index 00000000..3263d170
--- /dev/null
+++ b/src/com/android/tv/customization/CustomAction.java
@@ -0,0 +1,86 @@
+/*
+ * 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<CustomAction> {
+ 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;
+ }
+
+ /**
+ * Returns position priority defined in partner customization package.
+ * If there’s multiple custom options are at the front or back,
+ * options in each group will be sorted by their priority in ascending order.
+ */
+ public int getPositionPriority() {
+ return mPositionPriority;
+ }
+
+ @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
new file mode 100644
index 00000000..22298a10
--- /dev/null
+++ b/src/com/android/tv/customization/TvCustomizationManager.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.customization;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+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.text.TextUtils;
+import android.util.Log;
+
+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 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";
+
+ private static final HashMap<String, String> INTENT_CATEGORY_TO_ROW_ID;
+ static {
+ INTENT_CATEGORY_TO_ROW_ID = new HashMap<>();
+ INTENT_CATEGORY_TO_ROW_ID.put(CATEGORY_TV_CUSTOMIZATION + ".OPTIONS_ROW", ID_OPTIONS_ROW);
+ 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_TYPE_STRING = "string";
+
+ private final Context mContext;
+ private boolean mInitialized;
+ private String mCustomizationPackage;
+
+ private String mPartnerRowTitle;
+ private final Map<String, List<CustomAction>> mRowIdToCustomActionsMap = new HashMap<>();
+
+ public TvCustomizationManager(Context context) {
+ mContext = context;
+ mInitialized = false;
+ }
+
+ /**
+ * Initialize TV customization options.
+ * Run this API only on the main thread.
+ */
+ public void initialize() {
+ if (mInitialized) {
+ return;
+ }
+ mInitialized = true;
+ buildCustomActions();
+ if (!TextUtils.isEmpty(mCustomizationPackage)) {
+ buildPartnerRow();
+ }
+ }
+
+ private void buildCustomActions() {
+ mCustomizationPackage = null;
+ 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<ResolveInfo> activities = pm.queryIntentActivities(customOptionIntent,
+ PackageManager.GET_RECEIVERS | PackageManager.GET_RESOLVED_FILTER
+ | PackageManager.GET_META_DATA);
+ for (ResolveInfo info : activities) {
+ String packageName = info.activityInfo.packageName;
+ if (TextUtils.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
+ + " 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(mCustomizationPackage, info.activityInfo.name);
+
+ String rowId = INTENT_CATEGORY_TO_ROW_ID.get(intentCategory);
+ List<CustomAction> 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<CustomAction> 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<CustomAction> getCustomActions(String rowId) {
+ return mRowIdToCustomActionsMap.get(rowId);
+ }
+
+ private void buildPartnerRow() {
+ mPartnerRowTitle = null;
+ Resources res;
+ try {
+ res = mContext.getPackageManager()
+ .getResourcesForApplication(mCustomizationPackage);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Could not get resources for package " + mCustomizationPackage);
+ return;
+ }
+ int resId = res.getIdentifier(
+ RES_ID_PARTNER_ROW_TITLE, RES_TYPE_STRING, mCustomizationPackage);
+ 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 c99a6e03..659eab02 100644
--- a/src/com/android/tv/data/Channel.java
+++ b/src/com/android/tv/data/Channel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,20 +16,31 @@
package com.android.tv.data;
-import android.content.ContentValues;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.media.tv.TvContract;
-import android.os.AsyncTask;
+import android.media.tv.TvInputInfo;
+import android.net.Uri;
+import android.support.annotation.UiThread;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
import android.util.Log;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
+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.ArrayList;
-import java.util.List;
+import java.util.Arrays;
+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.
@@ -38,32 +49,100 @@ public final class Channel {
private static final String TAG = "Channel";
public static final long INVALID_ID = -1;
+ public static final int LOAD_IMAGE_TYPE_CHANNEL_LOGO = 1;
+ public static final int LOAD_IMAGE_TYPE_APP_LINK_ICON = 2;
+ public static final int LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART = 3;
+
+ /**
+ * When a TIS doesn't provide any information about app link, and it doesn't have a leanback
+ * launch intent, there will be no app link card for the TIS.
+ */
+ public static final int APP_LINK_TYPE_NONE = -1;
+ /**
+ * When a TIS provide a specific app link information, the app link card will be
+ * {@code APP_LINK_TYPE_CHANNEL} which contains all the provided information.
+ */
+ public static final int APP_LINK_TYPE_CHANNEL = 1;
+ /**
+ * When a TIS doesn't provide a specific app link information, but the app has a leanback launch
+ * intent, the app link card will be {@code APP_LINK_TYPE_APP} which launches the application.
+ */
+ public static final int APP_LINK_TYPE_APP = 2;
+
+ private static final int APP_LINK_TYPE_NOT_SET = 0;
+ private static final String INVALID_PACKAGE_NAME = "packageName";
+
+ private static final String[] PROJECTION_BASE = {
+ // Columns should 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_LOCKED,
+ };
+
+ // Additional fields added in MNC.
+ private static final String[] PROJECTION_ADDED_IN_MNC = {
+ // Columns should match what is read in Channel.fromCursor()
+ 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,
+ };
+
+ public static final String[] PROJECTION = createProjection();
+
+ private static String[] createProjection() {
+ if (TvCommonConstants.IS_MNC_OR_HIGHER) {
+ ArrayList<String> temp = new ArrayList<>(
+ PROJECTION_BASE.length + PROJECTION_ADDED_IN_MNC.length);
+ temp.addAll(Arrays.asList(PROJECTION_BASE));
+ temp.addAll(Arrays.asList(PROJECTION_ADDED_IN_MNC));
+ return temp.toArray(new String[temp.size()]);
+ } else {
+ return PROJECTION_BASE;
+ }
+ }
/** ID of this channel. Matches to BaseColumns._ID. */
private long mId;
+ private String mPackageName;
private String mInputId;
- private int mType;
- private int mOriginalNetworkId;
- private int mTransportStreamId;
+ private String mType;
private String mDisplayNumber;
private String mDisplayName;
private String mDescription;
- private boolean mIsBrowsable;
- private byte[] mData;
-
- private boolean mIsLogoLoaded;
- private LoadLogoTask mLoadLogoTask;
- private Bitmap mLogo;
-
- public interface LoadLogoCallback {
- void onLoadLogoFinished(Channel channel, Bitmap logo);
+ private String mVideoFormat;
+ private boolean mBrowsable;
+ private boolean mLocked;
+ private boolean mIsPassthrough;
+ private String mAppLinkText;
+ private int mAppLinkColor;
+ private String mAppLinkIconUri;
+ private String mAppLinkPosterArtUri;
+ private String mAppLinkIntentUri;
+ private Intent mAppLinkIntent;
+ private int mAppLinkType;
+
+ public interface LoadImageCallback {
+ void onLoadImageFinished(Channel channel, int type, Bitmap logo);
}
- private final List<LoadLogoCallback> mPendingLoadLogoCallbacks =
- new ArrayList<LoadLogoCallback>();
-
+ /**
+ * Creates {@code Channel} object from cursor.
+ * Suppress using this outside of ChannelDataManager
+ * so Channels could be managed by ChannelDataManager.
+ */
public static Channel fromCursor(Cursor cursor) {
+ // Columns read here should match Channel.PROJECTION
+
Channel channel = new Channel();
int index = cursor.getColumnIndex(TvContract.Channels._ID);
if (index >= 0) {
@@ -72,32 +151,25 @@ public final class Channel {
channel.mId = INVALID_ID;
}
- index = cursor.getColumnIndex(TvContract.Channels.COLUMN_INPUT_ID);
- if (index >= 0) {
- channel.mInputId = cursor.getString(index);
- } else {
- channel.mInputId = "inputId";
- }
-
- index = cursor.getColumnIndex(TvContract.Channels.COLUMN_TYPE);
+ index = cursor.getColumnIndex(TvContract.Channels.COLUMN_PACKAGE_NAME);
if (index >= 0) {
- channel.mType = cursor.getInt(index);
+ channel.mPackageName = Utils.intern(cursor.getString(index));
} else {
- channel.mType = 0;
+ channel.mPackageName = INVALID_PACKAGE_NAME;
}
- index = cursor.getColumnIndex(TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID);
+ index = cursor.getColumnIndex(TvContract.Channels.COLUMN_INPUT_ID);
if (index >= 0) {
- channel.mTransportStreamId = cursor.getInt(index);
+ channel.mInputId = Utils.intern(cursor.getString(index));
} else {
- channel.mTransportStreamId = 0;
+ channel.mInputId = "inputId";
}
- index = cursor.getColumnIndex(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID);
+ index = cursor.getColumnIndex(TvContract.Channels.COLUMN_TYPE);
if (index >= 0) {
- channel.mOriginalNetworkId = cursor.getInt(index);
+ channel.mType = Utils.intern(cursor.getString(index));
} else {
- channel.mOriginalNetworkId = 0;
+ channel.mType = "type";
}
index = cursor.getColumnIndex(TvContract.Channels.COLUMN_DISPLAY_NUMBER);
@@ -121,18 +193,43 @@ public final class Channel {
channel.mDescription = "description";
}
- index = cursor.getColumnIndex(TvContract.Channels.COLUMN_BROWSABLE);
+ index = cursor.getColumnIndex(TvContract.Channels.COLUMN_VIDEO_FORMAT);
if (index >= 0) {
- channel.mIsBrowsable = cursor.getInt(index) == 1;
+ channel.mVideoFormat = Utils.intern(cursor.getString(index));
} else {
- channel.mIsBrowsable = true;
+ channel.mVideoFormat = "";
}
- index = cursor.getColumnIndex(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA);
- if (index >= 0) {
- channel.mData = cursor.getBlob(index);
- } else {
- channel.mData = null;
+ index = cursor.getColumnIndex(TvContract.Channels.COLUMN_BROWSABLE);
+ channel.mBrowsable = index < 0 || cursor.getInt(index) == 1;
+
+ index = cursor.getColumnIndex(TvContract.Channels.COLUMN_LOCKED);
+ channel.mLocked = index < 0 || cursor.getInt(index) == 1;
+ if (TvCommonConstants.IS_MNC_OR_HIGHER) {
+ index = cursor.getColumnIndex(TvContract.Channels.COLUMN_APP_LINK_TEXT);
+ if (index >= 0) {
+ channel.mAppLinkText = cursor.getString(index);
+ }
+
+ index = cursor.getColumnIndex(TvContract.Channels.COLUMN_APP_LINK_COLOR);
+ if (index >= 0) {
+ channel.mAppLinkColor = cursor.getInt(index);
+ }
+
+ index = cursor.getColumnIndex(TvContract.Channels.COLUMN_APP_LINK_ICON_URI);
+ if (index >= 0) {
+ channel.mAppLinkIconUri = cursor.getString(index);
+ }
+
+ index = cursor.getColumnIndex(TvContract.Channels.COLUMN_APP_LINK_POSTER_ART_URI);
+ if (index >= 0) {
+ channel.mAppLinkPosterArtUri = cursor.getString(index);
+ }
+
+ index = cursor.getColumnIndex(TvContract.Channels.COLUMN_APP_LINK_INTENT_URI);
+ if (index >= 0) {
+ channel.mAppLinkIntentUri = cursor.getString(index);
+ }
}
return channel;
}
@@ -145,20 +242,24 @@ public final class Channel {
return mId;
}
- public String getInputId() {
- return mInputId;
+ public Uri getUri() {
+ if (isPassthrough()) {
+ return TvContract.buildChannelUriForPassthroughInput(mInputId);
+ } else {
+ return TvContract.buildChannelUri(mId);
+ }
}
- public int getType() {
- return mType;
+ public String getPackageName() {
+ return mPackageName;
}
- public int getOriginalNetworkId() {
- return mOriginalNetworkId;
+ public String getInputId() {
+ return mInputId;
}
- public int getTransportStreamId() {
- return mTransportStreamId;
+ public String getType() {
+ return mType;
}
public String getDisplayNumber() {
@@ -173,69 +274,166 @@ public final class Channel {
return mDescription;
}
+ public String getVideoFormat() {
+ return mVideoFormat;
+ }
+
+ public boolean isPassthrough() {
+ return mIsPassthrough;
+ }
+
+ public String getAppLinkText() {
+ return mAppLinkText;
+ }
+
+ public int getAppLinkColor() {
+ return mAppLinkColor;
+ }
+
+ public String getAppLinkIconUri() {
+ return mAppLinkIconUri;
+ }
+
+ public String getAppLinkPosterArtUri() {
+ return mAppLinkPosterArtUri;
+ }
+
+ public String getAppLinkIntentUri() {
+ return mAppLinkIntentUri;
+ }
+
+ /**
+ * Checks if two channels equal by checking ids.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Channel)) {
+ return false;
+ }
+ Channel other = (Channel) o;
+ // All pass-through TV channels have INVALID_ID value for mId.
+ return mId == other.mId && TextUtils.equals(mInputId, other.mInputId)
+ && mIsPassthrough == other.mIsPassthrough;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId, mInputId, mIsPassthrough);
+ }
+
public boolean isBrowsable() {
- return mIsBrowsable;
+ return mBrowsable;
}
- public void setDescription(String description) {
- mDescription = description;
+ public boolean isLocked() {
+ return mLocked;
}
public void setBrowsable(boolean browsable) {
- mIsBrowsable = browsable;
+ mBrowsable = browsable;
}
- public byte[] getData() {
- return mData;
+ public void setLocked(boolean locked) {
+ mLocked = locked;
}
- public ContentValues toContentValues() {
- ContentValues values = new ContentValues();
- values.put(TvContract.Channels.COLUMN_INPUT_ID, mInputId);
- values.put(TvContract.Channels.COLUMN_TYPE, mType);
- values.put(TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID, mTransportStreamId);
- values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, mDisplayNumber);
- values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, mDisplayName);
- values.put(TvContract.Channels.COLUMN_DESCRIPTION, mDescription);
- values.put(TvContract.Channels.COLUMN_BROWSABLE, mIsBrowsable ? 1 : 0);
- values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, mData);
- return values;
+ /**
+ * 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
+ * changed by TV app.
+ */
+ public boolean hasSameReadOnlyInfo(Channel other) {
+ return other != null
+ && Objects.equals(mId, other.mId)
+ && Objects.equals(mPackageName, other.mPackageName)
+ && Objects.equals(mInputId, other.mInputId)
+ && Objects.equals(mType, other.mType)
+ && Objects.equals(mDisplayNumber, other.mDisplayNumber)
+ && Objects.equals(mDisplayName, other.mDisplayName)
+ && Objects.equals(mDescription, other.mDescription)
+ && Objects.equals(mVideoFormat, other.mVideoFormat)
+ && mIsPassthrough == other.mIsPassthrough
+ && Objects.equals(mAppLinkText, other.mAppLinkText)
+ && mAppLinkColor == other.mAppLinkColor
+ && Objects.equals(mAppLinkIconUri, other.mAppLinkIconUri)
+ && Objects.equals(mAppLinkPosterArtUri, other.mAppLinkPosterArtUri)
+ && Objects.equals(mAppLinkIntentUri, other.mAppLinkIntentUri);
}
@Override
public String toString() {
- return new StringBuilder()
- .append("Channel{")
- .append("id=").append(mId)
- .append(", inputId=").append(mInputId)
- .append(", type=").append(mType)
- .append(", originalNetworkId=").append(mOriginalNetworkId)
- .append(", transportStreamId=").append(mTransportStreamId)
- .append(", displayNumber=").append(mDisplayNumber)
- .append(", displayName=").append(mDisplayName)
- .append(", description=").append(mDescription)
- .append(", browsable=").append(mIsBrowsable)
- .append(", data=").append(mData)
- .append("}")
- .toString();
- }
-
- public void copyFrom(Channel other) {
+ return "Channel{"
+ + "id=" + mId
+ + ", packageName=" + mPackageName
+ + ", inputId=" + mInputId
+ + ", type=" + mType
+ + ", displayNumber=" + mDisplayNumber
+ + ", displayName=" + mDisplayName
+ + ", description=" + mDescription
+ + ", videoFormat=" + mVideoFormat
+ + ", isPassthrough=" + mIsPassthrough
+ + ", browsable=" + mBrowsable
+ + ", locked=" + mLocked
+ + ", appLinkText=" + mAppLinkText + "}";
+ }
+
+ void copyFrom(Channel other) {
if (this == other) {
return;
}
mId = other.mId;
+ mPackageName = other.mPackageName;
mInputId = other.mInputId;
mType = other.mType;
- mTransportStreamId = other.mTransportStreamId;
- mOriginalNetworkId = other.mOriginalNetworkId;
mDisplayNumber = other.mDisplayNumber;
mDisplayName = other.mDisplayName;
mDescription = other.mDescription;
- mIsBrowsable = other.mIsBrowsable;
- mData = other.mData;
+ mVideoFormat = other.mVideoFormat;
+ mIsPassthrough = other.mIsPassthrough;
+ mBrowsable = other.mBrowsable;
+ mLocked = other.mLocked;
+ mAppLinkText = other.mAppLinkText;
+ mAppLinkColor = other.mAppLinkColor;
+ mAppLinkIconUri = other.mAppLinkIconUri;
+ mAppLinkPosterArtUri = other.mAppLinkPosterArtUri;
+ mAppLinkIntentUri = other.mAppLinkIntentUri;
+ mAppLinkIntent = other.mAppLinkIntent;
+ mAppLinkType = other.mAppLinkType;
+ }
+
+ /**
+ * 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");
+ }
+ String inputId = uri.getPathSegments().get(1);
+ return createPassthroughChannel(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();
}
+ /**
+ * 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.
+ */
public static final class Builder {
private final Channel mChannel;
@@ -243,15 +441,15 @@ public final class Channel {
mChannel = new Channel();
// Fill initial data.
mChannel.mId = INVALID_ID;
+ mChannel.mPackageName = INVALID_PACKAGE_NAME;
mChannel.mInputId = "inputId";
- mChannel.mType = 0;
- mChannel.mTransportStreamId = 0;
- mChannel.mOriginalNetworkId = 0;
+ mChannel.mType = "type";
mChannel.mDisplayNumber = "0";
mChannel.mDisplayName = "name";
mChannel.mDescription = "description";
- mChannel.mIsBrowsable = true;
- mChannel.mData = null;
+ mChannel.mBrowsable = true;
+ mChannel.mLocked = false;
+ mChannel.mIsPassthrough = false;
}
public Builder(Channel other) {
@@ -259,36 +457,35 @@ public final class Channel {
mChannel.copyFrom(other);
}
+ @VisibleForTesting
public Builder setId(long id) {
mChannel.mId = id;
return this;
}
- public Builder setInputId(String inputId) {
- mChannel.mInputId = inputId;
- return this;
- }
-
- public Builder setType(int type) {
- mChannel.mType = type;
+ @VisibleForTesting
+ public Builder setPackageName(String packageName) {
+ mChannel.mPackageName = packageName;
return this;
}
- public Builder setTransportStreamId(int transportStreamId) {
- mChannel.mTransportStreamId = transportStreamId;
+ public Builder setInputId(String inputId) {
+ mChannel.mInputId = inputId;
return this;
}
- public Builder setOriginalNetworkId(int originalNetworkId) {
- mChannel.mOriginalNetworkId = originalNetworkId;
+ public Builder setType(String type) {
+ mChannel.mType = type;
return this;
}
+ @VisibleForTesting
public Builder setDisplayNumber(String displayNumber) {
mChannel.mDisplayNumber = displayNumber;
return this;
}
+ @VisibleForTesting
public Builder setDisplayName(String displayName) {
mChannel.mDisplayName = displayName;
return this;
@@ -299,103 +496,227 @@ public final class Channel {
return this;
}
+ public Builder setVideoFormat(String videoFormat) {
+ mChannel.mVideoFormat = videoFormat;
+ return this;
+ }
+
public Builder setBrowsable(boolean browsable) {
- mChannel.mIsBrowsable = browsable;
+ mChannel.mBrowsable = browsable;
+ return this;
+ }
+
+ public Builder setLocked(boolean locked) {
+ mChannel.mLocked = locked;
+ return this;
+ }
+
+ public Builder setPassthrough(boolean isPassthrough) {
+ mChannel.mIsPassthrough = isPassthrough;
+ return this;
+ }
+
+ @VisibleForTesting
+ public Builder setAppLinkText(String appLinkText) {
+ mChannel.mAppLinkText = appLinkText;
return this;
}
- public Builder setData(byte[] data) {
- mChannel.mData = data;
+ public Builder setAppLinkColor(int appLinkColor) {
+ mChannel.mAppLinkColor = appLinkColor;
+ return this;
+ }
+
+ public Builder setAppLinkIconUri(String appLinkIconUri) {
+ mChannel.mAppLinkIconUri = appLinkIconUri;
+ return this;
+ }
+
+ public Builder setAppLinkPosterArtUri(String appLinkPosterArtUri) {
+ mChannel.mAppLinkPosterArtUri = appLinkPosterArtUri;
+ return this;
+ }
+
+ @VisibleForTesting
+ public Builder setAppLinkIntentUri(String appLinkIntentUri) {
+ mChannel.mAppLinkIntentUri = appLinkIntentUri;
return this;
}
public Channel build() {
- return mChannel;
+ Channel channel = new Channel();
+ channel.copyFrom(mChannel);
+ return channel;
}
}
- public boolean isLogoLoaded() {
- return mIsLogoLoaded;
+ /**
+ * Prefetches the images for this channel.
+ */
+ @UiThread
+ public void prefetchImage(Context context, int type, int maxWidth, int maxHeight) {
+ String uriString = getImageUriString(type);
+ if (!TextUtils.isEmpty(uriString)) {
+ ImageLoader.prefetchBitmap(context, uriString, maxWidth, maxHeight);
+ }
}
- public boolean isLogoLoading() {
- return mLoadLogoTask != null;
+ /**
+ * Loads the bitmap of this channel and returns it via {@code callback}.
+ * The loaded bitmap will be cached and resized with given params.
+ * <p>
+ * 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 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,
+ final LoadImageCallback callback) {
+ String uriString = getImageUriString(type);
+ ImageLoader.loadBitmap(context, uriString, maxWidth, maxHeight,
+ new ImageLoader.ImageLoaderCallback() {
+ @Override
+ public void onBitmapLoaded(Bitmap bitmap) {
+ if (callback != null) {
+ callback.onLoadImageFinished(Channel.this, type, bitmap);
+ }
+ }
+ });
}
- public Bitmap getLogo() {
- return mLogo;
+ /**
+ * 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) {
+ initAppLinkTypeAndIntent(context);
+ }
+ return mAppLinkType;
}
- // Assumes call from UI thread.
- public void loadLogo(Context context, LoadLogoCallback callback) {
- if (isLogoLoaded()) {
- callback.onLoadLogoFinished(this, mLogo);
- } else {
- mPendingLoadLogoCallbacks.add(callback);
- if (!isLogoLoading()) {
- mLoadLogoTask = new LoadLogoTask(context);
- mLoadLogoTask.execute();
- }
+ /**
+ * 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) {
+ initAppLinkTypeAndIntent(context);
}
+ return mAppLinkIntent;
}
- // Assumes call from UI thread.
- private void setLogo(Bitmap logo) {
- mIsLogoLoaded = true;
- if (isLogoLoading()) {
- mLoadLogoTask.cancel(true);
- mLoadLogoTask = null;
+ private void initAppLinkTypeAndIntent(Context context) {
+ mAppLinkType = APP_LINK_TYPE_NONE;
+ mAppLinkIntent = null;
+ PackageManager pm = context.getPackageManager();
+ if (!TextUtils.isEmpty(mAppLinkText) && !TextUtils.isEmpty(mAppLinkIntentUri)) {
+ try {
+ Intent intent = Intent.parseUri(mAppLinkIntentUri, 0);
+ if (intent.resolveActivityInfo(pm, 0) != null) {
+ mAppLinkIntent = intent;
+ mAppLinkIntent.putExtra(TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI,
+ getUri().toString());
+ mAppLinkType = APP_LINK_TYPE_CHANNEL;
+ return;
+ }
+ } catch (URISyntaxException e) {
+ // Do nothing.
+ }
+ }
+ if (mPackageName.equals(context.getApplicationContext().getPackageName())) {
+ return;
}
- mLogo = logo;
+ mAppLinkIntent = pm.getLeanbackLaunchIntentForPackage(mPackageName);
+ if (mAppLinkIntent != null) {
+ mAppLinkIntent.putExtra(TvCommonConstants.EXTRA_APP_LINK_CHANNEL_URI,
+ getUri().toString());
+ mAppLinkType = APP_LINK_TYPE_APP;
+ }
+ }
- for (LoadLogoCallback callback : mPendingLoadLogoCallbacks) {
- callback.onLoadLogoFinished(this, logo);
+ private String getImageUriString(int type) {
+ switch (type) {
+ case LOAD_IMAGE_TYPE_CHANNEL_LOGO:
+ return TvContract.buildChannelLogoUri(mId).toString();
+ case LOAD_IMAGE_TYPE_APP_LINK_ICON:
+ return mAppLinkIconUri;
+ case LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART:
+ return mAppLinkPosterArtUri;
}
- mPendingLoadLogoCallbacks.clear();
+ return null;
}
- private class LoadLogoTask extends AsyncTask<Void, Void, Bitmap> {
- private Context mContext;
+ public static class DefaultComparator implements Comparator<Channel> {
+ private final Context mContext;
+ private final TvInputManagerHelper mInputManager;
+ private final Map<String, String> mInputIdToLabelMap = new HashMap<>();
+ private boolean mDetectDuplicatesEnabled;
- LoadLogoTask(Context context) {
+ public DefaultComparator(Context context, TvInputManagerHelper inputManager) {
mContext = context;
+ mInputManager = inputManager;
+ }
+
+ public void setDetectDuplicatesEnabled(boolean detectDuplicatesEnabled) {
+ mDetectDuplicatesEnabled = detectDuplicatesEnabled;
}
@Override
- public Bitmap doInBackground(Void... params) {
- InputStream is = null;
- try {
- is = mContext.getContentResolver().openInputStream(
- TvContract.buildChannelLogoUri(mId));
- Bitmap bitmap = BitmapFactory.decodeStream(is);
- if (bitmap == null) {
- Log.e(TAG, "Failed to decode logo image for " + Channel.this);
+ public int compare(Channel lhs, Channel rhs) {
+ if (Objects.equals(lhs.getInputId(), rhs.getInputId())) {
+ // Compare the channel numbers if both channels belong to the same input.
+ int compare = ChannelNumber.compare(lhs.getDisplayNumber(), rhs.getDisplayNumber());
+ if (mDetectDuplicatesEnabled && compare == 0) {
+ Log.w(TAG, "Duplicate channels detected! - \""
+ + lhs.getDisplayNumber() + " " + lhs.getDisplayName() + "\" and \""
+ + rhs.getDisplayNumber() + " " + rhs.getDisplayName() + "\"");
}
- return bitmap;
- } catch (FileNotFoundException e) {
- // Logo may not exist.
- Log.i(TAG, "Logo not found for " + Channel.this);
- return null;
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- // Does nothing.
- }
+ return compare;
+ } else {
+ // Put channels from OEM/SOC inputs first.
+ boolean lhsIsPartner = mInputManager.isPartnerInput(lhs.getInputId());
+ boolean rhsIsPartner = mInputManager.isPartnerInput(rhs.getInputId());
+ if (lhsIsPartner != rhsIsPartner) {
+ return lhsIsPartner ? -1 : 1;
+ }
+
+ // Otherwise, compare the input labels.
+ String lhsLabel = getInputLabelForChannel(lhs);
+ String rhsLabel = getInputLabelForChannel(rhs);
+ if (lhsLabel == null && rhsLabel != null) {
+ return 1;
+ } else if (lhsLabel != null && rhsLabel == null) {
+ return -1;
+ } else if (lhsLabel == null /* && rhsLabel == null */) {
+ return 0;
}
+ return lhsLabel.compareTo(rhsLabel);
}
}
- @Override
- public void onPostExecute(Bitmap logo) {
- if (isCancelled()) {
- Log.w(TAG, "Load logo canceled for " + Channel.this);
- return;
+ @VisibleForTesting
+ String getInputLabelForChannel(Channel channel) {
+ String label = mInputIdToLabelMap.get(channel.getInputId());
+ if (label == null) {
+ TvInputInfo info = mInputManager.getTvInputInfo(channel.getInputId());
+ if (info != null) {
+ label = Utils.loadLabel(mContext, info);
+ if (label != null) {
+ mInputIdToLabelMap.put(channel.getInputId(), label);
+ }
+ }
}
- Log.i(TAG, "Loaded logo for " + Channel.this + ": " + logo);
- mLoadLogoTask = null;
- setLogo(logo);
+ return label;
}
}
}
diff --git a/src/com/android/tv/data/ChannelDataManager.java b/src/com/android/tv/data/ChannelDataManager.java
new file mode 100644
index 00000000..d09d1686
--- /dev/null
+++ b/src/com/android/tv/data/ChannelDataManager.java
@@ -0,0 +1,644 @@
+/*
+ * 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.data;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.media.tv.TvContract;
+import android.media.tv.TvContract.Channels;
+import android.media.tv.TvInputManager.TvInputCallback;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+import android.util.MutableInt;
+
+import com.android.tv.util.AsyncDbTask;
+import com.android.tv.util.TvInputManagerHelper;
+import com.android.tv.util.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 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.
+ */
+public class ChannelDataManager {
+ private static final String TAG = "ChannelDataManager";
+ private static final boolean DEBUG = false;
+
+ private static final int MSG_UPDATE_CHANNELS = 1000;
+
+ private final Context mContext;
+ private final TvInputManagerHelper mInputManager;
+ private boolean mStarted;
+ private boolean mDbLoadFinished;
+ private QueryAllChannelsTask mChannelsUpdateTask;
+ private final List<Runnable> mPostRunnablesAfterChannelUpdate = new ArrayList<>();
+
+ private final Set<Listener> mListeners = new HashSet<>();
+ private final Map<Long, ChannelWrapper> mChannelWrapperMap = new HashMap<>();
+ private final Map<String, MutableInt> mChannelCountMap = new HashMap<>();
+ private final Channel.DefaultComparator mChannelComparator;
+ private final List<Channel> mChannels = new ArrayList<>();
+
+ private final Handler mHandler;
+ private final Set<Long> mBrowsableUpdateChannelIds = new HashSet<>();
+ private final Set<Long> mLockedUpdateChannelIds = new HashSet<>();
+
+ private final ContentResolver mContentResolver;
+ private final ContentObserver mChannelObserver;
+
+ private final TvInputCallback mTvInputCallback = new TvInputCallback() {
+ @Override
+ public void onInputAdded(String inputId) {
+ boolean channelAdded = false;
+ for (ChannelWrapper channel : mChannelWrapperMap.values()) {
+ if (channel.mChannel.getInputId().equals(inputId)) {
+ channel.mInputRemoved = false;
+ addChannel(channel.mChannel);
+ channelAdded = true;
+ }
+ }
+ if (channelAdded) {
+ Collections.sort(mChannels, mChannelComparator);
+ for (Listener l : mListeners) {
+ l.onChannelListUpdated();
+ }
+ }
+ }
+
+ @Override
+ public void onInputRemoved(String inputId) {
+ boolean channelRemoved = false;
+ ArrayList<ChannelWrapper> removedChannels = new ArrayList<>();
+ for (ChannelWrapper channel : mChannelWrapperMap.values()) {
+ if (channel.mChannel.getInputId().equals(inputId)) {
+ channel.mInputRemoved = true;
+ channelRemoved = true;
+ removedChannels.add(channel);
+ }
+ }
+ if (channelRemoved) {
+ clearChannels();
+ for (ChannelWrapper channelWrapper : mChannelWrapperMap.values()) {
+ if (!channelWrapper.mInputRemoved) {
+ addChannel(channelWrapper.mChannel);
+ }
+ }
+ Collections.sort(mChannels, mChannelComparator);
+ for (Listener l : mListeners) {
+ l.onChannelListUpdated();
+ }
+ for (ChannelWrapper channel : removedChannels) {
+ channel.notifyChannelRemoved();
+ }
+ }
+ }
+ };
+
+ public ChannelDataManager(Context context, TvInputManagerHelper inputManager) {
+ this(context, inputManager, context.getContentResolver(), Looper.myLooper());
+ }
+
+ @VisibleForTesting
+ ChannelDataManager(Context context, TvInputManagerHelper inputManager,
+ ContentResolver contentResolver, Looper looper) {
+ mContext = context;
+ mInputManager = inputManager;
+ mContentResolver = contentResolver;
+ mChannelComparator = new Channel.DefaultComparator(context, inputManager);
+ // Detect duplicate channels while sorting.
+ mChannelComparator.setDetectDuplicatesEnabled(true);
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_UPDATE_CHANNELS) {
+ handleUpdateChannels();
+ }
+ }
+ };
+ mChannelObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ if (!mHandler.hasMessages(MSG_UPDATE_CHANNELS)) {
+ mHandler.sendEmptyMessage(MSG_UPDATE_CHANNELS);
+ }
+ }
+ };
+ }
+
+ @VisibleForTesting
+ ContentObserver getContentObserver() {
+ return mChannelObserver;
+ }
+
+ /**
+ * Starts the manager. If data is ready, {@link Listener#onLoadFinished()} will be called.
+ */
+ public void start() {
+ if (mStarted) {
+ return;
+ }
+ mStarted = true;
+ // 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);
+ mInputManager.addCallback(mTvInputCallback);
+ }
+
+ /**
+ * Stops the manager. It clears manager states and runs pending DB operations. Added listeners
+ * aren't automatically removed by this method.
+ */
+ public void stop() {
+ if (!mStarted) {
+ return;
+ }
+ mStarted = false;
+ mDbLoadFinished = false;
+
+ ChannelLogoFetcher.stopFetchingChannelLogos();
+ mInputManager.removeCallback(mTvInputCallback);
+ mContentResolver.unregisterContentObserver(mChannelObserver);
+ mHandler.removeCallbacksAndMessages(null);
+
+ mChannelWrapperMap.clear();
+ clearChannels();
+ mPostRunnablesAfterChannelUpdate.clear();
+ if (mChannelsUpdateTask != null) {
+ mChannelsUpdateTask.cancel(true);
+ mChannelsUpdateTask = null;
+ }
+ applyUpdatedValuesToDb();
+ }
+
+ /**
+ * Adds a {@link Listener}.
+ */
+ public void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a {@link Listener}.
+ */
+ public void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * 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);
+ if (channelWrapper == null) {
+ return;
+ }
+ channelWrapper.addListener(listener);
+ }
+
+ /**
+ * Removes a {@link ChannelListener} for a specific channel with the channel ID
+ * {@code channelId}.
+ */
+ public void removeChannelListener(Long channelId, ChannelListener listener) {
+ ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId);
+ if (channelWrapper == null) {
+ return;
+ }
+ channelWrapper.removeListener(listener);
+ }
+
+ /**
+ * Checks whether data is ready.
+ */
+ public boolean isDbLoadFinished() {
+ return mDbLoadFinished;
+ }
+
+ /**
+ * Returns the number of channels.
+ */
+ public int getChannelCount() {
+ return mChannels.size();
+ }
+
+ /**
+ * Returns a list of channels.
+ */
+ public List<Channel> getChannelList() {
+ return Collections.unmodifiableList(mChannels);
+ }
+
+ /**
+ * Returns a list of browsable channels.
+ */
+ public List<Channel> getBrowsableChannelList() {
+ List<Channel> channels = new ArrayList<>();
+ for (Channel channel : mChannels) {
+ if (channel.isBrowsable()) {
+ channels.add(channel);
+ }
+ }
+ return Collections.unmodifiableList(channels);
+ }
+
+ /**
+ * Returns the total channel count for a given input.
+ *
+ * @param inputId The ID of the input.
+ */
+ public int getChannelCountForInput(String inputId) {
+ MutableInt count = mChannelCountMap.get(inputId);
+ return count == null ? 0 : count.value;
+ }
+
+ /**
+ * 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) {
+ if (channel.isBrowsable()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Gets the channel with the channel ID {@code channelId}.
+ */
+ public Channel getChannel(Long channelId) {
+ ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId);
+ if (channelWrapper == null || channelWrapper.mInputRemoved) {
+ return null;
+ }
+ return channelWrapper.mChannel;
+ }
+
+ /**
+ * The value change will be applied to DB when applyPendingDbOperation is called.
+ */
+ public void updateBrowsable(Long channelId, boolean browsable) {
+ updateBrowsable(channelId, browsable, false);
+ }
+
+ /**
+ * 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.
+ */
+ public void updateBrowsable(Long channelId, boolean browsable,
+ boolean skipNotifyChannelBrowsableChanged) {
+ ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId);
+ if (channelWrapper == null) {
+ return;
+ }
+ if (channelWrapper.mChannel.isBrowsable() != browsable) {
+ channelWrapper.mChannel.setBrowsable(browsable);
+ if (browsable == channelWrapper.mBrowsableInDb) {
+ mBrowsableUpdateChannelIds.remove(channelWrapper.mChannel.getId());
+ } else {
+ mBrowsableUpdateChannelIds.add(channelWrapper.mChannel.getId());
+ }
+ channelWrapper.notifyChannelUpdated();
+ // When updateBrowsable is called multiple times in a method, we don't need to
+ // notify Listener.onChannelBrowsableChanged multiple times but only once. So
+ // we send a message instead of directly calling onChannelBrowsableChanged.
+ if (!skipNotifyChannelBrowsableChanged) {
+ notifyChannelBrowsableChanged();
+ }
+ }
+ }
+
+ public void notifyChannelBrowsableChanged() {
+ for (Listener l : mListeners) {
+ l.onChannelBrowsableChanged();
+ }
+ }
+
+ /**
+ * 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);
+ mChannelsUpdateTask = null;
+ }
+ mPostRunnablesAfterChannelUpdate.add(postRunnable);
+ if (!mHandler.hasMessages(MSG_UPDATE_CHANNELS)) {
+ mHandler.sendEmptyMessage(MSG_UPDATE_CHANNELS);
+ }
+ }
+
+ /**
+ * The value change will be applied to DB when applyPendingDbOperation is called.
+ */
+ public void updateLocked(Long channelId, boolean locked) {
+ ChannelWrapper channelWrapper = mChannelWrapperMap.get(channelId);
+ if (channelWrapper == null) {
+ return;
+ }
+ if (channelWrapper.mChannel.isLocked() != locked) {
+ channelWrapper.mChannel.setLocked(locked);
+ if (locked == channelWrapper.mLockedInDb) {
+ mLockedUpdateChannelIds.remove(channelWrapper.mChannel.getId());
+ } else {
+ mLockedUpdateChannelIds.add(channelWrapper.mChannel.getId());
+ }
+ channelWrapper.notifyChannelUpdated();
+ }
+ }
+
+ /**
+ * Applies the changed values by {@link #updateBrowsable} and {@link #updateLocked}
+ * to DB.
+ */
+ public void applyUpdatedValuesToDb() {
+ ArrayList<Long> browsableIds = new ArrayList<>();
+ ArrayList<Long> unbrowsableIds = new ArrayList<>();
+ for (Long id : mBrowsableUpdateChannelIds) {
+ ChannelWrapper channelWrapper = mChannelWrapperMap.get(id);
+ if (channelWrapper == null) {
+ continue;
+ }
+ if (channelWrapper.mChannel.isBrowsable()) {
+ browsableIds.add(id);
+ } else {
+ unbrowsableIds.add(id);
+ }
+ channelWrapper.mBrowsableInDb = channelWrapper.mChannel.isBrowsable();
+ }
+ String column = TvContract.Channels.COLUMN_BROWSABLE;
+ if (browsableIds.size() != 0) {
+ updateOneColumnValue(column, 1, browsableIds);
+ }
+ if (unbrowsableIds.size() != 0) {
+ updateOneColumnValue(column, 0, unbrowsableIds);
+ }
+ mBrowsableUpdateChannelIds.clear();
+
+ ArrayList<Long> lockedIds = new ArrayList<>();
+ ArrayList<Long> unlockedIds = new ArrayList<>();
+ for (Long id : mLockedUpdateChannelIds) {
+ ChannelWrapper channelWrapper = mChannelWrapperMap.get(id);
+ if (channelWrapper == null) {
+ continue;
+ }
+ if (channelWrapper.mChannel.isLocked()) {
+ lockedIds.add(id);
+ } else {
+ unlockedIds.add(id);
+ }
+ channelWrapper.mLockedInDb = channelWrapper.mChannel.isLocked();
+ }
+ column = TvContract.Channels.COLUMN_LOCKED;
+ if (lockedIds.size() != 0) {
+ updateOneColumnValue(column, 1, lockedIds);
+ }
+ if (unlockedIds.size() != 0) {
+ updateOneColumnValue(column, 0, unlockedIds);
+ }
+ 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());
+ }
+ }
+
+ private void addChannel(Channel channel) {
+ mChannels.add(channel);
+ String inputId = channel.getInputId();
+ MutableInt count = mChannelCountMap.get(inputId);
+ if (count == null) {
+ mChannelCountMap.put(inputId, new MutableInt(1));
+ } else {
+ count.value++;
+ }
+ }
+
+ private void clearChannels() {
+ mChannels.clear();
+ mChannelCountMap.clear();
+ }
+
+ private void handleUpdateChannels() {
+ if (mChannelsUpdateTask != null) {
+ mChannelsUpdateTask.cancel(true);
+ }
+ mChannelsUpdateTask = new QueryAllChannelsTask(mContentResolver);
+ mChannelsUpdateTask.executeOnDbThread();
+ }
+
+ public interface Listener {
+ /**
+ * 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.
+ */
+ void onChannelListUpdated();
+
+ /**
+ * Called when browsable of channels are changed.
+ */
+ void onChannelBrowsableChanged();
+ }
+
+ public interface ChannelListener {
+ /**
+ * Called when the channel has been removed in DB.
+ */
+ void onChannelRemoved(Channel channel);
+
+ /**
+ * Called when values of the channel has been changed.
+ */
+ void onChannelUpdated(Channel channel);
+ }
+
+ private class ChannelWrapper {
+ final Set<ChannelListener> mChannelListeners = new HashSet<>();
+ final Channel mChannel;
+ boolean mBrowsableInDb;
+ boolean mLockedInDb;
+ boolean mInputRemoved;
+
+ ChannelWrapper(Channel channel) {
+ mChannel = channel;
+ mBrowsableInDb = channel.isBrowsable();
+ mLockedInDb = channel.isLocked();
+ mInputRemoved = !mInputManager.hasTvInputInfo(channel.getInputId());
+ }
+
+ void addListener(ChannelListener listener) {
+ mChannelListeners.add(listener);
+ }
+
+ void removeListener(ChannelListener listener) {
+ mChannelListeners.remove(listener);
+ }
+
+ void notifyChannelUpdated() {
+ for (ChannelListener l : mChannelListeners) {
+ l.onChannelUpdated(mChannel);
+ }
+ }
+
+ void notifyChannelRemoved() {
+ for (ChannelListener l : mChannelListeners) {
+ l.onChannelRemoved(mChannel);
+ }
+ }
+ }
+
+ private final class QueryAllChannelsTask extends AsyncDbTask.AsyncChannelQueryTask {
+
+ public QueryAllChannelsTask(ContentResolver contentResolver) {
+ super(contentResolver);
+ }
+
+ @Override
+ protected void onPostExecute(List<Channel> channels) {
+ mChannelsUpdateTask = null;
+ if (channels == null) {
+ if (DEBUG) Log.e(TAG, "onPostExecute with null channels");
+ return;
+ }
+ Set<Long> removedChannelIds = new HashSet<>(mChannelWrapperMap.keySet());
+ List<ChannelWrapper> removedChannelWrappers = new ArrayList<>();
+ List<ChannelWrapper> updatedChannelWrappers = new ArrayList<>();
+
+ boolean channelAdded = false;
+ boolean channelUpdated = false;
+ boolean channelRemoved = false;
+ for (Channel channel : channels) {
+ long channelId = channel.getId();
+ boolean newlyAdded = !removedChannelIds.remove(channelId);
+ ChannelWrapper channelWrapper;
+ if (newlyAdded) {
+ channelWrapper = new ChannelWrapper(channel);
+ mChannelWrapperMap.put(channel.getId(), channelWrapper);
+ if (!channelWrapper.mInputRemoved) {
+ channelAdded = true;
+ }
+ } else {
+ channelWrapper = mChannelWrapperMap.get(channelId);
+ if (!channelWrapper.mChannel.hasSameReadOnlyInfo(channel)) {
+ // Channel data updated
+ Channel oldChannel = channelWrapper.mChannel;
+ // We assume that mBrowsable and mLocked are controlled by only TV app.
+ // The values for mBrowsable and mLocked are updated when
+ // {@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());
+ if (!channelWrapper.mInputRemoved) {
+ channelUpdated = true;
+ updatedChannelWrappers.add(channelWrapper);
+ }
+ }
+ }
+ }
+
+ for (long id : removedChannelIds) {
+ ChannelWrapper channelWrapper = mChannelWrapperMap.remove(id);
+ if (!channelWrapper.mInputRemoved) {
+ channelRemoved = true;
+ removedChannelWrappers.add(channelWrapper);
+ }
+ }
+ clearChannels();
+ for (ChannelWrapper channelWrapper : mChannelWrapperMap.values()) {
+ if (!channelWrapper.mInputRemoved) {
+ addChannel(channelWrapper.mChannel);
+ }
+ }
+ Collections.sort(mChannels, mChannelComparator);
+
+ if (!mDbLoadFinished) {
+ mDbLoadFinished = true;
+ for (Listener l : mListeners) {
+ l.onLoadFinished();
+ }
+ } else if (channelAdded || channelUpdated || channelRemoved) {
+ for (Listener l : mListeners) {
+ l.onChannelListUpdated();
+ }
+ }
+ for (ChannelWrapper channelWrapper : removedChannelWrappers) {
+ channelWrapper.notifyChannelRemoved();
+ }
+ for (ChannelWrapper channelWrapper : updatedChannelWrappers) {
+ channelWrapper.notifyChannelUpdated();
+ }
+ for (Runnable r : mPostRunnablesAfterChannelUpdate) {
+ r.run();
+ }
+ mPostRunnablesAfterChannelUpdate.clear();
+ ChannelLogoFetcher.startFetchingChannelLogos(mContext);
+ }
+ }
+
+ /**
+ * 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<Long> ids) {
+ AsyncDbTask.execute(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);
+ }
+ });
+ }
+}
diff --git a/src/com/android/tv/data/ChannelLogoFetcher.java b/src/com/android/tv/data/ChannelLogoFetcher.java
new file mode 100644
index 00000000..2f75cd9f
--- /dev/null
+++ b/src/com/android/tv/data/ChannelLogoFetcher.java
@@ -0,0 +1,319 @@
+/*
+ * 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.data;
+
+import android.content.Context;
+import android.database.Cursor;
+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.text.TextUtils;
+import android.util.Log;
+
+import com.android.tv.util.AsyncDbTask;
+import com.android.tv.util.BitmapUtils;
+import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
+
+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;
+
+/**
+ * Utility class for TMS data.
+ * 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 Object sLock = new Object();
+ private static final Set<Long> sChannelIdBlackListSet = new HashSet<>();
+ private static LoadChannelTask sQueryTask;
+ private static FetchLogoTask sFetchTask;
+
+ /**
+ * Fetch the channel logos from TMS data and insert them into TvProvider.
+ * The previous task is canceled and a new task starts.
+ */
+ public static void startFetchingChannelLogos(Context context) {
+ synchronized (sLock) {
+ stopFetchingChannelLogos();
+ if (DEBUG) Log.d(TAG, "Request to start fetching logos.");
+ sQueryTask = new LoadChannelTask(context);
+ sQueryTask.executeOnDbThread();
+ }
+ }
+
+ /**
+ * 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;
+ }
+ }
+ }
+
+ private ChannelLogoFetcher() {
+ }
+
+ private static final class LoadChannelTask extends AsyncDbTask<Void, Void, List<Channel>> {
+ private final Context mContext;
+
+ public LoadChannelTask(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ protected List<Channel> 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<Channel> 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<Channel> 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<Void, Void, Void> {
+ private final Context mContext;
+ private final List<Channel> mChannels;
+
+ public FetchLogoTask(Context context, List<Channel> channels) {
+ mContext = context;
+ mChannels = channels;
+ }
+
+ @Override
+ protected Void doInBackground(Void... arg) {
+ if (isCancelled()) {
+ 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<String, String> 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;
+ }
+ 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;
+ }
+
+ // Iterating channels.
+ for (Channel channel : mChannels) {
+ if (isCancelled()) {
+ if (DEBUG) Log.d(TAG, "Fetching the channel logos has been canceled");
+ return null;
+ }
+ // Download the channel logo.
+ 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[] splittedNames = channelName.split(NAME_SEPARATOR_FOR_DB);
+ if (splittedNames.length > 1) {
+ StringBuilder sb = new StringBuilder();
+ for (String splittedName : splittedNames) {
+ sb.append(splittedName);
+ }
+ logoUri = channelNameLogoUriMap.get(sb.toString());
+ if (DEBUG && TextUtils.isEmpty(logoUri)) {
+ Log.d(TAG, "Can't find a logo URI for channel '" + sb.toString() + "'");
+ }
+ }
+ if (TextUtils.isEmpty(logoUri)
+ && splittedNames[0].length() != channelName.length()) {
+ logoUri = channelNameLogoUriMap.get(splittedNames[0]);
+ if (DEBUG && TextUtils.isEmpty(logoUri)) {
+ Log.d(TAG, "Can't find a logo URI for channel '" + splittedNames[0]
+ + "'");
+ }
+ }
+ }
+ if (TextUtils.isEmpty(logoUri)) {
+ sChannelIdBlackListSet.add(channel.getId());
+ continue;
+ }
+ 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()) {
+ if (DEBUG) Log.d(TAG, "Fetching the channel logos has been canceled");
+ return null;
+ }
+
+ // Insert 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);
+ continue;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Inserting logo file to DB succeeded. {from=" + logoUri + ", to="
+ + dstLogoUri + "}");
+ }
+ }
+ if (DEBUG) Log.d(TAG, "Fetching logos has been finished successfully.");
+ return null;
+ }
+
+ private Map<String, String> readTmsFile(Context context, String fileName)
+ throws IOException {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+ context.getAssets().open(fileName)))) {
+ Map<String, String> 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]);
+ }
+ return channelNameLogoUriMap;
+ }
+ }
+
+ private void addChannelNames(Map<String, String> 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[] splittedNames = channelName.split(NAME_SEPARATOR_FOR_TMS);
+ if (splittedNames.length > 1) {
+ for (String name : splittedNames) {
+ name = name.trim();
+ if (channelNameLogoUriMap.get(name) == null) {
+ channelNameLogoUriMap.put(name, logoUri);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ synchronized (sLock) {
+ if (sFetchTask == this) {
+ sFetchTask = null;
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/data/ChannelMap.java b/src/com/android/tv/data/ChannelMap.java
deleted file mode 100644
index a7755e97..00000000
--- a/src/com/android/tv/data/ChannelMap.java
+++ /dev/null
@@ -1,312 +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.data;
-
-import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
-
-import android.app.Activity;
-import android.app.LoaderManager;
-import android.content.ContentUris;
-import android.content.CursorLoader;
-import android.content.Loader;
-import android.database.Cursor;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputInfo;
-import android.net.Uri;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.android.tv.input.TvInput;
-import com.android.tv.util.TvInputManagerHelper;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * The class that abstracts the channel information for each input and provides convenient
- * methods to access it.
- */
-public class ChannelMap implements LoaderManager.LoaderCallbacks<Cursor> {
- private static final String TAG = "ChannelMap";
- private static final int CURSOR_LOADER_ID = 0;
-
- private static final int BROWSABLE = 1;
-
- private final Activity mActivity;
- private final TvInput mInput;
- private long mCurrentChannelId;
- private Cursor mCursor;
- private final TvInputManagerHelper mTvInputManagerHelper;
- private final Runnable mOnLoadFinished;
- private boolean mIsLoadFinished;
- private int mIndexId;
- private int mIndexDisplayNumber;
- private int mIndexDisplayName;
- private int mIndexInputId;
- private int mIndexBrowsable;
- private int mBrowsableChannelCount;
- private final Map<Long, Channel> mChannels = new LinkedHashMap<Long, Channel>();
-
- public ChannelMap(Activity activity, TvInput tvInput, long initChannelId,
- TvInputManagerHelper tvInputManagerHelper, Runnable onLoadFinished) {
- mActivity = activity;
- mInput = tvInput;
- mCurrentChannelId = initChannelId;
- mTvInputManagerHelper = tvInputManagerHelper;
- mOnLoadFinished = onLoadFinished;
- mActivity.getLoaderManager().initLoader(CURSOR_LOADER_ID, null, this);
- }
-
- public boolean isLoadFinished() {
- return mIsLoadFinished;
- }
-
- public int getBrowsableChannelCount() {
- checkCursor();
- return mBrowsableChannelCount;
- }
-
- public TvInput getTvInput() {
- return mInput;
- }
-
- public boolean containsAndIsBrowsable(Channel c) {
- return mChannels.keySet().contains(c.getId())
- && (c.isBrowsable() || mBrowsableChannelCount == 0);
- }
-
- public Channel[] getChannelList(boolean browsableOnly) {
- if (mBrowsableChannelCount == 0 || !browsableOnly) {
- return mChannels.values().toArray(new Channel[0]);
- }
-
- Channel[] channels = new Channel[mBrowsableChannelCount];
- int index = 0;
- for (Channel channel : mChannels.values()) {
- if (channel.isBrowsable()) {
- channels[index++] = channel;
- }
- }
- return channels;
- }
-
- public Channel getCurrentChannel() {
- return mCurrentChannelId != Channel.INVALID_ID ? mChannels.get(mCurrentChannelId) : null;
- }
-
- public int size() {
- checkCursor();
- return mBrowsableChannelCount == 0 ? mCursor.getCount() : mBrowsableChannelCount;
- }
-
- public long getCurrentChannelId() {
- checkCursor();
- if (mCursor.getCount() < 1) {
- return Channel.INVALID_ID;
- }
- return mCursor.getLong(mIndexId);
- }
-
- public Uri getCurrentChannelUri() {
- checkCursor();
- if (mCursor.getCount() < 1) {
- return null;
- }
- long id = mCursor.getLong(mIndexId);
- return ContentUris.withAppendedId(TvContract.Channels.CONTENT_URI, id);
- }
-
- public String getCurrentDisplayNumber() {
- checkCursor();
- if (mCursor.getCount() < 1) {
- return null;
- }
- return mCursor.getString(mIndexDisplayNumber);
- }
-
- public String getCurrentDisplayName() {
- checkCursor();
- if (mCursor.getCount() < 1) {
- return null;
- }
- return mCursor.getString(mIndexDisplayName);
- }
-
- public boolean moveToNextChannel() {
- checkCursor();
- if (mCursor.getCount() <= 0) {
- return false;
- }
-
- int browsableChannelCount = size();
- int oldPosition = mCursor.getPosition();
- boolean ignoreBrowsable = mBrowsableChannelCount == 0;
-
- while(browsableChannelCount > 0 && (mCursor.moveToNext() || mCursor.moveToFirst())) {
- if (mCursor.getInt(mIndexBrowsable) == BROWSABLE || ignoreBrowsable) {
- --browsableChannelCount;
- if (mTvInputManagerHelper.getInputState(getInputId()) == INPUT_STATE_DISCONNECTED) {
- continue;
- }
- mCurrentChannelId = mCursor.getLong(mIndexId);
- return true;
- }
- }
- mCursor.moveToPosition(oldPosition);
- return false;
- }
-
- public boolean moveToPreviousChannel() {
- checkCursor();
- if (mCursor.getCount() <= 0) {
- return false;
- }
-
- int browsableChannelCount = size();
- int oldPosition = mCursor.getPosition();
- boolean ignoreBrowsable = mBrowsableChannelCount == 0;
-
- while(browsableChannelCount > 0 && (mCursor.moveToPrevious() || mCursor.moveToLast())) {
- if (mCursor.getInt(mIndexBrowsable) == BROWSABLE || ignoreBrowsable) {
- --browsableChannelCount;
- if (mTvInputManagerHelper.getInputState(getInputId()) == INPUT_STATE_DISCONNECTED) {
- continue;
- }
- mCurrentChannelId = mCursor.getLong(mIndexId);
- return true;
- }
- }
- mCursor.moveToPosition(oldPosition);
- return false;
- }
-
- public boolean moveToChannel(long id) {
- checkCursor();
- if (mCursor.getCount() == 0) {
- return false;
- }
- int position = mCursor.getPosition();
- mCursor.moveToFirst();
- do {
- if (mCursor.getLong(mIndexId) == id) {
- mCurrentChannelId = mCursor.getLong(mIndexId);
- return true;
- }
- } while (mCursor.moveToNext());
- mCursor.moveToPosition(position);
- return false;
- }
-
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- Uri uri = mInput.buildChannelsUri(null);
- String[] projection = {
- TvContract.Channels._ID,
- TvContract.Channels.COLUMN_DISPLAY_NUMBER,
- TvContract.Channels.COLUMN_DISPLAY_NAME,
- TvContract.Channels.COLUMN_PACKAGE_NAME,
- TvContract.Channels.COLUMN_INPUT_ID,
- TvContract.Channels.COLUMN_BROWSABLE };
- String sortOrder = mInput.buildChannelsSortOrder();
- return new CursorLoader(mActivity, uri, projection, null, null, sortOrder);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- Log.d(TAG, "onLoadFinished()");
- mCursor = cursor;
- if (mCursor == null || mCursor.isClosed()) {
- return;
- }
- cursor.setNotificationUri(mActivity.getContentResolver(), TvContract.Channels.CONTENT_URI);
- mIndexId = mCursor.getColumnIndex(TvContract.Channels._ID);
- mIndexDisplayNumber = mCursor.getColumnIndex(TvContract.Channels.COLUMN_DISPLAY_NUMBER);
- mIndexDisplayName = mCursor.getColumnIndex(TvContract.Channels.COLUMN_DISPLAY_NAME);
- mIndexInputId = mCursor.getColumnIndex(TvContract.Channels.COLUMN_INPUT_ID);
- mIndexBrowsable = mCursor.getColumnIndex(TvContract.Channels.COLUMN_BROWSABLE);
- mBrowsableChannelCount = 0;
- mChannels.clear();
- if (mCursor.getCount() > 0) {
- long firstBrowsableChannelId = Channel.INVALID_ID;
-
- mCursor.moveToFirst();
- do {
- Channel channel = new Channel.Builder()
- .setId(mCursor.getLong(mIndexId))
- .setInputId(mCursor.getString(mIndexInputId))
- .setDisplayNumber(mCursor.getString(mIndexDisplayNumber))
- .setDisplayName(mCursor.getString(mIndexDisplayName))
- .setBrowsable(mCursor.getInt(mIndexBrowsable) == BROWSABLE)
- .build();
- mChannels.put(channel.getId(), channel);
- if (mCursor.getInt(mIndexBrowsable) == BROWSABLE) {
- ++mBrowsableChannelCount;
- if (firstBrowsableChannelId == Channel.INVALID_ID) {
- firstBrowsableChannelId = mCursor.getLong(mIndexId);
- }
- }
- } while (mCursor.moveToNext());
-
- if (mCurrentChannelId == Channel.INVALID_ID || !moveToChannel(mCurrentChannelId)) {
- if (firstBrowsableChannelId == Channel.INVALID_ID) {
- // If there's no browsable channel, we assume that all the channels are
- // browsable.
- mCursor.moveToFirst();
- } else {
- moveToChannel(firstBrowsableChannelId);
- }
- }
- mCurrentChannelId = mCursor.getLong(mIndexId);
- } else {
- mCurrentChannelId = Channel.INVALID_ID;
- }
- mIsLoadFinished = true;
- mOnLoadFinished.run();
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> cursor) {
- mCursor = null;
- // The framework will take care of closing the old cursor once we return.
- }
-
- public void close() {
- mActivity.getLoaderManager().destroyLoader(CURSOR_LOADER_ID);
- mCursor = null;
- }
-
- public void dump() {
- checkCursor();
- int oldPosition = mCursor.getPosition();
- mCursor.moveToFirst();
- do {
- Log.d(TAG, "Ch " + mCursor.getString(mIndexDisplayNumber) + " "
- + mCursor.getString(mIndexDisplayName));
- } while (mCursor.moveToNext());
- mCursor.moveToPosition(oldPosition);
- }
-
- private String getInputId() {
- return mCursor.getString(mIndexInputId);
- }
-
- private void checkCursor() {
- if (mCursor == null) {
- throw new IllegalStateException("Cursor not loaded");
- }
- }
-}
diff --git a/src/com/android/tv/data/ChannelNumber.java b/src/com/android/tv/data/ChannelNumber.java
new file mode 100644
index 00000000..8e9c3cb2
--- /dev/null
+++ b/src/com/android/tv/data/ChannelNumber.java
@@ -0,0 +1,122 @@
+/*
+ * 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.data;
+
+import android.support.annotation.NonNull;
+import android.view.KeyEvent;
+
+/**
+ * A convenience class to handle channel number.
+ */
+public final class ChannelNumber implements Comparable<ChannelNumber> {
+ 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
+ };
+
+ public String majorNumber;
+ public boolean hasDelimiter;
+ 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) {
+ this.majorNumber = majorNumber;
+ this.hasDelimiter = hasDelimiter;
+ this.minorNumber = minorNumber;
+ }
+
+ @Override
+ public String toString() {
+ if (hasDelimiter) {
+ return majorNumber + PRIMARY_CHANNEL_DELIMITER + minorNumber;
+ }
+ return majorNumber;
+ }
+
+ @Override
+ public int compareTo(@NonNull ChannelNumber another) {
+ int major = Integer.parseInt(majorNumber);
+ int minor = hasDelimiter ? Integer.parseInt(minorNumber) : 0;
+
+ int opponentMajor = Integer.parseInt(another.majorNumber);
+ int opponentMinor = another.hasDelimiter
+ ? Integer.parseInt(another.minorNumber) : 0;
+ if (major == opponentMajor) {
+ return minor - opponentMinor;
+ }
+ return major - opponentMajor;
+ }
+
+ public static boolean isChannelNumberDelimiterKey(int keyCode) {
+ for (int delimiterKeyCode : CHANNEL_DELIMITER_KEYCODES) {
+ if (delimiterKeyCode == keyCode) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ 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;
+ }
+ }
+ if (indexOfDelimiter == 0 || indexOfDelimiter == number.length() - 1) {
+ return null;
+ }
+ if (indexOfDelimiter < 0) {
+ ret.majorNumber = number;
+ return ret;
+ }
+ ret.hasDelimiter = true;
+ ret.majorNumber = number.substring(0, indexOfDelimiter);
+ ret.minorNumber = number.substring(indexOfDelimiter + 1);
+ return ret;
+ }
+
+ public static int compare(String lhs, String rhs) {
+ if (lhs == null && rhs == null) {
+ return 0;
+ } else if (lhs == null /* && rhs != null */) {
+ return -1;
+ } else if (lhs != null && rhs == null) {
+ return 1;
+ }
+ return parseChannelNumber(lhs).compareTo(parseChannelNumber(rhs));
+ }
+}
diff --git a/src/com/android/tv/data/DisplayMode.java b/src/com/android/tv/data/DisplayMode.java
index f88abd20..7f76dde6 100644
--- a/src/com/android/tv/data/DisplayMode.java
+++ b/src/com/android/tv/data/DisplayMode.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -30,7 +30,7 @@ public class DisplayMode {
private DisplayMode() { }
- public static final String getLabel(int mode, Context context) {
- return context.getResources().getStringArray(R.array.display_mode_label)[mode];
+ 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 f164d5aa..b12fd1aa 100644
--- a/src/com/android/tv/data/GenreItems.java
+++ b/src/com/android/tv/data/GenreItems.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -22,7 +22,10 @@ import android.media.tv.TvContract.Programs.Genres;
import com.android.tv.R;
public class GenreItems {
- public static final int POSITION_ALL_CHANNELS = 0;
+ /**
+ * Genre ID indicating all channels.
+ */
+ public static final int ID_ALL_CHANNELS = 0;
private static final String[] CANONICAL_GENRES = {
null, // All channels
@@ -36,72 +39,59 @@ public class GenreItems {
Genres.EDUCATION,
Genres.ANIMAL_WILDLIFE,
Genres.NEWS,
- Genres.GAMING
+ Genres.GAMING,
+ Genres.ARTS,
+ Genres.ENTERTAINMENT,
+ Genres.LIFE_STYLE,
+ Genres.MUSIC,
+ Genres.PREMIER,
+ Genres.TECH_SCIENCE
};
- private static String[] sItems;
-
private GenreItems() { }
- public static final String[] getItems(Context context) {
- if (sItems == null) {
- sItems = context.getResources().getStringArray(R.array.show_only_label);
+ /**
+ * 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) {
+ throw new IllegalArgumentException("Genre data mismatch");
}
- return sItems;
+ return items;
}
- public static final String getLabel(Context context, int item) {
- return getItems(context)[item];
+ /**
+ * Returns the number of genres including all channels.
+ */
+ public static int getGenreCount() {
+ return CANONICAL_GENRES.length;
}
- public static final String getLabel(Context context, String canonicalGenre) {
- if (canonicalGenre == null) {
- return getItems(context)[POSITION_ALL_CHANNELS];
- }
-
- for (int i = 1; i < CANONICAL_GENRES.length; ++i) {
- if (CANONICAL_GENRES[i].equals(canonicalGenre)) {
- return getItems(context)[i];
- }
- }
- return getItems(context)[POSITION_ALL_CHANNELS];
- }
-
- public static final String getCanonicalGenre(int item) {
- return CANONICAL_GENRES[item];
- }
-
- public static final String getCanonicalGenre(Context context, String item) {
- int index = 0;
- for (String genre : getItems(context)) {
- if (genre.equals(item)) {
- return CANONICAL_GENRES[index];
- }
- ++index;
- }
- return null;
- }
-
- public static final int getPosition(Context context, String item) {
- int index = 0;
- for (String genre : getItems(context)) {
- if (genre.equals(item)) {
- return index;
- }
- ++index;
+ /**
+ * 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) {
+ return null;
}
- return POSITION_ALL_CHANNELS;
+ return CANONICAL_GENRES[id];
}
- public static final int getPosition(String canonicalGenre) {
+ /**
+ * 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) {
- return POSITION_ALL_CHANNELS;
+ return ID_ALL_CHANNELS;
}
for (int i = 1; i < CANONICAL_GENRES.length; ++i) {
if (CANONICAL_GENRES[i].equals(canonicalGenre)) {
return i;
}
}
- return POSITION_ALL_CHANNELS;
+ return ID_ALL_CHANNELS;
}
}
diff --git a/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java b/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java
new file mode 100644
index 00000000..77b6c9b8
--- /dev/null
+++ b/src/com/android/tv/data/OnCurrentProgramUpdatedListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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.data;
+
+public interface OnCurrentProgramUpdatedListener {
+ /**
+ * Called when the current program is updated.
+ */
+ void onCurrentProgramUpdated(long channelId, Program program);
+}
diff --git a/src/com/android/tv/data/Program.java b/src/com/android/tv/data/Program.java
index 9035adec..7f2f73a5 100644
--- a/src/com/android/tv/data/Program.java
+++ b/src/com/android/tv/data/Program.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,27 +16,68 @@
package com.android.tv.data;
-import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
import android.media.tv.TvContentRating;
import android.media.tv.TvContract;
+import android.support.annotation.NonNull;
+import android.support.annotation.UiThread;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.tv.R;
+import com.android.tv.util.ImageLoader;
+import com.android.tv.util.Utils;
+
+import java.util.Arrays;
+import java.util.Objects;
/**
* A convenience class to create and insert program information entries into the database.
*/
-public final class Program {
- public static final long INVALID_ID = -1;
+public final class Program implements Comparable<Program> {
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_DUMP_DESCRIPTION = false;
+ private static final String TAG = "Program";
+
+ public static final String[] PROJECTION = {
+ // Columns should match what is read in Program.fromCursor()
+ TvContract.Programs.COLUMN_CHANNEL_ID,
+ TvContract.Programs.COLUMN_TITLE,
+ TvContract.Programs.COLUMN_EPISODE_TITLE,
+ TvContract.Programs.COLUMN_SEASON_NUMBER,
+ TvContract.Programs.COLUMN_EPISODE_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
+ };
private long mChannelId;
private String mTitle;
+ private String mEpisodeTitle;
+ private int mSeasonNumber;
+ private int mEpisodeNumber;
private long mStartTimeUtcMillis;
private long mEndTimeUtcMillis;
private String mDescription;
- private String mLongDescription;
- private String mVideoDefinitionLevel;
+ private int mVideoWidth;
+ private int mVideoHeight;
private String mPosterArtUri;
private String mThumbnailUri;
+ private int[] mCanonicalGenreIds;
private TvContentRating[] mContentRatings;
+ public interface LoadPosterArtCallback {
+ void onLoadPosterArtFinished(Program program, Bitmap posterArt);
+ }
+
private Program() {
// Do nothing.
}
@@ -45,110 +86,163 @@ public final class Program {
return mChannelId;
}
- public void setChannelId(long channelId) {
- mChannelId = channelId;
+ /**
+ * Returns {@code true} if this program is valid or {@code false} otherwise.
+ */
+ public boolean isValid() {
+ return mChannelId >= 0;
+ }
+
+ /**
+ * Returns {@code true} if the program is valid and {@code false} otherwise.
+ */
+ public static boolean isValid(Program program) {
+ return program != null && program.isValid();
}
public String getTitle() {
return mTitle;
}
- public void setTitle(String title) {
- mTitle = title;
+ public String getEpisodeTitle() {
+ return mEpisodeTitle;
}
- public long getStartTimeUtcMillis() {
- return mStartTimeUtcMillis;
+ public String getEpisodeDisplayTitle(Context context) {
+ if (mSeasonNumber > 0 && mEpisodeNumber > 0 && !TextUtils.isEmpty(mEpisodeTitle)) {
+ return String.format(context.getResources().getString(R.string.episode_format),
+ mSeasonNumber, mEpisodeNumber, mEpisodeTitle);
+ }
+ return mEpisodeTitle;
}
- public void setStartTimeUtcMillis(long startTimeUtcMillis) {
- mStartTimeUtcMillis = startTimeUtcMillis;
+ public long getStartTimeUtcMillis() {
+ return mStartTimeUtcMillis;
}
public long getEndTimeUtcMillis() {
return mEndTimeUtcMillis;
}
- public void setEndTimeUtcMillis(long endTimeUtcMillis) {
- mEndTimeUtcMillis = endTimeUtcMillis;
+ /**
+ * Returns the program duration.
+ */
+ public long getDurationMillis() {
+ return mEndTimeUtcMillis - mStartTimeUtcMillis;
}
public String getDescription() {
return mDescription;
}
- public void setDescription(String description) {
- mDescription = description;
+ public int getVideoWidth() {
+ return mVideoWidth;
}
- public String getLongDescription() {
- return mLongDescription;
- }
-
- public void setLongDescription(String longDescription) {
- mLongDescription = longDescription;
- }
-
- public String getVideoDefinitionLevel() {
- return mVideoDefinitionLevel;
- }
-
- public void setVideoDefinitionLevel(String videoDefinitionLevel) {
- mVideoDefinitionLevel = videoDefinitionLevel;
+ public int getVideoHeight() {
+ return mVideoHeight;
}
public TvContentRating[] getContentRatings() {
return mContentRatings;
}
- public void setContentRatings(TvContentRating[] contentRatings) {
- mContentRatings = contentRatings;
- }
-
public String getPosterArtUri() {
return mPosterArtUri;
}
- public void setPosterArtUri(String posterArtUri) {
- mPosterArtUri = posterArtUri;
- }
-
public String getThumbnailUri() {
return mThumbnailUri;
}
- public void setThumbnailUri(String thumbnailUri) {
- mThumbnailUri = thumbnailUri;
+ /**
+ * Returns array of canonical genres for this program.
+ * This is expected to be called rarely.
+ */
+ public String[] getCanonicalGenres() {
+ if (mCanonicalGenreIds == null) {
+ return null;
+ }
+ String[] genres = new String[mCanonicalGenreIds.length];
+ for (int i = 0; i < mCanonicalGenreIds.length; i++) {
+ genres[i] = GenreItems.getCanonicalGenre(mCanonicalGenreIds[i]);
+ }
+ return genres;
+ }
+
+ /**
+ * Returns if this program has the genre.
+ */
+ public boolean hasGenre(int genreId) {
+ if (genreId == GenreItems.ID_ALL_CHANNELS) {
+ return true;
+ }
+ if (mCanonicalGenreIds != null) {
+ for (int id : mCanonicalGenreIds) {
+ if (id == genreId) {
+ return true;
+ }
+ }
+ }
+ return false;
}
- public ContentValues toContentValues() {
- ContentValues values = new ContentValues();
- values.put(TvContract.Programs.COLUMN_CHANNEL_ID, mChannelId);
- values.put(TvContract.Programs.COLUMN_TITLE, mTitle);
- values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, mStartTimeUtcMillis);
- values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, mEndTimeUtcMillis);
- values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, mDescription);
- values.put(TvContract.Programs.COLUMN_LONG_DESCRIPTION, mLongDescription);
- values.put(TvContract.Programs.COLUMN_POSTER_ART_URI, mPosterArtUri);
- values.put(TvContract.Programs.COLUMN_THUMBNAIL_URI, mThumbnailUri);
- return values;
+ @Override
+ public int hashCode() {
+ return Objects.hash(mChannelId, mStartTimeUtcMillis, mEndTimeUtcMillis,
+ mTitle, mEpisodeTitle, mDescription, mVideoWidth, mVideoHeight,
+ mPosterArtUri, mThumbnailUri, Arrays.hashCode(mContentRatings),
+ Arrays.hashCode(mCanonicalGenreIds), mSeasonNumber, mEpisodeNumber);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Program)) {
+ return false;
+ }
+ Program program = (Program) other;
+ return mChannelId == program.mChannelId
+ && mStartTimeUtcMillis == program.mStartTimeUtcMillis
+ && mEndTimeUtcMillis == program.mEndTimeUtcMillis
+ && Objects.equals(mTitle, program.mTitle)
+ && Objects.equals(mEpisodeTitle, program.mEpisodeTitle)
+ && Objects.equals(mDescription, program.mDescription)
+ && mVideoWidth == program.mVideoWidth
+ && mVideoHeight == program.mVideoHeight
+ && Objects.equals(mPosterArtUri, program.mPosterArtUri)
+ && Objects.equals(mThumbnailUri, program.mThumbnailUri)
+ && Arrays.equals(mContentRatings, program.mContentRatings)
+ && Arrays.equals(mCanonicalGenreIds, program.mCanonicalGenreIds)
+ && mSeasonNumber == program.mSeasonNumber
+ && mEpisodeNumber == program.mEpisodeNumber;
+ }
+
+ @Override
+ public int compareTo(@NonNull Program other) {
+ return Long.compare(mStartTimeUtcMillis, other.mStartTimeUtcMillis);
}
@Override
public String toString() {
- return new StringBuilder()
- .append("Program{")
- .append(", channelId=").append(mChannelId)
+ StringBuilder builder = new StringBuilder();
+ builder.append("Program{")
+ .append("channelId=").append(mChannelId)
.append(", title=").append(mTitle)
- .append(", startTimeUtcSec=").append(mStartTimeUtcMillis)
- .append(", endTimeUtcSec=").append(mEndTimeUtcMillis)
- .append(", description=").append(mDescription)
- .append(", longDescription=").append(mLongDescription)
- .append(", videoDefinitionLevel=").append(mVideoDefinitionLevel)
+ .append(", episodeTitle=").append(mEpisodeTitle)
+ .append(", seasonNumber=").append(mSeasonNumber)
+ .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(Utils.contentRatingsToString(mContentRatings))
.append(", posterArtUri=").append(mPosterArtUri)
.append(", thumbnailUri=").append(mThumbnailUri)
- .append("}")
- .toString();
+ .append(", canonicalGenres=").append(Arrays.toString(mCanonicalGenreIds));
+ if (DEBUG_DUMP_DESCRIPTION) {
+ builder.append(", description=").append(mDescription);
+ }
+ return builder.append("}").toString();
}
public void copyFrom(Program other) {
@@ -158,13 +252,94 @@ public final class Program {
mChannelId = other.mChannelId;
mTitle = other.mTitle;
+ mEpisodeTitle = other.mEpisodeTitle;
+ mSeasonNumber = other.mSeasonNumber;
+ mEpisodeNumber = other.mEpisodeNumber;
mStartTimeUtcMillis = other.mStartTimeUtcMillis;
mEndTimeUtcMillis = other.mEndTimeUtcMillis;
mDescription = other.mDescription;
- mLongDescription = other.mLongDescription;
- mVideoDefinitionLevel = other.mVideoDefinitionLevel;
+ mVideoWidth = other.mVideoWidth;
+ mVideoHeight = other.mVideoHeight;
mPosterArtUri = other.mPosterArtUri;
mThumbnailUri = other.mThumbnailUri;
+ mCanonicalGenreIds = other.mCanonicalGenreIds;
+ mContentRatings = other.mContentRatings;
+ }
+
+ public static Program fromCursor(Cursor cursor) {
+ // Columns read here should match Program.PROJECTION
+
+ Builder builder = new Builder();
+ int index = cursor.getColumnIndex(TvContract.Programs.COLUMN_CHANNEL_ID);
+ if (index >= 0) {
+ builder.setChannelId(cursor.getLong(index));
+ }
+
+ index = cursor.getColumnIndex(TvContract.Programs.COLUMN_TITLE);
+ if (index >= 0) {
+ builder.setTitle(cursor.getString(index));
+ }
+
+ index = cursor.getColumnIndex(TvContract.Programs.COLUMN_EPISODE_TITLE);
+ if (index >= 0) {
+ builder.setEpisodeTitle(cursor.getString(index));
+ }
+
+ index = cursor.getColumnIndex(TvContract.Programs.COLUMN_SEASON_NUMBER);
+ if(index >= 0) {
+ builder.setSeasonNumber(cursor.getInt(index));
+ }
+
+ index = cursor.getColumnIndex(TvContract.Programs.COLUMN_EPISODE_NUMBER);
+ if(index >= 0) {
+ builder.setEpisodeNumber(cursor.getInt(index));
+ }
+
+ index = cursor.getColumnIndex(TvContract.Programs.COLUMN_SHORT_DESCRIPTION);
+ if (index >= 0) {
+ builder.setDescription(cursor.getString(index));
+ }
+
+ index = cursor.getColumnIndex(TvContract.Programs.COLUMN_POSTER_ART_URI);
+ if (index >= 0) {
+ builder.setPosterArtUri(cursor.getString(index));
+ }
+
+ index = cursor.getColumnIndex(TvContract.Programs.COLUMN_THUMBNAIL_URI);
+ if (index >= 0) {
+ builder.setThumbnailUri(cursor.getString(index));
+ }
+
+ index = cursor.getColumnIndex(TvContract.Programs.COLUMN_CANONICAL_GENRE);
+ if (index >= 0) {
+ builder.setCanonicalGenres(cursor.getString(index));
+ }
+
+ index = cursor.getColumnIndex(TvContract.Programs.COLUMN_CONTENT_RATING);
+ if (index >= 0) {
+ builder.setContentRatings(Utils.stringToContentRatings(cursor.getString(index)));
+ }
+
+ index = cursor.getColumnIndex(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS);
+ if (index >= 0) {
+ builder.setStartTimeUtcMillis(cursor.getLong(index));
+ }
+
+ index = cursor.getColumnIndex(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS);
+ if (index >= 0) {
+ builder.setEndTimeUtcMillis(cursor.getLong(index));
+ }
+
+ index = cursor.getColumnIndex(TvContract.Programs.COLUMN_VIDEO_WIDTH);
+ if (index >= 0) {
+ builder.setVideoWidth((int) cursor.getLong(index));
+ }
+ index = cursor.getColumnIndex(TvContract.Programs.COLUMN_VIDEO_HEIGHT);
+ if (index >= 0) {
+ builder.setVideoHeight((int) cursor.getLong(index));
+ }
+
+ return builder.build();
}
public static final class Builder {
@@ -175,10 +350,11 @@ public final class Program {
// Fill initial data.
mProgram.mChannelId = Channel.INVALID_ID;
mProgram.mTitle = "title";
+ mProgram.mSeasonNumber = -1;
+ mProgram.mEpisodeNumber = -1;
mProgram.mStartTimeUtcMillis = -1;
mProgram.mEndTimeUtcMillis = -1;
mProgram.mDescription = "description";
- mProgram.mLongDescription = "long_description";
}
public Builder(Program other) {
@@ -196,6 +372,21 @@ public final class Program {
return this;
}
+ public Builder setEpisodeTitle(String episodeTitle) {
+ mProgram.mEpisodeTitle = episodeTitle;
+ return this;
+ }
+
+ public Builder setSeasonNumber(int seasonNumber) {
+ mProgram.mSeasonNumber = seasonNumber;
+ return this;
+ }
+
+ public Builder setEpisodeNumber(int episodeNumber) {
+ mProgram.mEpisodeNumber = episodeNumber;
+ return this;
+ }
+
public Builder setStartTimeUtcMillis(long startTimeUtcMillis) {
mProgram.mStartTimeUtcMillis = startTimeUtcMillis;
return this;
@@ -211,13 +402,13 @@ public final class Program {
return this;
}
- public Builder setLongDescription(String longDescription) {
- mProgram.mLongDescription = longDescription;
+ public Builder setVideoWidth(int width) {
+ mProgram.mVideoWidth = width;
return this;
}
- public Builder setVideoDefinitionLevel(String videoDefinitionLevel) {
- mProgram.mVideoDefinitionLevel = videoDefinitionLevel;
+ public Builder setVideoHeight(int height) {
+ mProgram.mVideoHeight = height;
return this;
}
@@ -236,8 +427,70 @@ public final class Program {
return this;
}
+ public Builder setCanonicalGenres(String genres) {
+ if (TextUtils.isEmpty(genres)) {
+ return this;
+ }
+ String[] canonicalGenres = TvContract.Programs.Genres.decode(genres);
+ if (canonicalGenres.length > 0) {
+ int[] temp = new int[canonicalGenres.length];
+ int i = 0;
+ for (String canonicalGenre : canonicalGenres) {
+ int genreId = GenreItems.getId(canonicalGenre);
+ if (genreId == GenreItems.ID_ALL_CHANNELS) {
+ // Skip if the genre is unknown.
+ continue;
+ }
+ temp[i++] = genreId;
+ }
+ if (i < canonicalGenres.length) {
+ temp = Arrays.copyOf(temp, i);
+ }
+ mProgram.mCanonicalGenreIds=temp;
+ }
+ return this;
+ }
+
public Program build() {
- return mProgram;
+ Program program = new Program();
+ program.copyFrom(mProgram);
+ return program;
+ }
+ }
+
+ /**
+ * Prefetches the program poster art.<p>
+ */
+ @UiThread
+ public void prefetchPosterArt(Context context, int posterArtWidth, int posterArtHeight) {
+ if (mPosterArtUri == null) {
+ return;
+ }
+ ImageLoader.prefetchBitmap(context, mPosterArtUri, posterArtWidth, posterArtHeight);
+ }
+
+ /**
+ * Loads the program poster art and returns it via {@code callback}.<p>
+ * <p>
+ * Note that it may directly call {@code callback} if the program poster art already is loaded.
+ */
+ @UiThread
+ public void loadPosterArt(Context context, int posterArtWidth, int posterArtHeight,
+ final LoadPosterArtCallback callback) {
+ if (mPosterArtUri == null) {
+ return;
}
+ ImageLoader.loadBitmap(context, mPosterArtUri, posterArtWidth, posterArtHeight,
+ new ImageLoader.ImageLoaderCallback() {
+ @Override
+ public void onBitmapLoaded(Bitmap bitmap) {
+ if (DEBUG) {
+ Log.i(TAG, "Loaded poster art for " + Program.this + ": " + bitmap);
+ }
+ if (callback != null) {
+ callback.onLoadPosterArtFinished(Program.this, bitmap);
+ }
+ }
+ });
}
}
diff --git a/src/com/android/tv/data/ProgramDataManager.java b/src/com/android/tv/data/ProgramDataManager.java
new file mode 100644
index 00000000..80733bc5
--- /dev/null
+++ b/src/com/android/tv/data/ProgramDataManager.java
@@ -0,0 +1,695 @@
+/*
+ * 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.data;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.media.tv.TvContract.Programs;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.LruCache;
+
+import com.android.tv.MainActivity.MemoryManageable;
+import com.android.tv.util.AsyncDbTask;
+import com.android.tv.util.Clock;
+import com.android.tv.util.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+public class ProgramDataManager implements MemoryManageable {
+ private static final String TAG = "ProgramDataManager";
+ private static final boolean DEBUG = false;
+
+ // To prevent from too many program update operations at the same time, we give random interval
+ // between PERIODIC_PROGRAM_UPDATE_MIN_MS and PERIODIC_PROGRAM_UPDATE_MAX_MS.
+ private static final long PERIODIC_PROGRAM_UPDATE_MIN_MS = TimeUnit.MINUTES.toMillis(5);
+ private static final long PERIODIC_PROGRAM_UPDATE_MAX_MS = TimeUnit.MINUTES.toMillis(10);
+ 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);
+
+ // 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 int MSG_UPDATE_CURRENT_PROGRAMS = 1000;
+ private static final int MSG_UPDATE_ONE_CURRENT_PROGRAM = 1001;
+ private static final int MSG_UPDATE_PREFETCH_PROGRAM = 1002;
+
+ private final Clock mClock;
+ private final ContentResolver mContentResolver;
+ private boolean mStarted;
+ private long mProgramPrefetchUpdateWaitMs;
+ private ProgramsUpdateTask mProgramsUpdateTask;
+ private final LongSparseArray<UpdateCurrentProgramForChannelTask> mProgramUpdateTaskMap =
+ new LongSparseArray<>();
+ private long mLastPrefetchTaskRunMs;
+ private ProgramsPrefetchTask mProgramsPrefetchTask;
+ private final Map<Long, Program> mChannelIdCurrentProgramMap = new HashMap<>();
+ private final Map<Long, List<OnCurrentProgramUpdatedListener>>
+ mOnCurrentProgramUpdatedListenersMap = new HashMap<>();
+ private final Handler mHandler;
+ private final List<Listener> mListeners = new ArrayList<>();
+
+ private final ContentObserver mProgramObserver;
+
+ private Map<Long, ArrayList<Program>> mChannelIdProgramCache = new HashMap<>();
+
+ // Any program that ends prior to this time will be removed from the cache
+ // when a channel's current program is updated.
+ // Note that there's no limit for end time.
+ private long mPrefetchTimeRangeStartMs;
+
+ private boolean mPauseProgramUpdate = false;
+ private final LruCache<Long, Program> mZeroLengthProgramCache = new LruCache<>(10);
+
+ public ProgramDataManager(Context context) {
+ this(context.getContentResolver(), Clock.SYSTEM, Looper.myLooper());
+ }
+
+ @VisibleForTesting
+ ProgramDataManager(ContentResolver contentResolver, Clock time, Looper looper) {
+ 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;
+ }
+ // 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;
+ }
+
+ @VisibleForTesting
+ ContentObserver getContentObserver() {
+ return mProgramObserver;
+ }
+
+ /**
+ * 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.
+ */
+ public void start() {
+ if (mStarted) {
+ return;
+ }
+ mStarted = true;
+ // Should be called directly instead of posting MSG_UPDATE_CURRENT_PROGRAMS message
+ // to the handler. If not, another DB task can be executed before loading current programs.
+ handleUpdateCurrentPrograms();
+ mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
+ mContentResolver.registerContentObserver(Programs.CONTENT_URI,
+ true, mProgramObserver);
+ }
+
+ /**
+ * Stops the manager. It clears manager states and runs pending DB operations. Added listeners
+ * aren't automatically removed by this method.
+ */
+ public void stop() {
+ if (!mStarted) {
+ return;
+ }
+ mStarted = false;
+
+ mContentResolver.unregisterContentObserver(mProgramObserver);
+ mHandler.removeCallbacksAndMessages(null);
+
+ clearTask(mProgramUpdateTaskMap);
+ cancelPrefetchTask();
+ if (mProgramsUpdateTask != null) {
+ mProgramsUpdateTask.cancel(true);
+ mProgramsUpdateTask = null;
+ }
+ }
+
+ /**
+ * Returns the current program at the specified channel.
+ */
+ public Program getCurrentProgram(long channelId) {
+ return mChannelIdCurrentProgramMap.get(channelId);
+ }
+
+ /**
+ * 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.
+ **/
+ void onProgramUpdated();
+ }
+
+ /**
+ * Adds the {@link Listener}.
+ */
+ public void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes the {@link Listener}.
+ */
+ public void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Returns the programs for the given channel which ends after the given start time.
+ *
+ * @return {@link List} with Programs. It may includes dummy program if the entry needs DB
+ * operations to get.
+ */
+ public List<Program> getPrograms(long channelId, long startTime) {
+ ArrayList<Program> cachedPrograms = mChannelIdProgramCache.get(channelId);
+ if (cachedPrograms == null) {
+ return Collections.emptyList();
+ }
+ int startIndex = getProgramIndexAt(cachedPrograms, startTime);
+ return Collections.unmodifiableList(
+ cachedPrograms.subList(startIndex, cachedPrograms.size()));
+ }
+
+ // Returns the index of program that is played at the specified time.
+ // If there isn't, return the first program among programs that starts after the given time
+ // if returnNextProgram is {@code true}.
+ private int getProgramIndexAt(List<Program> programs, long time) {
+ Program key = mZeroLengthProgramCache.get(time);
+ if (key == null) {
+ key = createDummyProgram(time, time);
+ mZeroLengthProgramCache.put(time, key);
+ }
+ int index = Collections.binarySearch(programs, key);
+ if (index < 0) {
+ index = -(index + 1); // change it to index to be added.
+ if (index > 0 && isProgramPlayedAt(programs.get(index - 1), time)) {
+ // A program is played at that time.
+ return index - 1;
+ }
+ return index;
+ }
+ return index;
+ }
+
+ private boolean isProgramPlayedAt(Program program, long time) {
+ return program.getStartTimeUtcMillis() <= time && time <= program.getEndTimeUtcMillis();
+ }
+
+ /**
+ * 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.
+ */
+ public void addOnCurrentProgramUpdatedListener(
+ long channelId, OnCurrentProgramUpdatedListener listener) {
+ List<OnCurrentProgramUpdatedListener> listeners =
+ mOnCurrentProgramUpdatedListenersMap.get(channelId);
+ if (listeners == null) {
+ listeners = new ArrayList<>();
+ mOnCurrentProgramUpdatedListenersMap.put(channelId, listeners);
+ }
+ listeners.add(listener);
+ }
+
+ /**
+ * Removes the listener previously added by
+ * {@link #addOnCurrentProgramUpdatedListener(long, OnCurrentProgramUpdatedListener)}.
+ */
+ public void removeOnCurrentProgramUpdatedListener(
+ long channelId, OnCurrentProgramUpdatedListener listener) {
+ List<OnCurrentProgramUpdatedListener> listeners =
+ mOnCurrentProgramUpdatedListenersMap.get(channelId);
+ if (listeners != null) {
+ listeners.remove(listener);
+ // Do not remove list from map although it's empty to reduce GC.
+ // The unused list is removed in {@link #performTrimMemory} which is called when
+ // the memory usage is high.
+ }
+ }
+
+ private void notifyCurrentProgramUpdate(long channelId, Program program) {
+ List<OnCurrentProgramUpdatedListener> listeners =
+ mOnCurrentProgramUpdatedListenersMap.get(channelId);
+ if (listeners != null) {
+ for (OnCurrentProgramUpdatedListener listener : listeners) {
+ listener.onCurrentProgramUpdated(channelId, program);
+ }
+ }
+ listeners = mOnCurrentProgramUpdatedListenersMap.get(Channel.INVALID_ID);
+ if (listeners != null) {
+ for (OnCurrentProgramUpdatedListener listener : listeners) {
+ listener.onCurrentProgramUpdated(channelId, program);
+ }
+ }
+ }
+
+ private void updateCurrentProgram(long channelId, Program program) {
+ Program previousProgram = mChannelIdCurrentProgramMap.put(channelId, program);
+ if (!Objects.equals(program, previousProgram)) {
+ removePreviousProgramsAndUpdateCurrentProgramInCache(channelId, program);
+ notifyCurrentProgramUpdate(channelId, program);
+ }
+
+ long delayedTime;
+ if (program == null) {
+ 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);
+ }
+
+ private void removePreviousProgramsAndUpdateCurrentProgramInCache(
+ long channelId, Program currentProgram) {
+ if (!Program.isValid(currentProgram)) {
+ return;
+ }
+ ArrayList<Program> cachedPrograms = mChannelIdProgramCache.remove(channelId);
+ if (cachedPrograms == null) {
+ return;
+ }
+ ListIterator<Program> i = cachedPrograms.listIterator();
+ while (i.hasNext()) {
+ Program cachedProgram = i.next();
+ if (cachedProgram.getEndTimeUtcMillis() <= mPrefetchTimeRangeStartMs) {
+ // Remove previous programs which will not be shown in program guide.
+ i.remove();
+ continue;
+ }
+
+ 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()) {
+ // The dummy program starts earlier than the current program. Adjust its end time.
+ 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()));
+ }
+ break;
+ }
+ mChannelIdProgramCache.put(channelId, cachedPrograms);
+ }
+
+ private void handleUpdateCurrentPrograms() {
+ if (mProgramsUpdateTask != null) {
+ mHandler.sendEmptyMessageDelayed(MSG_UPDATE_CURRENT_PROGRAMS,
+ CURRENT_PROGRAM_UPDATE_WAIT_MS);
+ return;
+ }
+ clearTask(mProgramUpdateTaskMap);
+ mHandler.removeMessages(MSG_UPDATE_ONE_CURRENT_PROGRAM);
+ mProgramsUpdateTask = new ProgramsUpdateTask(mContentResolver, mClock.currentTimeMillis());
+ mProgramsUpdateTask.executeOnDbThread();
+ }
+
+ private class ProgramsPrefetchTask
+ extends AsyncDbTask<Void, Void, Map<Long, ArrayList<Program>>> {
+ private final long mStartTimeMs;
+ private final long mEndTimeMs;
+
+ private boolean mSuccess;
+
+ public ProgramsPrefetchTask() {
+ long time = mClock.currentTimeMillis();
+ mStartTimeMs = Utils
+ .floorTime(time - PROGRAM_GUIDE_SNAP_TIME_MS, PROGRAM_GUIDE_SNAP_TIME_MS);
+ mEndTimeMs = mStartTimeMs + PROGRAM_GUIDE_MAX_TIME_RANGE;
+ mSuccess = false;
+ }
+
+ @Override
+ protected Map<Long, ArrayList<Program>> doInBackground(Void... params) {
+ Map<Long, ArrayList<Program>> programMap = new HashMap<>();
+ if (DEBUG) {
+ 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();
+ final int RETRY_COUNT = 3;
+ Program lastReadProgram = null;
+ for (int retryCount = RETRY_COUNT; retryCount > 0; retryCount--) {
+ if (isProgramUpdatePaused()) {
+ return null;
+ }
+ programMap.clear();
+ try (Cursor c = mContentResolver.query(uri, Program.PROJECTION, null, null,
+ SORT_BY_TIME)) {
+ if (c == null) {
+ continue;
+ }
+ while (c.moveToNext()) {
+ if (isCancelled()) {
+ if (DEBUG) {
+ Log.d(TAG, "ProgramsPrefetchTask canceled.");
+ }
+ return null;
+ }
+ Program program = Program.fromCursor(c);
+ if (isDuplicateProgram(program, lastReadProgram)) {
+ continue;
+ } else {
+ lastReadProgram = program;
+ }
+ ArrayList<Program> programs = programMap.get(program.getChannelId());
+ if (programs == null) {
+ programs = new ArrayList<>();
+ programMap.put(program.getChannelId(), programs);
+ }
+ programs.add(program);
+ }
+ mSuccess = true;
+ break;
+ } catch (IllegalStateException e) {
+ if (DEBUG) {
+ Log.d(TAG, "Database is changed while querying. Will retry.");
+ }
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Ends programs prefetch for " + programMap.size() + " channels");
+ }
+ return programMap;
+ }
+
+ @Override
+ protected void onPostExecute(Map<Long, ArrayList<Program>> programs) {
+ mProgramsPrefetchTask = null;
+ if (isProgramUpdatePaused()) {
+ // ProgramsPrefetchTask will run again once setPauseProgramUpdate(false) is called.
+ return;
+ }
+ long nextMessageDelayedTime;
+ if (mSuccess) {
+ mChannelIdProgramCache = programs;
+ notifyProgramUpdated();
+ long currentTime = mClock.currentTimeMillis();
+ mLastPrefetchTaskRunMs = currentTime;
+ nextMessageDelayedTime =
+ 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);
+ }
+ }
+ }
+
+ private void notifyProgramUpdated() {
+ for (Listener listener : mListeners) {
+ listener.onProgramUpdated();
+ }
+ }
+
+ private class ProgramsUpdateTask extends AsyncDbTask.AsyncQueryTask<List<Program>> {
+ public ProgramsUpdateTask(ContentResolver contentResolver, long time) {
+ 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);
+ }
+
+ @Override
+ public List<Program> onQuery(Cursor c) {
+ final List<Program> programs = new ArrayList<>();
+ if (c != null) {
+ Program lastReadProgram = null;
+ while (c.moveToNext()) {
+ if (isCancelled()) {
+ return programs;
+ }
+ Program program = Program.fromCursor(c);
+ if (isDuplicateProgram(program, lastReadProgram)) {
+ continue;
+ } else {
+ lastReadProgram = program;
+ }
+ programs.add(program);
+ }
+ }
+ return programs;
+ }
+
+ @Override
+ protected void onPostExecute(List<Program> programs) {
+ if (DEBUG) {
+ Log.d(TAG, "ProgramsUpdateTask done");
+ }
+ mProgramsUpdateTask = null;
+ if (programs == null) {
+ return;
+ }
+ Set<Long> removedChannelIds = new HashSet<>(mChannelIdCurrentProgramMap.keySet());
+ for (Program program : programs) {
+ long channelId = program.getChannelId();
+ updateCurrentProgram(channelId, program);
+ removedChannelIds.remove(channelId);
+ }
+ for (Long channelId : removedChannelIds) {
+ mChannelIdProgramCache.remove(channelId);
+ mChannelIdCurrentProgramMap.remove(channelId);
+ notifyCurrentProgramUpdate(channelId, null);
+ }
+ }
+ }
+
+ private class UpdateCurrentProgramForChannelTask extends AsyncDbTask.AsyncQueryTask<Program> {
+ 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);
+ mChannelId = channelId;
+ }
+
+ @Override
+ public Program onQuery(Cursor c) {
+ Program program = null;
+ if (c != null && c.moveToNext()) {
+ program = Program.fromCursor(c);
+ }
+ return program;
+ }
+
+ @Override
+ protected void onPostExecute(Program program) {
+ mProgramUpdateTaskMap.remove(mChannelId);
+ updateCurrentProgram(mChannelId, program);
+ }
+ }
+
+ private class MyHandler extends Handler {
+ public MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ 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;
+ }
+ long delayMillis = mLastPrefetchTaskRunMs + mProgramPrefetchUpdateWaitMs
+ - mClock.currentTimeMillis();
+ if (delayMillis > 0) {
+ mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PREFETCH_PROGRAM, delayMillis);
+ } else {
+ mProgramsPrefetchTask = new ProgramsPrefetchTask();
+ mProgramsPrefetchTask.executeOnDbThread();
+ }
+ 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.
+ */
+ public void setPauseProgramUpdate(boolean pauseProgramUpdate) {
+ if (mPauseProgramUpdate && !pauseProgramUpdate) {
+ if (!mHandler.hasMessages(MSG_UPDATE_PREFETCH_PROGRAM)) {
+ // MSG_UPDATE_PRFETCH_PROGRAM can be empty
+ // if prefetch task is launched while program update is paused.
+ // Update immediately in that case.
+ mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
+ }
+ }
+ mPauseProgramUpdate = pauseProgramUpdate;
+ }
+
+ private boolean isProgramUpdatePaused() {
+ // Although pause is requested, we need to keep updating if cache is empty.
+ return mPauseProgramUpdate && !mChannelIdProgramCache.isEmpty();
+ }
+
+ /**
+ * 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.
+ */
+ public void setPrefetchTimeRange(long startTimeMs) {
+ if (mPrefetchTimeRangeStartMs > startTimeMs) {
+ // TODO: Do not throw exception and simply refresh the cache.
+ throw new IllegalArgumentException("");
+ }
+ mPrefetchTimeRangeStartMs = startTimeMs;
+ }
+
+ private void clearTask(LongSparseArray<UpdateCurrentProgramForChannelTask> tasks) {
+ for (int i = 0; i < tasks.size(); i++) {
+ tasks.valueAt(i).cancel(true);
+ }
+ tasks.clear();
+ }
+
+ private void cancelPrefetchTask() {
+ if (mProgramsPrefetchTask != null) {
+ mProgramsPrefetchTask.cancel(true);
+ mProgramsPrefetchTask = null;
+ }
+ }
+
+ // Create dummy program which indicates data isn't loaded yet so DB query is required.
+ private Program createDummyProgram(long startTimeMs, long endTimeMs) {
+ return new Program.Builder()
+ .setChannelId(Channel.INVALID_ID)
+ .setStartTimeUtcMillis(startTimeMs)
+ .setEndTimeUtcMillis(endTimeMs).build();
+ }
+
+ private static boolean isDuplicateProgram(Program p1, Program p2) {
+ if (p1 == null || p2 == null) {
+ return false;
+ }
+ boolean isDuplicate = p1.getChannelId() == p2.getChannelId()
+ && p1.getStartTimeUtcMillis() == p2.getStartTimeUtcMillis()
+ && p1.getEndTimeUtcMillis() == p2.getEndTimeUtcMillis();
+ if (isDuplicate) {
+ Log.w(TAG, "Duplicate programs detected! - \"" + p1.getTitle() + "\" and \""
+ + p2.getTitle() + "\"");
+ }
+ return isDuplicate;
+ }
+
+ @Override
+ public void performTrimMemory(int level) {
+ // Removes unused listeners.
+ Iterator<Entry<Long, List<OnCurrentProgramUpdatedListener>>> it =
+ mOnCurrentProgramUpdatedListenersMap.entrySet().iterator();
+ while (it.hasNext()) {
+ if (it.next().getValue().isEmpty()) {
+ it.remove();
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/data/StreamInfo.java b/src/com/android/tv/data/StreamInfo.java
index 9b348d77..af5b4e3d 100644
--- a/src/com/android/tv/data/StreamInfo.java
+++ b/src/com/android/tv/data/StreamInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -19,18 +19,19 @@ package com.android.tv.data;
import android.media.tv.TvInputInfo;
public interface StreamInfo {
- public final int VIDEO_DEFINITION_LEVEL_UNKNOWN = 0;
- public final int VIDEO_DEFINITION_LEVEL_SD = 1;
- public final int VIDEO_DEFINITION_LEVEL_HD = 2;
- public final int VIDEO_DEFINITION_LEVEL_FULL_HD = 3;
- public final int VIDEO_DEFINITION_LEVEL_ULTRA_HD = 4;
+ int VIDEO_DEFINITION_LEVEL_UNKNOWN = 0;
+ int VIDEO_DEFINITION_LEVEL_SD = 1;
+ int VIDEO_DEFINITION_LEVEL_HD = 2;
+ int VIDEO_DEFINITION_LEVEL_FULL_HD = 3;
+ int VIDEO_DEFINITION_LEVEL_ULTRA_HD = 4;
- public final int AUDIO_CHANNEL_COUNT_UNKNOWN = 0;
+ int AUDIO_CHANNEL_COUNT_UNKNOWN = 0;
- TvInputInfo getCurrentTvInputInfo();
+ Channel getCurrentChannel();
int getVideoWidth();
int getVideoHeight();
+ float getVideoFrameRate();
int getVideoDefinitionLevel();
int getAudioChannelCount();
boolean hasClosedCaption();
diff --git a/src/com/android/tv/dialog/EditInputDialogFragment.java b/src/com/android/tv/dialog/EditInputDialogFragment.java
deleted file mode 100644
index 068cd707..00000000
--- a/src/com/android/tv/dialog/EditInputDialogFragment.java
+++ /dev/null
@@ -1,176 +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.dialog;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
-import android.media.tv.TvInputInfo;
-import android.media.tv.TvInputManager;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.ArrayAdapter;
-import android.widget.EditText;
-import android.widget.Spinner;
-import android.widget.Toast;
-
-import com.android.tv.R;
-import com.android.tv.util.TvSettings;
-import com.android.tv.util.Utils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class EditInputDialogFragment extends DialogFragment implements OnItemSelectedListener {
- public static final String DIALOG_TAG = EditInputDialogFragment.class.getName();
-
- private TvInputManager mTvInputManager;
- private SharedPreferences mPreferences;
-
- // A mapping from the display name of each TV input to its ID (String).
- private final Map<String, String> mInputIdMap = new HashMap<String, String>();
-
- // A list of display names for all TV inputs.
- private String[] mDisplayNames;
-
- private ArrayAdapter<String> mAdapter;
- private int mSelected;
- private EditText mEditText;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mPreferences = Utils.getSharedPreferencesOfDisplayNameForInput(getActivity());
- mAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1,
- new ArrayList<String>());
-
- mTvInputManager = (TvInputManager) getActivity().getSystemService(Context.TV_INPUT_SERVICE);
- setupAdapter();
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- LayoutInflater inflater = getActivity().getLayoutInflater();
- View view = inflater.inflate(R.layout.dialog_edit_input, null);
- Spinner dropdown = (Spinner) view.findViewById(R.id.spinner);
- dropdown.setAdapter(mAdapter);
- dropdown.setOnItemSelectedListener(this);
- mEditText = (EditText) view.findViewById(R.id.input_edit_text);
- mEditText.addTextChangedListener(new TextWatcher() {
- @Override
- public void afterTextChanged(Editable s) {
- AlertDialog dialog = (AlertDialog) getDialog();
- dialog.getButton(DialogInterface.BUTTON_POSITIVE)
- .setEnabled(!TextUtils.isEmpty(s.toString()));
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- /* do nothing */
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- /* do nothing */
- }
- });
-
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setTitle(R.string.new_input_device_name)
- .setView(view)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- if (mSelected < 0 || mSelected >= mDisplayNames.length) {
- return;
- }
- String oldDisplayName = mDisplayNames[mSelected];
- String newDisplayName = mEditText.getText().toString().trim();
- if (oldDisplayName.equals(newDisplayName)) {
- return;
- }
- if (mInputIdMap.get(newDisplayName) != null) {
- Toast.makeText(getActivity(), R.string.name_already_exists,
- Toast.LENGTH_SHORT).show();
- return;
- }
- String inputId = mInputIdMap.get(oldDisplayName);
- String key = TvSettings.PREF_DISPLAY_INPUT_NAME + inputId;
- mPreferences.edit().putString(key, newDisplayName).commit();
- }
- })
- .setNeutralButton(R.string.reset, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- if (mSelected < 0 || mSelected >= mDisplayNames.length) {
- return;
- }
- String oldDisplayName = mDisplayNames[mSelected];
- String inputId = mInputIdMap.get(oldDisplayName);
- mPreferences.edit().remove(
- TvSettings.PREF_DISPLAY_INPUT_NAME + inputId).commit();
- }
- })
- .setNegativeButton(android.R.string.cancel, null);
-
- final AlertDialog dialog = builder.create();
- dialog.setOnShowListener(new DialogInterface.OnShowListener() {
- @Override
- public void onShow(DialogInterface dlg) {
- dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
- }
- });
-
- return dialog;
- }
-
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- mSelected = position;
- mEditText.requestFocus();
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- }
-
- private void setupAdapter() {
- List<TvInputInfo> inputs = mTvInputManager.getTvInputList();
- if (inputs.size() < 1) {
- return;
- }
- for (TvInputInfo input : inputs) {
- String id = input.getId();
- String name = Utils.getDisplayNameForInput(getActivity(), input);
- mInputIdMap.put(name, id);
- }
- mDisplayNames = mInputIdMap.keySet().toArray(new String[0]);
- mAdapter.addAll(mDisplayNames);
- mAdapter.notifyDataSetChanged();
- }
-}
diff --git a/src/com/android/tv/dialog/FullscreenDialogFragment.java b/src/com/android/tv/dialog/FullscreenDialogFragment.java
new file mode 100644
index 00000000..75539f51
--- /dev/null
+++ b/src/com/android/tv/dialog/FullscreenDialogFragment.java
@@ -0,0 +1,112 @@
+/*
+ * 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.dialog;
+
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+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.
+ */
+public class FullscreenDialogFragment extends SafeDismissDialogFragment {
+ public static final String DIALOG_TAG = FullscreenDialogFragment.class.getSimpleName();
+
+ private final int mViewLayoutResId;
+ private final String mTrackerLabel;
+ private DialogView mDialogView;
+
+ /**
+ * Constructor of FullscreenDialogFragment. View class of viewLayoutResId should
+ * implement {@link DialogView}.
+ */
+ public FullscreenDialogFragment(int viewLayoutResId, String trackerLabel) {
+ mViewLayoutResId = viewLayoutResId;
+ mTrackerLabel = trackerLabel;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ FullscreenDialog dialog =
+ new FullscreenDialog(getActivity(), R.style.Theme_TV_dialog_Fullscreen);
+ LayoutInflater inflater = LayoutInflater.from(getActivity());
+ View v = inflater.inflate(mViewLayoutResId, null);
+ dialog.setContentView(v);
+ mDialogView = (DialogView) v;
+ mDialogView.initialize((MainActivity) getActivity(), dialog);
+ return dialog;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mDialogView.onDestroy();
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return mTrackerLabel;
+ }
+
+ private class FullscreenDialog extends TvDialog {
+ public FullscreenDialog(Context context, int theme) {
+ super(context, theme);
+ }
+
+ @Override
+ public void setContentView(View dialogView) {
+ super.setContentView(dialogView);
+ mDialogView = (DialogView) dialogView;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ boolean handled = super.dispatchKeyEvent(event);
+ return handled || ((View) mDialogView).dispatchKeyEvent(event);
+ }
+
+ @Override
+ public void onBackPressed() {
+ mDialogView.onBackPressed();
+ }
+ }
+
+ /**
+ * Interface for the view of {@link FullscreenDialogFragment}.
+ */
+ public interface DialogView {
+ /**
+ * Called after the view is inflated and attached to the dialog.
+ */
+ void initialize(MainActivity activity, Dialog dialog);
+ /**
+ * Called when a back key is pressed.
+ */
+ void onBackPressed();
+ /**
+ * Called when {@link DialogFragment#onDestroy} is called.
+ */
+ void onDestroy();
+ }
+}
diff --git a/src/com/android/tv/dialog/PinDialogFragment.java b/src/com/android/tv/dialog/PinDialogFragment.java
new file mode 100644
index 00000000..84464461
--- /dev/null
+++ b/src/com/android/tv/dialog/PinDialogFragment.java
@@ -0,0 +1,672 @@
+/*
+ * 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.dialog;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+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.util.TvSettings;
+
+public class PinDialogFragment extends SafeDismissDialogFragment {
+ private static final String TAG = "PinDialogFragment";
+ private static final boolean DBG = true;
+
+ /**
+ * 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.
+ */
+ public static final int PIN_DIALOG_TYPE_UNLOCK_PROGRAM = 1;
+
+ /**
+ * 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
+ */
+ 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;
+
+ 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);
+ }
+
+ 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 };
+
+ private int mType;
+ private ResultListener mListener;
+ private int mRetCode;
+
+ private TextView mWrongPinView;
+ private View mEnterPinView;
+ private TextView mTitleView;
+ private PinNumberPicker[] mPickers;
+ private SharedPreferences mSharedPreferences;
+ private String mPrevPin;
+ private String mPin;
+ private int mWrongPinCount;
+ private long mDisablePinUntil;
+ private final Handler mHandler = new Handler();
+
+ public PinDialogFragment(int type, ResultListener listener) {
+ mType = type;
+ mListener = listener;
+ mRetCode = PIN_DIALOG_RESULT_FAIL;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setStyle(STYLE_NO_TITLE, 0);
+ mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ mDisablePinUntil = TvSettings.getDisablePinUntil(getActivity());
+ if (ActivityManager.isRunningInTestHarness()) {
+ // Skip PIN dialog half the time.
+ if (Math.random() < 0.5) {
+ exit(PIN_DIALOG_RESULT_SUCCESS);
+ }
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dlg = super.onCreateDialog(savedInstanceState);
+ dlg.getWindow().getAttributes().windowAnimations = R.style.pin_dialog_animation;
+ PinNumberPicker.loadResources(dlg.getContext());
+ return dlg;
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ // Dialog size is determined by its windows size, not inflated view size.
+ // 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);
+ }
+ }
+
+ @Override
+ 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);
+ mEnterPinView = v.findViewById(R.id.enter_pin);
+ mTitleView = (TextView) mEnterPinView.findViewById(R.id.title);
+ if (TextUtils.isEmpty(getPin())) {
+ // If PIN isn't set, user should set a PIN.
+ // Successfully setting a new set is considered as entering correct PIN.
+ mType = PIN_DIALOG_TYPE_NEW_PIN;
+ }
+ switch (mType) {
+ case PIN_DIALOG_TYPE_UNLOCK_CHANNEL:
+ mTitleView.setText(R.string.pin_enter_unlock_channel);
+ break;
+ case PIN_DIALOG_TYPE_UNLOCK_PROGRAM:
+ mTitleView.setText(R.string.pin_enter_unlock_program);
+ break;
+ case PIN_DIALOG_TYPE_ENTER_PIN:
+ mTitleView.setText(R.string.pin_enter_pin);
+ break;
+ case PIN_DIALOG_TYPE_NEW_PIN:
+ if (TextUtils.isEmpty(getPin())) {
+ mTitleView.setText(R.string.pin_enter_create_pin);
+ } else {
+ mTitleView.setText(R.string.pin_enter_old_pin);
+ mType = PIN_DIALOG_TYPE_OLD_PIN;
+ }
+ }
+
+ mPickers = new PinNumberPicker[NUMBER_PICKERS_RES_ID.length];
+ for (int i = 0; i < NUMBER_PICKERS_RES_ID.length; i++) {
+ mPickers[i] = (PinNumberPicker) v.findViewById(NUMBER_PICKERS_RES_ID[i]);
+ mPickers[i].setValueRangeAndResetText(0, 9);
+ mPickers[i].setPinDialogFragment(this);
+ mPickers[i].updateFocus(false);
+ }
+ for (int i = 0; i < NUMBER_PICKERS_RES_ID.length - 1; i++) {
+ mPickers[i].setNextNumberPicker(mPickers[i + 1]);
+ }
+
+ if (mType != PIN_DIALOG_TYPE_NEW_PIN) {
+ updateWrongPin();
+ }
+ return v;
+ }
+
+ public void setResultListener(ResultListener listener) {
+ mListener = listener;
+ }
+
+ private final Runnable mUpdateEnterPinRunnable = new Runnable() {
+ @Override
+ public void run() {
+ updateWrongPin();
+ }
+ };
+
+ private void updateWrongPin() {
+ if (getActivity() == null) {
+ // The activity is already detached. No need to update.
+ mHandler.removeCallbacks(null);
+ return;
+ }
+
+ int remainingSeconds = (int) ((mDisablePinUntil - System.currentTimeMillis()) / 1000);
+ boolean enabled = remainingSeconds < 1;
+ if (enabled) {
+ mWrongPinView.setVisibility(View.INVISIBLE);
+ mEnterPinView.setVisibility(View.VISIBLE);
+ mWrongPinCount = 0;
+ } else {
+ mEnterPinView.setVisibility(View.INVISIBLE);
+ mWrongPinView.setVisibility(View.VISIBLE);
+ mWrongPinView.setText(getResources().getQuantityString(R.plurals.pin_enter_countdown,
+ remainingSeconds, remainingSeconds));
+ mHandler.postDelayed(mUpdateEnterPinRunnable, 1000);
+ }
+ }
+
+ private void exit(int retCode) {
+ mRetCode = retCode;
+ 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);
+ }
+ }
+
+ private void handleWrongPin() {
+ if (++mWrongPinCount >= MAX_WRONG_PIN_COUNT) {
+ mDisablePinUntil = System.currentTimeMillis() + DISABLE_PIN_DURATION_MILLIS;
+ TvSettings.setDisablePinUntil(getActivity(), mDisablePinUntil);
+ updateWrongPin();
+ } else {
+ showToast(R.string.pin_toast_wrong);
+ }
+ }
+
+ private void showToast(int resId) {
+ Toast.makeText(getActivity(), resId, Toast.LENGTH_SHORT).show();
+ }
+
+ private void done(String pin) {
+ if (DBG) 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_ENTER_PIN:
+ // TODO: Implement limited number of retrials and timeout logic.
+ if (TextUtils.isEmpty(getPin()) || pin.equals(getPin())) {
+ exit(PIN_DIALOG_RESULT_SUCCESS);
+ } else {
+ resetPinInput();
+ handleWrongPin();
+ }
+ break;
+ case PIN_DIALOG_TYPE_NEW_PIN:
+ resetPinInput();
+ if (mPrevPin == null) {
+ mPrevPin = pin;
+ mTitleView.setText(R.string.pin_enter_again);
+ } else {
+ if (pin.equals(mPrevPin)) {
+ setPin(pin);
+ exit(PIN_DIALOG_RESULT_SUCCESS);
+ } else {
+ if (TextUtils.isEmpty(getPin())) {
+ mTitleView.setText(R.string.pin_enter_create_pin);
+ } else {
+ mTitleView.setText(R.string.pin_enter_new_pin);
+ }
+ mPrevPin = null;
+ showToast(R.string.pin_toast_not_match);
+ }
+ }
+ break;
+ case PIN_DIALOG_TYPE_OLD_PIN:
+ // Call resetPinInput() here because we'll get additional PIN input
+ // regardless of the result.
+ resetPinInput();
+ if (pin.equals(getPin())) {
+ mType = PIN_DIALOG_TYPE_NEW_PIN;
+ mTitleView.setText(R.string.pin_enter_new_pin);
+ } else {
+ handleWrongPin();
+ }
+ break;
+ }
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ private void setPin(String pin) {
+ if (DBG) Log.d(TAG, "setPin: " + pin);
+ mPin = pin;
+ mSharedPreferences.edit().putString(TvSettings.PREF_PIN, pin).apply();
+ }
+
+ private String getPin() {
+ if (mPin == null) {
+ mPin = mSharedPreferences.getString(TvSettings.PREF_PIN, "");
+ }
+ return mPin;
+ }
+
+ private String getPinInput() {
+ String result = "";
+ try {
+ for (PinNumberPicker pnp : mPickers) {
+ pnp.updateText();
+ result += pnp.getValue();
+ }
+ } catch (IllegalStateException e) {
+ result = "";
+ }
+ return result;
+ }
+
+ private void resetPinInput() {
+ for (PinNumberPicker pnp : mPickers) {
+ pnp.setValueRangeAndResetText(0, 9);
+ }
+ mPickers[0].requestFocus();
+ }
+
+ public static class PinNumberPicker extends FrameLayout {
+ private static final int NUMBER_VIEWS_RES_ID[] = {
+ R.id.previous2_number,
+ R.id.previous_number,
+ R.id.current_number,
+ R.id.next_number,
+ R.id.next2_number };
+ private static final int CURRENT_NUMBER_VIEW_INDEX = 2;
+ private static final int NOT_INITIALIZED = Integer.MIN_VALUE;
+
+ private static Animator sFocusedNumberEnterAnimator;
+ private static Animator sFocusedNumberExitAnimator;
+ private static Animator sAdjacentNumberEnterAnimator;
+ private static Animator sAdjacentNumberExitAnimator;
+
+ private static float sAlphaForFocusedNumber;
+ private static float sAlphaForAdjacentNumber;
+
+ private int mMinValue;
+ private int mMaxValue;
+ private int mCurrentValue;
+ // a value for setting mCurrentValue at the end of scroll animation.
+ private int mNextValue;
+ private final int mNumberViewHeight;
+ private PinDialogFragment mDialog;
+ private PinNumberPicker mNextNumberPicker;
+ private boolean mCancelAnimation;
+
+ private final View mNumberViewHolder;
+ // When the PinNumberPicker has focus, mBackgroundView will show the focused background.
+ // Also, this view is used for handling the text change animation of the current number
+ // view which is required when the current number view text is changing from INITIAL_TEXT
+ // to "0".
+ private final TextView mBackgroundView;
+ private final TextView[] mNumberViews;
+ private final AnimatorSet mFocusGainAnimator;
+ private final AnimatorSet mFocusLossAnimator;
+ private final AnimatorSet mScrollAnimatorSet;
+
+ public PinNumberPicker(Context context) {
+ this(context, null);
+ }
+
+ public PinNumberPicker(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PinNumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ 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);
+ mBackgroundView = (TextView) view.findViewById(R.id.focused_background);
+ mNumberViews = new TextView[NUMBER_VIEWS_RES_ID.length];
+ for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) {
+ 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();
+ }
+ 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;
+ }
+ }
+ } 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;
+ }
+ });
+ 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(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("");
+ }
+ });
+
+ 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(mBackgroundView, "alpha", 1f, 0f));
+ 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);
+ }
+ });
+ }
+
+ static void loadResources(Context context) {
+ if (sFocusedNumberEnterAnimator == null) {
+ TypedValue outValue = new TypedValue();
+ 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);
+ 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);
+ }
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ int keyCode = event.getKeyCode();
+ if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
+ mNextValue = adjustValueInValidRange(keyCode - KeyEvent.KEYCODE_0);
+ updateFocus(false);
+ } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+ || keyCode == KeyEvent.KEYCODE_ENTER) {
+ if (mNextNumberPicker == null) {
+ String pin = mDialog.getPinInput();
+ if (!TextUtils.isEmpty(pin)) {
+ mDialog.done(pin);
+ }
+ } else {
+ mNextNumberPicker.requestFocus();
+ }
+ return true;
+ }
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ 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);
+ }
+ });
+ scrollAnimator.setDuration(
+ getResources().getInteger(R.integer.pin_number_scroll_duration));
+
+ if (scrollUp) {
+ sAdjacentNumberExitAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1]);
+ sFocusedNumberExitAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX]);
+ sFocusedNumberEnterAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1]);
+ sAdjacentNumberEnterAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 2]);
+ } else {
+ sAdjacentNumberEnterAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 2]);
+ sFocusedNumberEnterAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1]);
+ sFocusedNumberExitAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX]);
+ sAdjacentNumberExitAnimator.setTarget(mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1]);
+ }
+
+ mScrollAnimatorSet.playTogether(scrollAnimator,
+ sAdjacentNumberExitAnimator, sFocusedNumberExitAnimator,
+ sFocusedNumberEnterAnimator, sAdjacentNumberEnterAnimator);
+ mScrollAnimatorSet.start();
+ }
+
+ void setValueRangeAndResetText(int min, int max) {
+ if (min > max) {
+ throw new IllegalArgumentException(
+ "The min value should be greater than or equal to the max value");
+ } else if (min == NOT_INITIALIZED) {
+ throw new IllegalArgumentException(
+ "The min value should be greater than Integer.MIN_VALUE.");
+ }
+ mMinValue = min;
+ mMaxValue = max;
+ mNextValue = mCurrentValue = NOT_INITIALIZED;
+ for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) {
+ mNumberViews[i].setText(i == CURRENT_NUMBER_VIEW_INDEX ? INITIAL_TEXT : "");
+ }
+ mBackgroundView.setText(INITIAL_TEXT);
+ }
+
+ void setPinDialogFragment(PinDialogFragment dlg) {
+ mDialog = dlg;
+ }
+
+ void setNextNumberPicker(PinNumberPicker picker) {
+ mNextNumberPicker = picker;
+ }
+
+ int getValue() {
+ if (mCurrentValue < mMinValue || mCurrentValue > mMaxValue) {
+ throw new IllegalStateException("Value is not set");
+ }
+ return mCurrentValue;
+ }
+
+ void updateFocus(boolean withAnimation) {
+ mScrollAnimatorSet.end();
+ mFocusGainAnimator.end();
+ mFocusLossAnimator.end();
+ updateText();
+ if (mNumberViewHolder.isFocused()) {
+ if (withAnimation) {
+ mBackgroundView.setText(String.valueOf(mCurrentValue));
+ mFocusGainAnimator.start();
+ } else {
+ mBackgroundView.setAlpha(1f);
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1].setAlpha(sAlphaForAdjacentNumber);
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1].setAlpha(sAlphaForAdjacentNumber);
+ }
+ } else {
+ if (withAnimation) {
+ mFocusLossAnimator.start();
+ } else {
+ mBackgroundView.setAlpha(0f);
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX - 1].setAlpha(0f);
+ mNumberViews[CURRENT_NUMBER_VIEW_INDEX + 1].setAlpha(0f);
+ }
+ mNumberViewHolder.setScrollY(mNumberViewHeight);
+ }
+ }
+
+ private void updateText() {
+ boolean wasNotInitialized = false;
+ if (mNumberViewHolder.isFocused() && mCurrentValue == NOT_INITIALIZED) {
+ mNextValue = mCurrentValue = mMinValue;
+ wasNotInitialized = true;
+ }
+ if (mCurrentValue >= mMinValue && mCurrentValue <= mMaxValue) {
+ for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) {
+ if (wasNotInitialized && i == CURRENT_NUMBER_VIEW_INDEX) {
+ // 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)));
+ }
+ }
+ }
+ }
+
+ 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");
+ }
+ return (value < mMinValue) ? value + interval
+ : (value > mMaxValue) ? value - interval : value;
+ }
+ }
+}
diff --git a/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java b/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java
index 74c6078c..1875f411 100644
--- a/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java
+++ b/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -18,7 +18,6 @@ package com.android.tv.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
-import android.app.DialogFragment;
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Loader;
@@ -31,14 +30,20 @@ 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 DialogFragment implements
+public class RecentlyWatchedDialogFragment extends SafeDismissDialogFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
- public static final String DIALOG_TAG = RecentlyWatchedDialogFragment.class.getName();
+ public static final String DIALOG_TAG = RecentlyWatchedDialogFragment.class.getSimpleName();
+
+ private static final String EMPTY_STRING = "";
+ private static final String TRACKER_LABEL = "Recently watched history";
private SimpleCursorAdapter mAdapter;
@@ -46,6 +51,9 @@ public class RecentlyWatchedDialogFragment extends DialogFragment implements
public Dialog onCreateDialog(Bundle savedInstanceState) {
getLoaderManager().initLoader(0, null, this);
+ final ChannelDataManager dataChannelManager =
+ ((MainActivity) getActivity()).getChannelDataManager();
+
String[] from = {
TvContract.WatchedPrograms._ID,
TvContract.WatchedPrograms.COLUMN_CHANNEL_ID,
@@ -63,8 +71,19 @@ public class RecentlyWatchedDialogFragment extends DialogFragment implements
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
String name = cursor.getColumnName(columnIndex);
if (TvContract.WatchedPrograms.COLUMN_CHANNEL_ID.equals(name)) {
- long channleId = cursor.getLong(columnIndex);
- ((TextView) view).setText(String.valueOf(channleId));
+ 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)) {
@@ -95,6 +114,11 @@ public class RecentlyWatchedDialogFragment extends DialogFragment implements
}
@Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] projection = {
TvContract.WatchedPrograms._ID,
diff --git a/src/com/android/tv/dialog/SafeDismissDialogFragment.java b/src/com/android/tv/dialog/SafeDismissDialogFragment.java
new file mode 100644
index 00000000..c734653c
--- /dev/null
+++ b/src/com/android/tv/dialog/SafeDismissDialogFragment.java
@@ -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.
+ */
+
+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;
+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 {
+ private MainActivity mActivity;
+ private boolean mAttached = false;
+ 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);
+ mAttached = true;
+ mActivity = (MainActivity) activity;
+ mTracker = ((TvApplication) activity.getApplication()).getTracker();
+ if (mDismissPending) {
+ mDismissPending = false;
+ dismiss();
+ return;
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mTracker.sendScreenView(getTrackerLabel());
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mActivity != null) {
+ mActivity.getOverlayManager().onDialogDestroyed();
+ }
+ super.onDestroy();
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mAttached = false;
+ mTracker = null;
+ }
+
+ /**
+ * Dismiss safely regardless of the DialogFragment's life cycle.
+ */
+ @Override
+ public void dismiss() {
+ if (!mAttached) {
+ // dismiss() before getFragmentManager() is set cause NPE in dismissInternal().
+ // FragmentManager is set when a fragment is used in a transaction,
+ // so wait here until we can dismiss safely.
+ mDismissPending = true;
+ } else {
+ 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.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
new file mode 100644
index 00000000..75f93bb2
--- /dev/null
+++ b/src/com/android/tv/dialog/WebDialogFragment.java
@@ -0,0 +1,96 @@
+/*
+ * 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.dialog;
+
+import android.app.DialogFragment;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+/**
+ * A DialogFragment that shows a web view.
+ */
+public class WebDialogFragment extends SafeDismissDialogFragment {
+ private static final String TAG = "WebDialogFragment";
+ private static final String URL = "URL";
+ private static final String TITLE = "TITLE";
+ private static final String TRACKER_LABEL = "TRACKER_LABEL";
+
+ private String mTrackerLabel;
+
+ /**
+ * Create a new WebDialogFragment to show a particular web page.
+ *
+ * @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) {
+ WebDialogFragment f = new WebDialogFragment();
+ Bundle args = new Bundle();
+ args.putString(URL, url);
+ args.putString(TITLE, title);
+ args.putString(TRACKER_LABEL, trackerLabel);
+ f.setArguments(args);
+ return f;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ 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;
+ setStyle(style, 0);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ String title = getArguments().getString(TITLE);
+ getDialog().setTitle(title);
+
+ WebView webView = new WebView(getActivity());
+ webView.setWebViewClient(new WebViewClient());
+ String url = getArguments().getString(URL);
+ webView.loadUrl(url);
+ Log.d(TAG, "Loading web content from " + url);
+
+ return webView;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ // Ensure the dialog is fullscreen, even if the webview doesn't have its content yet.
+ getDialog().getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return mTrackerLabel;
+ }
+}
diff --git a/src/com/android/tv/guide/GenreListAdapter.java b/src/com/android/tv/guide/GenreListAdapter.java
new file mode 100644
index 00000000..2913599c
--- /dev/null
+++ b/src/com/android/tv/guide/GenreListAdapter.java
@@ -0,0 +1,116 @@
+/*
+ * 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.guide;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+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.
+ */
+public class GenreListAdapter extends RecyclerView.Adapter<GenreListAdapter.GenreRowHolder> {
+ private static final String TAG = "GenreListAdapter";
+ private static final boolean DEBUG = false;
+
+ private final Context mContext;
+ private final ProgramManager mProgramManager;
+ private final ProgramGuide mProgramGuide;
+ private String[] mGenreLabels;
+
+ public GenreListAdapter(Context context, ProgramManager programManager, ProgramGuide guide) {
+ mContext = context;
+ mProgramManager = programManager;
+ mProgramManager.addListener(new ProgramManager.ListenerAdapter() {
+ @Override
+ public void onGenresUpdated() {
+ mGenreLabels = GenreItems.getLabels(mContext);
+ notifyDataSetChanged();
+ }
+ });
+ mProgramGuide = guide;
+ }
+
+ @Override
+ public int getItemCount() {
+ List<Integer> filteredGenreIds = mProgramManager.getFilteredGenreIds();
+ if (filteredGenreIds == null) {
+ // Genre item would be available after program manager builds genre filter.
+ return 0;
+ }
+ return filteredGenreIds.size();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return R.layout.program_guide_side_panel_row;
+ }
+
+ @Override
+ public void onBindViewHolder(GenreRowHolder holder, int position) {
+ List<Integer> filteredGenreIds = mProgramManager.getFilteredGenreIds();
+ int genreId = filteredGenreIds.get(position);
+ holder.onBind(genreId, mGenreLabels[genreId]);
+ }
+
+ @Override
+ public GenreRowHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View itemView = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
+ return new GenreRowHolder(itemView, mProgramGuide);
+ }
+
+ public static class GenreRowHolder extends RecyclerView.ViewHolder implements
+ View.OnFocusChangeListener {
+ private final ProgramGuide mProgramGuide;
+ private int mGenreId;
+
+ // Should be called from main thread.
+ public GenreRowHolder(View itemView, ProgramGuide programGuide) {
+ super(itemView);
+ mProgramGuide = programGuide;
+ }
+
+ public void onBind(int genreId, String genreLabel) {
+ mGenreId = genreId;
+
+ TextView textView = (TextView) itemView;
+ textView.setText(genreLabel);
+
+ itemView.setOnFocusChangeListener(this);
+ }
+
+ @Override
+ public void onFocusChange(View view, boolean hasFocus) {
+ if (hasFocus) {
+ if (DEBUG) {
+ Log.d(TAG, "onFocusChanged " + ((TextView) view).getText()
+ + "(" + mGenreId + ") hasFocus");
+ }
+ mProgramGuide.requestGenreChange(mGenreId);
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/guide/GuideUtils.java b/src/com/android/tv/guide/GuideUtils.java
new file mode 100644
index 00000000..5d11f061
--- /dev/null
+++ b/src/com/android/tv/guide/GuideUtils.java
@@ -0,0 +1,56 @@
+/*
+ * 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.guide;
+
+import java.util.concurrent.TimeUnit;
+
+public class GuideUtils {
+ private static int sWidthPerHour = 0;
+
+ /**
+ * Sets the width in pixels that corresponds to an hour in program guide.
+ * Assume that this is called from main thread only, so, no synchronization.
+ */
+ public static void setWidthPerHour(int widthPerHour) {
+ sWidthPerHour = widthPerHour;
+ }
+
+ /**
+ * Gets the number of pixels in program guide table that corresponds to the given milliseconds.
+ */
+ public static int convertMillisToPixel(long millis) {
+ return (int) (millis * sWidthPerHour / TimeUnit.HOURS.toMillis(1));
+ }
+
+ /**
+ * Gets the number of pixels in program guide table that corresponds to the given range.
+ */
+ public static int convertMillisToPixel(long startMillis, long endMillis) {
+ // Convert to pixels first to avoid accumulation of rounding errors.
+ return GuideUtils.convertMillisToPixel(endMillis)
+ - GuideUtils.convertMillisToPixel(startMillis);
+ }
+
+ /**
+ * Gets the time in millis that corresponds to the given pixels in the program guide.
+ */
+ public static long convertPixelToMillis(int pixel) {
+ return pixel * TimeUnit.HOURS.toMillis(1) / sWidthPerHour;
+ }
+
+ private GuideUtils() { }
+}
diff --git a/src/com/android/tv/guide/ProgramGrid.java b/src/com/android/tv/guide/ProgramGrid.java
new file mode 100644
index 00000000..27c8a0c4
--- /dev/null
+++ b/src/com/android/tv/guide/ProgramGrid.java
@@ -0,0 +1,340 @@
+/*
+ * 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.guide;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link VerticalGridView} for the program table view.
+ */
+public class ProgramGrid extends VerticalGridView {
+ private static final String TAG = "ProgramGrid";
+
+ private static final int INVALID_INDEX = -1;
+ private static final long FOCUS_AREA_RIGHT_MARGIN_MILLIS = TimeUnit.MINUTES.toMillis(15);
+
+ private final ViewTreeObserver.OnGlobalFocusChangeListener mGlobalFocusChangeListener =
+ new ViewTreeObserver.OnGlobalFocusChangeListener() {
+ @Override
+ public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+ if (newFocus != mNextFocusByUpDown) {
+ // If focus is changed by other buttons than UP/DOWN buttons,
+ // we clear the focus state.
+ clearUpDownFocusState(newFocus);
+ }
+ mNextFocusByUpDown = null;
+ if (newFocus != ProgramGrid.this && contains(newFocus)) {
+ mLastFocusedView = newFocus;
+ }
+ }
+ };
+
+ private final ProgramManager.Listener mProgramManagerListener =
+ new ProgramManager.ListenerAdapter() {
+ @Override
+ public void onTimeRangeUpdated() {
+ // When time range is changed, we clear the focus state.
+ clearUpDownFocusState(null);
+ }
+ };
+
+ private ProgramManager mProgramManager;
+ private View mNextFocusByUpDown;
+
+ // New focus will be overlapped with [mFocusRangeLeft, mFocusRangeRight].
+ private int mFocusRangeLeft;
+ private int mFocusRangeRight;
+
+ private View mLastFocusedView;
+ private final Rect mTempRect = new Rect();
+
+ private boolean mKeepCurrentProgram;
+
+ private ChildFocusListener mChildFocusListener;
+
+ interface 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);
+ }
+
+ public ProgramGrid(Context context) {
+ this(context, null);
+ }
+
+ public ProgramGrid(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ProgramGrid(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ clearUpDownFocusState(null);
+
+ // Don't cache anything that is off screen. Normally it is good to prefetch and prepopulate
+ // off screen views in order to reduce jank, however the program guide is capable to scroll
+ // in all four directions so not only would we prefetch views in the scrolling direction
+ // but also keep views in the perpendicular direction up to date.
+ // E.g. when scrolling horizontally we would have to update rows above and below the current
+ // view port even though they are not visible.
+ setItemViewCacheSize(0);
+ }
+
+ /**
+ * 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) {
+ mChildFocusListener.onRequestChildFocus(getFocusedChild(), child);
+ }
+ super.requestChildFocus(child, focused);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ getViewTreeObserver().addOnGlobalFocusChangeListener(mGlobalFocusChangeListener);
+ mProgramManager.addListener(mProgramManagerListener);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ getViewTreeObserver().removeOnGlobalFocusChangeListener(mGlobalFocusChangeListener);
+ mProgramManager.removeListener(mProgramManagerListener);
+ clearUpDownFocusState(null);
+ }
+
+ @Override
+ public View focusSearch(View focused, int direction) {
+ mNextFocusByUpDown = null;
+ if (focused == null || !contains(focused)) {
+ return super.focusSearch(focused, direction);
+ }
+ if (direction == View.FOCUS_UP || direction == View.FOCUS_DOWN) {
+ updateUpDownFocusState(focused);
+ View nextFocus = focusFind(focused, direction);
+ if (nextFocus != null) {
+ return nextFocus;
+ }
+ }
+ return super.focusSearch(focused, direction);
+ }
+
+ /**
+ * Resets focus states. If the logic to keep the last focus needs to be cleared, it should
+ * be called.
+ */
+ public void resetFocusState() {
+ mLastFocusedView = null;
+ clearUpDownFocusState(null);
+ }
+
+ private View focusFind(View focused, int direction) {
+ int focusedChildIndex = getFocusedChildIndex();
+ if (focusedChildIndex == INVALID_INDEX) {
+ Log.w(TAG, "No child view has focus");
+ return null;
+ }
+ int nextChildIndex = direction == View.FOCUS_UP ? focusedChildIndex - 1
+ : focusedChildIndex + 1;
+ if (nextChildIndex < 0 || nextChildIndex >= getChildCount()) {
+ return focused;
+ }
+ View nextChild = getChildAt(nextChildIndex);
+ ArrayList<View> 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;
+ }
+ }
+ }
+ if (index != INVALID_INDEX) {
+ mNextFocusByUpDown = focusables.get(index);
+ return mNextFocusByUpDown;
+ }
+
+ Log.w(TAG, "focusFind doesn't find proper focusable");
+ return null;
+ }
+
+ // Returned value is not the position of VerticalGridView. But it's the index of ViewGroup
+ // among visible children.
+ private int getFocusedChildIndex() {
+ for (int i = 0; i < getChildCount(); ++i) {
+ if (getChildAt(i).hasFocus()) {
+ return i;
+ }
+ }
+ return INVALID_INDEX;
+ }
+
+ private void updateUpDownFocusState(View focused) {
+ int rightMostFocusablePosition = getRightMostFocusablePosition();
+ Rect focusedRect = mTempRect;
+
+ // In order to avoid from focusing small width item, we clip the position with
+ // mostRightFocusablePosition.
+ focused.getGlobalVisibleRect(focusedRect);
+ mFocusRangeLeft = Math.min(mFocusRangeLeft, rightMostFocusablePosition);
+ mFocusRangeRight = Math.min(mFocusRangeRight, rightMostFocusablePosition);
+ focusedRect.left = Math.min(focusedRect.left, rightMostFocusablePosition);
+ focusedRect.right = Math.min(focusedRect.right, rightMostFocusablePosition);
+
+ if (focusedRect.left > mFocusRangeRight || focusedRect.right < mFocusRangeLeft) {
+ Log.w(TAG, "The current focus is out of [mFocusRangeLeft, mFocusRangeRight]");
+ mFocusRangeLeft = focusedRect.left;
+ mFocusRangeRight = focusedRect.right;
+ return;
+ }
+ mFocusRangeLeft = Math.max(mFocusRangeLeft, focusedRect.left);
+ mFocusRangeRight = Math.min(mFocusRangeRight, focusedRect.right);
+ }
+
+ private void clearUpDownFocusState(View focus) {
+ mFocusRangeLeft = 0;
+ mFocusRangeRight = getRightMostFocusablePosition();
+ mNextFocusByUpDown = null;
+ mKeepCurrentProgram = focus != null && focus instanceof ProgramItemView
+ && ((ProgramItemView) focus).getTableEntry().isCurrentProgram();
+ }
+
+ private int getRightMostFocusablePosition() {
+ if (!getGlobalVisibleRect(mTempRect)) {
+ return Integer.MAX_VALUE;
+ }
+ 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());
+ }
+
+ @Override
+ public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ if (mLastFocusedView != null && mLastFocusedView.isShown()) {
+ if (mLastFocusedView.requestFocus()) {
+ return true;
+ }
+ }
+ return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+ }
+
+ private static void findFocusables(View v, ArrayList<View> 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
new file mode 100644
index 00000000..03bda694
--- /dev/null
+++ b/src/com/android/tv/guide/ProgramGuide.java
@@ -0,0 +1,899 @@
+/*
+ * 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.guide;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.preference.PreferenceManager;
+import android.support.v17.leanback.widget.OnChildSelectedListener;
+import android.support.v17.leanback.widget.SearchOrbView;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewTreeObserver;
+
+import com.android.tv.ChannelTuner;
+import com.android.tv.MainActivity;
+import com.android.tv.R;
+import com.android.tv.analytics.DurationTimer;
+import com.android.tv.analytics.Tracker;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.GenreItems;
+import com.android.tv.data.ProgramDataManager;
+import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter;
+import com.android.tv.util.SystemProperties;
+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.
+ */
+public class ProgramGuide implements ProgramGrid.ChildFocusListener {
+ private static final String TAG = "ProgramGuide";
+ private static final boolean DEBUG = false;
+
+ // Whether we should show the guide partially. The first time the user enters the program guide,
+ // we show the grid partially together with the genre side panel on the left. Next time
+ // the program guide is entered, we recover the previous state (partial or full).
+ private static final String KEY_SHOW_GUIDE_PARTIAL = "show_guide_partial";
+ private static final long TIME_INDICATOR_UPDATE_FREQUENCY = TimeUnit.SECONDS.toMillis(1);
+ private static final long HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
+ private static final long HALF_HOUR_IN_MILLIS = HOUR_IN_MILLIS / 2;
+ // We keep the duration between mStartTime and the current time larger than this value.
+ // 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 int MSG_PROGRAM_TABLE_FADE_IN_ANIM = 1000;
+
+ private static final int SELECTION_ROW = 2; // Row that is focused
+ private static final String SCREEN_NAME = "EPG";
+
+ private final MainActivity mActivity;
+ private final ProgramManager mProgramManager;
+ private final ChannelTuner mChannelTuner;
+ private final Tracker mTracker;
+ private final DurationTimer mVisibleDuration = new DurationTimer();
+ private final Runnable mPreShowRunnable;
+ private final Runnable mPostHideRunnable;
+
+ private final int mWidthPerHour;
+ private final long mViewPortMillis;
+ private final int mRowHeight;
+ private final int mDetailHeight;
+ private final int mTableFadeAnimDuration;
+ private final int mAnimationDuration;
+ private final int mDetailPadding;
+ private final SearchOrbView mSearchOrb;
+ private int mCurrentTimeIndicatorWidth;
+
+ private final View mContainer;
+ private final View mSidePanel;
+ private final VerticalGridView mSidePanelGridView;
+ private final View mTable;
+ private final TimelineRow mTimelineRow;
+ private final ProgramGrid mGrid;
+ private final TimeListAdapter mTimeListAdapter;
+ private final View mCurrentTimeIndicator;
+
+ private final Animator mShowAnimatorFull;
+ private final Animator mShowAnimatorPartial;
+ // mHideAnimatorFull and mHideAnimatorPartial are created from the same animation xmls.
+ // When we share the one animator for two different animations, the starting value
+ // is broken, even though the starting value is not defined in XML.
+ private final Animator mHideAnimatorFull;
+ private final Animator mHideAnimatorPartial;
+ private final Animator mPartialToFullAnimator;
+ private final Animator mFullToPartialAnimator;
+ private final Animator mProgramTableFadeOutAnimator;
+ private final Animator mProgramTableFadeInAnimator;
+
+ // When the program guide is popped up, we keep the previous state of the guide.
+ private boolean mShowGuidePartial;
+ private final SharedPreferences mSharedPreference;
+ private View mSelectedRow;
+ private Animator mDetailOutAnimator;
+ private Animator mDetailInAnimator;
+
+ private long mStartUtcTime;
+ private boolean mTimelineAnimation;
+ private int mLastRequestedGenreId = GenreItems.ID_ALL_CHANNELS;
+ private boolean mIsDuringResetRowSelection;
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_PROGRAM_TABLE_FADE_IN_ANIM) {
+ mProgramTableFadeInAnimator.start();
+ }
+ }
+ };
+
+ private final Runnable mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ hide();
+ }
+ };
+ private final long mShowDurationMillis;
+
+ 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, Tracker tracker, Runnable preShowRunnable,
+ Runnable postHideRunnable) {
+ mActivity = activity;
+ mProgramManager = new ProgramManager(tvInputManagerHelper,
+ channelDataManager,
+ programDataManager);
+ mChannelTuner = channelTuner;
+ mTracker = tracker;
+ mPreShowRunnable = preShowRunnable;
+ mPostHideRunnable = postHideRunnable;
+
+ Resources res = activity.getResources();
+
+ mWidthPerHour = res.getDimensionPixelSize(R.dimen.program_guide_table_width_per_hour);
+ GuideUtils.setWidthPerHour(mWidthPerHour);
+
+ 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);
+ mViewPortMillis = (gridWidth * HOUR_IN_MILLIS) / mWidthPerHour;
+
+ mRowHeight = res.getDimensionPixelSize(R.dimen.program_guide_table_item_row_height);
+ mDetailHeight = res.getDimensionPixelSize(R.dimen.program_guide_table_detail_height);
+ mTableFadeAnimDuration =
+ res.getInteger(R.integer.program_guide_table_detail_fade_anim_duration);
+ mShowDurationMillis = res.getInteger(R.integer.program_guide_show_duration);
+ mAnimationDuration =
+ res.getInteger(R.integer.program_guide_table_detail_toggle_anim_duration);
+ mDetailPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_detail_padding);
+
+ mContainer = mActivity.findViewById(R.id.program_guide);
+ 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.setAdapter(genreListAdapter);
+ mSidePanelGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+ mSidePanelGridView.setWindowAlignmentOffset(mActivity.getResources()
+ .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 (SystemProperties.USE_EPG_SEARCH.getValue()) {
+ 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();
+ }
+ });
+ 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);
+ }
+ });
+ } else {
+ mSearchOrb = null;
+ }
+
+ mTable = mContainer.findViewById(R.id.program_guide_table);
+
+ 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.setAdapter(mTimeListAdapter);
+
+ ProgramTableAdapter programTableAdapter = new ProgramTableAdapter(mActivity,
+ tvInputManagerHelper, mProgramManager, this);
+ programTableAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
+ @Override
+ public void onChanged() {
+ // It is usually called when Genre is changed.
+ // Reset selection of ProgramGrid
+ resetRowSelection();
+
+ // Align EPG at vertical center, if EPG table height is less than the screen size.
+ Resources res = mActivity.getResources();
+ int screenHeight = mContainer.getHeight();
+ 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);
+ }
+ }
+ });
+
+ 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.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.setFocusScrollStrategy(ProgramGrid.FOCUS_SCROLL_ALIGNED);
+ mGrid.setWindowAlignmentOffset(SELECTION_ROW * 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);
+ }
+ };
+ 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);
+ 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,
+ 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.INVISIBLE);
+ }
+ });
+ 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.INVISIBLE);
+ }
+ });
+
+ 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);
+ mProgramTableFadeInAnimator.setTarget(mTable);
+ mProgramTableFadeInAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(mTable));
+ mSharedPreference = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ mShowGuidePartial = mSharedPreference.getBoolean(KEY_SHOW_GUIDE_PARTIAL, true);
+ }
+
+ @Override
+ public void onRequestChildFocus(View oldFocus, View newFocus) {
+ if (oldFocus != null && newFocus != null) {
+ int selectionRowOffset = SELECTION_ROW * mRowHeight;
+ if (oldFocus.getTop() < newFocus.getTop()) {
+ // Selection moves downwards
+ // Adjust scroll offset to be at the bottom of the target row and to expand up. This
+ // will set the scroll target to be one row height up from its current position.
+ mGrid.setWindowAlignmentOffset(selectionRowOffset + mRowHeight + mDetailHeight);
+ mGrid.setItemAlignmentOffsetPercent(100);
+ } else if (oldFocus.getTop() > newFocus.getTop()) {
+ // Selection moves upwards
+ // Adjust scroll offset to be at the top of the target row and to expand down. This
+ // will set the scroll target to be one row height down from its current position.
+ mGrid.setWindowAlignmentOffset(selectionRowOffset);
+ mGrid.setItemAlignmentOffsetPercent(0);
+ }
+ }
+ }
+
+ private Animator createAnimator(int sidePanelAnimResId, int sidePanelGridAnimResId,
+ int tableAnimResId) {
+ List<Animator> 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.
+ */
+ public void show() {
+ if (mContainer.getVisibility() == View.VISIBLE) {
+ return;
+ }
+ mTracker.sendShowEpg();
+ mTracker.sendScreenView(SCREEN_NAME);
+ if (mPreShowRunnable != null) {
+ mPreShowRunnable.run();
+ }
+ mVisibleDuration.start();
+
+ mProgramManager.programGuideVisibilityChanged(true);
+ mStartUtcTime = Utils.floorTime(
+ System.currentTimeMillis() - MIN_DURATION_FROM_START_TIME_TO_CURRENT_TIME,
+ HALF_HOUR_IN_MILLIS);
+ mProgramManager.setInitialTimeRange(mStartUtcTime, mStartUtcTime + mViewPortMillis);
+ mProgramManager.addListener(mProgramManagerListener);
+ mProgramManager.buildGenreFilters();
+ mLastRequestedGenreId = GenreItems.ID_ALL_CHANNELS;
+ mTimeListAdapter.update(mStartUtcTime);
+ mTimelineRow.resetScroll();
+
+ 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);
+ }
+
+ mContainer.setVisibility(View.VISIBLE);
+ mSidePanelGridView.setSelectedPosition(0);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // setVisibility is not immediately applied. In order to start animation after
+ // making it visible, we post mShowAnimatorXXX.start() instead of calling
+ // mShowAnimatorXXX.start() in show().
+ if (mShowGuidePartial) {
+ mShowAnimatorPartial.start();
+ } else {
+ mShowAnimatorFull.start();
+ }
+ }
+ });
+
+ mTimelineAnimation = true;
+ startCurrentTimeIndicator();
+ scheduleHide();
+ }
+
+ /**
+ * Hide the program guide.
+ */
+ public void hide() {
+ if (!isActive()) {
+ return;
+ }
+ mTracker.sendHideEpg(mVisibleDuration.reset());
+ cancelHide();
+ mProgramManager.programGuideVisibilityChanged(false);
+ mProgramManager.removeListener(mProgramManagerListener);
+ if (isFull()) {
+ mHideAnimatorFull.start();
+ } else {
+ mHideAnimatorPartial.start();
+ }
+
+ // Clears fade-out/in animation for genre change
+ if (mProgramTableFadeOutAnimator.isRunning()) {
+ mProgramTableFadeOutAnimator.cancel();
+ }
+ if (mProgramTableFadeInAnimator.isRunning()) {
+ mProgramTableFadeInAnimator.cancel();
+ }
+ mHandler.removeMessages(MSG_PROGRAM_TABLE_FADE_IN_ANIM);
+ mTable.setAlpha(1.0f);
+
+ mTimelineAnimation = false;
+ stopCurrentTimeIndicator();
+ if (mPostHideRunnable != null) {
+ mPostHideRunnable.run();
+ }
+ }
+
+ 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();
+ }
+
+ private 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()) {
+ 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.
+ */
+ public void onBackPressed() {
+ hide();
+ }
+
+ /**
+ * Gets {@link VerticalGridView} for "genre select" side panel.
+ */
+ public VerticalGridView getSidePanel() {
+ return mSidePanelGridView;
+ }
+
+ /**
+ * Requests change genre to {@code genreId}.
+ */
+ public void requestGenreChange(int genreId) {
+ if (mLastRequestedGenreId == genreId) {
+ // When Recycler.onLayout() removes its children to recycle,
+ // View tries to find next focus candidate immediately
+ // so GenreListAdapter can take focus back while it's hiding.
+ // Returns early here to prevent re-entrance.
+ return;
+ }
+ mLastRequestedGenreId = genreId;
+ if (mProgramTableFadeOutAnimator.isStarted()) {
+ // 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);
+ 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);
+ return;
+ }
+ if (mProgramTableFadeInAnimator.isStarted()) {
+ mProgramTableFadeInAnimator.cancel();
+ }
+
+ mProgramTableFadeOutAnimator.start();
+ }
+
+ private void startCurrentTimeIndicator() {
+ mHandler.post(mUpdateTimeIndicator);
+ }
+
+ private void stopCurrentTimeIndicator() {
+ mHandler.removeCallbacks(mUpdateTimeIndicator);
+ }
+
+ private void positionCurrentTimeIndicator() {
+ int offset = GuideUtils.convertMillisToPixel(mStartUtcTime, System.currentTimeMillis())
+ - mTimelineRow.getScrollOffset();
+ if (offset < 0) {
+ mCurrentTimeIndicator.setVisibility(View.GONE);
+ } else {
+ if (mCurrentTimeIndicatorWidth == 0) {
+ mCurrentTimeIndicator.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ mCurrentTimeIndicatorWidth = mCurrentTimeIndicator.getMeasuredWidth();
+ }
+ mCurrentTimeIndicator.setPaddingRelative(
+ offset - mCurrentTimeIndicatorWidth / 2, 0, 0, 0);
+ mCurrentTimeIndicator.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void resetTimelineScroll() {
+ if (mProgramManager.getFromUtcMillis() != mStartUtcTime) {
+ boolean timelineAnimation = mTimelineAnimation;
+ mTimelineAnimation = false;
+ // mProgramManagerListener.onTimeRangeUpdated() will be called by shiftTime().
+ mProgramManager.shiftTime(mStartUtcTime - mProgramManager.getFromUtcMillis());
+ mTimelineAnimation = timelineAnimation;
+ }
+ }
+
+ private void onHorizontalScrolled(int dx) {
+ if (DEBUG) Log.d(TAG, "onHorizontalScrolled(dx=" + dx + ")");
+ positionCurrentTimeIndicator();
+ for (int i = 0, n = mGrid.getChildCount(); i < n; ++i) {
+ mGrid.getChildAt(i).findViewById(R.id.row).scrollBy(dx, 0);
+ }
+ }
+
+ private void resetRowSelection() {
+ if (mDetailOutAnimator != null) {
+ mDetailOutAnimator.end();
+ }
+ if (mDetailInAnimator != null) {
+ mDetailInAnimator.cancel();
+ }
+ mSelectedRow = null;
+ mIsDuringResetRowSelection = true;
+ mGrid.setSelectedPosition(
+ Math.max(mProgramManager.getChannelIndex(mChannelTuner.getCurrentChannel()),
+ 0));
+ mGrid.resetFocusState();
+ mIsDuringResetRowSelection = false;
+ }
+
+ private void selectRow(View row) {
+ if (row == null || row == mSelectedRow) {
+ return;
+ }
+ if (mSelectedRow == null
+ || mGrid.getChildAdapterPosition(mSelectedRow) == RecyclerView.NO_POSITION) {
+ if (mSelectedRow != null) {
+ View oldDetailView = mSelectedRow.findViewById(R.id.detail);
+ oldDetailView.setVisibility(View.GONE);
+ }
+ View detailView = row.findViewById(R.id.detail);
+ detailView.findViewById(R.id.detail_content_full).setAlpha(1);
+ detailView.findViewById(R.id.detail_content_full).setTranslationY(0);
+ setLayoutHeight(detailView, mDetailHeight);
+ detailView.setVisibility(View.VISIBLE);
+
+ final ProgramRow programRow = (ProgramRow) row.findViewById(R.id.row);
+ programRow.post(new Runnable() {
+ @Override
+ public void run() {
+ programRow.focusCurrentProgram();
+ }
+ });
+ } else {
+ animateRowChange(mSelectedRow, row);
+ }
+ mSelectedRow = row;
+ }
+
+ private void animateRowChange(View outRow, View inRow) {
+ if (mDetailOutAnimator != null) {
+ mDetailOutAnimator.end();
+ }
+ if (mDetailInAnimator != null) {
+ 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;
+ }
+
+ View outDetail = outRow != null ? outRow.findViewById(R.id.detail) : null;
+ 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(), direction * mDetailPadding));
+ fadeOutAnimator.setStartDelay(0);
+ fadeOutAnimator.setDuration(mAnimationDuration);
+ fadeOutAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(outDetailContent));
+
+ Animator collapseAnimator =
+ createHeightAnimator(outDetail, 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);
+ }
+ });
+
+ AnimatorSet outAnimator = new AnimatorSet();
+ outAnimator.playTogether(fadeOutAnimator, collapseAnimator);
+ outAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mDetailOutAnimator = null;
+ }
+ });
+ mDetailOutAnimator = outAnimator;
+ outAnimator.start();
+ }
+
+ View inDetail = inRow != null ? inRow.findViewById(R.id.detail) : null;
+ if (inDetail != null) {
+ final View inDetailContent = inDetail.findViewById(R.id.detail_content_full);
+
+ Animator expandAnimator = 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,
+ direction * -mDetailPadding, 0f));
+ fadeInAnimator.setStartDelay(mAnimationDuration + mTableFadeAnimDuration);
+ fadeInAnimator.setDuration(mAnimationDuration);
+ fadeInAnimator.addListener(new HardwareLayerAnimatorListenerAdapter(inDetailContent));
+
+ AnimatorSet inAnimator = new AnimatorSet();
+ inAnimator.playTogether(expandAnimator, fadeInAnimator);
+ inAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mDetailInAnimator = null;
+ }
+ });
+ mDetailInAnimator = inAnimator;
+ inAnimator.start();
+ }
+ }
+
+ private 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);
+ }
+ } else {
+ if (target.getVisibility() != View.VISIBLE) {
+ target.setVisibility(View.VISIBLE);
+ }
+ setLayoutHeight(target, value);
+ }
+ }
+ });
+ return animator;
+ }
+
+ private int getLayoutHeight(View view) {
+ LayoutParams layoutParams = view.getLayoutParams();
+ return layoutParams.height;
+ }
+
+ private void setLayoutHeight(View view, int height) {
+ LayoutParams layoutParams = view.getLayoutParams();
+ if (height != layoutParams.height) {
+ layoutParams.height = height;
+ view.setLayoutParams(layoutParams);
+ }
+ }
+
+ 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;
+
+ @Override
+ public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+ if (DEBUG) Log.d(TAG, "onGlobalFocusChanged " + oldFocus + " -> " + newFocus);
+ if (!isActive()) {
+ return;
+ }
+ int fromLocation = getLocation(oldFocus);
+ int toLocation = getLocation(newFocus);
+ if (fromLocation == SIDE_PANEL && toLocation == PROGRAM_TABLE) {
+ startFull();
+ } else if (fromLocation == PROGRAM_TABLE && toLocation == SIDE_PANEL) {
+ startPartial();
+ }
+ }
+
+ private int getLocation(View view) {
+ if (view == null) {
+ return UNKNOWN;
+ }
+ for (Object obj = view; obj instanceof View; obj = ((View) obj).getParent()) {
+ if (obj == mSidePanel) {
+ return SIDE_PANEL;
+ } else if (obj == mGrid) {
+ return PROGRAM_TABLE;
+ }
+ }
+ return UNKNOWN;
+ }
+ }
+
+ private class ProgramManagerListener extends ProgramManager.ListenerAdapter {
+ @Override
+ public void onTimeRangeUpdated() {
+ int scrollOffset = (int) (mWidthPerHour * mProgramManager.getShiftedTime()
+ / HOUR_IN_MILLIS);
+ if (DEBUG) {
+ 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
new file mode 100644
index 00000000..7d3c0190
--- /dev/null
+++ b/src/com/android/tv/guide/ProgramItemView.java
@@ -0,0 +1,365 @@
+/*
+ * 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.guide;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.StateListDrawable;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
+import android.util.AttributeSet;
+import android.util.Log;
+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.TvApplication;
+import com.android.tv.analytics.Tracker;
+import com.android.tv.data.Channel;
+import com.android.tv.guide.ProgramManager.TableEntry;
+import com.android.tv.util.Utils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.concurrent.TimeUnit;
+
+public class ProgramItemView extends TextView {
+ private static final String TAG = "ProgramItemView";
+
+ private static final long FOCUS_UPDATE_FREQUENCY = TimeUnit.SECONDS.toMillis(1);
+ 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 };
+
+ // 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 int sVisibleThreshold;
+ private static int sMinProgramDisplayDurationPixels;
+ private static int sItemPadding;
+ private static TextAppearanceSpan sProgramTitleStyle;
+ private static TextAppearanceSpan sGrayedOutProgramTitleStyle;
+ private static TextAppearanceSpan sEpisodeTitleStyle;
+ private static TextAppearanceSpan sGrayedOutEpisodeTitleStyle;
+
+ private TableEntry mTableEntry;
+ private int mMaxWidthForRipple;
+
+ // If set this flag disables requests to re-layout the parent view as a result of changing
+ // this view, improving performance. This also prevents the parent view to lose child focus
+ // 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(View view) {
+ TableEntry entry = ((ProgramItemView) view).mTableEntry;
+ Tracker tracker = ((TvApplication) view.getContext().getApplicationContext())
+ .getTracker();
+ tracker.sendEpgItemClicked();
+ if (entry.isCurrentProgram()) {
+ final MainActivity tvActivity = (MainActivity) view.getContext();
+ final Channel channel = tvActivity.getChannelDataManager()
+ .getChannel(entry.channelId);
+ view.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ tvActivity.tuneToChannel(channel);
+ tvActivity.hideOverlaysForTune();
+ }
+ }, entry.getWidth() > ((ProgramItemView) view).mMaxWidthForRipple ? 0 :
+ view.getResources().getInteger(R.integer.program_guide_ripple_anim_duration));
+ }
+ }
+ };
+
+ 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);
+ }
+ }
+ }
+ };
+
+ private final Runnable mUpdateFocus = new Runnable() {
+ @Override
+ public void run() {
+ refreshDrawableState();
+ TableEntry entry = mTableEntry;
+ if (entry.isCurrentProgram()) {
+ Drawable background = getBackground();
+ 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);
+ }
+
+ public ProgramItemView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ProgramItemView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ private void initIfNeeded() {
+ if (sVisibleThreshold != 0) {
+ return;
+ }
+ Resources res = getContext().getResources();
+
+ sVisibleThreshold = res.getDimensionPixelOffset(
+ R.dimen.program_guide_table_item_visible_threshold);
+ sMinProgramDisplayDurationPixels = res.getDimensionPixelOffset(
+ R.dimen.program_guide_table_item_min_program_display_width);
+
+ sItemPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_item_padding);
+
+ ColorStateList programTitleColor = ColorStateList.valueOf(res.getColor(
+ R.color.program_guide_table_item_program_title_text_color));
+ ColorStateList grayedOutProgramTitleColor = res.getColorStateList(
+ R.color.program_guide_table_item_grayed_out_program_text_color);
+ ColorStateList episodeTitleColor = ColorStateList.valueOf(res.getColor(
+ R.color.program_guide_table_item_program_episode_title_text_color));
+ ColorStateList grayedOutEpisodeTitleColor = ColorStateList.valueOf(res.getColor(
+ R.color.program_guide_table_item_grayed_out_program_episode_title_text_color));
+ 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
+ protected void onFinishInflate() {
+ initIfNeeded();
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ if (mTableEntry != null) {
+ int states[] = super.onCreateDrawableState(extraSpace
+ + STATE_CURRENT_PROGRAM.length + STATE_TOO_WIDE.length);
+ if (mTableEntry.isCurrentProgram()) {
+ mergeDrawableStates(states, STATE_CURRENT_PROGRAM);
+ }
+ if (mTableEntry.getWidth() > mMaxWidthForRipple) {
+ mergeDrawableStates(states, STATE_TOO_WIDE);
+ }
+ return states;
+ }
+ return super.onCreateDrawableState(extraSpace);
+ }
+
+ public TableEntry getTableEntry() {
+ return mTableEntry;
+ }
+
+ public void onBind(TableEntry entry, ProgramListAdapter adapter) {
+ mTableEntry = entry;
+ setOnClickListener(ON_CLICKED);
+ setOnFocusChangeListener(ON_FOCUS_CHANGED);
+ ProgramManager programManager = adapter.getProgramManager();
+
+ ViewGroup.LayoutParams layoutParams = getLayoutParams();
+ layoutParams.width = entry.getWidth();
+ setLayoutParams(layoutParams);
+
+ String title = entry.program != null ? entry.program.getTitle() : null;
+ String episode = entry.program != null ?
+ entry.program.getEpisodeDisplayTitle(getContext()) : null;
+
+ TextAppearanceSpan titleStyle = sGrayedOutProgramTitleStyle;
+ TextAppearanceSpan episodeStyle = sGrayedOutEpisodeTitleStyle;
+
+ if (entry.getWidth() < sVisibleThreshold) {
+ setText(null);
+ } else {
+ if (entry.isGap()) {
+ if (entry.isBlocked()) {
+ title = adapter.getBlockedProgramTitle();
+ } else {
+ title = adapter.getNoInfoProgramTitle();
+ }
+ episode = null;
+ } else if (entry.hasGenre(programManager.getSelectedGenreId())) {
+ titleStyle = sProgramTitleStyle;
+ episodeStyle = sEpisodeTitleStyle;
+ }
+
+ SpannableStringBuilder description = new SpannableStringBuilder();
+ description.append(title);
+ if (!TextUtils.isEmpty(episode)) {
+ description.append('\n');
+
+ // Add a 'zero-width joiner'/ZWJ in order to ensure we have the same line height for
+ // all lines. This is a non-printing character so it will not change the horizontal
+ // spacing however it will affect the line height. As we ensure the ZWJ has the same
+ // text style as the title it will make sure the line height is consistent.
+ description.append('\u200D');
+
+ int middle = description.length();
+ description.append(episode);
+
+ description.setSpan(titleStyle, 0, middle, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ description.setSpan(episodeStyle, middle, description.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else {
+ description.setSpan(titleStyle, 0, description.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ setText(description);
+ }
+
+ int start = GuideUtils.convertMillisToPixel(entry.entryStartUtcMillis);
+ int guideStart = GuideUtils.convertMillisToPixel(programManager.getFromUtcMillis());
+ layoutVisibleArea(guideStart - start);
+
+ // Maximum width for us to use a ripple
+ mMaxWidthForRipple = GuideUtils.convertMillisToPixel(
+ programManager.getFromUtcMillis(), programManager.getToUtcMillis());
+ }
+
+ /**
+ * 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.
+ *
+ * @param offset Offset of the start position from the enclosing view's start position.
+ */
+ public void layoutVisibleArea(int offset) {
+ int width = mTableEntry.getWidth();
+ int startPadding = Math.max(0, offset);
+ if (startPadding > 0 && width - startPadding < sMinProgramDisplayDurationPixels) {
+ startPadding = Math.max(0, width - sMinProgramDisplayDurationPixels);
+ }
+
+ if (startPadding + sItemPadding != getPaddingStart()) {
+ mPreventParentRelayout = true; // The size of this view is kept, no need to tell parent.
+ setPaddingRelative(startPadding + sItemPadding, 0, sItemPadding, 0);
+ mPreventParentRelayout = false;
+ }
+ }
+
+ public void onUnbind() {
+ if (getHandler() != null) {
+ getHandler().removeCallbacks(mUpdateFocus);
+ }
+
+ setTag(null);
+ setOnFocusChangeListener(null);
+ setOnClickListener(null);
+ }
+
+ private static int getProgress(long start, long end) {
+ long currentTime = System.currentTimeMillis();
+ if (currentTime <= start) {
+ return 0;
+ } else if (currentTime >= end) {
+ return MAX_PROGRESS;
+ }
+ return (int) (((currentTime - start) * MAX_PROGRESS) / (end - start));
+ }
+
+ private static void setProgress(Drawable drawable, int id, int progress) {
+ if (drawable instanceof StateListDrawable) {
+ StateListDrawable stateDrawable = (StateListDrawable) drawable;
+ for (int i = 0; i < getStateCount(stateDrawable); ++i) {
+ setProgress(getStateDrawable(stateDrawable, i), id, progress);
+ }
+ } else if (drawable instanceof LayerDrawable) {
+ LayerDrawable layerDrawable = (LayerDrawable) drawable;
+ for (int i = 0; i < layerDrawable.getNumberOfLayers(); ++i) {
+ setProgress(layerDrawable.getDrawable(i), id, progress);
+ if (layerDrawable.getId(i) == id) {
+ layerDrawable.getDrawable(i).setLevel(progress);
+ }
+ }
+ }
+ }
+
+ private static int getStateCount(StateListDrawable stateListDrawable) {
+ try {
+ Object stateCount = StateListDrawable.class.getDeclaredMethod("getStateCount")
+ .invoke(stateListDrawable);
+ return (int) stateCount;
+ } catch (NoSuchMethodException|IllegalAccessException|IllegalArgumentException
+ |InvocationTargetException e) {
+ Log.e(TAG, "Failed to call StateListDrawable.getStateCount()", e);
+ return 0;
+ }
+ }
+
+ private static Drawable getStateDrawable(StateListDrawable stateListDrawable, int index) {
+ try {
+ Object drawable = StateListDrawable.class
+ .getDeclaredMethod("getStateDrawable", Integer.TYPE)
+ .invoke(stateListDrawable, index);
+ return (Drawable) drawable;
+ } catch (NoSuchMethodException|IllegalAccessException|IllegalArgumentException
+ |InvocationTargetException e) {
+ Log.e(TAG, "Failed to call StateListDrawable.getStateDrawable(" + index + ")", e);
+ return null;
+ }
+ }
+
+ @Override
+ public void requestLayout() {
+ if (mPreventParentRelayout) {
+ // Trivial layout, no need to tell parent.
+ forceLayout();
+ } else {
+ super.requestLayout();
+ }
+ }
+}
diff --git a/src/com/android/tv/guide/ProgramListAdapter.java b/src/com/android/tv/guide/ProgramListAdapter.java
new file mode 100644
index 00000000..88ba435e
--- /dev/null
+++ b/src/com/android/tv/guide/ProgramListAdapter.java
@@ -0,0 +1,131 @@
+/*
+ * 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.guide;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v7.widget.RecyclerView;
+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;
+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<ProgramListAdapter.ProgramViewHolder> implements
+ TableEntriesUpdatedListener {
+ private static final String TAG = "ProgramListAdapter";
+ private static final boolean DEBUG = false;
+
+ private final String mNoInfoProgramTitle;
+ private final String mBlockedProgramTitle;
+
+ private final ProgramManager mProgramManager;
+ private final int mChannelIndex;
+
+ private long mChannelId;
+
+ public ProgramListAdapter(Context context, ProgramManager programManager,
+ int channelIndex) {
+ Resources res = context.getResources();
+ mNoInfoProgramTitle = res.getString(
+ R.string.program_title_for_no_information);
+ mBlockedProgramTitle = res.getString(
+ R.string.program_title_for_blocked_channel);
+
+ mProgramManager = programManager;
+ mChannelIndex = channelIndex;
+ onTableEntriesUpdated();
+ }
+
+ @Override
+ public void onTableEntriesUpdated() {
+ Channel channel = mProgramManager.getChannel(mChannelIndex);
+ if (channel == null) {
+ // The channel has just been removed. Do nothing.
+ } else {
+ mChannelId = channel.getId();
+ if (DEBUG) Log.d(TAG, "update for channel " + mChannelId);
+ notifyDataSetChanged();
+ }
+ }
+
+ public ProgramManager getProgramManager() {
+ return mProgramManager;
+ }
+
+ public String getNoInfoProgramTitle() {
+ return mNoInfoProgramTitle;
+ }
+
+ public String getBlockedProgramTitle() {
+ return mBlockedProgramTitle;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mProgramManager.getTableEntryCount(mChannelId);
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return R.layout.program_guide_table_item;
+ }
+
+ @Override
+ public void onBindViewHolder(ProgramViewHolder holder, int position) {
+ holder.onBind(mProgramManager.getTableEntry(mChannelId, position), this);
+ }
+
+ @Override
+ public void onViewRecycled(ProgramViewHolder holder) {
+ holder.onUnbind();
+ }
+
+ @Override
+ public ProgramViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View itemView = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
+ return new ProgramViewHolder(itemView);
+ }
+
+ public static class ProgramViewHolder extends RecyclerView.ViewHolder {
+ // Should be called from main thread.
+ public ProgramViewHolder(View itemView) {
+ super(itemView);
+ }
+
+ public void onBind(TableEntry entry, ProgramListAdapter adapter) {
+ if (DEBUG) {
+ Log.d(TAG, "onBind. View = " + itemView + ", Entry = " + entry);
+ }
+
+ ((ProgramItemView) itemView).onBind(entry, adapter);
+ }
+
+ public void onUnbind() {
+ ((ProgramItemView) itemView).onUnbind();
+ }
+ }
+}
diff --git a/src/com/android/tv/guide/ProgramManager.java b/src/com/android/tv/guide/ProgramManager.java
new file mode 100644
index 00000000..3310e33e
--- /dev/null
+++ b/src/com/android/tv/guide/ProgramManager.java
@@ -0,0 +1,603 @@
+/*
+ * 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.guide;
+
+import android.util.Log;
+
+import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.GenreItems;
+import com.android.tv.data.Program;
+import com.android.tv.data.ProgramDataManager;
+import com.android.tv.util.TvInputManagerHelper;
+import com.android.tv.util.Utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages the channels and programs for the program guide.
+ */
+public class ProgramManager {
+ private static final String TAG = "ProgramManager";
+ private static final boolean DEBUG = false;
+
+ /**
+ * If the first entry's visible duration is shorter than this value, we clip the entry out.
+ * Note: If this value is larger than 1 min, it could cause mismatches between the entry's
+ * position and detailed view's time range.
+ */
+ static final long FIRST_ENTRY_MIN_DURATION = TimeUnit.MINUTES.toMillis(1);
+
+ private static final long INVALID_ID = -1;
+
+ private final TvInputManagerHelper mTvInputManagerHelper;
+ private final ChannelDataManager mChannelDataManager;
+ private final ProgramDataManager mProgramDataManager;
+
+ private long mStartUtcMillis;
+ private long mEndUtcMillis;
+ private long mFromUtcMillis;
+ private long mToUtcMillis;
+ private Program mSelectedProgram;
+
+ /**
+ * 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 static class TableEntry {
+ /** Channel ID which this entry is included. */
+ public final long channelId;
+
+ /** Program corresponding to the entry. {@code null} means that this entry is a gap. */
+ public final Program program;
+
+ /** Start time of entry in UTC milliseconds. */
+ public final long entryStartUtcMillis;
+
+ /** End time of entry in UTC milliseconds */
+ public final long entryEndUtcMillis;
+
+ private final boolean mIsBlocked;
+
+ private TableEntry(long startUtcMillis, long endUtcMillis) {
+ this(INVALID_ID, null, startUtcMillis, endUtcMillis, false);
+ }
+
+ private TableEntry(long channelId, long startUtcMillis, long endUtcMillis) {
+ this(channelId, null, startUtcMillis, endUtcMillis, false);
+ }
+
+ private TableEntry(long channelId, long startUtcMillis, long endUtcMillis,
+ boolean blocked) {
+ this(channelId, null, startUtcMillis, endUtcMillis, blocked);
+ }
+
+ private TableEntry(long channelId, Program program,
+ long entryStartUtcMillis, long entryEndUtcMillis) {
+ this(channelId, program, entryStartUtcMillis, entryEndUtcMillis, false);
+ }
+
+ private TableEntry(long channelId, Program program,
+ long entryStartUtcMillis, long entryEndUtcMillis, boolean isBlocked) {
+ this.channelId = channelId;
+ this.program = program;
+ this.entryStartUtcMillis = entryStartUtcMillis;
+ this.entryEndUtcMillis = entryEndUtcMillis;
+ mIsBlocked = isBlocked;
+ }
+
+ /**
+ * Returns true if this is a gap.
+ */
+ public boolean isGap() {
+ return !Program.isValid(program);
+ }
+
+ /**
+ * Returns true if this channel is blocked.
+ */
+ public boolean isBlocked() {
+ return mIsBlocked;
+ }
+
+ /**
+ * Returns true if this program is on the air.
+ */
+ public boolean isCurrentProgram() {
+ long current = System.currentTimeMillis();
+ return entryStartUtcMillis <= current && entryEndUtcMillis > 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<Channel> mChannels = new ArrayList<>();
+ private final Map<Long, List<TableEntry>> mChannelIdEntriesMap = new HashMap<>();
+ private final List<List<Channel>> mGenreChannelList = new ArrayList<>();
+ private final List<Integer> mFilteredGenreIds = new ArrayList<>();
+
+ // Position of selected genre to filter channel list.
+ private int mSelectedGenreId = GenreItems.ID_ALL_CHANNELS;
+ // Channel list after applying genre filter.
+ // Should be matched with mSelectedGenreId always.
+ private List<Channel> mFilteredChannels = mChannels;
+
+ private final List<Listener> mListeners = new ArrayList<>();
+ private final List<TableEntriesUpdatedListener>
+ mTableEntriesUpdatedListeners = new ArrayList<>();
+
+ public ProgramManager(TvInputManagerHelper tvInputManagerHelper,
+ ChannelDataManager channelDataManager,
+ ProgramDataManager programDataManager) {
+ mTvInputManagerHelper = tvInputManagerHelper;
+ mChannelDataManager = channelDataManager;
+ mChannelDataManager.addListener(new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ updateChannels(true);
+ }
+
+ @Override
+ public void onChannelListUpdated() {
+ updateChannels(true);
+ }
+
+ @Override
+ public void onChannelBrowsableChanged() {
+ updateChannels(true);
+ }
+ });
+
+ mProgramDataManager = programDataManager;
+ mProgramDataManager.addListener(new ProgramDataManager.Listener() {
+ @Override
+ public void onProgramUpdated() {
+ updateTableEntries(true, true);
+ }
+ });
+ }
+
+ public void programGuideVisibilityChanged(boolean visible) {
+ mProgramDataManager.setPauseProgramUpdate(visible);
+ }
+
+ /**
+ * Add a {@link Listener}.
+ */
+ public void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Register a listener to be invoked when table entries are updated.
+ */
+ public void addTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) {
+ mTableEntriesUpdatedListeners.add(listener);
+ }
+
+ /**
+ * Remove a {@link Listener}.
+ */
+ public void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Remove a previously installed table entries update listener.
+ */
+ public void removeTableEntriesUpdatedListener(TableEntriesUpdatedListener listener) {
+ mTableEntriesUpdatedListeners.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<Channel>());
+ }
+ 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) {
+ if (genreId == mSelectedGenreId) {
+ return;
+ }
+ mFilteredChannels = mGenreChannelList.get(genreId);
+ mSelectedGenreId = genreId;
+ if (DEBUG) {
+ Log.d(TAG, "resetChannelListWithGenre: " + GenreItems.getCanonicalGenre(genreId)
+ + " has " + mFilteredChannels.size() + " channels out of " + mChannels.size());
+ }
+ if (mGenreChannelList.get(mSelectedGenreId) == null) {
+ throw new IllegalStateException("Genre filter isn't ready.");
+ }
+ notifyChannelsUpdated();
+ }
+
+ /**
+ * Returns list genre ID's which has a channel.
+ */
+ public List<Integer> getFilteredGenreIds() {
+ return mFilteredGenreIds;
+ }
+
+ public int getSelectedGenreId() {
+ return mSelectedGenreId;
+ }
+
+ // Note that This can be happens only if program guide isn't shown
+ // because an user has to select channels as browsable through UI.
+ private void updateChannels(boolean notify) {
+ if (DEBUG) Log.d(TAG, "updateChannels");
+ mChannels = mChannelDataManager.getBrowsableChannelList();
+ mSelectedGenreId = GenreItems.ID_ALL_CHANNELS;
+ mFilteredChannels = mChannels;
+ if (notify) {
+ notifyChannelsUpdated();
+ }
+ updateTableEntries(notify, false);
+ }
+
+ private void updateTableEntries(boolean notify, boolean clear) {
+ if (clear) {
+ mChannelIdEntriesMap.clear();
+ }
+ 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
+ // getParentalControlSettings once.
+ List<TableEntry> entries = createProgramEntries(channelId, parentalControlsEnabled);
+ mChannelIdEntriesMap.put(channelId, entries);
+
+ int size = entries.size();
+ if (DEBUG) {
+ Log.d(TAG, "Programs are loaded for channel " + channel.getId()
+ + ", loaded size = " + size);
+ }
+ if (size == 0) {
+ continue;
+ }
+ TableEntry lastEntry = entries.get(size - 1);
+ if (mEndUtcMillis < lastEntry.entryEndUtcMillis
+ && lastEntry.entryEndUtcMillis != Long.MAX_VALUE) {
+ mEndUtcMillis = lastEntry.entryEndUtcMillis;
+ }
+ }
+ if (mEndUtcMillis > mStartUtcMillis) {
+ for (Channel channel : mChannels) {
+ long channelId = channel.getId();
+ List<TableEntry> entries = mChannelIdEntriesMap.get(channelId);
+ if (entries.isEmpty()) {
+ entries.add(new TableEntry(channelId, mStartUtcMillis, mEndUtcMillis));
+ } else {
+ TableEntry lastEntry = entries.get(entries.size() - 1);
+ if (mEndUtcMillis > lastEntry.entryEndUtcMillis) {
+ 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.entryStartUtcMillis, mEndUtcMillis,
+ lastEntry.mIsBlocked));
+ }
+ }
+ }
+ }
+
+ if (notify) {
+ notifyTableEntriesUpdated();
+ }
+ buildGenreFilters();
+ }
+
+ private void notifyGenresUpdated() {
+ for (Listener listener : mListeners) {
+ listener.onGenresUpdated();
+ }
+ }
+
+ private void notifyChannelsUpdated() {
+ for (Listener listener : mListeners) {
+ listener.onChannelsUpdated();
+ }
+ }
+
+ private void notifyTimeRangeUpdated() {
+ for (Listener listener : mListeners) {
+ listener.onTimeRangeUpdated();
+ }
+ }
+
+ private void notifyTableEntriesUpdated() {
+ for (TableEntriesUpdatedListener listener : mTableEntriesUpdatedListeners) {
+ listener.onTableEntriesUpdated();
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Set the initial time range to manage.
+ */
+ public void setInitialTimeRange(long startUtcMillis, long endUtcMillis) {
+ mStartUtcMillis = startUtcMillis;
+ if (endUtcMillis > mEndUtcMillis) {
+ mEndUtcMillis = endUtcMillis;
+ }
+ updateChannels(true);
+
+ mProgramDataManager.setPrefetchTimeRange(mStartUtcMillis);
+
+ // Need to clear when the UI starts.
+ updateTableEntries(true, true);
+ setTimeRange(startUtcMillis, endUtcMillis);
+ }
+
+ private void setTimeRange(long fromUtcMillis, long toUtcMillis) {
+ if (DEBUG) {
+ Log.d(TAG, "setTimeRange. {FromTime="
+ + Utils.toTimeString(fromUtcMillis) + ", ToTime="
+ + Utils.toTimeString(toUtcMillis) + "}");
+ }
+ if (mFromUtcMillis != fromUtcMillis || mToUtcMillis != toUtcMillis) {
+ mFromUtcMillis = fromUtcMillis;
+ mToUtcMillis = toUtcMillis;
+ notifyTimeRangeUpdated();
+ }
+ }
+
+ /**
+ * 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 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<TableEntry> createProgramEntries(long channelId, boolean parentalControlsEnabled) {
+ List<TableEntry> entries = new ArrayList<>();
+ boolean channelLocked = parentalControlsEnabled
+ && mChannelDataManager.getChannel(channelId).isLocked();
+ if (channelLocked) {
+ entries.add(new TableEntry(channelId, mStartUtcMillis, Long.MAX_VALUE, true));
+ } else {
+ long lastProgramEndTime = mStartUtcMillis;
+ List<Program> programs = mProgramDataManager.getPrograms(channelId, mStartUtcMillis);
+ for (Program program : programs) {
+ if (program.getChannelId() == INVALID_ID) {
+ // Dummy program.
+ continue;
+ }
+ 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));
+ lastProgramEndTime = programStartTime;
+ }
+ if (programEndTime > lastProgramEndTime) {
+ entries.add(new TableEntry(channelId, program, lastProgramEndTime,
+ programEndTime));
+ lastProgramEndTime = programEndTime;
+ }
+ }
+ }
+
+ if (entries.size() > 1) {
+ TableEntry secondEntry = entries.get(1);
+ if (secondEntry.entryStartUtcMillis < mStartUtcMillis + FIRST_ENTRY_MIN_DURATION) {
+ // 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,
+ mStartUtcMillis, secondEntry.entryEndUtcMillis));
+ }
+ }
+ return entries;
+ }
+
+ /**
+ * Get the currently selected channel.
+ */
+ public Channel getSelectedChannel() {
+ return mChannelDataManager.getChannel(mSelectedProgram.getChannelId());
+ }
+
+ /**
+ * Get the currently selected program.
+ */
+ public Program getSelectedProgram() {
+ return mSelectedProgram;
+ }
+
+ public interface Listener {
+ void onGenresUpdated();
+ void onChannelsUpdated();
+ void onTimeRangeUpdated();
+ }
+
+ public interface TableEntriesUpdatedListener {
+ void onTableEntriesUpdated();
+ }
+
+ public static class ListenerAdapter implements Listener {
+ @Override
+ public void onGenresUpdated() { }
+
+ @Override
+ public void onChannelsUpdated() { }
+
+ @Override
+ public void onTimeRangeUpdated() { }
+ }
+
+ /**
+ * Shifts the time range by the given time. Also makes ProgramGuide scroll the views.
+ */
+ public 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.
+ */
+ public long getShiftedTime() {
+ return mFromUtcMillis - mStartUtcMillis;
+ }
+
+ /**
+ * Returns the start time set by {@link #setInitialTimeRange}.
+ */
+ public long getStartTime() {
+ return mStartUtcMillis;
+ }
+
+ /**
+ * Returns the program index of the program at {@code time}.
+ */
+ public int getProgramIndex(long channelId, long time) {
+ List<TableEntry> 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;
+ }
+}
diff --git a/src/com/android/tv/guide/ProgramRow.java b/src/com/android/tv/guide/ProgramRow.java
new file mode 100644
index 00000000..4f38b879
--- /dev/null
+++ b/src/com/android/tv/guide/ProgramRow.java
@@ -0,0 +1,321 @@
+/*
+ * 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.guide;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.v7.widget.LinearLayoutManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+
+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 {
+ private static final String TAG = "ProgramRow";
+ private static final boolean DEBUG = false;
+
+ private static final long ONE_HOUR_MILLIS = TimeUnit.HOURS.toMillis(1);
+ private static final long HALF_HOUR_MILLIS = ONE_HOUR_MILLIS / 2;
+
+ private ProgramManager mProgramManager;
+
+ private boolean mKeepFocusToCurrentProgram;
+ private ChildFocusListener mChildFocusListener;
+
+ interface ChildFocusListener {
+ /**
+ * Is called after focus is moved. Only children to {@code ProgramRow} will be passed.
+ * See {@code ProgramRow#setChildFocusListener(ChildFocusListener)}.
+ */
+ void onChildFocus(View oldFocus, View newFocus);
+ }
+
+ private final ViewTreeObserver.OnGlobalFocusChangeListener mGlobalFocusChangeListener =
+ new ViewTreeObserver.OnGlobalFocusChangeListener() {
+ @Override
+ public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+ updateCurrentFocus(oldFocus, newFocus);
+ }
+ };
+
+ /**
+ * Used only for debugging.
+ */
+ private Channel mChannel;
+
+ public ProgramRow(Context context) {
+ this(context, null);
+ }
+
+ public ProgramRow(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ProgramRow(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Registers a listener focus events occurring on children to the {@code ProgramRow}.
+ */
+ public void setChildFocusListener(ChildFocusListener childFocusListener) {
+ mChildFocusListener = childFocusListener;
+ }
+
+ @Override
+ public void onScrolled(int dx, int dy) {
+ super.onScrolled(dx, dy);
+ int childCount = getChildCount();
+ if (DEBUG) {
+ Log.d(TAG, "onScrolled by " + dx);
+ Log.d(TAG, "channelId=" + mChannel.getId() + ", childCount=" + childCount);
+ Log.d(TAG, "ProgramRow {" + Utils.toRectString(this) + "}");
+ }
+ for (int i = 0; i < childCount; ++i) {
+ ProgramItemView child = (ProgramItemView) getChildAt(i);
+ if (getLeft() <= child.getRight() && child.getLeft() <= getRight()) {
+ child.layoutVisibleArea(getLayoutDirection() == LAYOUT_DIRECTION_LTR
+ ? getLeft() - child.getLeft() : child.getRight() - getRight());
+ }
+ }
+ }
+
+ /**
+ * Moves focus to the current program.
+ */
+ public void focusCurrentProgram() {
+ View currentProgram = getCurrentProgramView();
+ if (currentProgram == null) {
+ currentProgram = getChildAt(0);
+ }
+ updateCurrentFocus(null, currentProgram);
+ }
+
+ private void updateCurrentFocus(View oldFocus, View newFocus) {
+ if (mChildFocusListener == null) {
+ return;
+ }
+
+ mChildFocusListener.onChildFocus(isChild(oldFocus) ? oldFocus : null,
+ isChild(newFocus) ? newFocus : null);
+ }
+
+ private boolean isChild(View view) {
+ if (view == null) {
+ return false;
+ }
+
+ for (ViewParent p = view.getParent(); p != null; p = p.getParent()) {
+ if (p == this) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // 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;
+ }
+
+ // 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;
+ }
+
+ @Override
+ public View focusSearch(View focused, int direction) {
+ TableEntry focusedEntry = ((ProgramItemView) focused).getTableEntry();
+ long fromMillis = mProgramManager.getFromUtcMillis();
+ long toMillis = mProgramManager.getToUtcMillis();
+
+ 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));
+ return focused;
+ }
+ } else if (isDirectionEnd(direction) || direction == View.FOCUS_FORWARD) {
+ if (focusedEntry.entryEndUtcMillis > toMillis + ONE_HOUR_MILLIS) {
+ // The current entry ends outside of the view; Scroll to the right.
+ scrollByTime(ONE_HOUR_MILLIS);
+ return focused;
+ }
+ }
+
+ View target = super.focusSearch(focused, direction);
+ if (!(target instanceof ProgramItemView)) {
+ if (isDirectionEnd(direction) || direction == View.FOCUS_FORWARD) {
+ if (focusedEntry.entryEndUtcMillis != toMillis) {
+ // The focused entry is the last entry; Align to the right edge.
+ scrollByTime(focusedEntry.entryEndUtcMillis - mProgramManager.getToUtcMillis());
+ return focused;
+ }
+ }
+ return target;
+ }
+
+ TableEntry targetEntry = ((ProgramItemView) target).getTableEntry();
+
+ if (isDirectionStart(direction) || direction == View.FOCUS_BACKWARD) {
+ 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));
+ }
+ } 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));
+ }
+ }
+
+ return target;
+ }
+
+ private void scrollByTime(long timeToScroll) {
+ if (DEBUG) {
+ Log.d(TAG, "scrollByTime(timeToScroll="
+ + TimeUnit.MILLISECONDS.toMinutes(timeToScroll) + "min)");
+ }
+ mProgramManager.shiftTime(timeToScroll);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ getViewTreeObserver().addOnGlobalFocusChangeListener(mGlobalFocusChangeListener);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ getViewTreeObserver().removeOnGlobalFocusChangeListener(mGlobalFocusChangeListener);
+ }
+
+ @Override
+ public void onChildDetachedFromWindow(View child) {
+ if (child.hasFocus()) {
+ // Focused view can be detached only if it's updated.
+ TableEntry entry = ((ProgramItemView) child).getTableEntry();
+ if (entry.isCurrentProgram()) {
+ if (DEBUG) Log.d(TAG, "Keep focus to the current program");
+ // Current program is visible in the guide.
+ // Updated entries including current program's will be attached again soon
+ // so give focus back in onChildAttachedToWindow().
+ mKeepFocusToCurrentProgram = true;
+ }
+ // TODO: Try to keep focus for non-current program.
+ }
+ super.onChildDetachedFromWindow(child);
+ }
+
+ @Override
+ public void onChildAttachedToWindow(View child) {
+ super.onChildAttachedToWindow(child);
+ if (mKeepFocusToCurrentProgram) {
+ TableEntry entry = ((ProgramItemView) child).getTableEntry();
+ if (entry.isCurrentProgram()) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ requestFocus();
+ }
+ });
+ mKeepFocusToCurrentProgram = false;
+ }
+ }
+ }
+
+ @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();
+ }
+
+ if (DEBUG) Log.d(TAG, "onRequestFocusInDescendants");
+
+ boolean result = super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+ if (!result) {
+ // The default focus search logic of LeanbackLibrary is sometimes failed.
+ // As a fallback solution, we request focus to the first focusable view.
+ for (int i = 0; i < getChildCount(); ++i) {
+ View child = getChildAt(i);
+ if (child.isShown() && child.hasFocusable()) {
+ return child.requestFocus();
+ }
+ }
+ }
+ return result;
+ }
+
+ private View getCurrentProgramView() {
+ for (int i = 0; i < getChildCount(); ++i) {
+ TableEntry entry = ((ProgramItemView) getChildAt(i)).getTableEntry();
+ if (entry.isCurrentProgram()) {
+ return getChildAt(i);
+ }
+ }
+ return null;
+ }
+
+ public void setChannel(Channel channel) {
+ mChannel = channel;
+ }
+
+ /**
+ * Sets the instance of {@link ProgramManager}
+ */
+ public void setProgramManager(ProgramManager programManager) {
+ mProgramManager = programManager;
+ }
+
+ /**
+ * 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.getProgramIndex(
+ 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);
+ }
+ }
+}
diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java
new file mode 100644
index 00000000..df764668
--- /dev/null
+++ b/src/com/android/tv/guide/ProgramTableAdapter.java
@@ -0,0 +1,509 @@
+/*
+ * 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.guide;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.media.tv.TvContentRating;
+import android.os.Handler;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.RecycledViewPool;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.tv.R;
+import com.android.tv.data.Channel;
+import com.android.tv.data.Program;
+import com.android.tv.guide.ProgramManager.TableEntriesUpdatedListener;
+import com.android.tv.parental.ParentalControlSettings;
+import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter;
+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.
+ */
+public class ProgramTableAdapter extends
+ RecyclerView.Adapter<ProgramTableAdapter.ProgramRowHolder> {
+ private static final String TAG = "ProgramTableAdapter";
+ private static final boolean DEBUG = false;
+
+ private final Context mContext;
+ private final TvInputManagerHelper mTvInputManagerHelper;
+ private final ProgramManager mProgramManager;
+ private final ProgramGuide mProgramGuide;
+ private final Handler mHandler = new Handler();
+ private final List<ProgramListAdapter> mProgramListAdapters = new ArrayList<>();
+ private final RecycledViewPool mRecycledViewPool;
+
+ private final int mChannelLogoWidth;
+ private final int mChannelLogoHeight;
+ private final int mImageWidth;
+ private final int mImageHeight;
+ private final String mProgramTitleForNoInformation;
+ private final String mProgramTitleForBlockedChannel;
+ private final int mChannelTextColor;
+ private final int mChannelBlockedTextColor;
+ private final int mDetailTextColor;
+ private final int mDetailGrayedTextColor;
+ private final int mAnimationDuration;
+ private final int mDetailPadding;
+ private final TextAppearanceSpan mEpisodeTitleStyle;
+
+ public ProgramTableAdapter(Context context, TvInputManagerHelper tvInputManagerHelper,
+ ProgramManager programManager, ProgramGuide programGuide) {
+ mContext = context;
+ mTvInputManagerHelper = tvInputManagerHelper;
+ mProgramManager = programManager;
+ mProgramGuide = programGuide;
+
+ Resources res = context.getResources();
+ mChannelLogoWidth = res.getDimensionPixelSize(
+ R.dimen.program_guide_table_header_column_channel_logo_width);
+ mChannelLogoHeight = res.getDimensionPixelSize(
+ R.dimen.program_guide_table_header_column_channel_logo_height);
+ mImageWidth = res.getDimensionPixelSize(
+ R.dimen.program_guide_table_detail_image_width);
+ mImageHeight = res.getDimensionPixelSize(
+ R.dimen.program_guide_table_detail_image_height);
+ mProgramTitleForNoInformation = res.getString(
+ R.string.program_title_for_no_information);
+ mProgramTitleForBlockedChannel = res.getString(
+ R.string.program_title_for_blocked_channel);
+ mChannelTextColor = res.getColor(
+ R.color.program_guide_table_header_column_channel_number_text_color);
+ mChannelBlockedTextColor = res.getColor(R.color
+ .program_guide_table_header_column_channel_number_blocked_text_color);
+ mDetailTextColor = res.getColor(
+ R.color.program_guide_table_detail_title_text_color);
+ mDetailGrayedTextColor = res.getColor(
+ R.color.program_guide_table_detail_title_grayed_text_color);
+ mAnimationDuration =
+ res.getInteger(R.integer.program_guide_table_detail_fade_anim_duration);
+ mDetailPadding = res.getDimensionPixelOffset(
+ R.dimen.program_guide_table_detail_padding);
+
+ int episodeTitleSize = res.getDimensionPixelSize(
+ R.dimen.program_guide_table_detail_episode_title_text_size);
+ ColorStateList episodeTitleColor = ColorStateList.valueOf(
+ res.getColor(R.color.program_guide_table_detail_episode_title_text_color));
+ mEpisodeTitleStyle = new TextAppearanceSpan(null, 0, episodeTitleSize,
+ episodeTitleColor, null);
+
+ 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() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ update();
+ }
+ });
+ }
+ });
+ update();
+ }
+
+ private void update() {
+ if (DEBUG) Log.d(TAG, "update " + mProgramManager.getChannelCount() + " channels");
+ for (TableEntriesUpdatedListener listener : mProgramListAdapters) {
+ mProgramManager.removeTableEntriesUpdatedListener(listener);
+ }
+ mProgramListAdapters.clear();
+ for (int i = 0; i < mProgramManager.getChannelCount(); i++) {
+ ProgramListAdapter listAdapter = new ProgramListAdapter(mContext, mProgramManager, i);
+ mProgramManager.addTableEntriesUpdatedListener(listAdapter);
+ mProgramListAdapters.add(listAdapter);
+ }
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getItemCount() {
+ return mProgramListAdapters.size();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return R.layout.program_guide_table_row;
+ }
+
+ @Override
+ public void onBindViewHolder(ProgramRowHolder holder, int position) {
+ holder.onBind(position);
+ }
+
+ @Override
+ public ProgramRowHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View itemView = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
+ ProgramRow programRow = (ProgramRow) itemView.findViewById(R.id.row);
+ programRow.setRecycledViewPool(mRecycledViewPool);
+ return new ProgramRowHolder(itemView);
+ }
+
+ // TODO: make it static
+ public class ProgramRowHolder extends RecyclerView.ViewHolder implements
+ ProgramRow.ChildFocusListener, Program.LoadPosterArtCallback,
+ Channel.LoadImageCallback {
+
+ private final ViewGroup mContainer;
+ private final ProgramRow mProgramRow;
+ private ProgramManager.TableEntry mSelectedEntry;
+ private Animator mDetailOutAnimator;
+ private Animator mDetailInAnimator;
+ private final Runnable mDetailInStarter = new Runnable() {
+ @Override
+ public void run() {
+ mProgramRow.removeOnScrollListener(mOnScrollListener);
+ if (mDetailInAnimator != null) {
+ mDetailInAnimator.start();
+ }
+ }
+ };
+
+ private final RecyclerView.OnScrollListener mOnScrollListener =
+ new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ onHorizontalScrolled();
+ }
+ };
+
+ // Members of Program Details
+ private final ViewGroup mDetailView;
+ private final ImageView mImageView;
+ private final ImageView mBlockView;
+ private final TextView mTitleView;
+ private final TextView mTimeView;
+ private final TextView mDescriptionView;
+ private final TextView mAspectRatioView;
+ private final TextView mResolutionView;
+
+ // Members of Channel Header
+ private Channel mChannel;
+ private final View mChannelHeaderView;
+ private final TextView mChannelNumberView;
+ private final TextView mChannelNameView;
+ private final ImageView mChannelLogoView;
+ private final ImageView mChannelBlockView;
+
+ public ProgramRowHolder(View itemView) {
+ super(itemView);
+
+ mContainer = (ViewGroup) itemView;
+ mProgramRow = (ProgramRow) mContainer.findViewById(R.id.row);
+
+ mDetailView = (ViewGroup) mContainer.findViewById(R.id.detail);
+ mImageView = (ImageView) mDetailView.findViewById(R.id.image);
+ mBlockView = (ImageView) mDetailView.findViewById(R.id.block);
+ mTitleView = (TextView) mDetailView.findViewById(R.id.title);
+ mTimeView = (TextView) mDetailView.findViewById(R.id.time);
+ mDescriptionView = (TextView) mDetailView.findViewById(R.id.desc);
+ mAspectRatioView = (TextView) mDetailView.findViewById(R.id.aspect_ratio);
+ mResolutionView = (TextView) mDetailView.findViewById(R.id.resolution);
+
+ mChannelHeaderView = mContainer.findViewById(R.id.header_column);
+ mChannelNumberView = (TextView) mContainer.findViewById(R.id.channel_number);
+ mChannelNameView = (TextView) mContainer.findViewById(R.id.channel_name);
+ mChannelLogoView = (ImageView) mContainer.findViewById(R.id.channel_logo);
+ mChannelBlockView = (ImageView) mContainer.findViewById(R.id.channel_block);
+ }
+
+ public void onBind(int position) {
+ onBindChannel(mProgramManager.getChannel(position));
+
+ mProgramRow.swapAdapter(mProgramListAdapters.get(position), true);
+ mProgramRow.setProgramManager(mProgramManager);
+ mProgramRow.setChannel(mProgramManager.getChannel(position));
+ mProgramRow.setChildFocusListener(this);
+ mProgramRow.resetScroll(mProgramGuide.getTimelineRowScrollOffset());
+
+ mDetailView.setVisibility(View.GONE);
+
+ // The bottom-left of the last channel header view will have a rounded corner.
+ mChannelHeaderView.setBackgroundResource((position < mProgramListAdapters.size() - 1)
+ ? R.drawable.program_guide_table_header_column_item_background
+ : R.drawable.program_guide_table_header_column_last_item_background);
+ }
+
+ private void onBindChannel(Channel channel) {
+ if (DEBUG) Log.d(TAG, "onBindChannel " + channel);
+
+ mChannel = channel;
+ if (channel == null) {
+ mChannelNumberView.setVisibility(View.GONE);
+ mChannelNameView.setVisibility(View.GONE);
+ mChannelLogoView.setVisibility(View.GONE);
+ mChannelBlockView.setVisibility(View.GONE);
+ return;
+ }
+
+ String displayNumber = channel.getDisplayNumber();
+ if (displayNumber == null) {
+ mChannelNumberView.setVisibility(View.GONE);
+ } else {
+ int size;
+ if (displayNumber.length() <= 4) {
+ size = R.dimen.program_guide_table_header_column_channel_number_large_font_size;
+ } else {
+ size = R.dimen.program_guide_table_header_column_channel_number_small_font_size;
+ }
+ mChannelNumberView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ mChannelNumberView.getContext().getResources().getDimension(size));
+ mChannelNumberView.setText(displayNumber);
+ mChannelNumberView.setVisibility(View.VISIBLE);
+ }
+ mChannelNumberView.setTextColor(isChannelLocked(channel)
+ ? mChannelBlockedTextColor : mChannelTextColor);
+
+ mChannelLogoView.setImageBitmap(null);
+ mChannelLogoView.setVisibility(View.GONE);
+ if (isChannelLocked(channel)) {
+ mChannelNameView.setVisibility(View.GONE);
+ mChannelBlockView.setVisibility(View.VISIBLE);
+ } else {
+ mChannelNameView.setText(channel.getDisplayName());
+ mChannelNameView.setVisibility(View.VISIBLE);
+ mChannelBlockView.setVisibility(View.GONE);
+
+ mChannel.loadBitmap(itemView.getContext(), Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO,
+ mChannelLogoWidth, mChannelLogoHeight, this);
+ }
+ }
+
+ @Override
+ public void onChildFocus(View oldFocus, View newFocus) {
+ if (newFocus == null) {
+ return;
+ }
+ mSelectedEntry = ((ProgramItemView) newFocus).getTableEntry();
+ if (oldFocus == null) {
+ updateDetailView();
+ return;
+ }
+
+ if (Program.isValid(mSelectedEntry.program)) {
+ Program program = mSelectedEntry.program;
+ if (getProgramBlock(program) == null) {
+ program.prefetchPosterArt(itemView.getContext(), mImageWidth, mImageHeight);
+ }
+ }
+
+ // -1 means the selection goes rightwards and 1 goes leftwards
+ int direction = oldFocus.getLeft() < newFocus.getLeft() ? -1 : 1;
+ View detailContentView = mDetailView.findViewById(R.id.detail_content);
+
+ if (mDetailInAnimator == null) {
+ mDetailOutAnimator = ObjectAnimator.ofPropertyValuesHolder(detailContentView,
+ PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f),
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_X,
+ 0f, direction * mDetailPadding));
+ mDetailOutAnimator.setDuration(mAnimationDuration);
+ mDetailOutAnimator.addListener(
+ new HardwareLayerAnimatorListenerAdapter(detailContentView) {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ super.onAnimationEnd(animator);
+ mDetailOutAnimator = null;
+ mHandler.removeCallbacks(mDetailInStarter);
+ mHandler.postDelayed(mDetailInStarter, mAnimationDuration);
+ }
+ });
+
+ mProgramRow.addOnScrollListener(mOnScrollListener);
+ mDetailOutAnimator.start();
+ } else {
+ if (mDetailInAnimator.isStarted()) {
+ mDetailInAnimator.cancel();
+ detailContentView.setAlpha(0);
+ }
+
+ mHandler.removeCallbacks(mDetailInStarter);
+ mHandler.postDelayed(mDetailInStarter, mAnimationDuration);
+ }
+
+ mDetailInAnimator = ObjectAnimator.ofPropertyValuesHolder(detailContentView,
+ PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f),
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_X,
+ direction * -mDetailPadding, 0f));
+ mDetailInAnimator.setDuration(mAnimationDuration);
+ mDetailInAnimator.addListener(
+ new HardwareLayerAnimatorListenerAdapter(detailContentView) {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ super.onAnimationStart(animator);
+ updateDetailView();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ super.onAnimationEnd(animator);
+ mDetailInAnimator = null;
+ }
+ });
+ }
+
+ private void updateDetailView() {
+ if (Program.isValid(mSelectedEntry.program)) {
+ mTitleView.setTextColor(mDetailTextColor);
+ Context context = itemView.getContext();
+ Program program = mSelectedEntry.program;
+
+ TvContentRating blockedRating = getProgramBlock(program);
+
+ mImageView.setImageBitmap(null);
+ mImageView.setVisibility(View.GONE);
+ if (blockedRating == null) {
+ program.loadPosterArt(context, mImageWidth, mImageHeight, this);
+ }
+
+ if (TextUtils.isEmpty(program.getEpisodeTitle())) {
+ mTitleView.setText(program.getTitle());
+ } else {
+ String title = program.getTitle();
+ String episodeTitle = program.getEpisodeDisplayTitle(mContext);
+ String fullTitle = title + " " + episodeTitle;
+
+ SpannableString text = new SpannableString(fullTitle);
+ text.setSpan(mEpisodeTitleStyle,
+ fullTitle.length() - episodeTitle.length(), fullTitle.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ mTitleView.setText(text);
+ }
+
+ updateTextView(mTimeView, Utils.getDurationString(context,
+ program.getStartTimeUtcMillis(),
+ program.getEndTimeUtcMillis(), false));
+
+ if (blockedRating == null) {
+ mBlockView.setVisibility(View.GONE);
+ updateTextView(mDescriptionView, program.getDescription());
+ } else {
+ mBlockView.setVisibility(View.VISIBLE);
+ updateTextView(mDescriptionView, getBlockedDescription(blockedRating));
+ }
+
+ updateTextView(mAspectRatioView, Utils.getAspectRatioString(
+ program.getVideoWidth(), program.getVideoHeight()));
+
+ int videoDefinitionLevel = Utils.getVideoDefinitionLevelFromSize(
+ program.getVideoWidth(), program.getVideoHeight());
+ updateTextView(mResolutionView, Utils.getVideoDefinitionLevelString(
+ context, videoDefinitionLevel));
+ } else {
+ mTitleView.setTextColor(mDetailGrayedTextColor);
+ if (mSelectedEntry.isBlocked()) {
+ updateTextView(mTitleView, mProgramTitleForBlockedChannel);
+ } else {
+ updateTextView(mTitleView, mProgramTitleForNoInformation);
+ }
+ mImageView.setVisibility(View.GONE);
+ mBlockView.setVisibility(View.GONE);
+ mTimeView.setVisibility(View.GONE);
+ mDescriptionView.setVisibility(View.GONE);
+ mAspectRatioView.setVisibility(View.GONE);
+ mResolutionView.setVisibility(View.GONE);
+ }
+ }
+
+ private TvContentRating getProgramBlock(Program program) {
+ ParentalControlSettings parental = mTvInputManagerHelper.getParentalControlSettings();
+ if (!parental.isParentalControlsEnabled()) {
+ return null;
+ }
+ return parental.getBlockedRating(program.getContentRatings());
+ }
+
+ private boolean isChannelLocked(Channel channel) {
+ return mTvInputManagerHelper.getParentalControlSettings().isParentalControlsEnabled()
+ && channel.isLocked();
+ }
+
+ private String getBlockedDescription(TvContentRating blockedRating) {
+ String name = mTvInputManagerHelper.getContentRatingsManager()
+ .getDisplayNameForRating(blockedRating);
+ if (TextUtils.isEmpty(name)) {
+ return mContext.getString(R.string.program_guide_content_locked);
+ } else {
+ return mContext.getString(R.string.program_guide_content_locked_format, name);
+ }
+ }
+
+ private void updateTextView(TextView textView, String text) {
+ if (!TextUtils.isEmpty(text)) {
+ textView.setVisibility(View.VISIBLE);
+ textView.setText(text);
+ } else {
+ textView.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onLoadPosterArtFinished(Program program, Bitmap posterArt) {
+ if (posterArt == null || mSelectedEntry == null || mSelectedEntry.program == null) {
+ return;
+ }
+
+ String posterArtUri = mSelectedEntry.program.getPosterArtUri();
+ if (posterArtUri == null || !posterArtUri.equals(program.getPosterArtUri())) {
+ return;
+ }
+
+ mImageView.setImageBitmap(posterArt);
+ mImageView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onLoadImageFinished(Channel channel, int type, Bitmap logo) {
+ if (logo == null || mChannel == null || mChannel.getId() != channel.getId()) {
+ return;
+ }
+
+ mChannelLogoView.setImageBitmap(logo);
+ mChannelNameView.setVisibility(View.GONE);
+ mChannelLogoView.setVisibility(View.VISIBLE);
+ }
+
+ private void onHorizontalScrolled() {
+ if (mDetailInAnimator != null) {
+ mHandler.removeCallbacks(mDetailInStarter);
+ mHandler.postDelayed(mDetailInStarter, mAnimationDuration);
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/guide/TimeListAdapter.java b/src/com/android/tv/guide/TimeListAdapter.java
new file mode 100644
index 00000000..868fed46
--- /dev/null
+++ b/src/com/android/tv/guide/TimeListAdapter.java
@@ -0,0 +1,98 @@
+/*
+ * 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.guide;
+
+import android.content.res.Resources;
+import android.support.v7.widget.RecyclerView;
+import android.text.format.DateFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.tv.R;
+
+import java.util.Date;
+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<TimeListAdapter.TimeViewHolder> {
+ private static final long TIME_UNIT_MS = TimeUnit.MINUTES.toMillis(30);
+ private static int sRowHeaderOverlapping;
+
+ // Nearest half hour at or before the start time.
+ private long mStartUtcMs;
+
+ public TimeListAdapter(Resources res) {
+ if (sRowHeaderOverlapping == 0) {
+ sRowHeaderOverlapping = Math.abs(res.getDimensionPixelOffset(
+ R.dimen.program_guide_table_header_row_overlap));
+ }
+ }
+
+ public void update(long startTimeMs) {
+ mStartUtcMs = startTimeMs;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getItemCount() {
+ return Integer.MAX_VALUE;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return R.layout.program_guide_table_header_row_item;
+ }
+
+ @Override
+ public void onBindViewHolder(TimeViewHolder holder, int position) {
+ long startTime = mStartUtcMs + position * TIME_UNIT_MS;
+ long endTime = startTime + TIME_UNIT_MS;
+
+ View itemView = holder.itemView;
+
+ TextView textView = (TextView) itemView.findViewById(R.id.time);
+ String time = DateFormat.getTimeFormat(itemView.getContext()).format(new Date(startTime));
+ textView.setText(time);
+
+ RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) itemView.getLayoutParams();
+ lp.width = GuideUtils.convertMillisToPixel(startTime, endTime);
+ if (position == 0) {
+ // Adjust width for the first entry to make the item starts from the fading edge.
+ lp.setMarginStart(sRowHeaderOverlapping - lp.width / 2);
+ } else {
+ lp.setMarginStart(0);
+ }
+ itemView.setLayoutParams(lp);
+ }
+
+ @Override
+ public TimeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View itemView = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
+ return new TimeViewHolder(itemView);
+ }
+
+ public static class TimeViewHolder extends RecyclerView.ViewHolder {
+ public TimeViewHolder(View itemView) {
+ super(itemView);
+ }
+ }
+}
diff --git a/src/com/android/tv/guide/TimelineGridView.java b/src/com/android/tv/guide/TimelineGridView.java
new file mode 100644
index 00000000..8af1c8a3
--- /dev/null
+++ b/src/com/android/tv/guide/TimelineGridView.java
@@ -0,0 +1,58 @@
+/*
+ * 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.guide;
+
+import android.content.Context;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class TimelineGridView extends RecyclerView {
+ public TimelineGridView(Context context) {
+ this(context, null);
+ }
+
+ public TimelineGridView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TimelineGridView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) {
+ @Override
+ public boolean onRequestChildFocus(RecyclerView parent, State state, View child,
+ View focused) {
+ // This disables the default scroll behavior for focus movement.
+ return true;
+ }
+ });
+
+ // RecyclerView is always focusable, however this is not desirable for us, so disable.
+ // See b/18863217 (ag/634046) for reasons to why RecyclerView is focusable.
+ setFocusable(false);
+
+ // Don't cache anything that is off screen. Normally it is good to prefetch and prepopulate
+ // off screen views in order to reduce jank, however the program guide is capable to scroll
+ // in all four directions so not only would we prefetch views in the scrolling direction
+ // but also keep views in the perpendicular direction up to date.
+ // E.g. when scrolling horizontally we would have to update rows above and below the current
+ // view port even though they are not visible.
+ setItemViewCacheSize(0);
+ }
+}
diff --git a/src/com/android/tv/guide/TimelineRow.java b/src/com/android/tv/guide/TimelineRow.java
new file mode 100644
index 00000000..891b14cd
--- /dev/null
+++ b/src/com/android/tv/guide/TimelineRow.java
@@ -0,0 +1,88 @@
+/*
+ * 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.guide;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+public class TimelineRow extends TimelineGridView {
+ private static final float FADING_EDGE_STRENGTH_START = 1.0f;
+
+ private int mScrollPosition;
+
+ public TimelineRow(Context context) {
+ this(context, null);
+ }
+
+ public TimelineRow(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TimelineRow(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void resetScroll() {
+ getLayoutManager().scrollToPosition(0);
+ }
+
+ /**
+ * Returns the current scroll position
+ */
+ public int getScrollOffset() {
+ return Math.abs(mScrollPosition);
+ }
+
+ /**
+ * Scrolls horizontally to the given position.
+ */
+ public void scrollTo(int scrollOffset, boolean smoothScroll) {
+ int dx = (scrollOffset - getScrollOffset())
+ * (getLayoutDirection() == LAYOUT_DIRECTION_LTR ? 1 : -1);
+ if (smoothScroll) {
+ smoothScrollBy(dx, 0);
+ } else {
+ scrollBy(dx, 0);
+ }
+ }
+
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ // Reset scroll
+ scrollTo(getScrollOffset(), false);
+ }
+
+ @Override
+ public void onScrolled(int dx, int dy) {
+ if (dx == 0 && dy == 0) {
+ mScrollPosition = 0;
+ } else {
+ mScrollPosition += dx;
+ }
+ }
+
+ @Override
+ protected float getLeftFadingEdgeStrength() {
+ return (getLayoutDirection() == LAYOUT_DIRECTION_LTR) ? FADING_EDGE_STRENGTH_START : 0;
+ }
+
+ @Override
+ protected float getRightFadingEdgeStrength() {
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ? FADING_EDGE_STRENGTH_START : 0;
+ }
+}
diff --git a/src/com/android/tv/input/TisTvInput.java b/src/com/android/tv/input/TisTvInput.java
deleted file mode 100644
index db77ee85..00000000
--- a/src/com/android/tv/input/TisTvInput.java
+++ /dev/null
@@ -1,95 +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.input;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ServiceInfo;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputInfo;
-import android.net.Uri;
-import android.text.TextUtils;
-
-import com.android.tv.data.ChannelMap;
-import com.android.tv.util.TvInputManagerHelper;
-import com.android.tv.util.Utils;
-
-public class TisTvInput extends TvInput {
- private final TvInputManagerHelper mInputManagerHelper;
- private final TvInputInfo mInputInfo;
- private final Context mContext;
- private final String mId;
-
- public TisTvInput(TvInputManagerHelper inputManagerHelper, TvInputInfo inputInfo,
- Context context) {
- mInputManagerHelper = inputManagerHelper;
- mInputInfo = inputInfo;
- mContext = context;
- mId = mInputInfo.getId();
- }
-
- @Override
- public String getId() {
- return mId;
- }
-
- @Override
- public String getDisplayName() {
- return Utils.getDisplayNameForInput(mContext, mInputInfo);
- }
-
- @Override
- public Intent getIntentForSetupActivity() {
- return mInputInfo.getIntentForSetupActivity();
- }
-
- @Override
- public Intent getIntentForSettingsActivity() {
- return mInputInfo.getIntentForSettingsActivity();
- }
-
- @Override
- public int getInputState() {
- return mInputManagerHelper.getInputState(mId);
- }
-
- @Override
- public boolean hasChannel(boolean browsableOnly) {
- return Utils.hasChannel(mContext, mInputInfo, browsableOnly);
- }
-
- @Override
- public ChannelMap buildChannelMap(Activity activity, long initialChannelId,
- Runnable onChannelsLoadFinished) {
- return new ChannelMap(activity, this, initialChannelId, mInputManagerHelper,
- onChannelsLoadFinished);
- }
-
- @Override
- public Uri buildChannelsUri(String genre) {
- if (genre == null) {
- return TvContract.buildChannelsUriForInput(mInputInfo.getId(), false);
- }
- return TvContract.buildChannelsUriForCanonicalGenre(mInputInfo.getId(), genre, true);
- }
-
- @Override
- public String buildChannelsSortOrder() {
- return Utils.CHANNEL_SORT_ORDER_BY_DISPLAY_NUMBER;
- }
-}
diff --git a/src/com/android/tv/input/TvInput.java b/src/com/android/tv/input/TvInput.java
deleted file mode 100644
index 1e0c9200..00000000
--- a/src/com/android/tv/input/TvInput.java
+++ /dev/null
@@ -1,51 +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.input;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-
-import com.android.tv.data.ChannelMap;
-
-public abstract class TvInput {
- abstract public String getId();
- abstract public String getDisplayName();
- abstract public Intent getIntentForSetupActivity();
- abstract public Intent getIntentForSettingsActivity();
- abstract public int getInputState();
- abstract public boolean hasChannel(boolean browsableOnly);
- abstract public ChannelMap buildChannelMap(Activity activity, long initialChannelId,
- Runnable onChannelsLoadFinished);
-
- // TvContract related method
- abstract public Uri buildChannelsUri(String genre);
- abstract public String buildChannelsSortOrder();
-
- @Override
- public int hashCode() {
- return getId().hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof TvInput) {
- return getId().equals(((TvInput) o).getId());
- }
- return false;
- }
-}
diff --git a/src/com/android/tv/input/UnifiedTvInput.java b/src/com/android/tv/input/UnifiedTvInput.java
deleted file mode 100644
index 1a28b9b4..00000000
--- a/src/com/android/tv/input/UnifiedTvInput.java
+++ /dev/null
@@ -1,104 +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.input;
-
-import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
-import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputInfo;
-import android.net.Uri;
-import android.text.TextUtils;
-
-import com.android.tv.R;
-import com.android.tv.data.ChannelMap;
-import com.android.tv.util.TvInputManagerHelper;
-import com.android.tv.util.Utils;
-
-import java.util.Collection;
-
-public class UnifiedTvInput extends TvInput {
- public static final String ID = "unified_tv_input_id";
-
- private final TvInputManagerHelper mInputManagerHelper;
- private final Context mContext;
-
- public UnifiedTvInput(TvInputManagerHelper inputManagerHelper, Context context) {
- mInputManagerHelper = inputManagerHelper;
- mContext = context;
- }
-
- @Override
- public String getId() {
- return ID;
- }
-
- @Override
- public String getDisplayName() {
- return mContext.getString(R.string.unified_tv_input_label);
- }
-
- @Override
- public Intent getIntentForSetupActivity() {
- return null;
- }
-
- @Override
- public Intent getIntentForSettingsActivity() {
- return null;
- }
-
- @Override
- public int getInputState() {
- return hasChannel(false) ? INPUT_STATE_CONNECTED : INPUT_STATE_DISCONNECTED;
- }
-
- @Override
- public boolean hasChannel(boolean browsableOnly) {
- Collection<TvInputInfo> inputInfos = mInputManagerHelper.getTvInputInfos(true);
- for (TvInputInfo inputInfo : inputInfos) {
- if (Utils.hasChannel(mContext, inputInfo, browsableOnly)) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public ChannelMap buildChannelMap(Activity activity, long initialChannelId,
- Runnable onChannelsLoadFinished) {
- return new ChannelMap(activity, this, initialChannelId, mInputManagerHelper,
- onChannelsLoadFinished);
- }
-
- @Override
- public Uri buildChannelsUri(String genre) {
- if (genre == null) {
- return TvContract.Channels.CONTENT_URI;
- }
- return TvContract.buildChannelsUriForCanonicalGenre(null, genre, true);
- }
-
- @Override
- public String buildChannelsSortOrder() {
- return TvContract.Channels.COLUMN_INPUT_ID + ", "
- + Utils.CHANNEL_SORT_ORDER_BY_DISPLAY_NUMBER;
- }
-}
diff --git a/src/com/android/tv/license/LicenseUtils.java b/src/com/android/tv/license/LicenseUtils.java
new file mode 100644
index 00000000..b972aad6
--- /dev/null
+++ b/src/com/android/tv/license/LicenseUtils.java
@@ -0,0 +1,58 @@
+/*
+ * 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.license;
+
+import android.content.res.AssetManager;
+
+import java.io.File;
+import java.io.IOException;
+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.
+ */
+ public static boolean hasRatingAttribution(AssetManager am) {
+ try (InputStream is = am.open("rating_sources.html")) {
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ private LicenseUtils() {
+ }
+}
diff --git a/src/com/android/tv/menu/ActionCardView.java b/src/com/android/tv/menu/ActionCardView.java
new file mode 100644
index 00000000..c4ddabe4
--- /dev/null
+++ b/src/com/android/tv/menu/ActionCardView.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.menu;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.tv.R;
+
+/**
+ * A view to render an item of TV options.
+ */
+public class ActionCardView extends FrameLayout implements ItemListRowView.CardView<MenuAction> {
+ private static final String TAG = MenuView.TAG;
+ private static final boolean DEBUG = MenuView.DEBUG;
+
+ private static final float OPACITY_DISABLED = 0.3f;
+ private static final float OPACITY_ENABLED = 1.0f;
+
+ private ImageView mIconView;
+ private TextView mLabelView;
+ private TextView mStateView;
+
+ public ActionCardView(Context context) {
+ this(context, null);
+ }
+
+ public ActionCardView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ActionCardView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mIconView = (ImageView) findViewById(R.id.action_card_icon);
+ mLabelView = (TextView) findViewById(R.id.action_card_label);
+ mStateView = (TextView) findViewById(R.id.action_card_state);
+ }
+
+ @Override
+ public void onBind(MenuAction action, boolean selected) {
+ if (DEBUG) {
+ Log.d(TAG, "onBind: action=" + action.getActionName(getContext()));
+ }
+ mIconView.setImageDrawable(action.getDrawable(getContext()));
+ mLabelView.setText(action.getActionName(getContext()));
+ mStateView.setText(action.getActionDescription(getContext()));
+ if (action.isEnabled()) {
+ setEnabled(true);
+ mIconView.setAlpha(OPACITY_ENABLED);
+ mLabelView.setAlpha(OPACITY_ENABLED);
+ mStateView.setAlpha(OPACITY_ENABLED);
+ } else {
+ setEnabled(false);
+ mIconView.setAlpha(OPACITY_DISABLED);
+ mLabelView.setAlpha(OPACITY_DISABLED);
+ mStateView.setAlpha(OPACITY_DISABLED);
+ }
+ }
+
+ @Override
+ public void onSelected() {
+ if (DEBUG) {
+ Log.d(TAG, "onSelected: action=" + mLabelView.getText());
+ }
+ }
+
+ @Override
+ public void onDeselected() {
+ if (DEBUG) {
+ Log.d(TAG, "onDeselected: action=" + mLabelView.getText());
+ }
+ }
+}
diff --git a/src/com/android/tv/menu/AppLinkCardView.java b/src/com/android/tv/menu/AppLinkCardView.java
new file mode 100644
index 00000000..cad63ced
--- /dev/null
+++ b/src/com/android/tv/menu/AppLinkCardView.java
@@ -0,0 +1,305 @@
+/*
+ * 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.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.v7.graphics.Palette;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+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.TvInputManagerHelper;
+
+/**
+ * A view to render an app link card.
+ */
+public class AppLinkCardView extends BaseCardView<Channel> implements Channel.LoadImageCallback {
+ private static final String TAG = MenuView.TAG;
+ private static final boolean DEBUG = MenuView.DEBUG;
+
+ private final float mCardHeight;
+ private final float mExtendedCardHeight;
+ private final float mTextViewHeight;
+ private final float mExtendedTextViewCardHeight;
+ private final int mCardImageWidth;
+ private final int mCardImageHeight;
+ private final int mIconWidth;
+ private final int mIconHeight;
+ private final int mIconPadding;
+ private final int mIconColorFilter;
+
+ private ImageView mImageView;
+ private View mGradientView;
+ private TextView mAppInfoView;
+ private TextView mMetaViewFocused;
+ private TextView mMetaViewUnfocused;
+ private View mMetaViewHolder;
+ private Channel mChannel;
+ private Intent mIntent;
+ private boolean mExtendViewOnFocus;
+ private final PackageManager mPackageManager;
+ private final TvInputManagerHelper mTvInputManagerHelper;
+
+ public AppLinkCardView(Context context) {
+ this(context, null);
+ }
+
+ public AppLinkCardView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AppLinkCardView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mCardImageWidth = getResources().getDimensionPixelSize(R.dimen.card_image_layout_width);
+ mCardImageHeight = getResources().getDimensionPixelSize(R.dimen.card_image_layout_height);
+ mCardHeight = getResources().getDimensionPixelSize(R.dimen.card_layout_height);
+ mExtendedCardHeight = getResources().getDimensionPixelOffset(
+ R.dimen.card_layout_height_extended);
+ mIconWidth = getResources().getDimensionPixelSize(R.dimen.app_link_card_icon_width);
+ mIconHeight = getResources().getDimensionPixelSize(R.dimen.app_link_card_icon_height);
+ mIconPadding = getResources().getDimensionPixelOffset(R.dimen.app_link_card_icon_padding);
+ mPackageManager = context.getPackageManager();
+ mTvInputManagerHelper = ((MainActivity) context).getTvInputManagerHelper();
+ mTextViewHeight = getResources().getDimensionPixelSize(
+ R.dimen.card_meta_layout_height);
+ mExtendedTextViewCardHeight = getResources().getDimensionPixelOffset(
+ R.dimen.card_meta_layout_height_extended);
+ mIconColorFilter = getResources().getColor(R.color.app_link_card_icon_color_filter, null);
+ }
+
+ /**
+ * Returns the intent which will be started once this card is clicked.
+ */
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ @Override
+ public void onBind(Channel channel, boolean selected) {
+ if (DEBUG) {
+ Log.d(TAG, "onBind(channel=" + channel.getDisplayName() + ", selected=" + selected
+ + ")");
+ }
+ mChannel = channel;
+ ApplicationInfo appInfo = mTvInputManagerHelper.getTvInputAppInfo(mChannel.getInputId());
+ int linkType = mChannel.getAppLinkType(getContext());
+ mIntent = mChannel.getAppLinkIntent(getContext());
+
+ switch (linkType) {
+ case Channel.APP_LINK_TYPE_CHANNEL:
+ setMetaViewText(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, this);
+ } 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:
+ setMetaViewText(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.");
+ }
+
+ if (mChannel.getAppLinkColor() == 0) {
+ mMetaViewHolder.setBackgroundResource(R.color.channel_card_meta_background);
+ } else {
+ mMetaViewHolder.setBackgroundColor(mChannel.getAppLinkColor());
+ }
+
+ 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, this);
+ } else {
+ setCardImageWithBanner(appInfo);
+ }
+
+ mMetaViewFocused.measure(MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ mExtendViewOnFocus = mMetaViewFocused.getLineCount() > 1;
+ if (mExtendViewOnFocus) {
+ setMetaViewFocusedAlpha(selected ? 1f : 0f);
+ } else {
+ setMetaViewFocusedAlpha(1f);
+ }
+
+ // Call super.onBind() at the end in order to make getCardHeight() return a proper value.
+ super.onBind(channel, selected);
+ }
+
+ @Override
+ public void onLoadImageFinished(Channel channel, int type, Bitmap bitmap) {
+ // mChannel can be changed before the image load finished.
+ if (!mChannel.hasSameReadOnlyInfo(channel)) {
+ return;
+ }
+ if (type == Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON) {
+ BitmapDrawable drawable = null;
+ if (bitmap != null) {
+ drawable = new BitmapDrawable(getResources(), bitmap);
+ if (bitmap.getWidth() > bitmap.getHeight()) {
+ drawable.setBounds(0, 0, mIconWidth,
+ mIconWidth * bitmap.getHeight() / bitmap.getWidth());
+ } else {
+ drawable.setBounds(0, 0, mIconHeight * bitmap.getWidth() / bitmap.getHeight(),
+ mIconHeight);
+ }
+ }
+ BitmapUtils.setColorFilterToDrawable(mIconColorFilter, drawable);
+ mAppInfoView.setCompoundDrawables(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);
+ if (mChannel.getAppLinkColor() == 0) {
+ extractAndSetMetaViewBackgroundColor(bitmap);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void 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);
+ mMetaViewFocused = (TextView) findViewById(R.id.app_link_text_focused);
+ mMetaViewUnfocused = (TextView) findViewById(R.id.app_link_text_unfocused);
+ }
+
+ @Override
+ protected void onFocusAnimationStart(boolean selected) {
+ if (mExtendViewOnFocus) {
+ setMetaViewFocusedAlpha(selected ? 1f : 0f);
+ }
+ }
+
+ @Override
+ protected void onSetFocusAnimatedValue(float animatedValue) {
+ super.onSetFocusAnimatedValue(animatedValue);
+ if (mExtendViewOnFocus) {
+ ViewGroup.LayoutParams params = mMetaViewUnfocused.getLayoutParams();
+ params.height = Math.round(mTextViewHeight
+ + (mExtendedTextViewCardHeight - mTextViewHeight) * animatedValue);
+ setMetaViewLayoutParams(params);
+ setMetaViewFocusedAlpha(animatedValue);
+ }
+ }
+
+ @Override
+ protected float getCardHeight() {
+ return (mExtendViewOnFocus && isFocused()) ? mExtendedCardHeight : mCardHeight;
+ }
+
+ // Try to set the card image with following order:
+ // 1) Provided poster art image, 2) Activity banner, 3) Application banner,
+ // 4) Activity logo, 5) Application logo, and 6) default image.
+ private void setCardImageWithBanner(ApplicationInfo appInfo) {
+ Drawable banner = null;
+ try {
+ banner = mPackageManager.getActivityBanner(mIntent);
+ } catch (PackageManager.NameNotFoundException e) {
+ // do nothing.
+ }
+ if (banner == null && appInfo != null && appInfo.banner != 0) {
+ banner = mPackageManager.getApplicationBanner(appInfo);
+ }
+ if (banner == null) {
+ try {
+ banner = mPackageManager.getActivityLogo(mIntent);
+ } catch (PackageManager.NameNotFoundException e) {
+ // do nothing.
+ }
+ }
+ if (banner == null && appInfo != null && appInfo.logo != 0) {
+ banner = mPackageManager.getApplicationLogo(appInfo);
+ }
+
+ if (banner == null) {
+ mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default);
+ mImageView.setBackgroundResource(R.color.channel_card);
+ } else {
+ 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);
+ extractAndSetMetaViewBackgroundColor(bitmap);
+ }
+ }
+
+ 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)));
+ }
+ });
+ }
+
+ private void setMetaViewLayoutParams(ViewGroup.LayoutParams params) {
+ mMetaViewFocused.setLayoutParams(params);
+ mMetaViewUnfocused.setLayoutParams(params);
+ }
+
+ private void setMetaViewText(String text) {
+ mMetaViewFocused.setText(text);
+ mMetaViewUnfocused.setText(text);
+ }
+
+ private void setMetaViewFocusedAlpha(float focusedAlpha) {
+ mMetaViewFocused.setAlpha(focusedAlpha);
+ mMetaViewUnfocused.setAlpha(1f - focusedAlpha);
+ }
+}
diff --git a/src/com/android/tv/menu/BaseCardView.java b/src/com/android/tv/menu/BaseCardView.java
new file mode 100644
index 00000000..25d4e313
--- /dev/null
+++ b/src/com/android/tv/menu/BaseCardView.java
@@ -0,0 +1,175 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Outline;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.widget.LinearLayout;
+
+import com.android.tv.R;
+
+/**
+ * A base class to render a card.
+ */
+public abstract class BaseCardView<T> extends LinearLayout implements ItemListRowView.CardView<T> {
+ 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;
+
+ private ValueAnimator mFocusAnimator;
+ private final int mFocusAnimDuration;
+ private final float mFocusTranslationZ;
+ private final float mVerticalCardMargin;
+ private final float mCardCornerRadius;
+ private float mFocusAnimatedValue;
+
+ public BaseCardView(Context context) {
+ this(context, null);
+ }
+
+ public BaseCardView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BaseCardView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ 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));
+ // 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);
+ }
+ });
+ }
+
+ /**
+ * Called when the view is displayed.
+ */
+ @Override
+ public void onBind(T item, boolean selected) {
+ // Note that getCardHeight() will be called by setFocusAnimatedValue().
+ // Therefore, be sure that getCardHeight() has a proper value before this method is called.
+ setFocusAnimatedValue(selected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F);
+ }
+
+ @Override
+ public void onSelected() {
+ if (isAttachedToWindow() && getVisibility() == View.VISIBLE) {
+ startFocusAnimation(SCALE_FACTOR_1F);
+ } else {
+ cancelFocusAnimationIfAny();
+ setFocusAnimatedValue(SCALE_FACTOR_1F);
+ }
+ }
+
+ @Override
+ public void onDeselected() {
+ if (isAttachedToWindow() && getVisibility() == View.VISIBLE) {
+ startFocusAnimation(SCALE_FACTOR_0F);
+ } else {
+ cancelFocusAnimationIfAny();
+ setFocusAnimatedValue(SCALE_FACTOR_0F);
+ }
+ }
+
+ /**
+ * Called when the focus animation started.
+ */
+ protected void onFocusAnimationStart(boolean selected) {
+ // do nothing.
+ }
+
+ /**
+ * 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}.
+ */
+ protected void onSetFocusAnimatedValue(float animatedValue) {
+ float scale = 1f + (mVerticalCardMargin / getCardHeight()) * animatedValue;
+ setScaleX(scale);
+ setScaleY(scale);
+ setTranslationZ(mFocusTranslationZ * animatedValue);
+ }
+
+ private void setFocusAnimatedValue(float animatedValue) {
+ mFocusAnimatedValue = animatedValue;
+ onSetFocusAnimatedValue(animatedValue);
+ }
+
+ private void startFocusAnimation(final float targetAnimatedValue) {
+ cancelFocusAnimationIfAny();
+ 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);
+ }
+
+ @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();
+ }
+
+ private void cancelFocusAnimationIfAny() {
+ if (mFocusAnimator != null) {
+ mFocusAnimator.cancel();
+ mFocusAnimator = null;
+ }
+ }
+
+ /**
+ * The implementation should return the height of the card.
+ */
+ protected abstract float getCardHeight();
+}
diff --git a/src/com/android/tv/menu/ChannelCardView.java b/src/com/android/tv/menu/ChannelCardView.java
new file mode 100644
index 00000000..d6086910
--- /dev/null
+++ b/src/com/android/tv/menu/ChannelCardView.java
@@ -0,0 +1,235 @@
+/*
+ * 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.graphics.Bitmap;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+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;
+
+/**
+ * A view to render channel card.
+ */
+public class ChannelCardView extends BaseCardView<Channel> implements
+ Program.LoadPosterArtCallback {
+ private static final String TAG = MenuView.TAG;
+ private static final boolean DEBUG = MenuView.DEBUG;
+
+ private final float mCardHeight;
+ private final float mExtendedCardHeight;
+ private final float mProgramNameViewHeight;
+ private final float mExtendedTextViewCardHeight;
+ private final int mCardImageWidth;
+ private final int mCardImageHeight;
+
+ private ImageView mImageView;
+ private View mGradientView;
+ private TextView mChannelNumberNameView;
+ private ProgressBar mProgressBar;
+ private TextView mMetaViewFocused;
+ private TextView mMetaViewUnfocused;
+ private Channel mChannel;
+ private Program mProgram;
+ private boolean mExtendViewOnFocus;
+ private final MainActivity mMainActivity;
+
+ public ChannelCardView(Context context) {
+ this(context, null);
+ }
+
+ public ChannelCardView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ChannelCardView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mCardImageWidth = getResources().getDimensionPixelSize(R.dimen.card_image_layout_width);
+ mCardImageHeight = getResources().getDimensionPixelSize(R.dimen.card_image_layout_height);
+ mCardHeight = getResources().getDimensionPixelSize(R.dimen.card_layout_height);
+ mExtendedCardHeight = getResources().getDimensionPixelSize(
+ R.dimen.card_layout_height_extended);
+ mProgramNameViewHeight = getResources().getDimensionPixelSize(
+ R.dimen.card_meta_layout_height);
+ mExtendedTextViewCardHeight = getResources().getDimensionPixelOffset(
+ R.dimen.card_meta_layout_height_extended);
+
+ mMainActivity = (MainActivity) context;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mImageView = (ImageView) findViewById(R.id.image);
+ mGradientView = findViewById(R.id.image_gradient);
+ mChannelNumberNameView = (TextView) findViewById(R.id.channel_number_and_name);
+ mMetaViewFocused = (TextView) findViewById(R.id.channel_title_focused);
+ mMetaViewUnfocused = (TextView) findViewById(R.id.channel_title_unfocused);
+ mProgressBar = (ProgressBar) findViewById(R.id.progress);
+ }
+
+ @Override
+ public void onBind(Channel channel, boolean selected) {
+ if (DEBUG) {
+ Log.d(TAG, "onBind(channel=" + channel.getDisplayName() + ", selected=" + selected
+ + ")");
+ }
+ mChannel = channel;
+ mProgram = null;
+ mChannelNumberNameView.setText(mChannel.getDisplayNumber() + " "
+ + mChannel.getDisplayName());
+ mChannelNumberNameView.setVisibility(VISIBLE);
+ mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default);
+ mImageView.setBackgroundResource(R.color.channel_card);
+ mGradientView.setVisibility(View.GONE);
+ mProgressBar.setVisibility(GONE);
+
+ setMetaViewEnabled(true);
+ if (mMainActivity.getParentalControlSettings().isParentalControlsEnabled()
+ && mChannel.isLocked()) {
+ setMetaViewText(R.string.program_title_for_blocked_channel);
+ return;
+ } else {
+ setMetaViewText("");
+ }
+
+ updateProgramInformation();
+ mMetaViewFocused.measure(
+ MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ if (mExtendViewOnFocus = mMetaViewFocused.getLineCount() > 1) {
+ setMetaViewFocusedAlpha(selected ? 1f : 0f);
+ } else {
+ setMetaViewFocusedAlpha(1f);
+ }
+
+ // Call super.onBind() at the end in order to make getCardHeight() return a proper value.
+ super.onBind(channel, selected);
+ }
+
+ @Override
+ public void onLoadPosterArtFinished(Program program, Bitmap posterArt) {
+ if (posterArt == null || mProgram == null
+ || program.getChannelId() != mProgram.getChannelId()
+ || program.getChannelId() != mChannel.getId()) {
+ return;
+ }
+ mImageView.setImageBitmap(posterArt);
+ mGradientView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ protected void onFocusAnimationStart(boolean selected) {
+ if (mExtendViewOnFocus) {
+ setMetaViewFocusedAlpha(selected ? 1f : 0f);
+ }
+ }
+
+ @Override
+ protected void onSetFocusAnimatedValue(float animatedValue) {
+ super.onSetFocusAnimatedValue(animatedValue);
+ if (mExtendViewOnFocus) {
+ ViewGroup.LayoutParams params = mMetaViewUnfocused.getLayoutParams();
+ params.height = Math.round(mProgramNameViewHeight
+ + (mExtendedTextViewCardHeight - mProgramNameViewHeight) * animatedValue);
+ setMetaViewLayoutParams(params);
+ setMetaViewFocusedAlpha(animatedValue);
+ }
+ }
+
+ @Override
+ protected float getCardHeight() {
+ return (mExtendViewOnFocus && isFocused()) ? mExtendedCardHeight : mCardHeight;
+ }
+
+ private void updateProgramInformation() {
+ if (mChannel == null) {
+ return;
+ }
+ mProgram = mMainActivity.getProgramDataManager().getCurrentProgram(mChannel.getId());
+ if (mProgram == null || TextUtils.isEmpty(mProgram.getTitle())) {
+ setMetaViewEnabled(false);
+ setMetaViewText(R.string.program_title_for_no_information);
+ } else {
+ setMetaViewText(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)));
+ }
+
+ 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,
+ ChannelCardView.this);
+ }
+ }
+
+ private void setMetaViewLayoutParams(ViewGroup.LayoutParams params) {
+ mMetaViewFocused.setLayoutParams(params);
+ mMetaViewUnfocused.setLayoutParams(params);
+ }
+
+ private void setMetaViewText(String text) {
+ mMetaViewFocused.setText(text);
+ mMetaViewUnfocused.setText(text);
+ }
+
+ private void setMetaViewText(int resId) {
+ mMetaViewFocused.setText(resId);
+ mMetaViewUnfocused.setText(resId);
+ }
+
+ private void setMetaViewEnabled(boolean enabled) {
+ mMetaViewFocused.setEnabled(enabled);
+ mMetaViewUnfocused.setEnabled(enabled);
+ }
+
+ private void setMetaViewFocusedAlpha(float focusedAlpha) {
+ mMetaViewFocused.setAlpha(focusedAlpha);
+ mMetaViewUnfocused.setAlpha(1f - focusedAlpha);
+ }
+}
diff --git a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java
new file mode 100644
index 00000000..1dca6834
--- /dev/null
+++ b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java
@@ -0,0 +1,109 @@
+/*
+ * 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.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.tv.R;
+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.
+ */
+public class ChannelsPosterPrefetcher {
+ private static final String TAG = "PosterPrefetcher";
+ private static final boolean DEBUG = false;
+
+ private final ProgramDataManager mProgramDataManager;
+ private final ChannelsRowAdapter mChannelsAdapter;
+ private final int mPosterArtWidth;
+ private final int mPosterArtHeight;
+ private final Context mContext;
+
+ private static final int MSG_PREFETCH_IMAGE = 1000;
+
+ private static final int ONDEMAND_POSTER_PREFETCH_DELAY_MILLIS = 500; // 500 milliseconds
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PREFETCH_IMAGE:
+ doPrefetchImages();
+ break;
+ }
+ }
+ };
+
+ /**
+ * 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);
+ mContext = context;
+ }
+
+ /**
+ * Start prefetching of program poster art of recommendation.
+ */
+ public void prefetch() {
+ if (DEBUG) {
+ Log.d(TAG, "startPrefetching()");
+ }
+ /*
+ * When a user browse channels, this method could be called many times. We don't need to
+ * 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);
+ }
+
+ private void doPrefetchImages() {
+ if (DEBUG) {
+ Log.d(TAG, "doPrefetchImages()");
+ }
+
+ List<Channel> channelList = mChannelsAdapter.getItemList();
+ if (channelList != null) {
+ for (Channel channel : channelList) {
+ if (!Channel.isValid(channel)) {
+ continue;
+ }
+ 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);
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/menu/ChannelsRow.java b/src/com/android/tv/menu/ChannelsRow.java
new file mode 100644
index 00000000..f08cbd57
--- /dev/null
+++ b/src/com/android/tv/menu/ChannelsRow.java
@@ -0,0 +1,81 @@
+/*
+ * 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 com.android.tv.R;
+import com.android.tv.common.TvCommonConstants;
+import com.android.tv.recommendation.RecentChannelEvaluator;
+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;
+
+ private Recommender mTvRecommendation;
+ private ChannelsRowAdapter mChannelsAdapter;
+ private ChannelsPosterPrefetcher mChannelsPosterPrefetcher;
+
+ public ChannelsRow(Context context) {
+ super(context,
+ TvCommonConstants.IS_MNC_OR_HIGHER
+ ? R.string.menu_title_channels : R.string.menu_title_channels_legacy,
+ R.dimen.card_layout_height,
+ null);
+ mTvRecommendation = new Recommender(getContext(), new Recommender.Listener() {
+ @Override
+ public void onRecommenderReady() {
+ mChannelsAdapter.update();
+ mChannelsPosterPrefetcher.prefetch();
+ }
+
+ @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);
+ setAdapter(mChannelsAdapter);
+ mChannelsPosterPrefetcher = new ChannelsPosterPrefetcher(context,
+ getMainActivity().getProgramDataManager(), mChannelsAdapter);
+ }
+
+ @Override
+ public void release() {
+ super.release();
+ mTvRecommendation.release();
+ mTvRecommendation = null;
+ }
+
+ /**
+ * Handle the update event of the recent channel.
+ */
+ public void onRecentChannelUpdated() {
+ mChannelsPosterPrefetcher.prefetch();
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+}
diff --git a/src/com/android/tv/menu/ChannelsRowAdapter.java b/src/com/android/tv/menu/ChannelsRowAdapter.java
new file mode 100644
index 00000000..8c76912a
--- /dev/null
+++ b/src/com/android/tv/menu/ChannelsRowAdapter.java
@@ -0,0 +1,193 @@
+/*
+ * 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.content.Intent;
+import android.view.View;
+
+import com.android.tv.MainActivity;
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.analytics.Tracker;
+import com.android.tv.common.TvCommonConstants;
+import com.android.tv.data.Channel;
+import com.android.tv.recommendation.Recommender;
+import com.android.tv.util.SetupUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An adapter of the Channels row.
+ */
+public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel> {
+ private static final int POSITION_FIRST_CARD = 0;
+ private static final int POSITION_SECOND_CARD = 1;
+ private static final int POSITION_THIRD_CARD = 2;
+ private final Context mContext;
+ private final Tracker mTracker;
+ private final Recommender mRecommender;
+ private final int mMaxCount;
+ private final int mMinCount;
+ private boolean mShowSetupCard;
+ private boolean mShowAppLinkCard;
+
+ private final View.OnClickListener mGuideOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mTracker.sendMenuClicked(R.string.channels_item_program_guide);
+ getMainActivity().getOverlayManager().showProgramGuide();
+ }
+ };
+
+ private final View.OnClickListener mSetupOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mTracker.sendMenuClicked(R.string.channels_item_setup);
+ getMainActivity().getOverlayManager().showSetupDialog();
+ }
+ };
+
+ private final View.OnClickListener mAppLinkOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mTracker.sendMenuClicked(R.string.channels_item_app_link);
+ Intent intent = ((AppLinkCardView) view).getIntent();
+ if (intent != null) {
+ getMainActivity().startActivitySafe(intent);
+ }
+ }
+ };
+
+ private final View.OnClickListener mChannelOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // Always send the label "Channels" because the channel ID or name or number might be
+ // sensitive.
+ mTracker.sendMenuClicked(R.string.menu_title_channels);
+ getMainActivity().tuneToChannel((Channel) view.getTag());
+ getMainActivity().hideOverlaysForTune();
+ }
+ };
+
+ public ChannelsRowAdapter(Context context, Recommender recommender,
+ int minCount, int maxCount) {
+ super(context);
+ mTracker = ((TvApplication) context.getApplicationContext()).getTracker();
+ mContext = context;
+ mRecommender = recommender;
+ mMinCount = minCount;
+ mMaxCount = maxCount;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ switch (position) {
+ case POSITION_FIRST_CARD:
+ return R.layout.menu_card_guide;
+ case POSITION_SECOND_CARD:
+ return mShowSetupCard
+ ? R.layout.menu_card_setup
+ : mShowAppLinkCard
+ ? R.layout.menu_card_app_link
+ : R.layout.menu_card_channel;
+ case POSITION_THIRD_CARD:
+ return (mShowSetupCard && mShowAppLinkCard)
+ ? R.layout.menu_card_app_link
+ : R.layout.menu_card_channel;
+ default:
+ return R.layout.menu_card_channel;
+ }
+ }
+
+ @Override
+ protected int getLayoutResId(int viewType) {
+ return viewType;
+ }
+
+ @Override
+ public void onBindViewHolder(MyViewHolder viewHolder, int position) {
+ super.onBindViewHolder(viewHolder, position);
+
+ int viewType = getItemViewType(position);
+ if (viewType == R.layout.menu_card_guide) {
+ viewHolder.itemView.setOnClickListener(mGuideOnClickListener);
+ } else if (viewType == R.layout.menu_card_setup) {
+ viewHolder.itemView.setOnClickListener(mSetupOnClickListener);
+ } else if (viewType == R.layout.menu_card_app_link) {
+ viewHolder.itemView.setOnClickListener(mAppLinkOnClickListener);
+ } else {
+ viewHolder.itemView.setTag(getItemList().get(position));
+ viewHolder.itemView.setOnClickListener(mChannelOnClickListener);
+ }
+ }
+
+ @Override
+ public void update() {
+ List<Channel> channelList = new ArrayList<>();
+ Channel dummyChannel = new Channel.Builder()
+ .build();
+ // For guide item
+ channelList.add(dummyChannel);
+ // For setup item
+ mShowSetupCard = SetupUtils.getInstance(mContext).hasNewInput(
+ ((MainActivity) mContext).getTvInputManagerHelper());
+ if (mShowSetupCard) {
+ channelList.add(dummyChannel);
+ }
+ if (TvCommonConstants.IS_MNC_OR_HIGHER) {
+ Channel currentChannel = ((MainActivity) mContext).getCurrentChannel();
+ mShowAppLinkCard = currentChannel != null
+ && currentChannel.getAppLinkType(mContext) != Channel.APP_LINK_TYPE_NONE;
+ if (mShowAppLinkCard) {
+ channelList.add(currentChannel);
+ }
+ }
+
+ channelList.addAll(getRecentChannels());
+ setItemList(channelList);
+ }
+
+ private List<Channel> getRecentChannels() {
+ List<Channel> channelList = new ArrayList<>();
+ for (Channel channel : mRecommender.recommendChannels(mMaxCount)) {
+ if (channel.isBrowsable()) {
+ channelList.add(channel);
+ }
+ }
+ int count = channelList.size();
+ // If the number of recommended channels is not enough, add more from the recent channel
+ // list.
+ if (count < mMinCount && mContext instanceof MainActivity) {
+ for (long channelId : ((MainActivity) mContext).getRecentChannels()) {
+ Channel channel = mRecommender.getChannel(channelId);
+ if (channel == null || channelList.contains(channel)
+ || !channel.isBrowsable()) {
+ continue;
+ }
+ channelList.add(channel);
+ if (++count >= mMinCount) {
+ break;
+ }
+ }
+ }
+
+ return Collections.unmodifiableList(channelList);
+ }
+}
diff --git a/src/com/android/tv/menu/CustomizableOptionsRowAdapter.java b/src/com/android/tv/menu/CustomizableOptionsRowAdapter.java
new file mode 100644
index 00000000..f69d5e86
--- /dev/null
+++ b/src/com/android/tv/menu/CustomizableOptionsRowAdapter.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.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.
+ */
+public abstract class CustomizableOptionsRowAdapter extends OptionsRowAdapter {
+ private final List<CustomAction> mCustomActions;
+
+ public CustomizableOptionsRowAdapter(Context context, List<CustomAction> customActions) {
+ super(context);
+ mCustomActions = customActions;
+ }
+
+ // Subclass should implement this and return list of {@link MenuAction}.
+ // Custom actions will be added at the first or the last position in addition.
+ // Note that {@link MenuAction} should have non-negative type
+ // because negative types are reserved for custom actions.
+ protected abstract List<MenuAction> createBaseActions();
+
+ // Subclass should implement this to perform proper action
+ // for {@link MenuAction} with the given type returned by {@link createBaseActions}.
+ protected abstract void executeBaseAction(int type);
+
+ @Override
+ protected List<MenuAction> createActions() {
+ List<MenuAction> actions = new ArrayList<>(createBaseActions());
+
+ if (mCustomActions != null) {
+ int position = 0;
+ for (int i = 0; i < mCustomActions.size(); i++) {
+ // 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());
+
+ if (customAction.isFront()) {
+ actions.add(position++, action);
+ } else {
+ actions.add(action);
+ }
+ }
+ }
+ return actions;
+ }
+
+ @Override
+ protected void executeAction(int type) {
+ if (type < 0) {
+ int position = -(type + 1);
+ getMainActivity().startActivitySafe(mCustomActions.get(position).getIntent());
+ } else {
+ executeBaseAction(type);
+ }
+ }
+
+ protected List<CustomAction> getCustomActions() {
+ return mCustomActions;
+ }
+}
diff --git a/src/com/android/tv/menu/GuideCardView.java b/src/com/android/tv/menu/GuideCardView.java
new file mode 100644
index 00000000..94d625bd
--- /dev/null
+++ b/src/com/android/tv/menu/GuideCardView.java
@@ -0,0 +1,50 @@
+/*
+ * 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.util.AttributeSet;
+
+import com.android.tv.R;
+import com.android.tv.data.Channel;
+
+/**
+ * A view to render a guide card.
+ */
+public class GuideCardView extends BaseCardView<Channel> {
+ private static final String TAG = "GuideCardView";
+ private static final boolean DEBUG = false;
+ private final float mCardHeight;
+
+ public GuideCardView(Context context) {
+ this(context, null, 0);
+ }
+
+ public GuideCardView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public GuideCardView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mCardHeight = getResources().getDimension(R.dimen.card_layout_height);
+ }
+
+ @Override
+ protected float getCardHeight() {
+ return mCardHeight;
+ }
+}
diff --git a/src/com/android/tv/menu/ItemListRow.java b/src/com/android/tv/menu/ItemListRow.java
new file mode 100644
index 00000000..ab634783
--- /dev/null
+++ b/src/com/android/tv/menu/ItemListRow.java
@@ -0,0 +1,74 @@
+/*
+ * 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 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.
+ */
+@SuppressWarnings("rawtypes")
+public class ItemListRow extends MenuRow {
+ private ItemListAdapter mAdapter;
+
+ public ItemListRow(Context context, int titleResId, int itemHeightResId,
+ ItemListAdapter adapter) {
+ this(context, context.getString(titleResId), itemHeightResId, adapter);
+ }
+
+ public ItemListRow(Context context, String title, int itemHeightResId,
+ ItemListAdapter adapter) {
+ super(context, title, itemHeightResId);
+ mAdapter = adapter;
+ }
+
+ /**
+ * Returns the adapter.
+ */
+ public ItemListAdapter<?> getAdapter() {
+ return mAdapter;
+ }
+
+ public void setAdapter(ItemListAdapter<?> adapter) {
+ mAdapter = adapter;
+ }
+
+ @Override
+ public void update() {
+ mAdapter.update();
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mAdapter.getItemCount() > 0;
+ }
+
+ @Override
+ public int getLayoutResId() {
+ return R.layout.item_list;
+ }
+
+ @Override
+ public String getId() {
+ return this.getClass().getName();
+ }
+}
diff --git a/src/com/android/tv/menu/ItemListRowView.java b/src/com/android/tv/menu/ItemListRowView.java
new file mode 100644
index 00000000..e9362a78
--- /dev/null
+++ b/src/com/android/tv/menu/ItemListRowView.java
@@ -0,0 +1,215 @@
+/*
+ * 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.support.v17.leanback.widget.HorizontalGridView;
+import android.support.v17.leanback.widget.OnChildSelectedListener;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+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 java.util.Collections;
+import java.util.List;
+
+/**
+ * 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<T> {
+ void onBind(T row, boolean selected);
+ void onSelected();
+ void onDeselected();
+ }
+
+ private HorizontalGridView mListView;
+ private CardView<?> mSelectedCard;
+
+ public ItemListRowView(Context context) {
+ this(context, null);
+ }
+
+ public ItemListRowView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ItemListRowView(Context context, AttributeSet attrs, int defStyle) {
+ this(context, attrs, defStyle, 0);
+ }
+
+ public ItemListRowView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mListView = (HorizontalGridView) getContentsView();
+ }
+
+ @Override
+ protected int getContentsViewId() {
+ return R.id.list_view;
+ }
+
+ @Override
+ public void onBind(MenuRow row) {
+ super.onBind(row);
+ ItemListAdapter<?> adapter = ((ItemListRow) row).getAdapter();
+ adapter.mItemListView = this;
+
+ mListView.setOnChildSelectedListener(this);
+ mListView.setAdapter(adapter);
+ }
+
+ @Override
+ public void initialize(int reason) {
+ super.initialize(reason);
+ setInitialFocusView(mListView);
+ mListView.setSelectedPosition(getAdapter().getInitialPosition());
+ }
+
+ private ItemListAdapter<?> getAdapter() {
+ return (ItemListAdapter<?>) mListView.getAdapter();
+ }
+
+ @Override
+ public void onChildSelected(ViewGroup parent, View child, int position, long id) {
+ if (DEBUG) Log.d(TAG, "onChildSelected: child=" + child);
+ if (mSelectedCard == child) {
+ return;
+ }
+ if (mSelectedCard != null) {
+ mSelectedCard.onDeselected();
+ }
+ mSelectedCard = (CardView<?>) child;
+ if (mSelectedCard != null) {
+ mSelectedCard.onSelected();
+ }
+ }
+
+ public static abstract class ItemListAdapter<T>
+ extends RecyclerView.Adapter<ItemListAdapter.MyViewHolder> {
+ private final MainActivity mMainActivity;
+ private final LayoutInflater mLayoutInflater;
+ private List<T> mItemList = Collections.emptyList();
+ private ItemListRowView mItemListView;
+
+ public ItemListAdapter(Context context) {
+ // Only MainActivity can use the main menu.
+ mMainActivity = (MainActivity) context;
+ mLayoutInflater = LayoutInflater.from(context);
+ }
+
+ /**
+ * 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}.
+ */
+ protected abstract int getLayoutResId(int viewType);
+
+ /**
+ * 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.
+ */
+ public int getInitialPosition() {
+ return 0;
+ }
+
+ /** The MainActivity that the corresponding ItemListView belongs to. */
+ protected MainActivity getMainActivity() {
+ return mMainActivity;
+ }
+
+ /** The item list. */
+ protected List<T> getItemList() {
+ return mItemList;
+ }
+
+ /**
+ * Sets the item list.
+ *
+ * <p>This sends an item change event, not a structural change event. The items of the same
+ * positions retain the same identity.
+ *
+ * <p>If there's any structural change and relayout and rebind is needed, call
+ * {@link #notifyDataSetChanged} explicitly.
+ */
+ protected void setItemList(List<T> itemList) {
+ int oldSize = mItemList.size();
+ int newSize = itemList.size();
+ mItemList = itemList;
+ if (oldSize > newSize) {
+ notifyItemRangeChanged(0, newSize);
+ notifyItemRangeRemoved(newSize, oldSize - newSize);
+ } else if (oldSize < newSize) {
+ notifyItemRangeChanged(0, oldSize);
+ notifyItemRangeInserted(oldSize, newSize - oldSize);
+ } else {
+ notifyItemRangeChanged(0, oldSize);
+ }
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return 0;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mItemList.size();
+ }
+
+ @Override
+ public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = mLayoutInflater.inflate(getLayoutResId(viewType), parent, false);
+ return new MyViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(MyViewHolder viewHolder, int position) {
+ @SuppressWarnings("unchecked")
+ CardView<T> cardView = (CardView<T>) viewHolder.itemView;
+ cardView.onBind(mItemList.get(position), cardView.equals(mItemListView.mSelectedCard));
+ }
+
+ public static class MyViewHolder extends RecyclerView.ViewHolder {
+ public MyViewHolder(View view) {
+ super(view);
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/menu/MenuAction.java b/src/com/android/tv/menu/MenuAction.java
new file mode 100644
index 00000000..5ef714dd
--- /dev/null
+++ b/src/com/android/tv/menu/MenuAction.java
@@ -0,0 +1,147 @@
+/*
+ * 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.graphics.drawable.Drawable;
+import android.text.TextUtils;
+
+import com.android.tv.MainActivity;
+import com.android.tv.R;
+import com.android.tv.TvOptionsManager;
+
+/**
+ * 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,
+ 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,
+ R.drawable.ic_tvoption_aspect);
+ public static final MenuAction PIP_ACTION =
+ new MenuAction(R.string.options_item_pip, TvOptionsManager.OPTION_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,
+ R.drawable.ic_tvoption_multi_track);
+ public static final MenuAction CHANNEL_SOURCES_ACTION =
+ new MenuAction(R.string.options_item_channel_sources,
+ TvOptionsManager.OPTION_CHANNEL_SOURCES,
+ R.drawable.ic_tvoption_channel_sources);
+ public static final MenuAction PARENTAL_CONTROLS_ACTION =
+ new MenuAction(R.string.options_item_parental_controls,
+ TvOptionsManager.OPTION_PARENTAL_CONTROLS,
+ R.drawable.ic_tvoption_parental);
+ public static final MenuAction ABOUT_ACTION =
+ new MenuAction(R.string.options_item_about,
+ TvOptionsManager.OPTION_ABOUT,
+ R.drawable.ic_tvoption_about);
+ // 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;
+ private Drawable mDrawable;
+ private int mDrawableResId;
+ private boolean mEnabled = true;
+
+ public MenuAction(int actionNameResId, int type, int drawableResId) {
+ mActionName = null;
+ mActionNameResId = actionNameResId;
+ mType = type;
+ mDrawable = null;
+ mDrawableResId = drawableResId;
+ }
+
+ public MenuAction(String actionName, int type, Drawable drawable) {
+ mActionName = actionName;
+ mActionNameResId = 0;
+ mType = type;
+ mDrawable = drawable;
+ mDrawableResId = 0;
+ }
+
+ public String getActionName(Context context) {
+ if (!TextUtils.isEmpty(mActionName)) {
+ return mActionName;
+ }
+ return context.getString(mActionNameResId);
+ }
+
+ public String getActionDescription(Context context) {
+ return ((MainActivity) context).getTvOptionsManager().getOptionString(mType);
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns Drawable.
+ */
+ public Drawable getDrawable(Context context) {
+ if (mDrawable == null) {
+ mDrawable = context.getDrawable(mDrawableResId);
+ }
+ 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/MenuRow.java b/src/com/android/tv/menu/MenuRow.java
new file mode 100644
index 00000000..38cda0bf
--- /dev/null
+++ b/src/com/android/tv/menu/MenuRow.java
@@ -0,0 +1,99 @@
+/*
+ * 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 com.android.tv.MainActivity;
+
+/**
+ * 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;
+ private final String mTitle;
+ private final int mHeight;
+
+ // TODO: Check if the heightResId is really necessary.
+ public MenuRow(Context context, int titleResId, int heightResId) {
+ this(context, context.getString(titleResId), heightResId);
+ }
+
+ public MenuRow(Context context, String title, int heightResId) {
+ mContext = context;
+ mTitle = title;
+ mHeight = context.getResources().getDimensionPixelSize(heightResId);
+ }
+
+ /**
+ * Returns the context.
+ */
+ protected Context getContext() {
+ return mContext;
+ }
+
+ protected MainActivity getMainActivity() {
+ return (MainActivity) mContext;
+ }
+
+ /**
+ * Returns the title of this row.
+ */
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns the height of this row.
+ */
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * Updates the contents in this row.
+ * This method is called only by the menu when necessary.
+ */
+ abstract public void update();
+
+ /**
+ * 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.
+ */
+ public void release() {
+ }
+
+ /**
+ * Returns the ID of the layout resource for this row.
+ */
+ abstract public int getLayoutResId();
+
+ /**
+ * Returns the ID of this row. This ID is used to select the row in the main menu.
+ */
+ abstract public String getId();
+}
diff --git a/src/com/android/tv/menu/MenuRowView.java b/src/com/android/tv/menu/MenuRowView.java
new file mode 100644
index 00000000..31ab3d93
--- /dev/null
+++ b/src/com/android/tv/menu/MenuRowView.java
@@ -0,0 +1,340 @@
+/*
+ * 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.content.res.Resources;
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+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.MenuView.MenuShowReason;
+
+public abstract class MenuRowView extends LinearLayout {
+ private static final String TAG = MenuRowView.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ /**
+ * For setting ListView visible, and TitleView visible with the selected text size and color
+ * without animation.
+ */
+ public static final int ANIM_NONE_SELECTED = 1;
+ /**
+ * For setting ListView gone, and TitleView visible with the deselected text size and color
+ * without animation.
+ */
+ public static final int ANIM_NONE_DESELECTED = 2;
+ /**
+ * An animation for the selected item list view.
+ */
+ public static final int ANIM_SELECTED = 3;
+ /**
+ * An animation for the deselected item list view.
+ */
+ public static final int ANIM_DESELECTED = 4;
+
+ private TextView mTitleView;
+ private View mContentsView;
+ private MenuView mMenuView;
+ private VerticalGridView mParentView;
+ private boolean mIsSelected;
+
+ private final float mTitleScaleSelected;
+ private final float mTitleAlphaSelected;
+ private final float mTitleAlphaDeselected;
+
+ /**
+ * The lastly focused view. It is used to keep the focus while navigating the menu rows and
+ * 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);
+ }
+ };
+
+ public MenuRowView(Context context) {
+ this(context, null);
+ }
+
+ public MenuRowView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public MenuRowView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public MenuRowView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ mTitleScaleSelected = getTitleScaleSelected();
+ mTitleAlphaSelected = getTitleAlphaSelected();
+ TypedValue outValue = new TypedValue();
+ context.getResources().getValue(
+ R.dimen.menu_row_title_alpha_deselected, outValue, true);
+ mTitleAlphaDeselected = outValue.getFloat();
+ }
+
+ protected float getTitleScaleSelected() {
+ Resources res = getContext().getResources();
+ int textSizeSelected =
+ res.getDimensionPixelSize(R.dimen.menu_row_title_text_size_selected);
+ int textSizeDeselected =
+ res.getDimensionPixelSize(R.dimen.menu_row_title_text_size_deselected);
+ return (float) textSizeSelected / textSizeDeselected;
+ }
+
+ protected float getTitleAlphaSelected() {
+ return 1.0f;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mTitleView = (TextView) findViewById(R.id.title);
+ mContentsView = findViewById(getContentsViewId());
+ if (mContentsView.isFocusable()) {
+ mContentsView.setOnFocusChangeListener(mOnFocusChangeListener);
+ }
+ if (mContentsView instanceof ViewGroup) {
+ setOnFocusChangeListenerToChildren((ViewGroup) mContentsView);
+ }
+ }
+
+ private void setOnFocusChangeListenerToChildren(ViewGroup parent) {
+ int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; ++i) {
+ View child = parent.getChildAt(i);
+ if (child.isFocusable()) {
+ child.setOnFocusChangeListener(mOnFocusChangeListener);
+ }
+ if (child instanceof ViewGroup) {
+ setOnFocusChangeListenerToChildren((ViewGroup) child);
+ }
+ }
+ }
+
+ abstract protected int getContentsViewId();
+
+ protected View getContentsView() {
+ return mContentsView;
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ updateView(mParentView.getChildAdapterPosition(this) == mParentView.getSelectedPosition()
+ ? ANIM_NONE_SELECTED : ANIM_NONE_DESELECTED);
+ }
+
+ /**
+ * 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}
+ */
+ public void initialize(@MenuShowReason int reason) {
+ mLastFocusView = null;
+ }
+
+ private void updateView(int animationType) {
+ boolean isSelected = animationType == ANIM_SELECTED || animationType == ANIM_NONE_SELECTED;
+ if (mIsSelected && isSelected) {
+ // Prevent from selected again so later calls to {@link updateView} cancels animation.
+ return;
+ }
+ mIsSelected = isSelected;
+ updateRowView(animationType);
+ updateTitleView(animationType);
+ }
+
+ private void updateRowView(int animationType) {
+ mContentsView.animate().cancel();
+ mContentsView.setAlpha(1f);
+ switch (animationType) {
+ case ANIM_NONE_SELECTED: {
+ mContentsView.setVisibility(View.VISIBLE);
+ break;
+ }
+ case ANIM_NONE_DESELECTED: {
+ mContentsView.setVisibility(View.GONE);
+ break;
+ }
+ case ANIM_SELECTED: {
+ mContentsView.setVisibility(View.VISIBLE);
+ mContentsView.setAlpha(0f);
+ mContentsView.animate()
+ .alpha(1f)
+ .setDuration(getMenuView().getRowSelectionAnimationDurationMs())
+ .withLayer();
+ break;
+ }
+ case ANIM_DESELECTED: {
+ mContentsView.setVisibility(View.GONE);
+ break;
+ }
+ }
+ }
+
+ private void updateTitleView(int animationType) {
+ boolean withAnimation = animationType == ANIM_SELECTED || animationType == ANIM_DESELECTED;
+ int duration = withAnimation ? getMenuView().getRowSelectionAnimationDurationMs() : 0;
+
+ mTitleView.animate().cancel();
+ switch (animationType) {
+ case ANIM_SELECTED:
+ mTitleView.animate()
+ .alpha(mTitleAlphaSelected)
+ .scaleX(mTitleScaleSelected)
+ .scaleY(mTitleScaleSelected)
+ .setDuration(duration)
+ .withLayer();
+ break;
+ case ANIM_NONE_SELECTED:
+ mTitleView.setAlpha(mTitleAlphaSelected);
+ mTitleView.setScaleX(mTitleScaleSelected);
+ mTitleView.setScaleY(mTitleScaleSelected);
+ break;
+ case ANIM_DESELECTED:
+ mTitleView.animate()
+ .alpha(mTitleAlphaDeselected)
+ .scaleX(1f)
+ .scaleY(1f)
+ .setDuration(duration)
+ .withLayer();
+ break;
+ case ANIM_NONE_DESELECTED:
+ mTitleView.setAlpha(mTitleAlphaDeselected);
+ mTitleView.setScaleX(1f);
+ mTitleView.setScaleY(1f);
+ break;
+ }
+ }
+
+ /**
+ * Updates the view contents.
+ * This method is called when the row is selected.
+ */
+ public void updateView(boolean withAnimation) {
+ int position = mParentView.getChildAdapterPosition(this);
+ int selectedPosition = mParentView.getSelectedPosition();
+ int animationType = ANIM_NONE_DESELECTED;
+ if (withAnimation) {
+ boolean scrollUp = mMenuView.getPreviousSelectedPosition() > selectedPosition;
+ switch (position - selectedPosition) {
+ case -2:
+ animationType = ANIM_NONE_DESELECTED;
+ break;
+ case -1:
+ animationType = scrollUp ? ANIM_NONE_DESELECTED : ANIM_DESELECTED;
+ break;
+ case 0:
+ animationType = ANIM_SELECTED;
+ break;
+ case 1:
+ animationType = scrollUp ? ANIM_DESELECTED : ANIM_NONE_DESELECTED;
+ break;
+ case 2:
+ animationType = ANIM_NONE_DESELECTED;
+ break;
+ }
+ } else {
+ animationType = (position == selectedPosition)
+ ? ANIM_NONE_SELECTED : ANIM_NONE_DESELECTED;
+ }
+ updateView(animationType);
+ }
+
+ protected MenuView getMenuView() {
+ return mMenuView;
+ }
+
+ public void setMenuView(MenuView view) {
+ mMenuView = view;
+ }
+
+ public void setParentView(VerticalGridView view) {
+ mParentView = view;
+ }
+
+ public void onBind(MenuRow row) {
+ if (DEBUG) Log.d(TAG, "onBind: row=" + row);
+ mRow = row;
+ mTitleView.setText(row.getTitle());
+
+ // mListView includes paddings to avoid an artifact while alpha animation.
+ // See res/layout/item_list.xml for more information.
+ ViewGroup.LayoutParams lp = mContentsView.getLayoutParams();
+ lp.height = row.getHeight() + mMenuView.getItemPaddingHeight()
+ - getContext().getResources().getDimensionPixelSize(
+ R.dimen.menu_list_margin_bottom);
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ // Expand view here so initial focused item can be shown.
+ updateView(ANIM_SELECTED);
+ return getInitialFocusView().requestFocus();
+ }
+
+ @NonNull
+ private View getInitialFocusView() {
+ if (mLastFocusView == null) {
+ return mContentsView;
+ }
+ return mLastFocusView;
+ }
+
+ /**
+ * 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)}.
+ */
+ protected void onChildFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ mLastFocusView = v;
+ }
+ }
+
+ /**
+ * Returns the ID of row object bound to this view.
+ */
+ public String getRowId() {
+ return mRow == null ? null : mRow.getId();
+ }
+}
diff --git a/src/com/android/tv/menu/MenuView.java b/src/com/android/tv/menu/MenuView.java
new file mode 100644
index 00000000..92243e13
--- /dev/null
+++ b/src/com/android/tv/menu/MenuView.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.menu;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.annotation.IntDef;
+import android.support.v17.leanback.widget.OnChildSelectedListener;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.OverScroller;
+
+import com.android.tv.ChannelTuner;
+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.analytics.Tracker;
+import com.android.tv.customization.CustomAction;
+import com.android.tv.customization.TvCustomizationManager;
+import com.android.tv.data.Channel;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A subclass of VerticalGridView that shows TV main menu.
+ */
+public class MenuView extends FrameLayout implements OnChildSelectedListener {
+ static final String TAG = "MenuView";
+ static final boolean DEBUG = false;
+
+ // TODO: Change the status to STATUS_NONE when the animation for STATUS_CHILD_SELECTING
+ // is ended.
+ public static final int STATUS_CHILD_SELECTING = 3;
+ public static final String SCREEN_NAME = "Menu";
+
+ @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})
+ 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;
+ public static final int REASON_PLAY_CONTROLS_PAUSE = 3;
+ public static final int REASON_PLAY_CONTROLS_PLAY_PAUSE = 4;
+ public static final int REASON_PLAY_CONTROLS_REWIND = 5;
+ public static final int REASON_PLAY_CONTROLS_FAST_FORWARD = 6;
+ public static final int REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS = 7;
+ public static final int REASON_PLAY_CONTROLS_JUMP_TO_NEXT = 8;
+
+ public static final List<String> sRowIdListForReason = new ArrayList<>();
+ static {
+ sRowIdListForReason.add(null); // REASON_NONE
+ sRowIdListForReason.add(ChannelsRow.ID); // REASON_GUIDE
+ sRowIdListForReason.add(PlayControlsRow.ID); // REASON_PLAY_CONTROLS_PLAY
+ sRowIdListForReason.add(PlayControlsRow.ID); // REASON_PLAY_CONTROLS_PAUSE
+ sRowIdListForReason.add(PlayControlsRow.ID); // REASON_PLAY_CONTROLS_PLAY_PAUSE
+ sRowIdListForReason.add(PlayControlsRow.ID); // REASON_PLAY_CONTROLS_REWIND
+ sRowIdListForReason.add(PlayControlsRow.ID); // REASON_PLAY_CONTROLS_FAST_FORWARD
+ sRowIdListForReason.add(PlayControlsRow.ID); // REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS
+ sRowIdListForReason.add(PlayControlsRow.ID); // REASON_PLAY_CONTROLS_JUMP_TO_NEXT
+ }
+
+ private final LayoutInflater mLayoutInflater;
+ private VerticalGridView mMenuList;
+ private final MenuAdapter mAdapter = new MenuAdapter();
+ private ChannelTuner mChannelTuner;
+ private int mPreviousSelectedPosition;
+
+ private Runnable mPreShowRunnable;
+ private Runnable mPostHideRunnable;
+
+ private final Animator mShowAnimator;
+ private final Animator mHideAnimator;
+ private final int mMenuHeight;
+ private final int mMenuRowTitleHeight;
+ private final int mMenuRowPaddingHeight;
+ private final long mShowDurationMillis;
+ private final int mRowSelectionAnimationDurationMs;
+ private final OverScroller mScroller;
+ private final DurationTimer mVisibleTimer = new DurationTimer();
+
+ private ChannelsRow mChannelsRow;
+
+ private Tracker mTracker;
+
+ private boolean mKeepVisible;
+ @MenuShowReason private int mShowReason = REASON_NONE;
+
+ private final Runnable mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ hide(true);
+ }
+ };
+
+ private final ChannelTuner.Listener mChannelTunerListener = new ChannelTuner.Listener() {
+ @Override
+ public void onLoadFinished() {}
+
+ @Override
+ public void onBrowsableChannelListChanged() {
+ update();
+ }
+
+ @Override
+ public void onCurrentChannelUnavailable(Channel channel) {}
+
+ @Override
+ public void onChannelChanged(Channel previousChannel, Channel currentChannel) {}
+ };
+
+ public MenuView(Context context) {
+ this(context, null, 0);
+ }
+
+ public MenuView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public MenuView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mLayoutInflater = LayoutInflater.from(context);
+ mShowAnimator = AnimatorInflater.loadAnimator(context, R.animator.menu_enter);
+ mShowAnimator.setTarget(this);
+ mHideAnimator = AnimatorInflater.loadAnimator(context, R.animator.menu_exit);
+ mHideAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Animation is still in running state at this point.
+ hideInternal();
+ }
+ });
+ mHideAnimator.setTarget(this);
+
+ Resources res = context.getResources();
+ mShowDurationMillis = res.getInteger(R.integer.menu_show_duration);
+ mMenuHeight = res.getDimensionPixelSize(R.dimen.menu_height);
+ mMenuRowTitleHeight = res.getDimensionPixelSize(R.dimen.menu_row_title_height);
+ mMenuRowPaddingHeight = res.getDimensionPixelOffset(R.dimen.menu_list_padding_top)
+ + res.getDimensionPixelOffset(R.dimen.menu_list_padding_bottom)
+ + res.getDimensionPixelOffset(R.dimen.menu_list_margin_top);
+ mRowSelectionAnimationDurationMs =
+ res.getInteger(R.integer.menu_row_selection_anim_duration);
+
+ mScroller = new OverScroller(context);
+ }
+
+ private MainActivity getMainActivity() {
+ return (MainActivity) getContext();
+ }
+
+ /**
+ * This method will be called from MainActivity.onStart()
+ */
+ public void onStart() {
+ Context context = getContext();
+
+ // Menu list(VerticalGridView) should be refreshed to forget the previous status.
+ // If not, mMenuList.setSelectedPosition() would not work properly.
+ mAdapter.notifyDataSetChanged();
+
+ MainActivity mainActivity = getMainActivity();
+ mTracker= ((TvApplication) mainActivity.getApplication()).getTracker();
+
+ // Build menu rows
+ TvCustomizationManager manager = mainActivity.getTvCustomizationManager();
+ List<MenuRow> itemList = new ArrayList<>();
+ itemList.add(new PlayControlsRow(context));
+ itemList.add(mChannelsRow = new ChannelsRow(context));
+ List<CustomAction> customActions =
+ manager.getCustomActions(TvCustomizationManager.ID_PARTNER_ROW);
+ String title = manager.getPartnerRowTitle();
+ if (customActions != null && !TextUtils.isEmpty(title)) {
+ itemList.add(new PartnerRow(context, title, customActions));
+ }
+ itemList.add(new TvOptionsRow(
+ context, manager.getCustomActions(TvCustomizationManager.ID_OPTIONS_ROW)));
+ itemList.add(new PipOptionsRow(context));
+
+ mAdapter.setItemList(itemList);
+ }
+
+ /**
+ * This method will be called from MainActivity.onStop()
+ */
+ public void onStop() {
+ mAdapter.resetItemList();
+ }
+
+ /**
+ * This method will be called when channels are updated.
+ */
+ public void onRecentChannelUpdated() {
+ if (mChannelsRow != null) {
+ mChannelsRow.onRecentChannelUpdated();
+ }
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mMenuList = (VerticalGridView) findViewById(R.id.menu_list);
+ mMenuList.setOnChildSelectedListener(this);
+ mMenuList.setScrollEnabled(false);
+ mMenuList.setAdapter(mAdapter);
+ // TODO: Use alignment features of GridView once the bugs of the features are fixed.
+ // NOTE: There's a problem that the menu jumps up/down, if a row whose position is less than
+ // the selected position is inserted or removed while the menu is displayed.
+ // The reason is because we use OverScroller to scroll the rows.
+ }
+
+ public void setPreShowCallback(Runnable preShowRunnable) {
+ mPreShowRunnable = preShowRunnable;
+ }
+
+ public void setPostHideCallback(Runnable postHideRunnable) {
+ mPostHideRunnable = postHideRunnable;
+ }
+
+ public boolean isActive() {
+ return getVisibility() == View.VISIBLE && !isHiding();
+ }
+
+ public boolean isHiding() {
+ return mHideAnimator.isStarted();
+ }
+
+ /**
+ * Returns the padding to the height of the item.
+ *
+ * <p>It is used to calculate the exact height of the item.
+ */
+ public int getItemPaddingHeight() {
+ return mMenuRowPaddingHeight;
+ }
+
+ /**
+ * Shows the main menu.
+ *
+ * @param reason A reason why this is called. See {@link MenuShowReason}
+ */
+ public void show(@MenuShowReason int reason) {
+ if (DEBUG) Log.d(TAG, "show reason:" + reason);
+ mTracker.sendShowMenu();
+ mVisibleTimer.start();
+ mShowReason = reason;
+ if (isHiding()) {
+ mHideAnimator.end();
+ }
+ String rowIdToSelect = sRowIdListForReason.get(reason);
+ if (getVisibility() == View.VISIBLE) {
+ if (rowIdToSelect != null) {
+ int position = mAdapter.getItemPosition(rowIdToSelect);
+ if (position >= 0) {
+ for (int i = 0; i < mMenuList.getChildCount(); ++i) {
+ MenuRowView rowView = (MenuRowView) mMenuList.getChildAt(i);
+ if (rowIdToSelect.equals(rowView.getRowId())) {
+ rowView.initialize(reason);
+ break;
+ }
+ }
+ mMenuList.setSelectedPosition(position);
+ requestFocus();
+ }
+ }
+ return;
+ }
+ if (rowIdToSelect == null) {
+ rowIdToSelect = ChannelsRow.ID;
+ }
+ // The child row views need be initialized before they become visible.
+ initializeChildren();
+ setVisibility(View.VISIBLE);
+ mTracker.sendScreenView(SCREEN_NAME);
+ if (mPreShowRunnable != null) {
+ mPreShowRunnable.run();
+ }
+ if (update()) {
+ // To apply the row insertion or removal immediately,
+ // notifyDataSetChanged need to be called after update.
+ // If we don't call this, the intermediate state might be shown.
+ mAdapter.notifyDataSetChanged();
+ }
+ int positionToSelect = mAdapter.getItemPosition(rowIdToSelect);
+ resetSelectedItemPosition(positionToSelect);
+ requestFocus();
+
+ // Abort animation because the scroll animation can occur while updating the adapter above.
+ mScroller.abortAnimation();
+ setScrollY(getScrollPosition(positionToSelect));
+ mShowAnimator.start();
+ scheduleHide();
+ }
+
+ int getItemPositionY(int position) {
+ return mMenuHeight - mMenuRowTitleHeight - mAdapter.getItemHeight(position);
+ }
+
+ private void initializeChildren() {
+ for (int i = 0, count = mMenuList.getChildCount(); i < count; ++i) {
+ MenuRowView rowView = (MenuRowView) mMenuList.getChildAt(i);
+ rowView.initialize(mShowReason);
+ }
+ }
+
+ private void resetSelectedItemPosition(int positionToSelect) {
+ mPreviousSelectedPosition = positionToSelect;
+ if (DEBUG) Log.d(TAG, "Row count of the main menu is " + mMenuList.getChildCount());
+ /*
+ * Must reset mMenuList's selected position after resetting selected position of child
+ * ListView. Otherwise it can be changed while resetting child ListView.
+ */
+ mMenuList.setSelectedPosition(mPreviousSelectedPosition);
+ for (int i = 0, count = mMenuList.getChildCount(); i < count; ++i) {
+ MenuRowView rowView = (MenuRowView) mMenuList.getChildAt(i);
+ if (DEBUG) {
+ Log.d(TAG, "The child position of the row " + i + " is "
+ + mMenuList.getChildAdapterPosition(rowView));
+ }
+ rowView.updateView(false);
+ }
+ }
+
+ public void hide(boolean withAnimation) {
+ removeCallbacks(mHideRunnable);
+ if (withAnimation) {
+ if (!isHiding()) {
+ mHideAnimator.start();
+ }
+ return;
+ }
+ if (isHiding()) {
+ mHideAnimator.end();
+ return;
+ }
+ hideInternal();
+ }
+
+ private void hideInternal() {
+ if (getVisibility() == View.GONE) {
+ return;
+ }
+ mTracker.sendHideMenu(mVisibleTimer.reset());
+ setVisibility(View.GONE);
+ if (mPostHideRunnable != null) {
+ mPostHideRunnable.run();
+ }
+ }
+
+ public void scheduleHide() {
+ removeCallbacks(mHideRunnable);
+ if (!mKeepVisible) {
+ postDelayed(mHideRunnable, mShowDurationMillis);
+ }
+ }
+
+ /**
+ * 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;
+ if (mKeepVisible) {
+ removeCallbacks(mHideRunnable);
+ } else if (isActive()) {
+ scheduleHide();
+ }
+ }
+
+ public void setChannelTuner(ChannelTuner channelTuner) {
+ if (mChannelTuner != null) {
+ mChannelTuner.removeListener(mChannelTunerListener);
+ }
+ mChannelTuner = channelTuner;
+ if (mChannelTuner != null) {
+ mChannelTuner.addListener(mChannelTunerListener);
+ }
+ update();
+ }
+
+ /**
+ * Updates the options row.
+ */
+ public void updateOptionsRow() {
+ if (DEBUG) {
+ Log.d(TAG, "update options row in main menu");
+ }
+ mAdapter.updateOptionsRow();
+ }
+
+ /**
+ * Updates the adapter.
+ *
+ * <p>Returns <@code true> if the adapter has been changed, otherwise {@code false}.
+ */
+ public boolean update() {
+ if (DEBUG) {
+ Log.d(TAG, "update main menu");
+ }
+ return mAdapter.update();
+ }
+
+ /**
+ * Returns a duration of the animation when the row selection changes.
+ */
+ public int getRowSelectionAnimationDurationMs() {
+ return mRowSelectionAnimationDurationMs;
+ }
+
+ @Override
+ public void computeScroll() {
+ super.computeScroll();
+ if (mScroller.computeScrollOffset()) {
+ setScrollY(mScroller.getCurrY());
+ invalidate();
+ }
+ }
+
+ private boolean scrollYSmooth(int position) {
+ int newScrollPosition = getScrollPosition(position);
+ if (newScrollPosition == getScrollY()) {
+ return false;
+ }
+ mScroller.startScroll(0, getScrollY(), 0, newScrollPosition - getScrollY(),
+ mRowSelectionAnimationDurationMs);
+ return true;
+ }
+
+ private int getScrollPosition(int selectedPosition) {
+ int visibleHeight = mMenuRowTitleHeight * selectedPosition
+ + mAdapter.getItemHeight(selectedPosition);
+ boolean lastItem = selectedPosition == mAdapter.getItemCount() - 1;
+ if (!lastItem) {
+ visibleHeight += mMenuRowTitleHeight;
+ }
+ return visibleHeight - mMenuHeight;
+ }
+
+ @Override
+ public void onChildSelected(ViewGroup parent, View child, int position, long id) {
+ boolean withAnimation = mPreviousSelectedPosition != position;
+ for (int i = 0; i < mMenuList.getChildCount(); i++) {
+ MenuRowView rowView = (MenuRowView) mMenuList.getChildAt(i);
+ rowView.updateView(withAnimation);
+ }
+ mPreviousSelectedPosition = position;
+ if (withAnimation) {
+ mScroller.abortAnimation();
+ scrollYSmooth(position);
+ }
+ }
+
+ /**
+ * Returns the previous selected position.
+ */
+ public int getPreviousSelectedPosition() {
+ return mPreviousSelectedPosition;
+ }
+
+ private class MenuAdapter extends RecyclerView.Adapter<MenuViewHolder> {
+ private List<MenuRow> mAllItems = Collections.emptyList();
+ private List<MenuRow> mVisibleItems = new ArrayList<>();
+
+ private void setItemList(List<MenuRow> items) {
+ mAllItems = items;
+ updateVisibleItems();
+ }
+
+ private void resetItemList() {
+ for (MenuRow item : mAllItems) {
+ item.release();
+ }
+ setItemList(Collections.<MenuRow>emptyList());
+ }
+
+ private void updateOptionsRow() {
+ if (isActive()) {
+ for (MenuRow item : mAllItems) {
+ if (item.getId().equals(TvOptionsRow.ID)) {
+ item.update();
+ }
+ }
+ }
+ }
+
+ private boolean update() {
+ if (isActive()) {
+ for (MenuRow item : mAllItems) {
+ item.update();
+ }
+ return updateVisibleItems();
+ }
+ return false;
+ }
+
+ private boolean updateVisibleItems() {
+ // To preserve the item focus, we need a fine-grained control using notifyItemXXXed()
+ // instead of using notifyDataSetChanged().
+ // We assume that the order of the adapters will not be changed.
+ List<MenuRow> oldVisibleItems = mVisibleItems;
+ mVisibleItems = new ArrayList<>();
+ boolean changed = false;
+ int oldSelectedPosition = mMenuList.getSelectedPosition();
+ MenuRow oldSelectedRow = null;
+ if (oldSelectedPosition >= 0 && oldSelectedPosition < oldVisibleItems.size()) {
+ oldSelectedRow = oldVisibleItems.get(oldSelectedPosition);
+ }
+ int position = 0;
+ int newSelectedPosition = 0;
+ for (MenuRow item : mAllItems) {
+ if (item.isVisible()) {
+ mVisibleItems.add(item);
+ if (!oldVisibleItems.contains(item)) {
+ notifyItemInserted(position);
+ changed = true;
+ }
+ if (item.equals(oldSelectedRow)) {
+ newSelectedPosition = position;
+ }
+ ++position;
+ } else if (oldVisibleItems.contains(item)) {
+ notifyItemRemoved(position);
+ changed = true;
+ }
+ }
+ if (DEBUG) Log.d(TAG, "Visible item count is " + mVisibleItems.size());
+ if (changed && scrollYSmooth(newSelectedPosition)) {
+ // Call invalidate() to make sure that computeScroll() is invoked.
+ invalidate();
+ }
+ return changed;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ // Each row needs to have a unique view type to avoid messing the focus up.
+ // If a row is recycled from a view of another type, the previous focus will not be
+ // preserved.
+ return mVisibleItems.get(position).getId().hashCode();
+ }
+
+ @Override
+ public MenuViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ for (MenuRow item : mVisibleItems) {
+ if (viewType == item.getId().hashCode()) {
+ MenuRowView view = (MenuRowView) mLayoutInflater.inflate(item.getLayoutResId(),
+ parent, false);
+ view.setMenuView(MenuView.this);
+ view.setParentView(mMenuList);
+ return new MenuViewHolder(view);
+ }
+ }
+ // Main menu is in the illegal state.
+ Log.e(TAG, "Error in creating view holder", new IllegalStateException(
+ "Can't create view holder due to the invalid view type " + viewType));
+ return null;
+ }
+
+ @Override
+ public void onBindViewHolder(MenuViewHolder viewHolder, int position) {
+ MenuRowView itemView = (MenuRowView) viewHolder.itemView;
+ MenuRow item = mVisibleItems.get(position);
+ itemView.onBind(item);
+ itemView.initialize(mShowReason);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mVisibleItems.size();
+ }
+
+ private int getItemPosition(String rowIdToSelect) {
+ if (rowIdToSelect == null) {
+ return -1;
+ }
+ int position = 0;
+ for (MenuRow item : mVisibleItems) {
+ if (rowIdToSelect.equals(item.getId())) {
+ return position;
+ }
+ ++position;
+ }
+ return -1;
+ }
+
+ private int getItemHeight(int position) {
+ if (position < 0 || position >= mVisibleItems.size()) {
+ return mMenuRowTitleHeight;
+ }
+ return mVisibleItems.get(position).getHeight() + mMenuRowPaddingHeight
+ + mMenuRowTitleHeight;
+ }
+ }
+
+ private static class MenuViewHolder extends RecyclerView.ViewHolder {
+ MenuViewHolder(View view) {
+ super(view);
+ }
+ }
+
+ private static class TvOptionsRow extends ItemListRow {
+ private static final String ID = TvOptionsRow.class.getName();
+ public TvOptionsRow(Context context, List<CustomAction> customActions) {
+ super(context, R.string.menu_title_options, R.dimen.action_card_height,
+ new TvOptionsRowAdapter(context, customActions));
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+ }
+
+ private static class PipOptionsRow extends ItemListRow {
+ public PipOptionsRow(Context context) {
+ super(context, R.string.menu_title_pip_options, R.dimen.action_card_height,
+ new PipOptionsRowAdapter(context));
+ }
+
+ @Override
+ public boolean isVisible() {
+ return super.isVisible() && getMainActivity().isPipEnabled();
+ }
+ }
+
+ private static class PartnerRow extends ItemListRow {
+ public PartnerRow(Context context, String title, List<CustomAction> customActions) {
+ super(context, title, R.dimen.action_card_height,
+ new PartnerOptionsRowAdapter(context, customActions));
+ }
+ }
+}
diff --git a/src/com/android/tv/menu/OptionsRowAdapter.java b/src/com/android/tv/menu/OptionsRowAdapter.java
new file mode 100644
index 00000000..df62026a
--- /dev/null
+++ b/src/com/android/tv/menu/OptionsRowAdapter.java
@@ -0,0 +1,152 @@
+/*
+ * 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.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;
+
+/*
+ * An adapter of options.
+ */
+public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter<MenuAction> {
+ private static final String CUSTOM_ACTION_LABEL = "custom action";
+ protected final Tracker mTracker;
+ private List<MenuAction> 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() {
+ @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) context.getApplicationContext()).getTracker();
+ }
+
+ /**
+ * Update action list and its content.
+ */
+ @Override
+ public void update() {
+ if (mActionList == null) {
+ mActionList = createActions();
+ updateActions();
+ setItemList(mActionList);
+ } else {
+ if (updateActions()) {
+ setItemList(mActionList);
+ }
+ }
+ }
+
+ @Override
+ protected int getLayoutResId(int viewType) {
+ return R.layout.menu_card_action;
+ }
+
+ protected abstract List<MenuAction> createActions();
+ protected abstract boolean 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}
+ */
+ protected MenuAction getAction(int position) {
+ 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);
+
+ viewHolder.itemView.setTag(getItemList().get(position));
+ viewHolder.itemView.setOnClickListener(mMenuActionOnClickListener);
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ // This makes 1:1 mapping from MenuAction to ActionCardView. That is, an ActionCardView will
+ // not be used(recycled) by other type of MenuAction. So the selection state of the view can
+ // 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
new file mode 100644
index 00000000..f3e09f80
--- /dev/null
+++ b/src/com/android/tv/menu/PartnerOptionsRowAdapter.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.menu;
+
+import android.content.Context;
+
+import com.android.tv.customization.CustomAction;
+
+import java.util.Collections;
+import java.util.List;
+
+public class PartnerOptionsRowAdapter extends CustomizableOptionsRowAdapter {
+ public PartnerOptionsRowAdapter(Context context, List<CustomAction> customActions) {
+ super(context, customActions);
+ }
+
+ @Override
+ protected List<MenuAction> createBaseActions() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ protected void executeBaseAction(int option) {
+ }
+
+ @Override
+ protected boolean 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
new file mode 100644
index 00000000..87203e9d
--- /dev/null
+++ b/src/com/android/tv/menu/PipOptionsRowAdapter.java
@@ -0,0 +1,137 @@
+/*
+ * 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<MenuAction> createActions() {
+ List<MenuAction> 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
new file mode 100644
index 00000000..957f2e94
--- /dev/null
+++ b/src/com/android/tv/menu/PlayControlsButton.java
@@ -0,0 +1,100 @@
+/*
+ * 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 android.util.AttributeSet;
+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 {
+ private static final float ALPHA_ENABLED = 1.0f;
+ private static final float ALPHA_DISABLED = 0.3f;
+
+ private final ImageView mButton;
+ private final ImageView mIcon;
+ private final TextView mLabel;
+
+ public PlayControlsButton(Context context) {
+ this(context, null);
+ }
+
+ public PlayControlsButton(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PlayControlsButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ 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);
+ mIcon = (ImageView) findViewById(R.id.icon);
+ mLabel = (TextView) findViewById(R.id.label);
+ }
+
+ /**
+ * Sets the resource ID of the image to be displayed in the center of this control.
+ */
+ public void setImageResId(int imageResId) {
+ mIcon.setImageResource(imageResId);
+ }
+
+ /**
+ * 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();
+ }
+ });
+ }
+
+ public void setLabel(String label) {
+ if (TextUtils.isEmpty(label)) {
+ mIcon.setVisibility(View.VISIBLE);
+ mLabel.setVisibility(View.GONE);
+ } else {
+ mIcon.setVisibility(View.GONE);
+ mLabel.setVisibility(View.VISIBLE);
+ mLabel.setText(label);
+ }
+ }
+
+ public void hideRippleAnimation() {
+ mButton.getDrawable().jumpToCurrentState();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ mButton.setEnabled(enabled);
+ mIcon.setEnabled(enabled);
+ mIcon.setAlpha(enabled ? ALPHA_ENABLED : ALPHA_DISABLED);
+ mLabel.setEnabled(enabled);
+ }
+}
diff --git a/src/com/android/tv/menu/PlayControlsRow.java b/src/com/android/tv/menu/PlayControlsRow.java
new file mode 100644
index 00000000..442407df
--- /dev/null
+++ b/src/com/android/tv/menu/PlayControlsRow.java
@@ -0,0 +1,54 @@
+/*
+ * 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 com.android.tv.R;
+import com.android.tv.TimeShiftManager;
+import com.android.tv.common.TvCommonConstants;
+
+public class PlayControlsRow extends MenuRow {
+ public static final String ID = PlayControlsRow.class.getName();
+
+ public PlayControlsRow(Context context) {
+ super(context, R.string.menu_title_play_controls, R.dimen.play_controls_height);
+ }
+
+ @Override
+ public void update() {
+ }
+
+ @Override
+ public int getLayoutResId() {
+ return R.layout.play_controls;
+ }
+
+ public TimeShiftManager getTimeShiftManager() {
+ return getMainActivity().getTimeShiftManager();
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public boolean isVisible() {
+ return TvCommonConstants.HAS_TIME_SHIFT_API;
+ }
+}
diff --git a/src/com/android/tv/menu/PlayControlsRowView.java b/src/com/android/tv/menu/PlayControlsRowView.java
new file mode 100644
index 00000000..96b0ece3
--- /dev/null
+++ b/src/com/android/tv/menu/PlayControlsRowView.java
@@ -0,0 +1,504 @@
+/*
+ * 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.content.res.Resources;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.tv.R;
+import com.android.tv.TimeShiftManager;
+import com.android.tv.TimeShiftManager.TimeShiftActionId;
+import com.android.tv.data.Program;
+import com.android.tv.menu.MenuView.MenuShowReason;
+
+public class PlayControlsRowView extends MenuRowView {
+ // Dimensions
+ private final int mTimeIndicatorLeftMargin;
+ private final int mTimeTextLeftMargin;
+ private final int mTimelineWidth;
+ // Views
+ private View mTitleView;
+ private View mBackgroundView;
+ private View mTimeIndicator;
+ private TextView mTimeText;
+ private View mProgressEmptyBefore;
+ private View mProgressWatched;
+ private View mProgressBuffered;
+ private View mProgressEmptyAfter;
+ private View mControlBar;
+ private PlayControlsButton mJumpPreviousButton;
+ private PlayControlsButton mRewindButton;
+ private PlayControlsButton mPlayPauseButton;
+ private PlayControlsButton mFastForwardButton;
+ private PlayControlsButton mJumpNextButton;
+ private TextView mProgramStartTimeText;
+ private TextView mProgramEndTimeText;
+ private View mUnavailableMessageText;
+ private TimeShiftManager mTimeShiftManager;
+
+ private long mProgramStartTimeMs;
+ private long mProgramEndTimeMs;
+
+ public PlayControlsRowView(Context context) {
+ this(context, null);
+ }
+
+ public PlayControlsRowView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PlayControlsRowView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ 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;
+ mTimelineWidth = res.getDimensionPixelSize(R.dimen.play_controls_width);
+ }
+
+ @Override
+ protected int getContentsViewId() {
+ return R.id.play_controls;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ // Clip the ViewGroup(body) to the rounded rectangle of outline.
+ findViewById(R.id.body).setClipToOutline(true);
+ mTitleView = findViewById(R.id.title);
+ mBackgroundView = 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);
+ mJumpPreviousButton = (PlayControlsButton) findViewById(R.id.jump_previous);
+ mRewindButton = (PlayControlsButton) findViewById(R.id.rewind);
+ mPlayPauseButton = (PlayControlsButton) findViewById(R.id.play_pause);
+ mFastForwardButton = (PlayControlsButton) findViewById(R.id.fast_forward);
+ mJumpNextButton = (PlayControlsButton) findViewById(R.id.jump_next);
+ 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, new Runnable() {
+ @Override
+ public void run() {
+ if (mTimeShiftManager.isAvailable()) {
+ mTimeShiftManager.jumpToPrevious();
+ updateAll();
+ }
+ }
+ });
+ initializeButton(mRewindButton, R.drawable.lb_ic_fast_rewind,
+ R.string.play_controls_description_fast_rewind, 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, 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, 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, new Runnable() {
+ @Override
+ public void run() {
+ if (mTimeShiftManager.isAvailable()) {
+ mTimeShiftManager.jumpToNext();
+ updateAll();
+ }
+ }
+ });
+ changeFocusableForDescendents(false);
+ }
+
+ private void changeFocusableForDescendents(boolean focusable) {
+ setFocusable(focusable);
+ setDescendantFocusability(focusable ? FOCUS_AFTER_DESCENDANTS : FOCUS_BLOCK_DESCENDANTS);
+ }
+
+ private void setRowEnable(boolean enable) {
+ setEnabled(enable);
+ changeFocusableForDescendents(enable);
+ mTitleView.setVisibility(enable ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ private void initializeButton(PlayControlsButton button, int imageResId,
+ int descriptionId, Runnable clickAction) {
+ button.setImageResId(imageResId);
+ button.setAction(clickAction);
+ button.findViewById(R.id.button)
+ .setContentDescription(getResources().getString(descriptionId));
+ }
+
+ @Override
+ public void onBind(MenuRow row) {
+ super.onBind(row);
+ PlayControlsRow playControlsRow = (PlayControlsRow) row;
+ mTimeShiftManager = playControlsRow.getTimeShiftManager();
+ mTimeShiftManager.setListener(new TimeShiftManager.Listener() {
+ @Override
+ public void onAvailabilityChanged() {
+ updateMenuVisibility();
+ PlayControlsRowView.this.onAvailabilityChanged();
+ }
+
+ @Override
+ public void onPlayStatusChanged(int status) {
+ updateMenuVisibility();
+ if (mTimeShiftManager.isAvailable()) {
+ updateAll();
+ }
+ }
+
+ @Override
+ public void onRecordStartTimeChanged() {
+ if (!mTimeShiftManager.isAvailable()) {
+ return;
+ }
+ updateAll();
+ }
+
+ @Override
+ public void onCurrentPositionChanged() {
+ if (!mTimeShiftManager.isAvailable()) {
+ return;
+ }
+ initializeTimeline();
+ updateAll();
+ }
+
+ @Override
+ public void onProgramInfoChanged() {
+ if (!mTimeShiftManager.isAvailable()) {
+ return;
+ }
+ initializeTimeline();
+ updateAll();
+ }
+
+ @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();
+ }
+ }
+ });
+ onAvailabilityChanged();
+ }
+
+ private void onAvailabilityChanged() {
+ if (mTimeShiftManager.isAvailable()) {
+ setRowEnable(true);
+ initializeTimeline();
+ mBackgroundView.setEnabled(true);
+ } else {
+ setRowEnable(false);
+ mBackgroundView.setEnabled(false);
+ }
+ updateAll();
+ }
+
+ private void initializeTimeline() {
+ Program program = mTimeShiftManager.getProgramAt(mTimeShiftManager.getCurrentPositionMs());
+ mProgramStartTimeMs = program.getStartTimeUtcMillis();
+ mProgramEndTimeMs = program.getEndTimeUtcMillis();
+ }
+
+ private void updateMenuVisibility() {
+ boolean keepMenuVisible =
+ mTimeShiftManager.isAvailable() && !mTimeShiftManager.isNormalPlaying();
+ getMenuView().setKeepVisible(keepMenuVisible);
+ }
+
+ @Override
+ public void updateView(boolean withAnimation) {
+ super.updateView(withAnimation);
+ updateAll();
+ postHideRippleAnimation();
+ }
+
+ @Override
+ protected float getTitleScaleSelected() {
+ return 1.0f;
+ }
+
+ @Override
+ protected float getTitleAlphaSelected() {
+ return 0.0f;
+ }
+
+ @Override
+ public void initialize(@MenuShowReason int reason) {
+ super.initialize(reason);
+ switch (reason) {
+ case MenuView.REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS:
+ if (mTimeShiftManager.isActionEnabled(
+ TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS)) {
+ setInitialFocusView(mJumpPreviousButton);
+ } else {
+ setInitialFocusView(mPlayPauseButton);
+ }
+ break;
+ case MenuView.REASON_PLAY_CONTROLS_REWIND:
+ if (mTimeShiftManager.isActionEnabled(
+ TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND)) {
+ setInitialFocusView(mRewindButton);
+ } else {
+ setInitialFocusView(mPlayPauseButton);
+ }
+ break;
+ case MenuView.REASON_PLAY_CONTROLS_FAST_FORWARD:
+ if (mTimeShiftManager.isActionEnabled(
+ TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD)) {
+ setInitialFocusView(mFastForwardButton);
+ } else {
+ setInitialFocusView(mPlayPauseButton);
+ }
+ break;
+ case MenuView.REASON_PLAY_CONTROLS_JUMP_TO_NEXT:
+ if (mTimeShiftManager.isActionEnabled(
+ TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT)) {
+ setInitialFocusView(mJumpNextButton);
+ } else {
+ setInitialFocusView(mPlayPauseButton);
+ }
+ break;
+ case MenuView.REASON_PLAY_CONTROLS_PLAY_PAUSE:
+ case MenuView.REASON_PLAY_CONTROLS_PLAY:
+ case MenuView.REASON_PLAY_CONTROLS_PAUSE:
+ default:
+ setInitialFocusView(mPlayPauseButton);
+ break;
+ }
+ postHideRippleAnimation();
+ }
+
+ private void postHideRippleAnimation() {
+ // 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();
+ }
+ });
+ }
+
+ @Override
+ protected void onChildFocusChange(View v, boolean hasFocus) {
+ super.onChildFocusChange(v, hasFocus);
+ if ((v.getParent().equals(mRewindButton) || v.getParent().equals(mFastForwardButton))
+ && !hasFocus) {
+ if (mTimeShiftManager.getPlayStatus() == TimeShiftManager.PLAY_STATUS_PLAYING) {
+ mTimeShiftManager.play();
+ updateButtons();
+ }
+ }
+ }
+
+ private void updateAll() {
+ updateTime();
+ updateProgress();
+ updateRecTimeText();
+ updateButtons();
+ }
+
+ private void updateTime() {
+ if (isEnabled()) {
+ mTimeText.setVisibility(View.VISIBLE);
+ mTimeIndicator.setVisibility(View.VISIBLE);
+ } else {
+ mTimeText.setVisibility(View.INVISIBLE);
+ mTimeIndicator.setVisibility(View.INVISIBLE);
+ 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);
+ }
+
+ 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,
+ Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordStartTimeMs()));
+ long currentPlayingTimeMs = Math.min(mProgramEndTimeMs,
+ Math.max(mProgramStartTimeMs, mTimeShiftManager.getCurrentPositionMs()));
+ long progressEndTimeMs = Math.min(mProgramEndTimeMs,
+ Math.max(mProgramStartTimeMs, System.currentTimeMillis()));
+
+ 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);
+ }
+
+ private void updateRecTimeText() {
+ if (isEnabled()) {
+ mProgramStartTimeText.setVisibility(View.VISIBLE);
+ mProgramEndTimeText.setVisibility(View.VISIBLE);
+ } else {
+ mProgramStartTimeText.setVisibility(View.INVISIBLE);
+ mProgramEndTimeText.setVisibility(View.INVISIBLE);
+ return;
+ }
+ mProgramStartTimeText.setText(getTimeString(mProgramStartTimeMs));
+ mProgramEndTimeText.setText(getTimeString(mProgramEndTimeMs));
+ }
+
+ private void updateButtons() {
+ if (isEnabled()) {
+ mControlBar.setVisibility(View.VISIBLE);
+ mUnavailableMessageText.setVisibility(View.GONE);
+ } else {
+ mControlBar.setVisibility(View.INVISIBLE);
+ mUnavailableMessageText.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ if (mTimeShiftManager.getPlayStatus() == TimeShiftManager.PLAY_STATUS_PAUSED) {
+ mPlayPauseButton.setImageResId(R.drawable.lb_ic_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));
+ }
+ 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));
+
+ PlayControlsButton button;
+ if (mTimeShiftManager.getPlayDirection() == TimeShiftManager.PLAY_DIRECTION_FORWARD) {
+ mRewindButton.setLabel(null);
+ button = mFastForwardButton;
+ } else {
+ mFastForwardButton.setLabel(null);
+ button = mRewindButton;
+ }
+ if (mTimeShiftManager.getDisplayedPlaySpeed() == TimeShiftManager.PLAY_SPEED_1X) {
+ button.setLabel(null);
+ } else {
+ button.setLabel(getResources().getString(R.string.play_controls_speed,
+ mTimeShiftManager.getDisplayedPlaySpeed()));
+ }
+ }
+
+ private String getTimeString(long timeMs) {
+ return DateFormat.getTimeFormat(getContext()).format(timeMs);
+ }
+
+ private int convertDurationToPixel(long duration) {
+ if (mProgramEndTimeMs <= mProgramStartTimeMs) {
+ return 0;
+ }
+ return (int) (duration * mTimelineWidth / (mProgramEndTimeMs - mProgramStartTimeMs));
+ }
+}
diff --git a/src/com/android/tv/menu/SetupCardView.java b/src/com/android/tv/menu/SetupCardView.java
new file mode 100644
index 00000000..7ad5e9d0
--- /dev/null
+++ b/src/com/android/tv/menu/SetupCardView.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.menu;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.tv.R;
+import com.android.tv.data.Channel;
+
+/**
+ * A view to render a guide card.
+ */
+public class SetupCardView extends BaseCardView<Channel> {
+ private static final String TAG = "GuideCardView";
+ private static final boolean DEBUG = false;
+
+ private static final int INVALID_COUNT = -1;
+
+ private final float mCardHeight;
+
+ public SetupCardView(Context context) {
+ this(context, null, 0);
+ }
+
+ public SetupCardView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SetupCardView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mCardHeight = getResources().getDimension(R.dimen.card_layout_height);
+ }
+
+ @Override
+ protected float getCardHeight() {
+ return mCardHeight;
+ }
+}
diff --git a/src/com/android/tv/menu/TvOptionsRowAdapter.java b/src/com/android/tv/menu/TvOptionsRowAdapter.java
new file mode 100644
index 00000000..5b203551
--- /dev/null
+++ b/src/com/android/tv/menu/TvOptionsRowAdapter.java
@@ -0,0 +1,194 @@
+/*
+ * 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.media.tv.TvTrackInfo;
+
+import com.android.tv.R;
+import com.android.tv.TvOptionsManager;
+import com.android.tv.customization.CustomAction;
+import com.android.tv.data.DisplayMode;
+import com.android.tv.ui.TvViewUiManager;
+import com.android.tv.ui.sidepanel.AboutFragment;
+import com.android.tv.ui.sidepanel.ClosedCaptionFragment;
+import com.android.tv.ui.sidepanel.DisplayModeFragment;
+import com.android.tv.ui.sidepanel.MultiAudioFragment;
+import com.android.tv.util.PipInputManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/*
+ * An adapter of options.
+ */
+public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter {
+ private int mPositionPipAction;
+ private boolean mHasPipAction = true;
+
+ public TvOptionsRowAdapter(Context context, List<CustomAction> customActions) {
+ super(context, customActions);
+ }
+
+ @Override
+ protected List<MenuAction> createBaseActions() {
+ List<MenuAction> actionList = new ArrayList<>();
+ actionList.add(MenuAction.SELECT_CLOSED_CAPTION_ACTION);
+ actionList.add(MenuAction.SELECT_DISPLAY_MODE_ACTION);
+ actionList.add(MenuAction.PIP_ACTION);
+ mPositionPipAction = actionList.size() - 1;
+ actionList.add(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION);
+ actionList.add(MenuAction.CHANNEL_SOURCES_ACTION);
+ actionList.add(MenuAction.PARENTAL_CONTROLS_ACTION);
+ actionList.add(MenuAction.ABOUT_ACTION);
+
+ for (MenuAction action : actionList) {
+ if (action.getType() != TvOptionsManager.OPTION_CHANNEL_SOURCES) {
+ setOptionChangedListener(action);
+ }
+ }
+
+ if (getCustomActions() != null) {
+ // Adjust Pip action position which will be changed by applying custom actions.
+ for (CustomAction customAction : getCustomActions()) {
+ if (customAction.isFront()) {
+ mPositionPipAction++;
+ }
+ }
+ }
+
+ return actionList;
+ }
+
+ @Override
+ protected boolean updateActions() {
+ boolean changed = false;
+ if (updatePipAction()) {
+ changed = true;
+ }
+ if (updateMultiAudioAction()) {
+ changed = true;
+ }
+ if (updateDisplayModeAction()) {
+ changed = true;
+ }
+ 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 (pipInputManager.getPipInputSize(false) < 2) {
+ if (mHasPipAction) {
+ removeAction(mPositionPipAction);
+ mHasPipAction = false;
+ return true;
+ }
+ } else {
+ if (!mHasPipAction) {
+ addAction(mPositionPipAction, MenuAction.PIP_ACTION);
+ mHasPipAction = true;
+ changed = true;
+ }
+ }
+
+ // Case 2
+ boolean isPipEnabled = getMainActivity().isPipEnabled();
+ boolean oldEnabled = MenuAction.PIP_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_ACTION.setEnabled(newEnabled);
+ changed = true;
+ }
+ }
+
+ // Case 3 & 4 - we just need to update the icon.
+ MenuAction.PIP_ACTION.setDrawableResId(
+ isPipEnabled ? R.drawable.ic_tvoption_pip : R.drawable.ic_tvoption_pip_off);
+ return changed;
+ }
+
+ private boolean updateMultiAudioAction() {
+ List<TvTrackInfo> 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;
+ }
+
+ private boolean updateDisplayModeAction() {
+ TvViewUiManager uiManager = getMainActivity().getTvViewUiManager();
+ boolean oldEnabled = MenuAction.SELECT_DISPLAY_MODE_ACTION.isEnabled();
+ boolean newEnabled = uiManager.isDisplayModeAvailable(DisplayMode.MODE_FULL)
+ || uiManager.isDisplayModeAvailable(DisplayMode.MODE_ZOOM);
+ if (oldEnabled != newEnabled) {
+ MenuAction.SELECT_DISPLAY_MODE_ACTION.setEnabled(newEnabled);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void executeBaseAction(int type) {
+ switch (type) {
+ case TvOptionsManager.OPTION_CLOSED_CAPTIONS:
+ getMainActivity().getOverlayManager().getSideFragmentManager().show(
+ new ClosedCaptionFragment());
+ break;
+ case TvOptionsManager.OPTION_DISPLAY_MODE:
+ getMainActivity().getOverlayManager().getSideFragmentManager().show(
+ new DisplayModeFragment());
+ break;
+ case TvOptionsManager.OPTION_PIP:
+ getMainActivity().togglePipView();
+ break;
+ case TvOptionsManager.OPTION_MULTI_AUDIO:
+ getMainActivity().getOverlayManager().getSideFragmentManager().show(
+ new MultiAudioFragment());
+ break;
+ case TvOptionsManager.OPTION_CHANNEL_SOURCES:
+ getMainActivity().showChannelSourcesFragment();
+ break;
+ case TvOptionsManager.OPTION_PARENTAL_CONTROLS:
+ getMainActivity().showParentalControlFragment();
+ break;
+ case TvOptionsManager.OPTION_ABOUT:
+ getMainActivity().getOverlayManager().getSideFragmentManager().show(
+ new AboutFragment());
+ break;
+ }
+ }
+}
diff --git a/src/com/android/tv/notification/NotificationService.java b/src/com/android/tv/notification/NotificationService.java
deleted file mode 100644
index 5b2a77c8..00000000
--- a/src/com/android/tv/notification/NotificationService.java
+++ /dev/null
@@ -1,325 +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.notification;
-
-import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputInfo;
-import android.media.tv.TvInputManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Message;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.tv.R;
-import com.android.tv.data.Program;
-import com.android.tv.recommendation.RandomRecommender;
-import com.android.tv.recommendation.RoutineWatchRecommender;
-import com.android.tv.recommendation.TvRecommendation;
-import com.android.tv.recommendation.TvRecommendation.ChannelRecord;
-import com.android.tv.util.TvInputManagerHelper;
-import com.android.tv.util.BitmapUtils;
-import com.android.tv.util.Utils;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-/**
- * A local service for notify recommendation at home launcher.
- */
-public class NotificationService extends Service {
- private static final boolean DEBUG = true;
- private static final String TAG = "NotificationService";
-
- public static final String ACTION_SHOW_RECOMMENDATION =
- "com.android.tv.notification.ACTION_SHOW_RECOMMENDATION";
- public static final String ACTION_HIDE_RECOMMENDATION =
- "com.android.tv.notification.ACTION_HIDE_RECOMMENDATION";
-
- private static final String TUNE_PARAMS_RECOMMENDATION_TYPE =
- "com.android.tv.recommendation_type";
- private static final String TYPE_RANDOM_RECOMMENDATION = "random";
- private static final String TYPE_ROUTINE_WATCH_RECOMMENDATION = "routine_watch";
-
- private static final String NOTIFY_TAG = "tv_recommendation";
- // TODO: find out proper number of notifications and whether to make it dynamically
- // configurable from system property or etc.
- private static final int NOTIFICATION_COUNT = 1;
-
- private static final int MSG_SHOW_RECOMMENDATION = 0;
- private static final int MSG_UPDATE_RECOMMENDATION = 1;
- private static final int MSG_HIDE_RECOMMENDATION = 2;
-
- 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 TvInputManager mTvInputManager;
- private TvInputManagerHelper mTvInputManagerHelper;
- private TvRecommendation mTvRecommendation;
- private NotificationManager mNotificationManager;
- private final HandlerThread mHandlerThread;
- private final Handler mHandler;
- private final String mRecommendationType;
-
- private float mNotificationCardMaxWidth;
- private float mNotificationCardHeight;
-
- public NotificationService() {
- mHandlerThread = new HandlerThread("tv notification");
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper()) {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SHOW_RECOMMENDATION: {
- removeMessages(MSG_UPDATE_RECOMMENDATION);
- mTvInputManagerHelper.update();
- showRecommendation();
- break;
- }
- case MSG_UPDATE_RECOMMENDATION: {
- mTvInputManagerHelper.update();
- if (!sendNotification((ChannelRecord) msg.obj, msg.arg1)) {
- obtainMessage(MSG_HIDE_RECOMMENDATION).sendToTarget();
- obtainMessage(MSG_SHOW_RECOMMENDATION).sendToTarget();
- }
- break;
- }
- case MSG_HIDE_RECOMMENDATION: {
- removeMessages(MSG_UPDATE_RECOMMENDATION);
- hideRecommendation();
- break;
- }
- default: {
- super.handleMessage(msg);
- }
- }
- }
- };
- mRecommendationType = TYPE_RANDOM_RECOMMENDATION;
- }
-
- @Override
- public void onCreate() {
- if (DEBUG) Log.d(TAG, "onCreate");
- super.onCreate();
- mNotificationCardMaxWidth = getResources().getDimensionPixelOffset(
- R.dimen.notif_card_img_max_width);
- mNotificationCardHeight = getResources().getDimensionPixelOffset(
- R.dimen.notif_card_img_height);
-
- mTvRecommendation = new TvRecommendation(this, mHandler, true);
- if (TYPE_RANDOM_RECOMMENDATION.equals(mRecommendationType)) {
- mTvRecommendation.registerTvRecommender(new RandomRecommender());
- } else if (TYPE_ROUTINE_WATCH_RECOMMENDATION.equals(mRecommendationType)) {
- mTvRecommendation.registerTvRecommender(new RoutineWatchRecommender(this));
- } else {
- throw new IllegalStateException("Undefined recommendation type: "
- + mRecommendationType);
- }
- mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
- mTvInputManagerHelper = new TvInputManagerHelper(mTvInputManager);
- mTvInputManagerHelper.start();
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (DEBUG) Log.d(TAG, "onStartCommand");
- if (intent != null) {
- String action = intent.getAction();
- if (ACTION_SHOW_RECOMMENDATION.equals(action)) {
- mHandler.removeMessages(MSG_SHOW_RECOMMENDATION);
- mHandler.removeMessages(MSG_HIDE_RECOMMENDATION);
- mHandler.obtainMessage(MSG_SHOW_RECOMMENDATION).sendToTarget();
- } else if (ACTION_HIDE_RECOMMENDATION.equals(action)) {
- mHandler.removeMessages(MSG_SHOW_RECOMMENDATION);
- mHandler.removeMessages(MSG_HIDE_RECOMMENDATION);
- mHandler.obtainMessage(MSG_HIDE_RECOMMENDATION).sendToTarget();
- }
- }
- return START_STICKY;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- private void showRecommendation() {
- if (DEBUG) Log.d(TAG, "showRecommendation");
- ChannelRecord[] channelRecords = mTvRecommendation.getRecommendedChannelList();
- int notifyId = 0;
- for (ChannelRecord cr : channelRecords) {
- if (sendNotification(cr, notifyId)) {
- ++notifyId;
- if (notifyId >= NOTIFICATION_COUNT) {
- break;
- }
- }
- }
- if (notifyId < NOTIFICATION_COUNT) {
- Message msg = mHandler.obtainMessage(MSG_SHOW_RECOMMENDATION);
- mHandler.sendMessageDelayed(msg, RECOMMENDATION_RETRY_TIME_MS);
- }
- }
-
- private void hideRecommendation() {
- if (DEBUG) Log.d(TAG, "hideRecommendation");
- for (int i = 0; i < NOTIFICATION_COUNT; ++i) {
- mNotificationManager.cancel(NOTIFY_TAG, i);
- }
- }
-
- private boolean sendNotification(ChannelRecord cr, int notifyId) {
- if (DEBUG) Log.d(TAG, "sendNotification (" + cr.getChannel().getDisplayName()
- + " notifyId=" + notifyId + ")");
- Intent intent = new Intent(Intent.ACTION_VIEW, cr.getChannelUri());
- intent.putExtra(TUNE_PARAMS_RECOMMENDATION_TYPE, mRecommendationType);
- PendingIntent notificationIntent = PendingIntent.getActivity(this, 0, intent, 0);
-
- // TODO: Move some checking logic into TvRecommendation.
- String inputId = Utils.getInputIdForChannel(this, cr.getChannelUri());
- if (TextUtils.isEmpty(inputId)
- || mTvInputManagerHelper.getInputState(inputId) == INPUT_STATE_DISCONNECTED) {
- return false;
- }
- TvInputInfo inputInfo = mTvInputManagerHelper.getTvInputInfo(inputId);
- if (inputInfo == null) {
- return false;
- }
- String inputDisplayName = Utils.getDisplayNameForInput(this, inputInfo);
-
- Program program = Utils.getCurrentProgram(this, cr.getChannelUri());
- if (program == null) {
- return false;
- }
- long programDurationMs = program.getEndTimeUtcMillis() - program.getStartTimeUtcMillis();
- long programLeftTimsMs = program.getEndTimeUtcMillis() - System.currentTimeMillis();
- int programProgress = (programDurationMs <= 0) ? -1
- : 100 - (int) (programLeftTimsMs * 100 / programDurationMs);
-
- // We doesn't trust TIS to provide us with proper sized image
- Bitmap posterArtBitmap = BitmapUtils.decodeSampledBitmapFromUriString(this,
- program.getPosterArtUri(), (int) mNotificationCardMaxWidth,
- (int) mNotificationCardHeight);
- if (posterArtBitmap == null) {
- Log.e(TAG, "Failed to decode logo image for " + program.getPosterArtUri());
- return false;
- }
-
- // TODO: Consider what is needed when the logo is not loaded yet.
- Bitmap channelLogo = cr.getChannel().getLogo();
- Bitmap largeIconBitmap = (channelLogo == null) ? posterArtBitmap
- : overlayChannelLogo(cr.getChannel().getLogo(), posterArtBitmap);
- Notification notification = new Notification.Builder(this)
- .setContentIntent(notificationIntent)
- .setContentTitle(program.getTitle())
- .setContentText(inputDisplayName + " " + cr.getChannel().getDisplayName())
- .setAutoCancel(true)
- .setLargeIcon(largeIconBitmap)
- .setSmallIcon(R.drawable.ic_launcher_s)
- .setCategory(Notification.CATEGORY_RECOMMENDATION)
- .setProgress((programProgress > 0) ? 100 : 0, programProgress, false)
- .build();
- notification.color =
- getResources().getColor(R.color.recommendation_card_background);
- if (!TextUtils.isEmpty(program.getThumbnailUri())) {
- notification.extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI,
- program.getThumbnailUri());
- }
- if (programProgress < RECOMMENDATION_THRESHOLD_PROGRESS
- || programLeftTimsMs > RECOMMENDATION_THRESHOLD_LEFT_TIME_MS) {
- mNotificationManager.notify(NOTIFY_TAG, notifyId, notification);
- Message msg = mHandler.obtainMessage(
- MSG_UPDATE_RECOMMENDATION, notifyId, 0, cr);
- // TODO: Need to decide we want to update the program progress or not.
- mHandler.sendMessageDelayed(msg, programDurationMs / MAX_PROGRAM_UPDATE_COUNT);
- return true;
- }
- return false;
- }
-
- private Bitmap overlayChannelLogo(Bitmap logo, Bitmap background) {
- final int NOTIF_CARD_IMG_HEIGHT =
- getResources().getDimensionPixelSize(R.dimen.notif_card_img_height);
- final int NOTIF_CARD_IMG_MAX_WIDTH =
- getResources().getDimensionPixelSize(R.dimen.notif_card_img_max_width);
- final int NOTIF_CARD_IMG_MIN_WIDTH =
- getResources().getDimensionPixelSize(R.dimen.notif_card_img_min_width);
- final int NOTIF_CH_LOGO_MAX_HEIGHT =
- getResources().getDimensionPixelSize(R.dimen.notif_ch_logo_max_height);
- final int NOTIF_CH_LOGO_MAX_WIDTH =
- getResources().getDimensionPixelSize(R.dimen.notif_ch_logo_max_width);
- final int NOTIF_CH_LOGO_PADDING_LEFT =
- getResources().getDimensionPixelSize(R.dimen.notif_ch_logo_padding_left);
- final int NOTIF_CH_LOGO_PADDING_BOTTOM =
- getResources().getDimensionPixelSize(R.dimen.notif_ch_logo_padding_bottom);
-
- Bitmap result = BitmapUtils.scaleBitmap(
- background, Integer.MAX_VALUE, NOTIF_CARD_IMG_HEIGHT);
- Bitmap scaledLogo = BitmapUtils.scaleBitmap(
- logo, NOTIF_CH_LOGO_MAX_WIDTH, NOTIF_CH_LOGO_MAX_HEIGHT);
- Canvas canvas = new Canvas(result);
- canvas.drawBitmap(result, new Matrix(), null);
- Rect rect = new Rect();
- if (result.getWidth() < NOTIF_CARD_IMG_MIN_WIDTH) {
- // TODO: check the positions.
- rect.left = NOTIF_CH_LOGO_PADDING_LEFT;
- rect.right = rect.left + scaledLogo.getWidth();
- rect.bottom = result.getHeight() - NOTIF_CH_LOGO_PADDING_BOTTOM;
- rect.top = rect.bottom - scaledLogo.getHeight();
- } else if (result.getWidth() < NOTIF_CARD_IMG_MAX_WIDTH) {
- rect.left = NOTIF_CH_LOGO_PADDING_LEFT;
- rect.right = rect.left + scaledLogo.getWidth();
- rect.bottom = result.getHeight() - NOTIF_CH_LOGO_PADDING_BOTTOM;
- rect.top = rect.bottom - scaledLogo.getHeight();
- } else {
- int marginLeft = (result.getWidth() - NOTIF_CARD_IMG_MAX_WIDTH) / 2;
- rect.left = NOTIF_CH_LOGO_PADDING_LEFT + marginLeft;
- rect.right = rect.left + scaledLogo.getWidth() + marginLeft;
- rect.bottom = result.getHeight() - NOTIF_CH_LOGO_PADDING_BOTTOM;
- rect.top = rect.bottom - scaledLogo.getHeight();
- }
- Paint paint = new Paint();
- paint.setAlpha(getResources().getInteger(R.integer.notif_card_ch_logo_alpha));
- canvas.drawBitmap(scaledLogo, null, rect, paint);
- return result;
- }
-}
diff --git a/src/com/android/tv/parental/ContentRatingLevelPolicy.java b/src/com/android/tv/parental/ContentRatingLevelPolicy.java
new file mode 100644
index 00000000..9bd15423
--- /dev/null
+++ b/src/com/android/tv/parental/ContentRatingLevelPolicy.java
@@ -0,0 +1,92 @@
+/*
+ * 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.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;
+
+public class ContentRatingLevelPolicy {
+ private static final int AGE_THRESHOLD_FOR_LEVEL_HIGH = 6;
+ 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() { }
+
+ public static Set<TvContentRating> getRatingsForLevel(
+ ParentalControlSettings settings, ContentRatingsManager manager,
+ @ContentRatingLevel int level) {
+ if (level == TvSettings.CONTENT_RATING_LEVEL_NONE) {
+ return new HashSet<>();
+ } else if (level == TvSettings.CONTENT_RATING_LEVEL_HIGH) {
+ return getRatingsForAge(settings, manager, AGE_THRESHOLD_FOR_LEVEL_HIGH);
+ } else if (level == TvSettings.CONTENT_RATING_LEVEL_MEDIUM) {
+ return getRatingsForAge(settings, manager, AGE_THRESHOLD_FOR_LEVEL_MEDIUM);
+ } else if (level == TvSettings.CONTENT_RATING_LEVEL_LOW) {
+ return getRatingsForAge(settings, manager, AGE_THRESHOLD_FOR_LEVEL_LOW);
+ }
+ throw new IllegalArgumentException("Unexpected rating level");
+ }
+
+ private static Set<TvContentRating> getRatingsForAge(
+ ParentalControlSettings settings, ContentRatingsManager manager, int age) {
+ Set<TvContentRating> ratings = new HashSet<>();
+
+ for (ContentRatingSystem contentRatingSystem : manager.getContentRatingSystems()) {
+ if (!settings.isContentRatingSystemEnabled(contentRatingSystem)) {
+ continue;
+ }
+ int ageLimit = age;
+ if (ageLimit == AGE_THRESHOLD_FOR_LEVEL_LOW) {
+ ageLimit = getMaxAge(contentRatingSystem);
+ }
+ for (Rating rating : contentRatingSystem.getRatings()) {
+ if (rating.getAgeHint() < ageLimit) {
+ continue;
+ }
+ 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());
+ ratings.add(tvContentRating);
+ }
+ }
+ }
+
+ return ratings;
+ }
+
+ private static int getMaxAge(ContentRatingSystem contentRatingSystem) {
+ int maxAge = 0;
+ for (Rating rating : contentRatingSystem.getRatings()) {
+ if (maxAge < rating.getAgeHint()) {
+ maxAge = rating.getAgeHint();
+ }
+ }
+ return maxAge;
+ }
+}
diff --git a/src/com/android/tv/parental/ContentRatingSystem.java b/src/com/android/tv/parental/ContentRatingSystem.java
index 7985adf5..6c00ee11 100644
--- a/src/com/android/tv/parental/ContentRatingSystem.java
+++ b/src/com/android/tv/parental/ContentRatingSystem.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,65 +16,94 @@
package com.android.tv.parental;
-import android.net.Uri;
+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;
+import java.util.Locale;
public class ContentRatingSystem {
- private final static String TAG = "ContentRatingSystem";
+ /*
+ * A comparator that implements the display order of a group of content rating systems.
+ */
+ public static final Comparator<ContentRatingSystem> DISPLAY_NAME_COMPARATOR =
+ new Comparator<ContentRatingSystem>() {
+ @Override
+ public int compare(ContentRatingSystem s1, ContentRatingSystem s2) {
+ String name1 = s1.getDisplayName();
+ String name2 = s2.getDisplayName();
+ return name1.compareTo(name2);
+ }
+ };
+
+ private static final String DELIMITER = "/";
- // Id of this content rating system. It should be unique in an XML file.
- private String mId;
+ // Name of this content rating system. It should be unique in an XML file.
+ private final String mName;
// Domain of this content rating system. It's package name now.
- private String mDomain;
+ private final String mDomain;
- // Display name of this content rating system. (e.g. TV-PG)
- private String mDisplayName;
+ // Title of this content rating system. (e.g. TV-PG)
+ private final String mTitle;
// Description of this content rating system.
- private String mDescription;
+ private final String mDescription;
// Country code of this content rating system.
- private String mCountry;
+ private final List<String> mCountries;
+
+ // Display name of this content rating system consisting of the associated country
+ // and its title. For example, "Canada (French)"
+ private final String mDisplayName;
// Ordered list of main content ratings. UX should respect the order.
- private List<Rating> mRatings;
+ private final List<Rating> mRatings;
// Ordered list of sub content ratings. UX should respect the order.
- private List<SubRating> mSubRatings;
+ private final List<SubRating> mSubRatings;
// List of orders. This describes the automatic lock/unlock relationship between ratings.
// For example, let say we have following order.
// <order>
- // <rating id="US_TVPG_Y" />
- // <rating id="US_TVPG_Y7" />
+ // <rating android:name="US_TVPG_Y" />
+ // <rating android:name="US_TVPG_Y7" />
// </order>
// This means that locking US_TVPG_Y7 automatically locks US_TVPG_Y and
// unlocking US_TVPG_Y automatically unlocks US_TVPG_Y7 from the UX.
// An user can still unlock US_TVPG_Y while US_TVPG_Y7 is locked by manually.
- private List<Order> mOrders;
+ private final List<Order> mOrders;
+
+ private final boolean mIsCustom;
- public String getId(){
- return mId;
+ public String getId() {
+ return mDomain + DELIMITER + mName;
+ }
+
+ public String getName(){
+ return mName;
}
public String getDomain() {
return mDomain;
}
- public String getDisplayName(){
- return mDisplayName;
+ public String getTitle(){
+ return mTitle;
}
public String getDescription(){
return mDescription;
}
- public String getCountry(){
- return mCountry;
+ public List<String> getCountries(){
+ return mCountries;
}
public List<Rating> getRatings(){
@@ -89,52 +118,97 @@ public class ContentRatingSystem {
return mOrders;
}
+ /**
+ * Returns the display name of the content rating system consisting of the associated country
+ * and its title. For example, "Canada (French)".
+ */
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ public boolean isCustom() {
+ return mIsCustom;
+ }
+
+ /**
+ * 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());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ContentRatingSystem) {
+ ContentRatingSystem other = (ContentRatingSystem) obj;
+ return this.mName.equals(other.mName) && this.mDomain.equals(other.mDomain);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * mName.hashCode() + mDomain.hashCode();
+ }
+
private ContentRatingSystem(
- String id, String domain, String displayName, String description, String country,
- List<Rating> ratings, List<SubRating> subRatings, List<Order> orders) {
- mId = id;
+ String name, String domain, String title, String description, List<String> countries,
+ String displayName, List<Rating> ratings, List<SubRating> subRatings,
+ List<Order> orders, boolean isCustom) {
+ mName = name;
mDomain = domain;
- mDisplayName = displayName;
+ mTitle = title;
mDescription = description;
- mCountry = country;
+ mCountries = countries;
+ mDisplayName = displayName;
mRatings = ratings;
mSubRatings = subRatings;
mOrders = orders;
+ mIsCustom = isCustom;
}
public static class Builder {
- private String mId;
+ private final Context mContext;
+ private String mName;
private String mDomain;
- private String mDisplayName;
+ private String mTitle;
private String mDescription;
- private String mCountry;
- private List<Rating.Builder> mRatingBuilders = new ArrayList<Rating.Builder>();
- private List<SubRating.Builder> mSubRatingBuilders = new ArrayList<SubRating.Builder>();
- private List<Order.Builder> mOrderBuilders = new ArrayList<Order.Builder>();
+ private List<String> mCountries;
+ private final List<Rating.Builder> mRatingBuilders = new ArrayList<>();
+ private final List<SubRating.Builder> mSubRatingBuilders = new ArrayList<>();
+ private final List<Order.Builder> mOrderBuilders = new ArrayList<>();
+ private boolean mIsCustom;
+
+ public Builder(Context context) {
+ mContext = context;
+ }
- public void setId(String id) {
- mId = id;
+ public void setName(String name) {
+ mName = name;
}
public void setDomain(String domain) {
mDomain = domain;
}
- public void setDisplayName(String displayName) {
- mDisplayName = displayName;
+ public void setTitle(String title) {
+ mTitle = title;
}
public void setDescription(String description) {
mDescription = description;
}
- public void setCountry(String country) {
- mCountry = country;
+ public void addCountry(String country) {
+ if (mCountries == null) {
+ mCountries = new ArrayList<>();
+ }
+ mCountries.add(new Locale("", country).getCountry());
}
public void addRatingBuilder(Rating.Builder ratingBuilder) {
// To provide easy access to the SubRatings in it,
- // Rating has reference to SubRating, not Id of it.
+ // Rating has reference to SubRating, not Name of it.
// (Note that Rating/SubRating is ordered list so we cannot use Map)
// To do so, we need to have list of all SubRatings which might not be available
// at this moment. Keep builders here and build it with SubRatings later.
@@ -148,22 +222,48 @@ public class ContentRatingSystem {
public void addOrderBuilder(Order.Builder orderBuilder) {
// To provide easy access to the Ratings in it,
- // Order has reference to Rating, not Id of it.
+ // Order has reference to Rating, not Name of it.
// (Note that Rating/SubRating is ordered list so we cannot use Map)
// To do so, we need to have list of all Rating which might not be available
// at this moment. Keep builders here and build it with Ratings later.
mOrderBuilders.add(orderBuilder);
}
+ public void setIsCustom(boolean isCustom) {
+ mIsCustom = isCustom;
+ }
+
public ContentRatingSystem build() {
- if (TextUtils.isEmpty(mId)) {
- throw new IllegalArgumentException("Id cannot be empty");
+ if (TextUtils.isEmpty(mName)) {
+ throw new IllegalArgumentException("Name cannot be empty");
}
if (TextUtils.isEmpty(mDomain)) {
throw new IllegalArgumentException("Domain cannot be empty");
}
- List<SubRating> subRatings = new ArrayList<SubRating>();
+ StringBuilder sb = new StringBuilder();
+ if (mCountries != null) {
+ if (mCountries.size() == 1) {
+ sb.append(new Locale("", mCountries.get(0)).getDisplayCountry());
+ } else if (mCountries.size() > 1) {
+ Locale locale = Locale.getDefault();
+ if (mCountries.contains(locale.getCountry())) {
+ // Shows the country name instead of "Other countries" if the current
+ // country is one of the countries this rating system applies to.
+ sb.append(locale.getDisplayCountry());
+ } else {
+ sb.append(mContext.getString(R.string.other_countries));
+ }
+ }
+ }
+ if (!TextUtils.isEmpty(mTitle)) {
+ sb.append(" (");
+ sb.append(mTitle);
+ sb.append(")");
+ }
+ String displayName = sb.toString();
+
+ List<SubRating> subRatings = new ArrayList<>();
if (mSubRatingBuilders != null) {
for (SubRating.Builder builder : mSubRatingBuilders) {
subRatings.add(builder.build());
@@ -173,217 +273,214 @@ public class ContentRatingSystem {
if (mRatingBuilders.size() <= 0) {
throw new IllegalArgumentException("Rating isn't available.");
}
- List<Rating> ratings = new ArrayList<Rating>();
- if (mRatingBuilders != null) {
- // Map string ID to object.
- for (Rating.Builder builder : mRatingBuilders) {
- ratings.add(builder.build(subRatings));
- }
+ List<Rating> ratings = new ArrayList<>();
+ // Map string ID to object.
+ for (Rating.Builder builder : mRatingBuilders) {
+ ratings.add(builder.build(subRatings));
+ }
- // Sanity check.
- for (SubRating subRating : subRatings) {
- boolean used = false;
- for (Rating rating : ratings) {
- if (rating.getSubRatings().contains(subRating)) {
- used = true;
- break;
- }
- }
- if (!used) {
- throw new IllegalArgumentException("Subrating " + subRating.getId() +
- " isn't used by any rating");
+ // Sanity check.
+ for (SubRating subRating : subRatings) {
+ boolean used = false;
+ for (Rating rating : ratings) {
+ if (rating.getSubRatings().contains(subRating)) {
+ used = true;
+ break;
}
}
+ if (!used) {
+ throw new IllegalArgumentException("Subrating " + subRating.getName() +
+ " isn't used by any rating");
+ }
}
- List<Order> orders = new ArrayList<Order>();
+ List<Order> orders = new ArrayList<>();
if (mOrderBuilders != null) {
for (Order.Builder builder : mOrderBuilders) {
orders.add(builder.build(ratings));
}
}
- return new ContentRatingSystem(mId, mDomain, mDisplayName, mDescription, mCountry,
- ratings, subRatings, orders);
+ return new ContentRatingSystem(mName, mDomain, mTitle, mDescription, mCountries,
+ displayName, ratings, subRatings, orders, mIsCustom);
}
}
public static class Rating {
- private String mId;
- private String mDisplayName;
- private String mDescription;
- private Uri mIconUri;
- private int mAgeHint;
- private List<SubRating> mSubRatings;
-
- public String getId() {
- return mId;
+ private final String mName;
+ private final String mTitle;
+ private final String mDescription;
+ private final Drawable mIcon;
+ private final int mContentAgeHint;
+ private final List<SubRating> mSubRatings;
+
+ public String getName() {
+ return mName;
}
- public String getDisplayName() {
- return mDisplayName;
+ public String getTitle() {
+ return mTitle;
}
public String getDescription() {
return mDescription;
}
- public Uri getIconUri() {
- return mIconUri;
+ public Drawable getIcon() {
+ return mIcon;
}
public int getAgeHint() {
- return mAgeHint;
+ return mContentAgeHint;
}
public List<SubRating> getSubRatings() {
return mSubRatings;
}
- private Rating(String id, String displayName, String description, Uri iconUri,
- int ageHint, List<SubRating> subRatings) {
- mId = id;
- mDisplayName = displayName;
+ private Rating(String name, String title, String description, Drawable icon,
+ int contentAgeHint, List<SubRating> subRatings) {
+ mName = name;
+ mTitle = title;
mDescription = description;
- mIconUri = iconUri;
- mAgeHint = ageHint;
+ mIcon = icon;
+ mContentAgeHint = contentAgeHint;
mSubRatings = subRatings;
}
public static class Builder {
- private String mId;
- private String mDisplayName;
+ private String mName;
+ private String mTitle;
private String mDescription;
- private Uri mIconUri;
- private Integer mAgeHint;
- private List<String> mSubRatingIds = new ArrayList<String>();
+ private Drawable mIcon;
+ private int mContentAgeHint = -1;
+ private final List<String> mSubRatingNames = new ArrayList<>();
public Builder() {
}
- public void setId(String id) {
- mId = id;
+ public void setName(String name) {
+ mName = name;
}
- public void setDisplayName(String displayName) {
- mDisplayName = displayName;
+ public void setTitle(String title) {
+ mTitle = title;
}
public void setDescription(String description) {
mDescription = description;
}
- public void setIconUri(Uri iconUri) {
- mIconUri = iconUri;
+ public void setIcon(Drawable icon) {
+ mIcon = icon;
}
- public void setAgeHint(int ageHint) {
- mAgeHint = (mAgeHint == null) ? new Integer(ageHint) : (Integer) ageHint;
+ public void setContentAgeHint(int contentAgeHint) {
+ mContentAgeHint = contentAgeHint;
}
- public void addSubRatingId(String subRatingId) {
- mSubRatingIds.add(subRatingId);
+ public void addSubRatingName(String subRatingName) {
+ mSubRatingNames.add(subRatingName);
}
private Rating build(List<SubRating> allDefinedSubRatings) {
- if (TextUtils.isEmpty(mId)) {
- throw new IllegalArgumentException("A rating should have non-empty id");
+ if (TextUtils.isEmpty(mName)) {
+ throw new IllegalArgumentException("A rating should have non-empty name");
}
- if (allDefinedSubRatings == null && mSubRatingIds.size() > 0) {
- throw new IllegalArgumentException("Invalid subrating for rating " +
- mId);
+ if (allDefinedSubRatings == null && mSubRatingNames.size() > 0) {
+ throw new IllegalArgumentException("Invalid subrating for rating " + mName);
}
- if (mAgeHint == null || mAgeHint < 0) {
- throw new IllegalArgumentException("Rating " + mId + " should define " +
- "non-negative ageHint");
+ if (mContentAgeHint < 0) {
+ throw new IllegalArgumentException("Rating " + mName + " should define " +
+ "non-negative contentAgeHint");
}
- List<SubRating> subRatings = new ArrayList<SubRating>();
- for (String subRatingId : mSubRatingIds) {
+ List<SubRating> subRatings = new ArrayList<>();
+ for (String subRatingId : mSubRatingNames) {
boolean found = false;
for (SubRating subRating : allDefinedSubRatings) {
- if (subRatingId.equals(subRating.getId())) {
+ if (subRatingId.equals(subRating.getName())) {
found = true;
subRatings.add(subRating);
break;
}
}
if (!found) {
- throw new IllegalArgumentException("Unknown subrating Id " + subRatingId +
- " in rating " + mId);
+ throw new IllegalArgumentException("Unknown subrating name " + subRatingId +
+ " in rating " + mName);
}
}
return new Rating(
- mId, mDisplayName, mDescription, mIconUri, (int) mAgeHint, subRatings);
+ mName, mTitle, mDescription, mIcon, mContentAgeHint, subRatings);
}
}
}
public static class SubRating {
- private String mId;
- private String mDisplayName;
- private String mDescription;
- private Uri mIconUri;
+ private final String mName;
+ private final String mTitle;
+ private final String mDescription;
+ private final Drawable mIcon;
- public String getId() {
- return mId;
+ public String getName() {
+ return mName;
}
- public String getDisplayName() {
- return mDisplayName;
+ public String getTitle() {
+ return mTitle;
}
public String getDescription() {
return mDescription;
}
- public Uri getIconUri() {
- return mIconUri;
+ public Drawable getIcon() {
+ return mIcon;
}
- private SubRating(String id, String displayName, String description, Uri iconUri) {
- mId = id;
- mDisplayName = displayName;
+ private SubRating(String name, String title, String description, Drawable icon) {
+ mName = name;
+ mTitle = title;
mDescription = description;
- mIconUri = iconUri;
+ mIcon = icon;
}
public static class Builder {
- private String mId;
- private String mDisplayName;
+ private String mName;
+ private String mTitle;
private String mDescription;
- private Uri mIconUri;
+ private Drawable mIcon;
public Builder() {
}
- public void setId(String id) {
- mId = id;
+ public void setName(String name) {
+ mName = name;
}
- public void setDisplayName(String displayName) {
- mDisplayName = displayName;
+ public void setTitle(String title) {
+ mTitle = title;
}
public void setDescription(String description) {
mDescription = description;
}
- public void setIconUri(Uri iconUri) {
- mIconUri = iconUri;
+ public void setIcon(Drawable icon) {
+ mIcon = icon;
}
private SubRating build() {
- if (TextUtils.isEmpty(mId)) {
- throw new IllegalArgumentException("A subrating should have non-empty id");
+ if (TextUtils.isEmpty(mName)) {
+ throw new IllegalArgumentException("A subrating should have non-empty name");
}
- return new SubRating(mId, mDisplayName, mDescription, mIconUri);
+ return new SubRating(mName, mTitle, mDescription, mIcon);
}
}
}
public static class Order {
- private List<Rating> mRatingOrder;
+ private final List<Rating> mRatingOrder;
public List<Rating> getRatingOrder() {
return mRatingOrder;
@@ -394,17 +491,17 @@ public class ContentRatingSystem {
}
public static class Builder {
- private final List<String> mRatingIds = new ArrayList<String>();
+ private final List<String> mRatingNames = new ArrayList<>();
public Builder() {
}
private Order build(List<Rating> ratings) {
- List<Rating> ratingOrder = new ArrayList<Rating>();
- for (String ratingId : mRatingIds) {
+ List<Rating> ratingOrder = new ArrayList<>();
+ for (String ratingName : mRatingNames) {
boolean found = false;
for (Rating rating : ratings) {
- if (ratingId.equals(rating.getId())) {
+ if (ratingName.equals(rating.getName())) {
found = true;
ratingOrder.add(rating);
break;
@@ -412,16 +509,16 @@ public class ContentRatingSystem {
}
if (!found) {
- throw new IllegalArgumentException("Unknown rating " + ratingId +
- " in order tag");
+ throw new IllegalArgumentException("Unknown rating " + ratingName +
+ " in rating-order tag");
}
}
return new Order(ratingOrder);
}
- public void addRatingId(String ratingId) {
- mRatingIds.add(ratingId);
+ public void addRatingName(String name) {
+ mRatingNames.add(name);
}
}
}
diff --git a/src/com/android/tv/parental/ContentRatingsManager.java b/src/com/android/tv/parental/ContentRatingsManager.java
index acc33e51..57c25f48 100644
--- a/src/com/android/tv/parental/ContentRatingsManager.java
+++ b/src/com/android/tv/parental/ContentRatingsManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -17,36 +17,100 @@
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.net.Uri;
+
+import com.android.tv.parental.ContentRatingSystem.Rating;
+import com.android.tv.parental.ContentRatingSystem.SubRating;
import java.util.ArrayList;
import java.util.List;
public class ContentRatingsManager {
- private final List<ContentRatingSystem> mContenRatings = new ArrayList<ContentRatingSystem>();
+ private final List<ContentRatingSystem> mContentRatingSystems = new ArrayList<>();
- private Context mContext;
+ private final Context mContext;
public ContentRatingsManager(Context context) {
mContext = context;
}
public void update() {
- mContenRatings.clear();
+ mContentRatingSystems.clear();
TvInputManager manager =
(TvInputManager) mContext.getSystemService(Context.TV_INPUT_SERVICE);
- List<Uri> uris = manager.getTvContentRatingSystemXmls();
- for (Uri uri : uris) {
- List<ContentRatingSystem> list = ContentRatingsParser.parse(mContext, uri);
+ ContentRatingsParser parser = new ContentRatingsParser(mContext);
+
+ List<TvContentRatingSystemInfo> infos = manager.getTvContentRatingSystemList();
+ for (TvContentRatingSystemInfo info : infos) {
+ List<ContentRatingSystem> list = parser.parse(info);
if (list != null) {
- mContenRatings.addAll(list);
+ mContentRatingSystems.addAll(list);
}
}
}
+ /**
+ * Returns a new list of all content rating systems defined.
+ */
public List<ContentRatingSystem> getContentRatingSystems() {
- return mContenRatings;
+ return new ArrayList<>(mContentRatingSystems);
+ }
+
+ /**
+ * Returns the long name of a given content rating including descriptors (sub-ratings) that is
+ * displayed to the user. For example, "TV-PG (L, S)".
+ */
+ public String getDisplayNameForRating(TvContentRating canonicalRating) {
+ Rating rating = getRating(canonicalRating);
+ if (rating == null) {
+ return null;
+ }
+ List<SubRating> subRatings = getSubRatings(rating, canonicalRating);
+ if (!subRatings.isEmpty()) {
+ StringBuilder builder = new StringBuilder();
+ for (SubRating subRating : subRatings) {
+ builder.append(subRating.getTitle());
+ builder.append(", ");
+ }
+ return rating.getTitle() + " (" + builder.substring(0, builder.length() - 2) + ")";
+ }
+ return rating.getTitle();
+ }
+
+ private Rating getRating(TvContentRating canonicalRating) {
+ if (canonicalRating == null || mContentRatingSystems == null) {
+ return null;
+ }
+ for (ContentRatingSystem system : mContentRatingSystems) {
+ if (system.getDomain().equals(canonicalRating.getDomain())
+ && system.getName().equals(canonicalRating.getRatingSystem())) {
+ for (Rating rating : system.getRatings()) {
+ if (rating.getName().equals(canonicalRating.getMainRating())) {
+ return rating;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private List<SubRating> getSubRatings(Rating rating, TvContentRating canonicalRating) {
+ List<SubRating> subRatings = new ArrayList<>();
+ if (rating == null || rating.getSubRatings() == null
+ || canonicalRating == null || canonicalRating.getSubRatings() == null) {
+ return subRatings;
+ }
+ for (String subRatingString : canonicalRating.getSubRatings()) {
+ for (SubRating subRating : rating.getSubRatings()) {
+ if (subRating.getName().equals(subRatingString)) {
+ subRatings.add(subRating);
+ break;
+ }
+ }
+ }
+ return subRatings;
}
}
diff --git a/src/com/android/tv/parental/ContentRatingsParser.java b/src/com/android/tv/parental/ContentRatingsParser.java
index b999f938..d9f62473 100644
--- a/src/com/android/tv/parental/ContentRatingsParser.java
+++ b/src/com/android/tv/parental/ContentRatingsParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,318 +16,421 @@
package com.android.tv.parental;
-import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
import android.content.res.XmlResourceParser;
+import android.media.tv.TvContentRatingSystemInfo;
import android.net.Uri;
import android.util.Log;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
+import com.android.tv.parental.ContentRatingSystem.Order;
import com.android.tv.parental.ContentRatingSystem.Rating;
import com.android.tv.parental.ContentRatingSystem.SubRating;
-import com.android.tv.parental.ContentRatingSystem.Order;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
public class ContentRatingsParser {
- private final static String TAG = "ContentRatingsParser";
-
- private final static String TAG_RATING_SYSTEM_DEFINITIONS = "rating-system-definitions";
- private final static String TAG_RATING_SYSTEM_DEFINITION = "rating-system-definition";
- private final static String TAG_SUB_RATING_DEFINITION = "sub-rating-definition";
- private final static String TAG_RATING_DEFINITION = "rating-definition";
- private final static String TAG_SUB_RATING = "sub-rating";
- private final static String TAG_ORDER = "order";
- private final static String TAG_RATING = "rating";
-
- private final static String ATTR_ID = "id";
- private final static String ATTR_DISPLAY_NAME = "displayName";
- private final static String ATTR_COUNTRY = "country";
- private final static String ATTR_ICON = "icon";
- private final static String ATTR_DESCRIPTION = "description";
- private final static String ATTR_AGE_HINT = "ageHint";
-
- private ContentRatingsParser() {
- // Prevent instantiation.
+ private static final String TAG = "ContentRatingsParser";
+ private static final boolean DEBUG = false;
+
+ public static final String DOMAIN_SYSTEM_RATINGS = "com.android.tv";
+
+ private static final String TAG_RATING_SYSTEM_DEFINITIONS = "rating-system-definitions";
+ private static final String TAG_RATING_SYSTEM_DEFINITION = "rating-system-definition";
+ private static final String TAG_SUB_RATING_DEFINITION = "sub-rating-definition";
+ private static final String TAG_RATING_DEFINITION = "rating-definition";
+ private static final String TAG_SUB_RATING = "sub-rating";
+ private static final String TAG_RATING = "rating";
+ private static final String TAG_RATING_ORDER = "rating-order";
+
+ private static final String ATTR_VERSION_CODE = "versionCode";
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_TITLE = "title";
+ private static final String ATTR_COUNTRY = "country";
+ private static final String ATTR_ICON = "icon";
+ private static final String ATTR_DESCRIPTION = "description";
+ private static final String ATTR_CONTENT_AGE_HINT = "contentAgeHint";
+ private static final String VERSION_CODE = "1";
+
+ private final Context mContext;
+ private Resources mResources;
+ private String mXmlVersionCode;
+
+ public ContentRatingsParser(Context context) {
+ mContext = context;
}
- public static List<ContentRatingSystem> parse(Context context, Uri uri) {
+ public List<ContentRatingSystem> parse(TvContentRatingSystemInfo info) {
List<ContentRatingSystem> ratingSystems = null;
- XmlResourceParser parser = null;
+ Uri uri = info.getXmlUri();
+ if (DEBUG) Log.d(TAG, "Parsing rating system for " + uri);
try {
- if (!uri.getScheme().equals(ContentResolver.SCHEME_ANDROID_RESOURCE)) {
- throw new IllegalArgumentException("Invalid URI scheme " + uri);
- }
String packageName = uri.getAuthority();
int resId = (int) ContentUris.parseId(uri);
- parser = context.getPackageManager().getXml(packageName, resId, null);
- if (parser == null) {
- throw new IllegalArgumentException("Cannot get XML with URI " + uri);
+ try (XmlResourceParser parser = mContext.getPackageManager()
+ .getXml(packageName, resId, null)) {
+ if (parser == null) {
+ throw new IllegalArgumentException("Cannot get XML with URI " + uri);
+ }
+ ratingSystems = parse(parser, packageName, !info.isSystemDefined());
}
- ratingSystems = parse(parser, packageName);
} catch (Exception e) {
// Catching all exceptions and print which URI is malformed XML with description
// and stack trace here.
- // TODO: We may want to print message to stdout. see b/16803331
+ // TODO: We may want to print message to stdout.
Log.w(TAG, "Error parsing XML " + uri, e);
- } finally {
- if (parser != null) {
- parser.close();
- }
}
-
return ratingSystems;
}
- private static void assertEquals(int a, int b, String msg) throws XmlPullParserException {
- if (a != b) {
- throw new XmlPullParserException(msg);
+ private List<ContentRatingSystem> parse(XmlResourceParser parser, String domain,
+ boolean isCustom)
+ throws XmlPullParserException, IOException {
+ try {
+ mResources = mContext.getPackageManager().getResourcesForApplication(domain);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Failed to get resources for " + domain, e);
+ mResources = mContext.getResources();
}
- }
-
- private static void assertEquals(String a, String b, String msg) throws XmlPullParserException {
- if (!b.equals(a)) {
- throw new XmlPullParserException(msg);
+ // TODO: find another way to replace the domain the content rating systems defined in TV.
+ // Live TV app provides public content rating systems. Therefore, the domain of
+ // the content rating systems defined in TV app should be com.android.tv instead of
+ // this app's package name.
+ if (domain.equals(mContext.getPackageName())) {
+ domain = DOMAIN_SYSTEM_RATINGS;
}
- }
- private static List<ContentRatingSystem> parse(XmlResourceParser parser, String domain)
- throws XmlPullParserException, IOException {
// Consume all START_DOCUMENT which can appear more than once.
- while (parser.next() == XmlPullParser.START_DOCUMENT);
+ while (parser.next() == XmlPullParser.START_DOCUMENT) {}
int eventType = parser.getEventType();
assertEquals(eventType, XmlPullParser.START_TAG, "Malformed XML: Not a valid XML file");
assertEquals(parser.getName(), TAG_RATING_SYSTEM_DEFINITIONS,
"Malformed XML: Should start with tag " + TAG_RATING_SYSTEM_DEFINITIONS);
- List<ContentRatingSystem> ratingSystems = new ArrayList<ContentRatingSystem>();
- while (true) {
- eventType = parser.nextTag();
+ boolean hasVersionAttr = false;
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ String attr = parser.getAttributeName(i);
+ if (ATTR_VERSION_CODE.equals(attr)) {
+ hasVersionAttr = true;
+ mXmlVersionCode = parser.getAttributeValue(i);
+ }
+ }
+ if (!hasVersionAttr) {
+ throw new XmlPullParserException("Malformed XML: Should contains a version attribute"
+ + " in " + TAG_RATING_SYSTEM_DEFINITIONS);
+ }
- switch (eventType) {
+ List<ContentRatingSystem> ratingSystems = new ArrayList<>();
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ switch (parser.getEventType()) {
case XmlPullParser.START_TAG:
- assertEquals(parser.getName(), TAG_RATING_SYSTEM_DEFINITION,
- "Malformed XML: Should contains " +
- TAG_RATING_SYSTEM_DEFINITION);
- ratingSystems.add(parseRatingSystem(parser, domain));
+ if (TAG_RATING_SYSTEM_DEFINITION.equals(parser.getName())) {
+ ratingSystems.add(parseRatingSystemDefinition(parser, domain, isCustom));
+ } else {
+ checkVersion("Malformed XML: Should contains " +
+ TAG_RATING_SYSTEM_DEFINITION);
+ }
break;
case XmlPullParser.END_TAG:
- assertEquals(parser.getName(), TAG_RATING_SYSTEM_DEFINITIONS,
- "Malformed XML: Should end with tag " +
- TAG_RATING_SYSTEM_DEFINITIONS);
- eventType = parser.next();
- assertEquals(eventType, XmlPullParser.END_DOCUMENT,
- "Malformed XML: Should end with tag " +
- TAG_RATING_SYSTEM_DEFINITIONS);
- return ratingSystems;
- default:
- throw new XmlPullParserException("Malformed XML: Error in " +
- TAG_RATING_SYSTEM_DEFINITIONS);
+ 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);
+ return ratingSystems;
+ } else {
+ 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");
}
- private static ContentRatingSystem parseRatingSystem(XmlResourceParser parser, String domain)
- throws XmlPullParserException, IOException {
- ContentRatingSystem.Builder builder = new ContentRatingSystem.Builder();
+ private static void assertEquals(int a, int b, String msg) throws XmlPullParserException {
+ if (a != b) {
+ throw new XmlPullParserException(msg);
+ }
+ }
+
+ private static void assertEquals(String a, String b, String msg) throws XmlPullParserException {
+ if (!b.equals(a)) {
+ throw new XmlPullParserException(msg);
+ }
+ }
+
+ private void checkVersion(String msg) throws XmlPullParserException {
+ if (!VERSION_CODE.equals(mXmlVersionCode)) {
+ throw new XmlPullParserException(msg);
+ }
+ }
+
+ private ContentRatingSystem parseRatingSystemDefinition(XmlResourceParser parser, String domain,
+ boolean isCustom) throws XmlPullParserException, IOException {
+ ContentRatingSystem.Builder builder = new ContentRatingSystem.Builder(mContext);
builder.setDomain(domain);
for (int i = 0; i < parser.getAttributeCount(); i++) {
String attr = parser.getAttributeName(i);
switch (attr) {
- case ATTR_ID:
- builder.setId(parser.getAttributeValue(i));
+ case ATTR_NAME:
+ builder.setName(parser.getAttributeValue(i));
break;
case ATTR_COUNTRY:
- builder.setCountry(parser.getAttributeValue(i));
+ for (String country : parser.getAttributeValue(i).split("\\s*,\\s*")) {
+ builder.addCountry(country);
+ }
break;
- case ATTR_DISPLAY_NAME:
- builder.setDisplayName(parser.getAttributeValue(i));
+ case ATTR_TITLE:
+ builder.setTitle(getTitle(parser, i));
break;
case ATTR_DESCRIPTION:
- builder.setDisplayName(parser.getAttributeValue(i));
+ builder.setDescription(
+ mResources.getString(parser.getAttributeResourceValue(i, 0)));
break;
default:
- throw new XmlPullParserException("Malformed XML: Unknown attribute " + attr +
- " in " + TAG_RATING_SYSTEM_DEFINITIONS);
+ checkVersion("Malformed XML: Unknown attribute " + attr + " in " +
+ TAG_RATING_SYSTEM_DEFINITION);
}
}
- while (true) {
- int eventType = parser.nextTag();
- switch (eventType) {
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ switch (parser.getEventType()) {
case XmlPullParser.START_TAG:
String tag = parser.getName();
switch (tag) {
case TAG_RATING_DEFINITION:
- builder.addRatingBuilder(parseRating(parser));
+ builder.addRatingBuilder(parseRatingDefinition(parser));
break;
case TAG_SUB_RATING_DEFINITION:
- builder.addSubRatingBuilder(parseSubRating(parser));
+ builder.addSubRatingBuilder(parseSubRatingDefinition(parser));
break;
- case TAG_ORDER:
+ case TAG_RATING_ORDER:
builder.addOrderBuilder(parseOrder(parser));
break;
default:
- throw new XmlPullParserException("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:
- assertEquals(parser.getName(), TAG_RATING_SYSTEM_DEFINITION,
- "Malformed XML: Tag mismatch for " + TAG_RATING_SYSTEM_DEFINITION);
- return builder.build();
- default:
- throw new XmlPullParserException("Malformed XML: Tag is expected in " +
- TAG_RATING_SYSTEM_DEFINITION);
+ if (TAG_RATING_SYSTEM_DEFINITION.equals(parser.getName())) {
+ builder.setIsCustom(isCustom);
+ return builder.build();
+ } else {
+ 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");
}
- private static Rating.Builder parseRating(XmlResourceParser parser)
+ private Rating.Builder parseRatingDefinition(XmlResourceParser parser)
throws XmlPullParserException, IOException {
Rating.Builder builder = new Rating.Builder();
for (int i = 0; i < parser.getAttributeCount(); i++) {
String attr = parser.getAttributeName(i);
switch (attr) {
- case ATTR_ID:
- builder.setId(parser.getAttributeValue(i));
+ case ATTR_NAME:
+ builder.setName(parser.getAttributeValue(i));
break;
- case ATTR_DISPLAY_NAME:
- builder.setDisplayName(parser.getAttributeValue(i));
+ case ATTR_TITLE:
+ builder.setTitle(getTitle(parser, i));
break;
case ATTR_DESCRIPTION:
- builder.setDisplayName(parser.getAttributeValue(i));
+ builder.setDescription(
+ mResources.getString(parser.getAttributeResourceValue(i, 0)));
break;
case ATTR_ICON:
- builder.setIconUri(Uri.parse(parser.getAttributeValue(i)));
+ builder.setIcon(
+ mResources.getDrawable(parser.getAttributeResourceValue(i, 0), null));
break;
- case ATTR_AGE_HINT:
- int ageHint = -1;
+ case ATTR_CONTENT_AGE_HINT:
+ int contentAgeHint = -1;
try {
- ageHint = Integer.parseInt(parser.getAttributeValue(i));
- } catch (NumberFormatException e) {
+ contentAgeHint = Integer.parseInt(parser.getAttributeValue(i));
+ } catch (NumberFormatException ignored) {
}
- if (ageHint < 0) {
- throw new XmlPullParserException("Malformed XML: " + ATTR_AGE_HINT +
+ if (contentAgeHint < 0) {
+ throw new XmlPullParserException("Malformed XML: " + ATTR_CONTENT_AGE_HINT +
" should be a non-negative number");
}
- builder.setAgeHint(ageHint);
+ builder.setContentAgeHint(contentAgeHint);
break;
default:
- throw new XmlPullParserException("Malformed XML: Unknown attribute " + attr +
- " in " + TAG_RATING_DEFINITION);
+ checkVersion("Malformed XML: Unknown attribute " + attr + " in " +
+ TAG_RATING_DEFINITION);
}
}
- while (true) {
- int eventType = parser.nextTag();
- switch (eventType) {
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ switch (parser.getEventType()) {
case XmlPullParser.START_TAG:
- assertEquals(parser.getName(), TAG_SUB_RATING,
- "Malformed XML: Only " + TAG_SUB_RATING + " is allowed in " +
- TAG_RATING_DEFINITION);
- if (parser.getAttributeCount() != 1 ||
- !ATTR_ID.equals(parser.getAttributeName(0))) {
- throw new XmlPullParserException("Malformed XML: " + TAG_SUB_RATING +
- " should only contain " + ATTR_ID);
- }
- builder.addSubRatingId(parser.getAttributeValue(0));
- eventType = parser.nextTag();
- if (eventType != XmlPullParser.END_TAG ||
- !TAG_SUB_RATING.equals(parser.getName())) {
- throw new XmlPullParserException("Malformed XML: " + TAG_SUB_RATING +
- " has child");
+ 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));
}
break;
case XmlPullParser.END_TAG:
- assertEquals(parser.getName(), TAG_RATING_DEFINITION,
- "Malformed XML: Tag mismatch for " + TAG_RATING_DEFINITION);
- return builder;
-
- default:
- throw new XmlPullParserException("Malformed XML: Error in " +
- TAG_RATING_DEFINITION);
+ if (TAG_RATING_DEFINITION.equals(parser.getName())) {
+ return builder;
+ } else {
+ checkVersion("Malformed XML: Tag mismatch for " + TAG_RATING_DEFINITION);
+ }
}
}
+ throw new XmlPullParserException(TAG_RATING_DEFINITION +
+ " section is incomplete or section ending tag is missing");
}
- private static SubRating.Builder parseSubRating(XmlResourceParser parser)
+ private SubRating.Builder parseSubRatingDefinition(XmlResourceParser parser)
throws XmlPullParserException, IOException {
SubRating.Builder builder = new SubRating.Builder();
for (int i = 0; i < parser.getAttributeCount(); i++) {
String attr = parser.getAttributeName(i);
switch (attr) {
- case ATTR_ID:
- builder.setId(parser.getAttributeValue(i));
+ case ATTR_NAME:
+ builder.setName(parser.getAttributeValue(i));
break;
- case ATTR_DISPLAY_NAME:
- builder.setDisplayName(parser.getAttributeValue(i));
+ case ATTR_TITLE:
+ builder.setTitle(getTitle(parser, i));
break;
case ATTR_DESCRIPTION:
- builder.setDisplayName(parser.getAttributeValue(i));
+ builder.setDescription(
+ mResources.getString(parser.getAttributeResourceValue(i, 0)));
break;
case ATTR_ICON:
- builder.setIconUri(Uri.parse(parser.getAttributeValue(i)));
+ builder.setIcon(
+ mResources.getDrawable(parser.getAttributeResourceValue(i, 0), null));
break;
default:
- throw new XmlPullParserException("Malformed XML: Unknown attribute " + attr +
- " in " + TAG_SUB_RATING_DEFINITION);
+ checkVersion("Malformed XML: Unknown attribute " + attr + " in " +
+ TAG_SUB_RATING_DEFINITION);
}
}
- assertEquals(parser.nextTag(), XmlPullParser.END_TAG,
- "Malformed XML: " + TAG_SUB_RATING_DEFINITION + " has child");
- assertEquals(parser.getName(), TAG_SUB_RATING_DEFINITION,
- "Malformed XML: " + TAG_SUB_RATING_DEFINITION + " isn't closed");
-
- return builder;
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ switch (parser.getEventType()) {
+ case XmlPullParser.END_TAG:
+ if (TAG_SUB_RATING_DEFINITION.equals(parser.getName())) {
+ return builder;
+ } else {
+ 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");
}
- private static Order.Builder parseOrder(XmlResourceParser parser)
+ private Order.Builder parseOrder(XmlResourceParser parser)
throws XmlPullParserException, IOException {
Order.Builder builder = new Order.Builder();
assertEquals(parser.getAttributeCount(), 0,
- "Malformed XML: Attribute isn't allowed in " + TAG_ORDER);
+ "Malformed XML: Attribute isn't allowed in " + TAG_RATING_ORDER);
- while (true) {
- int eventType = parser.nextTag();
- switch (eventType) {
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ switch (parser.getEventType()) {
case XmlPullParser.START_TAG:
- assertEquals(parser.getName(), TAG_RATING,
- "Malformed XML: Only " + TAG_RATING + " is allowed in " +
- TAG_ORDER);
- if (parser.getAttributeCount() != 1 ||
- !ATTR_ID.equals(parser.getAttributeName(0))) {
- throw new XmlPullParserException("Malformed XML: " + TAG_ORDER +
- " should only contain " + ATTR_ID);
- }
- builder.addRatingId(parser.getAttributeValue(0));
- eventType = parser.nextTag();
- if (eventType != XmlPullParser.END_TAG ||
- !TAG_RATING.equals(parser.getName())) {
- throw new XmlPullParserException("Malformed XML: " + TAG_RATING +
- " has child");
+ if (TAG_RATING.equals(parser.getName())) {
+ builder = parseRating(parser, builder);
+ } else {
+ checkVersion("Malformed XML: Only " + TAG_RATING + " is allowed in " +
+ TAG_RATING_ORDER);
}
break;
case XmlPullParser.END_TAG:
- assertEquals(parser.getName(), TAG_ORDER,
- "Malformed XML: Tag mismatch for " + TAG_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");
+ }
+
+ private Order.Builder parseRating(XmlResourceParser parser, Order.Builder builder)
+ throws XmlPullParserException, IOException {
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ String attr = parser.getAttributeName(i);
+ switch (attr) {
+ case ATTR_NAME:
+ builder.addRatingName(parser.getAttributeValue(i));
+ break;
default:
- throw new XmlPullParserException("Malformed XML: Error in " + TAG_ORDER);
+ checkVersion("Malformed XML: " + TAG_RATING_ORDER + " should only contain "
+ + ATTR_NAME);
}
}
+
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ if (parser.getEventType() == XmlPullParser.END_TAG) {
+ if (TAG_RATING.equals(parser.getName())) {
+ return builder;
+ } else {
+ checkVersion("Malformed XML: " + TAG_RATING + " has child");
+ }
+ }
+ }
+ throw new XmlPullParserException(TAG_RATING +
+ " section is incomplete or section ending tag is missing");
+ }
+
+ private Rating.Builder parseSubRating(XmlResourceParser parser, Rating.Builder builder)
+ throws XmlPullParserException, IOException {
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ String attr = parser.getAttributeName(i);
+ switch (attr) {
+ case ATTR_NAME:
+ builder.addSubRatingName(parser.getAttributeValue(i));
+ break;
+ default:
+ checkVersion("Malformed XML: " + TAG_SUB_RATING + " should only contain " +
+ ATTR_NAME);
+ }
+ }
+
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ if (parser.getEventType() == XmlPullParser.END_TAG) {
+ if (TAG_SUB_RATING.equals(parser.getName())) {
+ return builder;
+ } else {
+ checkVersion("Malformed XML: " + TAG_SUB_RATING + " has child");
+ }
+ }
+ }
+ 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
+ // string if that fails.
+ private String getTitle(XmlResourceParser parser, int index) {
+ int titleResId = parser.getAttributeResourceValue(index, 0);
+ if (titleResId != 0) {
+ return mResources.getString(titleResId);
+ }
+ return parser.getAttributeValue(index);
}
}
diff --git a/src/com/android/tv/parental/ParentalControlSettings.java b/src/com/android/tv/parental/ParentalControlSettings.java
new file mode 100644
index 00000000..d7e1846e
--- /dev/null
+++ b/src/com/android/tv/parental/ParentalControlSettings.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.parental;
+
+import android.content.Context;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvInputManager;
+
+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.
+ */
+ public static final int RATING_BLOCKED = 0;
+
+ /**
+ * 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.
+ */
+ public static final int RATING_NOT_BLOCKED = 2;
+
+ private final Context mContext;
+ private final TvInputManager mTvInputManager;
+
+ // mRatings is expected to be synchronized with mTvInputManager.getBlockedRatings().
+ private Set<TvContentRating> mRatings;
+ private Set<TvContentRating> mCustomRatings;
+
+ public ParentalControlSettings(Context context) {
+ mContext = context;
+ mTvInputManager = (TvInputManager) mContext.getSystemService(Context.TV_INPUT_SERVICE);
+ }
+
+ public boolean isParentalControlsEnabled() {
+ return mTvInputManager.isParentalControlsEnabled();
+ }
+
+ public void setParentalControlsEnabled(boolean enabled) {
+ mTvInputManager.setParentalControlsEnabled(enabled);
+ }
+
+ public void setContentRatingSystemEnabled(ContentRatingsManager manager,
+ ContentRatingSystem contentRatingSystem, boolean enabled) {
+ if (enabled) {
+ TvSettings.addContentRatingSystem(mContext, contentRatingSystem.getId());
+
+ // Ensure newly added system has ratings for current level set
+ updateRatingsForCurrentLevel(manager);
+ } else {
+ // Ensure no ratings are blocked for the selected rating system
+ for (TvContentRating tvContentRating : mTvInputManager.getBlockedRatings()) {
+ if (contentRatingSystem.ownsRating(tvContentRating)) {
+ mTvInputManager.removeBlockedRating(tvContentRating);
+ }
+ }
+
+ TvSettings.removeContentRatingSystem(mContext, contentRatingSystem.getId());
+ }
+ }
+
+ public boolean isContentRatingSystemEnabled(ContentRatingSystem contentRatingSystem) {
+ return TvSettings.hasContentRatingSystem(mContext, contentRatingSystem.getId());
+ }
+
+ public void loadRatings() {
+ mRatings = new HashSet<>(mTvInputManager.getBlockedRatings());
+ }
+
+ private void storeRatings() {
+ Set<TvContentRating> removed = new HashSet<>(mTvInputManager.getBlockedRatings());
+ removed.removeAll(mRatings);
+ for (TvContentRating tvContentRating : removed) {
+ mTvInputManager.removeBlockedRating(tvContentRating);
+ }
+
+ Set<TvContentRating> added = new HashSet<>(mRatings);
+ added.removeAll(mTvInputManager.getBlockedRatings());
+ for (TvContentRating tvContentRating : added) {
+ mTvInputManager.addBlockedRating(tvContentRating);
+ }
+ }
+
+ private void updateRatingsForCurrentLevel(ContentRatingsManager manager) {
+ @ContentRatingLevel int currentLevel = getContentRatingLevel();
+ if (currentLevel != TvSettings.CONTENT_RATING_LEVEL_CUSTOM) {
+ mRatings = ContentRatingLevelPolicy.getRatingsForLevel(this, manager, currentLevel);
+ storeRatings();
+ }
+ }
+
+ public void setContentRatingLevel(ContentRatingsManager manager,
+ @ContentRatingLevel int level) {
+ @ContentRatingLevel int currentLevel = getContentRatingLevel();
+ if (level == currentLevel) {
+ return;
+ }
+ if (currentLevel == TvSettings.CONTENT_RATING_LEVEL_CUSTOM) {
+ mCustomRatings = mRatings;
+ }
+ TvSettings.setContentRatingLevel(mContext, level);
+ if (level == TvSettings.CONTENT_RATING_LEVEL_CUSTOM) {
+ if (mCustomRatings != null) {
+ mRatings = new HashSet<>(mCustomRatings);
+ }
+ } else {
+ mRatings = ContentRatingLevelPolicy.getRatingsForLevel(this, manager, level);
+ }
+ storeRatings();
+ }
+
+ @ContentRatingLevel
+ public int getContentRatingLevel() {
+ return TvSettings.getContentRatingLevel(mContext);
+ }
+
+ /**
+ * Sets the blocked status of a given content rating.
+ * <p>
+ * Note that a call to this method automatically changes the current rating level to
+ * {@code TvSettings.CONTENT_RATING_LEVEL_CUSTOM} if needed.
+ * </p>
+ *
+ * @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) {
+ return setRatingBlockedInternal(contentRatingSystem, rating, null, blocked);
+ }
+
+ /**
+ * Checks whether any of given ratings is blocked.
+ *
+ * @param ratings The array of ratings to check
+ * @return {@code true} if a rating is blocked, {@code false} otherwise.
+ */
+ public boolean isRatingBlocked(TvContentRating[] ratings) {
+ return getBlockedRating(ratings) != null;
+ }
+
+ /**
+ * Checks whether any of given ratings is blocked and returns the first blocked rating.
+ *
+ * @param ratings The array of ratings to check
+ * @return The {@link TvContentRating} that is blocked.
+ */
+ public TvContentRating getBlockedRating(TvContentRating[] ratings) {
+ if (ratings == null) {
+ return null;
+ }
+ for (TvContentRating rating : ratings) {
+ if (mTvInputManager.isRatingBlocked(rating)) {
+ return rating;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Checks whether a given rating is blocked by the user or not.
+ *
+ * @param contentRatingSystem The content rating system where the given rating belongs.
+ * @param rating The content rating to check.
+ * @return {@code true} if blocked, {@code false} otherwise.
+ */
+ public boolean isRatingBlocked(ContentRatingSystem contentRatingSystem, Rating rating) {
+ return mRatings.contains(toTvContentRating(contentRatingSystem, rating));
+ }
+
+ /**
+ * Sets the blocked status of a given content sub-rating.
+ * <p>
+ * Note that a call to this method automatically changes the current rating level to
+ * {@code TvSettings.CONTENT_RATING_LEVEL_CUSTOM} if needed.
+ * </p>
+ *
+ * @param contentRatingSystem The content rating system where the given rating belongs.
+ * @param rating The content rating associated with the given sub-rating.
+ * @param subRating The content sub-rating to set.
+ * @return {@code true} if changed, {@code false} otherwise.
+ * @see #setRatingBlocked
+ */
+ public boolean setSubRatingBlocked(ContentRatingSystem contentRatingSystem, Rating rating,
+ SubRating subRating, boolean blocked) {
+ return setRatingBlockedInternal(contentRatingSystem, rating, subRating, blocked);
+ }
+
+ /**
+ * Checks whether a given content sub-rating is blocked by the user or not.
+ *
+ * @param contentRatingSystem The content rating system where the given rating belongs.
+ * @param rating The content rating associated with the given sub-rating.
+ * @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) {
+ 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);
+ boolean changed;
+ if (blocked) {
+ changed = mRatings.add(tvContentRating);
+ mTvInputManager.addBlockedRating(tvContentRating);
+ } else {
+ changed = mRatings.remove(tvContentRating);
+ mTvInputManager.removeBlockedRating(tvContentRating);
+ }
+ if (changed) {
+ changeToCustomLevel();
+ }
+ return changed;
+ }
+
+ private void changeToCustomLevel() {
+ if (getContentRatingLevel() != TvSettings.CONTENT_RATING_LEVEL_CUSTOM) {
+ TvSettings.setContentRatingLevel(mContext, TvSettings.CONTENT_RATING_LEVEL_CUSTOM);
+ }
+ }
+
+ /**
+ * 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)) {
+ return RATING_BLOCKED;
+ }
+ for (SubRating subRating : rating.getSubRatings()) {
+ if (isSubRatingEnabled(contentRatingSystem, rating, subRating)) {
+ return RATING_BLOCKED_PARTIAL;
+ }
+ }
+ 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, SubRating subRating) {
+ return TvContentRating.createRating(contentRatingSystem.getDomain(),
+ contentRatingSystem.getName(), rating.getName(), subRating.getName());
+ }
+}
diff --git a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
new file mode 100644
index 00000000..ca3cb176
--- /dev/null
+++ b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
@@ -0,0 +1,100 @@
+/*
+ * 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.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+
+import com.android.tv.analytics.Tracker;
+
+import java.util.Arrays;
+
+/**
+ * Creates HDMI plug broadcast receiver, and reports AC3 passthrough capabilities
+ * to Google Analytics. Call {@link #register} to start receiving notifications,
+ * and {@link #unregister} to stop.
+ */
+public final class AudioCapabilitiesReceiver {
+ private static final String PREFS_NAME = "com.android.tv.audio_capabilities";
+ private static final String SETTINGS_KEY_AC3_PASSTHRU_REPORTED = "ac3_passthrough_reported";
+ private static final String SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES = "ac3_passthrough";
+
+ private final Context mContext;
+ private final Tracker mTracker;
+ private final BroadcastReceiver mReceiver = new HdmiAudioPlugBroadcastReceiver();
+
+ /**
+ * Constructs a new audio capabilities receiver.
+ *
+ * @param context context for registering to receive broadcasts
+ * @param tracker tracker object used to upload capabilities info to Google Analytics
+ */
+ public AudioCapabilitiesReceiver(Context context, Tracker tracker) {
+ mContext = context;
+ mTracker = tracker;
+ }
+
+ public void register() {
+ mContext.registerReceiver(mReceiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG));
+ }
+
+ public void unregister() {
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ private final class HdmiAudioPlugBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (!action.equals(AudioManager.ACTION_HDMI_AUDIO_PLUG)) {
+ return;
+ }
+ reportAudioCapabilities(intent.getIntArrayExtra(AudioManager.EXTRA_ENCODINGS));
+ }
+ }
+
+ private void reportAudioCapabilities(int[] supportedEncodings) {
+ boolean newVal = supportedEncodings == null
+ ? false : Arrays.binarySearch(supportedEncodings, AudioFormat.ENCODING_AC3) >= 0;
+ boolean oldVal = getBoolean(SETTINGS_KEY_AC3_PASSTHRU_REPORTED, false);
+ boolean reported = getBoolean(SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES, false);
+
+ // Send the value just once. But we send it again if the value changed, to include
+ // the case where users have switched TV device with different AC3 passthrough capabilities.
+ if (!reported || oldVal != newVal) {
+ mTracker.sendAc3PassthroughCapabilities(newVal);
+ setBoolean(SETTINGS_KEY_AC3_PASSTHRU_REPORTED, true);
+ setBoolean(SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES, newVal);
+ }
+ }
+
+ private SharedPreferences getSharedPreferences() {
+ return mContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ }
+
+ private boolean getBoolean(String key, boolean def) {
+ return getSharedPreferences().getBoolean(key, def);
+ }
+
+ private void setBoolean(String key, boolean val) {
+ getSharedPreferences().edit().putBoolean(key, val).apply();
+ }
+}
diff --git a/src/com/android/tv/receiver/GlobalKeyReceiver.java b/src/com/android/tv/receiver/GlobalKeyReceiver.java
index ca925baf..bd81cee3 100644
--- a/src/com/android/tv/receiver/GlobalKeyReceiver.java
+++ b/src/com/android/tv/receiver/GlobalKeyReceiver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -22,28 +22,28 @@ import android.content.Intent;
import android.util.Log;
import android.view.KeyEvent;
-import com.android.tv.TvActivity;
+import com.android.tv.TvApplication;
/**
* Handles global keys.
*/
public class GlobalKeyReceiver extends BroadcastReceiver {
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static final String TAG = "GlobalKeyReceiver";
+ private static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON";
@Override
public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_GLOBAL_BUTTON.equals(intent.getAction())) {
+ 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, "onReceive: " + event);
int keyCode = event.getKeyCode();
int action = event.getAction();
- if (keyCode == KeyEvent.KEYCODE_TV && action == KeyEvent.ACTION_DOWN) {
- Intent newIntent = new Intent(context, TvActivity.class);
- newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(newIntent);
+ if (keyCode == KeyEvent.KEYCODE_TV && action == KeyEvent.ACTION_UP) {
+ ((TvApplication) context.getApplicationContext()).handleTvKey();
+ } else if (keyCode == KeyEvent.KEYCODE_TV_INPUT && action == KeyEvent.ACTION_UP) {
+ ((TvApplication) context.getApplicationContext()).handleTvInputKey();
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/receiver/NotificationReceiver.java b/src/com/android/tv/receiver/NotificationReceiver.java
index af38989b..0bcb44c4 100644
--- a/src/com/android/tv/receiver/NotificationReceiver.java
+++ b/src/com/android/tv/receiver/NotificationReceiver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -20,7 +20,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import com.android.tv.notification.NotificationService;
+import com.android.tv.recommendation.NotificationService;
public class NotificationReceiver extends BroadcastReceiver {
@Override
diff --git a/src/com/android/tv/receiver/PackageIntentsReceiver.java b/src/com/android/tv/receiver/PackageIntentsReceiver.java
index 2068362f..fd9d7baf 100644
--- a/src/com/android/tv/receiver/PackageIntentsReceiver.java
+++ b/src/com/android/tv/receiver/PackageIntentsReceiver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -20,56 +20,58 @@ 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.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
-import android.net.Uri;
import android.os.Handler;
import com.android.tv.TvActivity;
-import com.android.tv.util.TvSettings;
-import com.android.tv.util.Utils;
+import com.android.tv.util.SetupUtils;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
/**
* A class for handling the broadcast intents from PackageManager.
*/
public class PackageIntentsReceiver extends BroadcastReceiver {
-
// Delay before checking TvInputManager's input list.
// Sometimes TvInputManager's input list isn't updated yet when this receiver is called.
// So we should check the list after some delay.
private static final long TV_INPUT_UPDATE_DELAY_MS = 500;
private TvInputManager mTvInputManager;
- private SharedPreferences mPreferences;
private final Handler mHandler = new Handler();
- private Runnable mTvActivityUpdater;
- private Runnable mDisplayInputNameCleaner;
+ private Runnable mOnPackageUpdatedRunnable;
+ private boolean mPermissionGranted;
private void init(Context context) {
mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
- mPreferences = Utils.getSharedPreferencesOfDisplayNameForInput(context);
final Context applicationContext = context.getApplicationContext();
- mTvActivityUpdater = new Runnable() {
+ mOnPackageUpdatedRunnable = new Runnable() {
@Override
public void run() {
- enableTvActivityWithinPackageManager(applicationContext,
- !mTvInputManager.getTvInputList().isEmpty());
+ List<TvInputInfo> inputs = mTvInputManager.getTvInputList();
+ // Enable the MainActivity only if there is at least one tuner type input.
+ boolean enable = false;
+ for (TvInputInfo input : inputs) {
+ if (input.getType() == TvInputInfo.TYPE_TUNER) {
+ enable = true;
+ break;
+ }
+ }
+ enableTvActivityWithinPackageManager(applicationContext, enable);
+
+ SetupUtils.getInstance(applicationContext).onInputListUpdated(mTvInputManager);
}
};
- mDisplayInputNameCleaner = new Runnable() {
- @Override
- public void run() {
- cleanupUnusedDisplayInputName();
- }
- };
+ // Grant permission to already set up packages after the system has finished booting. (Note
+ // that the PackageIntentsReceiver filters the ACTION_BOOT_COMPLETED action.)
+ if (!mPermissionGranted) {
+ SetupUtils.grantEpgPermissionToSetUpPackages(applicationContext);
+ mPermissionGranted = true;
+ }
}
@Override
@@ -78,44 +80,8 @@ public class PackageIntentsReceiver extends BroadcastReceiver {
init(context);
}
- String action = intent.getAction();
- if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
- Uri uri = intent.getData();
- onPackageFullyRemoved(uri != null ? uri.getSchemeSpecificPart() : null);
- }
-
- mHandler.removeCallbacks(mTvActivityUpdater);
- mHandler.postDelayed(mTvActivityUpdater, TV_INPUT_UPDATE_DELAY_MS);
- }
-
- private void onPackageFullyRemoved(String packageName) {
- if (packageName == null || packageName.isEmpty()) {
- return;
- }
-
- mHandler.removeCallbacks(mDisplayInputNameCleaner);
- mHandler.postDelayed(mDisplayInputNameCleaner, TV_INPUT_UPDATE_DELAY_MS);
- }
-
- private void cleanupUnusedDisplayInputName() {
- Set<String> keys = mPreferences.getAll().keySet();
- HashSet<String> unusedKeys = new HashSet<String>(keys);
- for (String key : keys) {
- if (!key.startsWith(TvSettings.PREF_DISPLAY_INPUT_NAME)) {
- unusedKeys.remove(key);
- }
- }
- List<TvInputInfo> inputs = mTvInputManager.getTvInputList();
- for (TvInputInfo input : inputs) {
- unusedKeys.remove(TvSettings.PREF_DISPLAY_INPUT_NAME + input.getId());
- }
- if (!unusedKeys.isEmpty()) {
- SharedPreferences.Editor editor = mPreferences.edit();
- for (String key : unusedKeys) {
- editor.remove(key);
- }
- editor.commit();
- }
+ mHandler.removeCallbacks(mOnPackageUpdatedRunnable);
+ mHandler.postDelayed(mOnPackageUpdatedRunnable, TV_INPUT_UPDATE_DELAY_MS);
}
private void enableTvActivityWithinPackageManager(Context context, boolean enable) {
@@ -125,7 +91,7 @@ public class PackageIntentsReceiver extends BroadcastReceiver {
int newState = enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
if (pm.getComponentEnabledSetting(name) != newState) {
- pm.setComponentEnabledSetting(name, newState, PackageManager.DONT_KILL_APP);
+ pm.setComponentEnabledSetting(name, newState, 0);
}
}
}
diff --git a/src/com/android/tv/recommendation/ChannelRecord.java b/src/com/android/tv/recommendation/ChannelRecord.java
new file mode 100644
index 00000000..10aaeef7
--- /dev/null
+++ b/src/com/android/tv/recommendation/ChannelRecord.java
@@ -0,0 +1,92 @@
+/*
+ * 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.recommendation;
+
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.tv.data.Channel;
+import com.android.tv.data.Program;
+import com.android.tv.util.Utils;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+public class ChannelRecord {
+ // TODO: decide the value for max history size.
+ @VisibleForTesting static final int MAX_HISTORY_SIZE = 100;
+ private final Context mContext;
+ private final Deque<WatchedProgram> mWatchHistory;
+ private Program mCurrentProgram;
+ private Channel mChannel;
+ private long mTotalWatchDurationMs;
+ private boolean mInputRemoved;
+
+ public ChannelRecord(Context context, Channel channel, boolean inputRemoved) {
+ mContext = context;
+ mChannel = channel;
+ mWatchHistory = new ArrayDeque<>();
+ mInputRemoved = inputRemoved;
+ }
+
+ public Channel getChannel() {
+ return mChannel;
+ }
+
+ public void setChannel(Channel channel, boolean inputRemoved) {
+ mChannel = channel;
+ mInputRemoved = inputRemoved;
+ }
+
+ public boolean isInputRemoved() {
+ return mInputRemoved;
+ }
+
+ public void setInputRemoved(boolean removed) {
+ mInputRemoved = removed;
+ }
+
+ public long getLastWatchEndTimeMs() {
+ WatchedProgram p = mWatchHistory.peekLast();
+ return (p == null) ? 0 : p.getWatchEndTimeMs();
+ }
+
+ public Program getCurrentProgram() {
+ long time = System.currentTimeMillis();
+ if (mCurrentProgram == null || mCurrentProgram.getEndTimeUtcMillis() < time) {
+ mCurrentProgram = Utils.getCurrentProgram(mContext, mChannel.getId());
+ }
+ return mCurrentProgram;
+ }
+
+ public long getTotalWatchDurationMs() {
+ return mTotalWatchDurationMs;
+ }
+
+ public final WatchedProgram[] getWatchHistory() {
+ return mWatchHistory.toArray(new WatchedProgram[mWatchHistory.size()]);
+ }
+
+ public void logWatchHistory(WatchedProgram p) {
+ mWatchHistory.offer(p);
+ mTotalWatchDurationMs += p.getWatchedDurationMs();
+ if (mWatchHistory.size() > MAX_HISTORY_SIZE) {
+ WatchedProgram program = mWatchHistory.poll();
+ mTotalWatchDurationMs -= program.getWatchedDurationMs();
+ }
+ }
+}
diff --git a/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java b/src/com/android/tv/recommendation/FavoriteChannelEvaluator.java
new file mode 100644
index 00000000..9a6de7e2
--- /dev/null
+++ b/src/com/android/tv/recommendation/FavoriteChannelEvaluator.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.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
+ // When there is no watch history, use the current time as a default value.
+ private long mEarliestWatchStartTimeMs = System.currentTimeMillis();
+
+ @Override
+ protected void onChannelRecordListChanged(List<ChannelRecord> channelRecords) {
+ for (ChannelRecord cr : channelRecords) {
+ WatchedProgram[] watchedPrograms = cr.getWatchHistory();
+ if (watchedPrograms.length > 0
+ && mEarliestWatchStartTimeMs > watchedPrograms[0].getWatchStartTimeMs()) {
+ mEarliestWatchStartTimeMs = watchedPrograms[0].getWatchStartTimeMs();
+ }
+ }
+ }
+
+ @Override
+ public double evaluateChannel(long channelId) {
+ ChannelRecord cr = getRecommender().getChannelRecord(channelId);
+ if (cr == null) {
+ return NOT_RECOMMENDED;
+ }
+
+ if (cr.getTotalWatchDurationMs() == 0) {
+ return NOT_RECOMMENDED;
+ }
+
+ long watchPeriodMs = System.currentTimeMillis() - mEarliestWatchStartTimeMs;
+ 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
new file mode 100644
index 00000000..00cad116
--- /dev/null
+++ b/src/com/android/tv/recommendation/NotificationService.java
@@ -0,0 +1,459 @@
+/*
+ * 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.recommendation;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.media.tv.TvInputInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseLongArray;
+import android.view.View;
+
+import com.android.tv.R;
+import com.android.tv.data.Channel;
+import com.android.tv.data.Program;
+import com.android.tv.util.BitmapUtils;
+import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
+import com.android.tv.util.TvInputManagerHelper;
+import com.android.tv.util.Utils;
+
+import java.util.List;
+
+/**
+ * A local service for notify recommendation at home launcher.
+ */
+public class NotificationService extends Service implements Recommender.Listener {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "NotificationService";
+
+ public static final String ACTION_SHOW_RECOMMENDATION =
+ "com.android.tv.notification.ACTION_SHOW_RECOMMENDATION";
+ public static final String ACTION_HIDE_RECOMMENDATION =
+ "com.android.tv.notification.ACTION_HIDE_RECOMMENDATION";
+
+ private static final String TUNE_PARAMS_RECOMMENDATION_TYPE =
+ "com.android.tv.recommendation_type";
+ private static final String TYPE_RANDOM_RECOMMENDATION = "random";
+ private static final String TYPE_ROUTINE_WATCH_RECOMMENDATION = "routine_watch";
+ private static final String TYPE_ROUTINE_WATCH_AND_FAVORITE_CHANNEL_RECOMMENDATION =
+ "routine_watch_and_favorite";
+
+ private static final String NOTIFY_TAG = "tv_recommendation";
+ // TODO: find out proper number of notifications and whether to make it dynamically
+ // configurable from system property or etc.
+ private static final int NOTIFICATION_COUNT = 3;
+
+ private static final int MSG_INITIALIZE_RECOMMENDER = 1000;
+ private static final int MSG_SHOW_RECOMMENDATION = 1001;
+ 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 int MAX_PROGRAM_UPDATE_COUNT = 20;
+
+ private TvInputManagerHelper mTvInputManagerHelper;
+ private Recommender mRecommender;
+ private boolean mShowRecommendationAfterRecommenderReady;
+ private NotificationManager mNotificationManager;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private final String mRecommendationType;
+ private int mCurrentNotificationCount;
+ private long[] mNotificationChannels;
+
+ private float mNotificationCardMaxWidth;
+ private float mNotificationCardHeight;
+ private int mCardImageHeight;
+ private int mCardImageMaxWidth;
+ private int mCardImageMinWidth;
+ private int mChannelLogoMaxWidth;
+ private int mChannelLogoMaxHeight;
+ private int mLogoPaddingStart;
+ private int mLogoPaddingBottom;
+
+ public NotificationService() {
+ mRecommendationType = TYPE_ROUTINE_WATCH_AND_FAVORITE_CHANNEL_RECOMMENDATION;
+ }
+
+ @Override
+ public void onCreate() {
+ if (DEBUG) Log.d(TAG, "onCreate");
+ super.onCreate();
+
+ mCurrentNotificationCount = 0;
+ mNotificationChannels = new long[NOTIFICATION_COUNT];
+ 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);
+ 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);
+ mChannelLogoMaxWidth =
+ getResources().getDimensionPixelSize(R.dimen.notif_ch_logo_max_width);
+ mChannelLogoMaxHeight =
+ getResources().getDimensionPixelSize(R.dimen.notif_ch_logo_max_height);
+ mLogoPaddingStart =
+ getResources().getDimensionPixelOffset(R.dimen.notif_ch_logo_padding_start);
+ mLogoPaddingBottom =
+ getResources().getDimensionPixelOffset(R.dimen.notif_ch_logo_padding_bottom);
+
+ mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ mTvInputManagerHelper = new TvInputManagerHelper(this);
+ mTvInputManagerHelper.start();
+
+ mHandlerThread = new HandlerThread("tv notification");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_INITIALIZE_RECOMMENDER: {
+ mRecommender = new Recommender(
+ NotificationService.this, NotificationService.this, true);
+ if (TYPE_RANDOM_RECOMMENDATION.equals(mRecommendationType)) {
+ 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)) {
+ mRecommender.registerEvaluator(
+ new FavoriteChannelEvaluator(), 0.5, 0.5);
+ mRecommender.registerEvaluator(new RoutineWatchEvaluator(), 1.0, 1.0);
+ } else {
+ throw new IllegalStateException("Undefined recommendation type: "
+ + mRecommendationType);
+ }
+ }
+ case MSG_SHOW_RECOMMENDATION: {
+ if (!mRecommender.isReady()) {
+ mShowRecommendationAfterRecommenderReady = true;
+ } else {
+ showRecommendation();
+ }
+ break;
+ }
+ case MSG_UPDATE_RECOMMENDATION: {
+ int notificationId = msg.arg1;
+ Channel channel = ((Channel) msg.obj);
+ if (mNotificationChannels[notificationId] == Channel.INVALID_ID
+ || !sendNotification(channel.getId(), notificationId)) {
+ changeRecommendation(notificationId);
+ }
+ break;
+ }
+ case MSG_HIDE_RECOMMENDATION: {
+ if (!mRecommender.isReady()) {
+ mShowRecommendationAfterRecommenderReady = false;
+ } else {
+ hideAllRecommendation();
+ }
+ break;
+ }
+ default: {
+ super.handleMessage(msg);
+ }
+ }
+ }
+ };
+ mHandler.sendEmptyMessage(MSG_INITIALIZE_RECOMMENDER);
+ }
+
+ @Override
+ public void onDestroy() {
+ mRecommender.release();
+ mRecommender = null;
+ mHandlerThread.quit();
+ mHandlerThread = null;
+ mHandler = null;
+ super.onDestroy();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (DEBUG) Log.d(TAG, "onStartCommand");
+ if (intent != null) {
+ String action = intent.getAction();
+ if (ACTION_SHOW_RECOMMENDATION.equals(action)) {
+ mHandler.removeMessages(MSG_SHOW_RECOMMENDATION);
+ mHandler.removeMessages(MSG_HIDE_RECOMMENDATION);
+ mHandler.obtainMessage(MSG_SHOW_RECOMMENDATION).sendToTarget();
+ } else if (ACTION_HIDE_RECOMMENDATION.equals(action)) {
+ mHandler.removeMessages(MSG_SHOW_RECOMMENDATION);
+ mHandler.removeMessages(MSG_UPDATE_RECOMMENDATION);
+ mHandler.removeMessages(MSG_HIDE_RECOMMENDATION);
+ mHandler.obtainMessage(MSG_HIDE_RECOMMENDATION).sendToTarget();
+ }
+ }
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onRecommenderReady() {
+ if (DEBUG) Log.d(TAG, "onRecommendationReady");
+ if (mShowRecommendationAfterRecommenderReady) {
+ mHandler.sendEmptyMessage(MSG_SHOW_RECOMMENDATION);
+ mShowRecommendationAfterRecommenderReady = false;
+ }
+ }
+
+ @Override
+ public void onRecommendationChanged() {
+ if (DEBUG) Log.d(TAG, "onRecommendationChanged");
+ SparseLongArray notificationChannels = new SparseLongArray();
+ for (int i = 0; i < NOTIFICATION_COUNT; ++i) {
+ if (mNotificationChannels[i] == Channel.INVALID_ID) {
+ continue;
+ }
+ notificationChannels.put(i, mNotificationChannels[i]);
+ }
+ List<Channel> channels = mRecommender.recommendChannels();
+ for (Channel c : channels) {
+ int index = notificationChannels.indexOfValue(c.getId());
+ if (index >= 0) {
+ notificationChannels.removeAt(index);
+ }
+ }
+ // Cancel notification whose channels are not recommended anymore.
+ if (notificationChannels.size() > 0) {
+ for (int i = 0; i < notificationChannels.size(); ++i) {
+ int notificationId = notificationChannels.keyAt(i);
+ mNotificationManager.cancel(NOTIFY_TAG, notificationId);
+ mNotificationChannels[notificationId] = Channel.INVALID_ID;
+ --mCurrentNotificationCount;
+ }
+ showRecommendation();
+ }
+ }
+
+ private void showRecommendation() {
+ if (DEBUG) Log.d(TAG, "showRecommendation");
+ List<Channel> channels = mRecommender.recommendChannels();
+ for (Channel c : channels) {
+ if (mCurrentNotificationCount >= NOTIFICATION_COUNT) {
+ break;
+ }
+ if (!isNotifiedChannel(c.getId())) {
+ sendNotification(c.getId(), getAvailableNotificationId());
+ }
+ }
+ if (mCurrentNotificationCount < NOTIFICATION_COUNT) {
+ Message msg = mHandler.obtainMessage(MSG_SHOW_RECOMMENDATION);
+ mHandler.sendMessageDelayed(msg, RECOMMENDATION_RETRY_TIME_MS);
+ }
+ }
+
+ private void changeRecommendation(int notificationId) {
+ if (DEBUG) Log.d(TAG, "changeRecommendation");
+ List<Channel> channels = mRecommender.recommendChannels();
+ if (mNotificationChannels[notificationId] != Channel.INVALID_ID) {
+ mNotificationChannels[notificationId] = Channel.INVALID_ID;
+ --mCurrentNotificationCount;
+ }
+ for (Channel c : channels) {
+ if (!isNotifiedChannel(c.getId())) {
+ if(sendNotification(c.getId(), notificationId)) {
+ return;
+ }
+ }
+ }
+ mNotificationManager.cancel(NOTIFY_TAG, notificationId);
+ }
+
+ 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;
+ }
+
+ private boolean sendNotification(final long channelId, final int notificationId) {
+ final ChannelRecord cr = mRecommender.getChannelRecord(channelId);
+ if (cr == null) {
+ return false;
+ }
+ final Channel channel = cr.getChannel();
+ if (DEBUG) Log.d(TAG, "sendNotification (" + channel.getDisplayName()
+ + " notifyId=" + notificationId + ")");
+ Intent intent = new Intent(Intent.ACTION_VIEW, channel.getUri());
+ intent.putExtra(TUNE_PARAMS_RECOMMENDATION_TYPE, mRecommendationType);
+ final PendingIntent notificationIntent = PendingIntent.getActivity(this, 0, intent, 0);
+
+ // TODO: Move some checking logic into TvRecommendation.
+ String inputId = Utils.getInputIdForChannel(this, channel.getId());
+ if (TextUtils.isEmpty(inputId)) {
+ return false;
+ }
+ TvInputInfo inputInfo = mTvInputManagerHelper.getTvInputInfo(inputId);
+ if (inputInfo == null) {
+ return false;
+ }
+ final String inputDisplayName = inputInfo.loadLabel(this).toString();
+
+ final Program program = Utils.getCurrentProgram(this, channel.getId());
+ if (program == null) {
+ return false;
+ }
+ final long programDurationMs = program.getEndTimeUtcMillis()
+ - program.getStartTimeUtcMillis();
+ 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.
+ if (programProgress >= RECOMMENDATION_THRESHOLD_PROGRESS
+ && programLeftTimsMs <= RECOMMENDATION_THRESHOLD_LEFT_TIME_MS) {
+ return false;
+ }
+
+ // We don't trust TIS to provide us with proper sized image
+ 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,
+ mChannelLogoMaxHeight, new Channel.LoadImageCallback() {
+ @Override
+ public void onLoadImageFinished(Channel channel, int type, Bitmap channelLogo) {
+ // This callback will run on the main thread.
+ Bitmap largeIconBitmap = (channelLogo == null) ? posterArtBitmap
+ : overlayChannelLogo(channelLogo, posterArtBitmap);
+ Notification notification =
+ new Notification.Builder(NotificationService.this)
+ .setContentIntent(notificationIntent)
+ .setContentTitle(program.getTitle())
+ .setContentText(inputDisplayName + " "
+ + channel.getDisplayName())
+ .setContentInfo(channel.getDisplayName())
+ .setAutoCancel(true)
+ .setLargeIcon(largeIconBitmap)
+ .setSmallIcon(R.drawable.ic_launcher_s)
+ .setCategory(Notification.CATEGORY_RECOMMENDATION)
+ .setProgress((programProgress > 0) ? 100 : 0,
+ programProgress,
+ false)
+ .setSortKey(mRecommender.getChannelSortKey(channelId))
+ .build();
+ notification.color =
+ getResources().getColor(R.color.recommendation_card_background);
+ if (!TextUtils.isEmpty(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);
+ mHandler.sendMessageDelayed(msg,
+ programDurationMs / MAX_PROGRAM_UPDATE_COUNT);
+ }
+ });
+
+ if (mNotificationChannels[notificationId] == Channel.INVALID_ID) {
+ ++mCurrentNotificationCount;
+ }
+ mNotificationChannels[notificationId] = channel.getId();
+
+ return true;
+ }
+
+ private Bitmap overlayChannelLogo(Bitmap logo, Bitmap background) {
+ Bitmap result = BitmapUtils.scaleBitmap(
+ background, Integer.MAX_VALUE, mCardImageHeight);
+ Bitmap scaledLogo = BitmapUtils.scaleBitmap(
+ logo, mChannelLogoMaxWidth, mChannelLogoMaxHeight);
+ Canvas canvas = new Canvas(result);
+ canvas.drawBitmap(result, new Matrix(), null);
+ Rect rect = new Rect();
+ int startPadding;
+ if (result.getWidth() < mCardImageMinWidth) {
+ // TODO: check the positions.
+ startPadding = mLogoPaddingStart;
+ rect.bottom = result.getHeight() - mLogoPaddingBottom;
+ rect.top = rect.bottom - scaledLogo.getHeight();
+ } else if (result.getWidth() < mCardImageMaxWidth) {
+ startPadding = mLogoPaddingStart;
+ rect.bottom = result.getHeight() - mLogoPaddingBottom;
+ rect.top = rect.bottom - scaledLogo.getHeight();
+ } else {
+ int marginStart = (result.getWidth() - mCardImageMaxWidth) / 2;
+ startPadding = mLogoPaddingStart + marginStart;
+ rect.bottom = result.getHeight() - mLogoPaddingBottom;
+ rect.top = rect.bottom - scaledLogo.getHeight();
+ }
+ if (getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) {
+ rect.left = startPadding;
+ rect.right = startPadding + scaledLogo.getWidth();
+ } else {
+ rect.right = result.getWidth() - startPadding;
+ rect.left = rect.right - scaledLogo.getWidth();
+ }
+ Paint paint = new Paint();
+ paint.setAlpha(getResources().getInteger(R.integer.notif_card_ch_logo_alpha));
+ canvas.drawBitmap(scaledLogo, null, rect, paint);
+ return result;
+ }
+
+ private boolean isNotifiedChannel(long channelId) {
+ for (int i = 0; i < NOTIFICATION_COUNT; ++i) {
+ if (mNotificationChannels[i] == channelId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int getAvailableNotificationId() {
+ for (int i = 0; i < NOTIFICATION_COUNT; ++i) {
+ if (mNotificationChannels[i] == Channel.INVALID_ID) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/src/com/android/tv/recommendation/RandomRecommender.java b/src/com/android/tv/recommendation/RandomEvaluator.java
index 6e7aaede..20c97f43 100644
--- a/src/com/android/tv/recommendation/RandomRecommender.java
+++ b/src/com/android/tv/recommendation/RandomEvaluator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,12 +16,9 @@
package com.android.tv.recommendation;
-import com.android.tv.recommendation.TvRecommendation.ChannelRecord;
-import com.android.tv.recommendation.TvRecommendation.TvRecommender;
-
-public class RandomRecommender extends TvRecommender {
+public class RandomEvaluator extends Recommender.Evaluator {
@Override
- public double calculateScore(final ChannelRecord cr) {
+ public double evaluateChannel(long channelId) {
return Math.random();
}
}
diff --git a/src/com/android/tv/recommendation/RecentChannelEvaluator.java b/src/com/android/tv/recommendation/RecentChannelEvaluator.java
new file mode 100644
index 00000000..c3482af9
--- /dev/null
+++ b/src/com/android/tv/recommendation/RecentChannelEvaluator.java
@@ -0,0 +1,64 @@
+/*
+ * 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.recommendation;
+
+import java.util.concurrent.TimeUnit;
+
+public class RecentChannelEvaluator extends Recommender.Evaluator {
+ private static final long WATCH_DURATION_MS_LOWER_BOUND = TimeUnit.MINUTES.toMillis(3);
+ private static final long WATCH_DURATION_MS_UPPER_BOUND = TimeUnit.MINUTES.toMillis(7);
+
+ private static final double MAX_SCORE_FOR_LOWER_BOUND = 0.1;
+
+ private long mLastWatchLogUpdateTimeMs;
+
+ public RecentChannelEvaluator() {
+ mLastWatchLogUpdateTimeMs = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onNewWatchLog(ChannelRecord channelRecord) {
+ mLastWatchLogUpdateTimeMs = System.currentTimeMillis();
+ }
+
+ @Override
+ public double evaluateChannel(long channelId) {
+ ChannelRecord cr = getRecommender().getChannelRecord(channelId);
+ if (cr == null) {
+ return NOT_RECOMMENDED;
+ }
+ WatchedProgram[] watchHistory = cr.getWatchHistory();
+ double maxScore = 0.0;
+ for (int i = watchHistory.length - 1; i >= 0; --i) {
+ double recentWatchScore =
+ (double) watchHistory[i].getWatchEndTimeMs() / mLastWatchLogUpdateTimeMs;
+ double watchDurationScore;
+ double watchDuration = watchHistory[i].getWatchedDurationMs();
+ 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;
+ } else {
+ watchDurationScore = 1.0;
+ }
+ maxScore = Math.max(maxScore, watchDurationScore * recentWatchScore);
+ }
+ return (maxScore > 0.0) ? maxScore : NOT_RECOMMENDED;
+ }
+}
diff --git a/src/com/android/tv/recommendation/RecentChannelRecommender.java b/src/com/android/tv/recommendation/RecentChannelRecommender.java
deleted file mode 100644
index d2fb73e8..00000000
--- a/src/com/android/tv/recommendation/RecentChannelRecommender.java
+++ /dev/null
@@ -1,47 +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.recommendation;
-
-import com.android.tv.recommendation.TvRecommendation.ChannelRecord;
-import com.android.tv.recommendation.TvRecommendation.TvRecommender;
-
-import java.util.concurrent.TimeUnit;
-
-public class RecentChannelRecommender extends TvRecommender {
- private static final long MIN_WATCH_DURATION_MS = TimeUnit.MINUTES.toMillis(7);
-
- private long mLastWatchLogUpdateTimeMs;
-
- public RecentChannelRecommender() {
- mLastWatchLogUpdateTimeMs = System.currentTimeMillis();
- }
-
- @Override
- public void onNewWatchLog(ChannelRecord channelRecord) {
- mLastWatchLogUpdateTimeMs = System.currentTimeMillis();
- }
-
- @Override
- public double calculateScore(final ChannelRecord cr) {
- if (cr.getLastWatchedTimeMs() == 0l
- || cr.getLastWatchDurationMs() < MIN_WATCH_DURATION_MS) {
- return NOT_RECOMMENDED;
- }
-
- return ((double) cr.getLastWatchedTimeMs()) / mLastWatchLogUpdateTimeMs;
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/recommendation/RecommendationDataManager.java b/src/com/android/tv/recommendation/RecommendationDataManager.java
new file mode 100644
index 00000000..2445cce8
--- /dev/null
+++ b/src/com/android/tv/recommendation/RecommendationDataManager.java
@@ -0,0 +1,583 @@
+/*
+ * 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.recommendation;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvInputManager.TvInputCallback;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.support.annotation.NonNull;
+
+import com.android.tv.data.Channel;
+import com.android.tv.data.Program;
+
+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.ConcurrentHashMap;
+
+public class RecommendationDataManager {
+ private static final String TAG = "RecommendationDataManager";
+
+ 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_WATCHED_PROGRAM_ID = 3;
+ static {
+ sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ sUriMatcher.addURI(TvContract.AUTHORITY, "channel", MATCH_CHANNEL);
+ sUriMatcher.addURI(TvContract.AUTHORITY, "channel/#", MATCH_CHANNEL_ID);
+ sUriMatcher.addURI(TvContract.AUTHORITY, "watched_program/#", MATCH_WATCHED_PROGRAM_ID);
+ }
+
+ private static final int MSG_START = 1000;
+ private static final int MSG_STOP = 1001;
+ private static final int MSG_UPDATE_CHANNEL = 1002;
+ private static final int MSG_UPDATE_CHANNELS = 1003;
+ private static final int MSG_UPDATE_WATCH_HISTORY = 1004;
+ private static final int MSG_NOTIFY_CHANNEL_RECORD_MAP_LOADED = 1005;
+ private static final int MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED = 1006;
+
+ private static final int MSG_FIRST = MSG_START;
+ private static final int MSG_LAST = MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED;
+
+ private static final int INVALID_INDEX = -1;
+
+ private static RecommendationDataManager sManager;
+ private final ContentObserver mContentObserver;
+ private final Map<Long, ChannelRecord> mChannelRecordMap = new ConcurrentHashMap<>();
+ private final Map<Long, ChannelRecord> mAvailableChannelRecordMap = new ConcurrentHashMap<>();
+
+ private Context mContext;
+ private boolean mStarted;
+ private boolean mCancelLoadTask;
+ private boolean mChannelRecordMapLoaded;
+ private int mIndexWatchChannelId = -1;
+ private int mIndexProgramTitle = -1;
+ private int mIndexProgramStartTime = -1;
+ private int mIndexProgramEndTime = -1;
+ private int mIndexWatchStartTime = -1;
+ private int mIndexWatchEndTime = -1;
+ private TvInputManager mTvInputManager;
+ private final Set<String> mInputs = new HashSet<>();
+
+ private final HandlerThread mHandlerThread;
+
+ @SuppressWarnings("unchecked")
+ private final Handler mHandler;
+
+ private final List<ListenerRecord> 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.
+ */
+ public synchronized static RecommendationDataManager acquireManager(
+ Context context, @NonNull Listener listener) {
+ if (sManager == null) {
+ sManager = new RecommendationDataManager(context);
+ }
+ sManager.addListener(listener);
+ sManager.start();
+ return sManager;
+ }
+
+ /**
+ * Removes the {@link Listener}, and releases RecommendationDataManager
+ * if there are no listeners remained.
+ */
+ public void release(@NonNull Listener listener) {
+ removeListener(listener);
+ synchronized (mListeners) {
+ if (mListeners.size() == 0) {
+ stop();
+ }
+ }
+ }
+ private final TvInputCallback mInternalCallback =
+ new TvInputCallback() {
+ @Override
+ public void onInputStateChanged(String inputId, int state) { }
+
+ @Override
+ public void onInputAdded(String inputId) {
+ if (!mStarted) {
+ return;
+ }
+ mInputs.add(inputId);
+ if (!mChannelRecordMapLoaded) {
+ return;
+ }
+ boolean channelRecordMapChanged = false;
+ for (ChannelRecord channelRecord : mChannelRecordMap.values()) {
+ if (channelRecord.getChannel().getInputId().equals(inputId)) {
+ channelRecord.setInputRemoved(false);
+ mAvailableChannelRecordMap.put(channelRecord.getChannel().getId(),
+ channelRecord);
+ channelRecordMapChanged = true;
+ }
+ }
+ if (channelRecordMapChanged
+ && !mHandler.hasMessages(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED)) {
+ mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED);
+ }
+ }
+
+ @Override
+ public void onInputRemoved(String inputId) {
+ if (!mStarted) {
+ return;
+ }
+ mInputs.remove(inputId);
+ if (!mChannelRecordMapLoaded) {
+ return;
+ }
+ boolean channelRecordMapChanged = false;
+ for (ChannelRecord channelRecord : mChannelRecordMap.values()) {
+ if (channelRecord.getChannel().getInputId().equals(inputId)) {
+ channelRecord.setInputRemoved(true);
+ mAvailableChannelRecordMap.remove(channelRecord.getChannel().getId());
+ channelRecordMapChanged = true;
+ }
+ }
+ if (channelRecordMapChanged
+ && !mHandler.hasMessages(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED)) {
+ mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED);
+ }
+ }
+
+ @Override
+ public void onInputUpdated(String inputId) { }
+ };
+
+ private RecommendationDataManager(Context context) {
+ mContext = context.getApplicationContext();
+ mHandlerThread = new HandlerThread("RecommendationDataManager");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START:
+ onStart();
+ break;
+ case MSG_STOP:
+ if (mStarted) {
+ onStop();
+ }
+ break;
+ case MSG_UPDATE_CHANNEL:
+ if (mStarted) {
+ onUpdateChannel((Uri) msg.obj);
+ }
+ break;
+ case MSG_UPDATE_CHANNELS:
+ if (mStarted) {
+ onUpdateChannels((Uri) msg.obj);
+ }
+ break;
+ case MSG_UPDATE_WATCH_HISTORY:
+ if (mStarted) {
+ onLoadWatchHistory((Uri) msg.obj);
+ }
+ break;
+ case MSG_NOTIFY_CHANNEL_RECORD_MAP_LOADED:
+ if (mStarted) {
+ onNotifyChannelRecordMapLoaded();
+ }
+ break;
+ case MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED:
+ if (mStarted) {
+ onNotifyChannelRecordMapChanged();
+ }
+ break;
+ }
+ }
+ };
+ mContentObserver = new RecommendationContentObserver(mHandler);
+ }
+
+ /**
+ * 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.
+ */
+ public int getChannelRecordCount() {
+ return mAvailableChannelRecordMap.size();
+ }
+
+ /**
+ * Returns a Collection of ChannelRecords.
+ */
+ public Collection<ChannelRecord> getChannelRecords() {
+ return Collections.unmodifiableCollection(mAvailableChannelRecordMap.values());
+ }
+
+ private void start() {
+ mHandler.sendEmptyMessage(MSG_START);
+ }
+
+ private void stop() {
+ for (int what = MSG_FIRST; what <= MSG_LAST; ++what) {
+ mHandler.removeMessages(what);
+ }
+ mHandler.sendEmptyMessage(MSG_STOP);
+ mHandlerThread.quitSafely();
+ sManager = null;
+ }
+
+ private int getListenerIndexLocked(Listener listener) {
+ for (int i = 0; i < mListeners.size(); ++i) {
+ if (mListeners.get(i).mListener == listener) {
+ return i;
+ }
+ }
+ return INVALID_INDEX;
+ }
+
+ private void addListener(Listener listener) {
+ synchronized (mListeners) {
+ if (getListenerIndexLocked(listener) == INVALID_INDEX) {
+ mListeners.add((new ListenerRecord(listener)));
+ }
+ }
+ }
+
+ private void removeListener(Listener listener) {
+ synchronized (mListeners) {
+ int idx = getListenerIndexLocked(listener);
+ if (idx != INVALID_INDEX) {
+ mListeners.remove(idx);
+ }
+ }
+ }
+
+ private void onStart() {
+ if (!mStarted) {
+ mStarted = true;
+ mCancelLoadTask = false;
+ mContext.getContentResolver().registerContentObserver(
+ TvContract.Channels.CONTENT_URI, true, mContentObserver);
+ mContext.getContentResolver().registerContentObserver(
+ TvContract.WatchedPrograms.CONTENT_URI, true, mContentObserver);
+ mHandler.obtainMessage(MSG_UPDATE_CHANNELS, TvContract.Channels.CONTENT_URI)
+ .sendToTarget();
+ mHandler.obtainMessage(MSG_UPDATE_WATCH_HISTORY, TvContract.WatchedPrograms.CONTENT_URI)
+ .sendToTarget();
+ mTvInputManager = (TvInputManager) mContext.getSystemService(Context.TV_INPUT_SERVICE);
+ mTvInputManager.registerCallback(mInternalCallback, mHandler);
+ for (TvInputInfo input : mTvInputManager.getTvInputList()) {
+ mInputs.add(input.getId());
+ }
+ }
+ if (mChannelRecordMapLoaded) {
+ mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_LOADED);
+ }
+ }
+
+ private void onStop() {
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ mCancelLoadTask = true;
+ mChannelRecordMap.clear();
+ mAvailableChannelRecordMap.clear();
+ mInputs.clear();
+ mTvInputManager.unregisterCallback(mInternalCallback);
+ mStarted = false;
+ }
+
+ private void onUpdateChannel(Uri uri) {
+ Channel channel = null;
+ try (Cursor cursor = mContext.getContentResolver().query(uri, Channel.PROJECTION,
+ null, null, null)) {
+ if (cursor != null && cursor.moveToFirst()) {
+ channel = Channel.fromCursor(cursor);
+ }
+ }
+ boolean isChannelRecordMapChanged = false;
+ if (channel == null) {
+ long channelId = ContentUris.parseId(uri);
+ mChannelRecordMap.remove(channelId);
+ isChannelRecordMapChanged = mAvailableChannelRecordMap.remove(channelId) != null;
+ } else if (updateChannelRecordMapFromChannel(channel)) {
+ isChannelRecordMapChanged = true;
+ }
+ if (isChannelRecordMapChanged && mChannelRecordMapLoaded
+ && !mHandler.hasMessages(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED)) {
+ mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED);
+ }
+ }
+
+ private void onUpdateChannels(Uri uri) {
+ List<Channel> channels = new ArrayList<>();
+ try (Cursor cursor = mContext.getContentResolver().query(uri, Channel.PROJECTION,
+ null, null, null)) {
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ if (mCancelLoadTask) {
+ return;
+ }
+ channels.add(Channel.fromCursor(cursor));
+ }
+ }
+ }
+ boolean isChannelRecordMapChanged = false;
+ Set<Long> removedChannelIdSet = new HashSet<>(mChannelRecordMap.keySet());
+ // Builds removedChannelIdSet.
+ for (Channel channel : channels) {
+ if (updateChannelRecordMapFromChannel(channel)) {
+ isChannelRecordMapChanged = true;
+ }
+ removedChannelIdSet.remove(channel.getId());
+ }
+
+ if (!removedChannelIdSet.isEmpty()) {
+ for (Long channelId : removedChannelIdSet) {
+ mChannelRecordMap.remove(channelId);
+ if (mAvailableChannelRecordMap.remove(channelId) != null) {
+ isChannelRecordMapChanged = true;
+ }
+ }
+ }
+ if (isChannelRecordMapChanged && mChannelRecordMapLoaded
+ && !mHandler.hasMessages(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED)) {
+ mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_CHANGED);
+ }
+ }
+
+ private void onLoadWatchHistory(Uri uri) {
+ List<WatchedProgram> history = new ArrayList<>();
+ try (Cursor cursor = mContext.getContentResolver().query(uri, null, null, null, null)) {
+ if (cursor != null && cursor.moveToLast()) {
+ do {
+ if (mCancelLoadTask) {
+ return;
+ }
+ history.add(createWatchedProgramFromWatchedProgramCursor(cursor));
+ } while (cursor.moveToPrevious());
+ }
+ }
+ for (WatchedProgram watchedProgram : history) {
+ final ChannelRecord channelRecord =
+ updateChannelRecordFromWatchedProgram(watchedProgram);
+ if (mChannelRecordMapLoaded && channelRecord != null) {
+ synchronized (mListeners) {
+ for (ListenerRecord l : mListeners) {
+ l.postNewWatchLog(channelRecord);
+ }
+ }
+ }
+ }
+ if (!mChannelRecordMapLoaded) {
+ mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_LOADED);
+ }
+ }
+
+ 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);
+ }
+
+ 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));
+ }
+
+ private void onNotifyChannelRecordMapLoaded() {
+ mChannelRecordMapLoaded = true;
+ synchronized (mListeners) {
+ for (ListenerRecord l : mListeners) {
+ l.postChannelRecordLoaded();
+ }
+ }
+ }
+
+ private void onNotifyChannelRecordMapChanged() {
+ synchronized (mListeners) {
+ for (ListenerRecord l : mListeners) {
+ l.postChannelRecordChanged();
+ }
+ }
+ }
+
+ /**
+ * Returns true if ChannelRecords are added into mChannelRecordMap or removed from it.
+ */
+ private boolean updateChannelRecordMapFromChannel(Channel channel) {
+ if (!channel.isBrowsable()) {
+ mChannelRecordMap.remove(channel.getId());
+ return mAvailableChannelRecordMap.remove(channel.getId()) != null;
+ }
+ ChannelRecord channelRecord = mChannelRecordMap.get(channel.getId());
+ boolean inputRemoved = !mInputs.contains(channel.getInputId());
+ if (channelRecord == null) {
+ ChannelRecord record = new ChannelRecord(mContext, channel, inputRemoved);
+ mChannelRecordMap.put(channel.getId(), record);
+ if (!inputRemoved) {
+ mAvailableChannelRecordMap.put(channel.getId(), record);
+ return true;
+ }
+ return false;
+ }
+ boolean oldInputRemoved = channelRecord.isInputRemoved();
+ channelRecord.setChannel(channel, inputRemoved);
+ return oldInputRemoved != inputRemoved;
+ }
+
+ private ChannelRecord updateChannelRecordFromWatchedProgram(WatchedProgram program) {
+ ChannelRecord channelRecord = null;
+ if (program != null && program.getWatchEndTimeMs() != 0l) {
+ channelRecord = mChannelRecordMap.get(program.getProgram().getChannelId());
+ if (channelRecord != null
+ && channelRecord.getLastWatchEndTimeMs() < program.getWatchEndTimeMs()) {
+ channelRecord.logWatchHistory(program);
+ }
+ }
+ return channelRecord;
+ }
+
+ private class RecommendationContentObserver extends ContentObserver {
+ public RecommendationContentObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(final boolean selfChange, final Uri uri) {
+ switch (sUriMatcher.match(uri)) {
+ case MATCH_CHANNEL:
+ if (!mHandler.hasMessages(MSG_UPDATE_CHANNELS, TvContract.Channels.CONTENT_URI)) {
+ mHandler.obtainMessage(MSG_UPDATE_CHANNELS, TvContract.Channels.CONTENT_URI)
+ .sendToTarget();
+ }
+ break;
+ case MATCH_CHANNEL_ID:
+ mHandler.obtainMessage(MSG_UPDATE_CHANNEL, uri).sendToTarget();
+ break;
+ case MATCH_WATCHED_PROGRAM_ID:
+ if (!mHandler.hasMessages(MSG_UPDATE_WATCH_HISTORY,
+ TvContract.WatchedPrograms.CONTENT_URI)) {
+ mHandler.obtainMessage(MSG_UPDATE_WATCH_HISTORY, uri).sendToTarget();
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * A listener interface to receive notification about the recommendation data.
+ */
+ public interface Listener {
+ /**
+ * Called when loading channel record map from database is finished.
+ * It will be called after RecommendationDataManager.start() is finished.
+ */
+ void onChannelRecordLoaded();
+
+ /**
+ * Called when a new watch log is added into the corresponding channelRecord.
+ *
+ * @param channelRecord The channel record corresponds to the new watch log.
+ */
+ void onNewWatchLog(ChannelRecord channelRecord);
+
+ /**
+ * Called when the channel record map changes.
+ */
+ void onChannelRecordChanged();
+ }
+
+ private static class ListenerRecord {
+ private Listener mListener;
+ private final Handler mHandler;
+
+ public ListenerRecord(Listener listener) {
+ mHandler = new Handler();
+ mListener = listener;
+ }
+
+ public void postChannelRecordLoaded() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mListener != null) {
+ mListener.onChannelRecordLoaded();
+ }
+ }
+ });
+ }
+
+ public void postNewWatchLog(final ChannelRecord channelRecord) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mListener != null) {
+ mListener.onNewWatchLog(channelRecord);
+ }
+ }
+ });
+ }
+
+ public void postChannelRecordChanged() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mListener != null) {
+ mListener.onChannelRecordChanged();
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/src/com/android/tv/recommendation/Recommender.java b/src/com/android/tv/recommendation/Recommender.java
new file mode 100644
index 00000000..0561449e
--- /dev/null
+++ b/src/com/android/tv/recommendation/Recommender.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.recommendation;
+
+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;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+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";
+ private static final long MINIMUM_RECOMMENDATION_UPDATE_PERIOD = TimeUnit.MINUTES.toMillis(5);
+ private static final Comparator<Pair<Channel, Double>> mChannelScoreComparator =
+ new Comparator<Pair<Channel, Double>>() {
+ @Override
+ public int compare(Pair<Channel, Double> lhs, Pair<Channel, Double> rhs) {
+ // Sort the scores with descending order.
+ return rhs.second.compareTo(lhs.second);
+ }
+ };
+
+ private final List<EvaluatorWrapper> mEvaluators = new ArrayList<>();
+ private final boolean mIncludeRecommendedOnly;
+ private final Listener mListener;
+
+ private final Map<Long, String> mChannelSortKey = new HashMap<>();
+ private final RecommendationDataManager mDataManager;
+ private List<Channel> mPreviousRecommendedChannels = new ArrayList<>();
+ private long mLastRecommendationUpdatedTimeUtcMillis;
+ private boolean mChannelRecordLoaded;
+
+ /**
+ * Create a recommender object.
+ *
+ * @param includeRecommendedOnly true to include only recommended results, or false.
+ */
+ public Recommender(Context context, Listener listener, boolean includeRecommendedOnly) {
+ mListener = listener;
+ mIncludeRecommendedOnly = includeRecommendedOnly;
+ mDataManager = RecommendationDataManager.acquireManager(context, this);
+ }
+
+ @VisibleForTesting
+ Recommender(Listener listener, boolean includeRecommendedOnly,
+ RecommendationDataManager dataManager) {
+ mListener = listener;
+ mIncludeRecommendedOnly = includeRecommendedOnly;
+ mDataManager = dataManager;
+ }
+
+ public boolean isReady() {
+ return mChannelRecordLoaded;
+ }
+
+ public void release() {
+ mDataManager.release(this);
+ }
+
+ public void registerEvaluator(Evaluator evaluator) {
+ 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
+ * {@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}.
+ */
+ public void registerEvaluator(Evaluator evaluator, double baseScore, double weight) {
+ mEvaluators.add(new EvaluatorWrapper(this, evaluator, baseScore, weight));
+ }
+
+ public List<Channel> recommendChannels() {
+ return recommendChannels(mDataManager.getChannelRecordCount());
+ }
+
+ /**
+ * 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}.
+ */
+ public List<Channel> recommendChannels(int size) {
+ List<Pair<Channel, Double>> records = new ArrayList<>();
+ Collection<ChannelRecord> channelRecordList = mDataManager.getChannelRecords();
+ for (ChannelRecord cr : channelRecordList) {
+ double maxScore = Evaluator.NOT_RECOMMENDED;
+ for (EvaluatorWrapper evaluator : mEvaluators) {
+ double score = evaluator.getScaledEvaluatorScore(cr.getChannel().getId());
+ if (score > maxScore) {
+ maxScore = score;
+ }
+ }
+ if (!mIncludeRecommendedOnly || maxScore != Evaluator.NOT_RECOMMENDED) {
+ records.add(new Pair<>(cr.getChannel(), maxScore));
+ }
+ }
+ if (size > records.size()) {
+ size = records.size();
+ }
+ Collections.sort(records, mChannelScoreComparator);
+
+ List<Channel> results = new ArrayList<>();
+
+ mChannelSortKey.clear();
+ String sortKeyFormat = "%0" + String.valueOf(size).length() + "d";
+ for (int i = 0; i < size; ++i) {
+ // Channel with smaller sort key has higher priority.
+ mChannelSortKey.put(records.get(i).first.getId(), String.format(sortKeyFormat, i));
+ results.add(records.get(i).first);
+ }
+ return Collections.unmodifiableList(results);
+ }
+
+ /**
+ * Returns the {@link Channel} object for a given channel ID from the channel pool that this
+ * recommendation engine has.
+ *
+ * @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.
+ */
+ public Channel getChannel(long channelId) {
+ ChannelRecord record = mDataManager.getChannelRecord(channelId);
+ return record == null ? null : record.getChannel();
+ }
+
+ /**
+ * Returns the {@link ChannelRecord} object for a given channel ID.
+ *
+ * @param channelId The channel ID to receive the {@link ChannelRecord} object for.
+ * @return the {@link ChannelRecord} object for the given channel ID.
+ */
+ public ChannelRecord getChannelRecord(long channelId) {
+ return mDataManager.getChannelRecord(channelId);
+ }
+
+ /**
+ * 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
+ * of non-recommended channel, it returns {@link #INVALID_CHANNEL_SORT_KEY}.
+ */
+ public String getChannelSortKey(long channelId) {
+ String key = mChannelSortKey.get(channelId);
+ return key == null ? INVALID_CHANNEL_SORT_KEY : key;
+ }
+
+ @Override
+ public void onChannelRecordLoaded() {
+ mChannelRecordLoaded = true;
+ mListener.onRecommenderReady();
+ List<ChannelRecord> channels = new ArrayList<>(mDataManager.getChannelRecords());
+ for (EvaluatorWrapper evaluator : mEvaluators) {
+ evaluator.onChannelListChanged(Collections.unmodifiableList(channels));
+ }
+ }
+
+ @Override
+ public void onNewWatchLog(ChannelRecord channelRecord) {
+ for (EvaluatorWrapper evaluator : mEvaluators) {
+ evaluator.onNewWatchLog(channelRecord);
+ }
+ checkRecommendationChanged();
+ }
+
+ @Override
+ public void onChannelRecordChanged() {
+ if (mChannelRecordLoaded) {
+ List<ChannelRecord> channels = new ArrayList<>(mDataManager.getChannelRecords());
+ for (EvaluatorWrapper evaluator : mEvaluators) {
+ evaluator.onChannelListChanged(Collections.unmodifiableList(channels));
+ }
+ }
+ checkRecommendationChanged();
+ }
+
+ private void checkRecommendationChanged() {
+ long currentTimeUtcMillis = System.currentTimeMillis();
+ if (currentTimeUtcMillis - mLastRecommendationUpdatedTimeUtcMillis
+ < MINIMUM_RECOMMENDATION_UPDATE_PERIOD) {
+ return;
+ }
+ mLastRecommendationUpdatedTimeUtcMillis = currentTimeUtcMillis;
+ List<Channel> recommendedChannels = recommendChannels();
+ if (!recommendedChannels.equals(mPreviousRecommendedChannels)) {
+ mPreviousRecommendedChannels = recommendedChannels;
+ mListener.onRecommendationChanged();
+ }
+ }
+
+ @VisibleForTesting
+ void setLastRecommendationUpdatedTimeUtcMs(long newUpdatedTimeMs) {
+ mLastRecommendationUpdatedTimeUtcMillis = newUpdatedTimeMs;
+ }
+
+ public static abstract class Evaluator {
+ public static final double NOT_RECOMMENDED = -1.0;
+ private Recommender mRecommender;
+
+ protected Evaluator() {}
+
+ protected void onChannelRecordListChanged(List<ChannelRecord> 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) {
+ }
+
+ /**
+ * 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
+ */
+ protected abstract double evaluateChannel(final long channelId);
+
+ protected void setRecommender(Recommender recommender) {
+ mRecommender = recommender;
+ }
+
+ protected Recommender getRecommender() {
+ return mRecommender;
+ }
+ }
+
+ private static class EvaluatorWrapper {
+ private static final double DEFAULT_BASE_SCORE = 0.0;
+ private static final double DEFAULT_WEIGHT = 1.0;
+
+ private final Evaluator mEvaluator;
+ // The minimum score of the Recommender unless it gives up to provide the score.
+ private final double mBaseScore;
+ // The weight of the recommender. The return-value of getScore() will be multiplied by
+ // this value.
+ private final double mWeight;
+
+ public EvaluatorWrapper(Recommender recommender, Evaluator evaluator,
+ double baseScore, double weight) {
+ mEvaluator = evaluator;
+ evaluator.setRecommender(recommender);
+ mBaseScore = baseScore;
+ mWeight = weight;
+ }
+
+ /**
+ * 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).
+ */
+ 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);
+ }
+ // 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);
+ score = 1.0;
+ }
+ return mBaseScore + score * mWeight;
+ }
+
+ public void onNewWatchLog(ChannelRecord channelRecord) {
+ mEvaluator.onNewWatchLog(channelRecord);
+ }
+
+ public void onChannelListChanged(List<ChannelRecord> channelRecords) {
+ mEvaluator.onChannelRecordListChanged(channelRecords);
+ }
+ }
+
+ public interface Listener {
+ /**
+ * Called after channel record map is loaded.
+ */
+ void onRecommenderReady();
+
+ /**
+ * Called when the recommendation changes.
+ */
+ void onRecommendationChanged();
+ }
+}
diff --git a/src/com/android/tv/recommendation/RoutineWatchEvaluator.java b/src/com/android/tv/recommendation/RoutineWatchEvaluator.java
new file mode 100644
index 00000000..8f6f203d
--- /dev/null
+++ b/src/com/android/tv/recommendation/RoutineWatchEvaluator.java
@@ -0,0 +1,246 @@
+/*
+ * 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.recommendation;
+
+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;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+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;
+ 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);
+ private static final long MAX_DIFF_MS_FOR_OLD_PROGRAM = TimeUnit.DAYS.toMillis(56);
+ private static final String REGULAR_EXPRESSION_FOR_WHITE_SPACES = "\\s+";
+
+ @Override
+ public double evaluateChannel(long channelId) {
+ ChannelRecord cr = getRecommender().getChannelRecord(channelId);
+ if (cr == null) {
+ return NOT_RECOMMENDED;
+ }
+
+ Program currentProgram = cr.getCurrentProgram();
+ if (currentProgram == null) {
+ return NOT_RECOMMENDED;
+ }
+
+ WatchedProgram[] watchHistory = cr.getWatchHistory();
+ if (watchHistory.length < 1) {
+ return NOT_RECOMMENDED;
+ }
+
+ Program watchedProgram = watchHistory[watchHistory.length - 1].getProgram();
+ long startTimeDiffMsWithCurrentProgram = currentProgram.getStartTimeUtcMillis()
+ - watchedProgram.getStartTimeUtcMillis();
+ if (startTimeDiffMsWithCurrentProgram >= MAX_DIFF_MS_FOR_OLD_PROGRAM) {
+ return NOT_RECOMMENDED;
+ }
+
+ double maxScore = NOT_RECOMMENDED;
+ long watchedDurationMs = watchHistory[watchHistory.length - 1].getWatchedDurationMs();
+ for (int i = watchHistory.length - 2; i >= 0; --i) {
+ if (watchedProgram.getStartTimeUtcMillis()
+ == watchHistory[i].getProgram().getStartTimeUtcMillis()) {
+ watchedDurationMs += watchHistory[i].getWatchedDurationMs();
+ } else {
+ 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();
+ if (startTimeDiffMsWithCurrentProgram >= MAX_DIFF_MS_FOR_OLD_PROGRAM) {
+ return maxScore;
+ }
+ }
+ }
+ double score = calculateRoutineWatchScore(
+ currentProgram, watchedProgram, watchedDurationMs);
+ if (score >= REQUIRED_MIN_SCORE && score > maxScore) {
+ maxScore = score;
+ }
+ return maxScore;
+ }
+
+ private double calculateRoutineWatchScore(
+ Program currentProgram, Program watchedProgram, long watchedDurationMs) {
+ double timeMatchScore = calculateTimeMatchScore(currentProgram, watchedProgram);
+ 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;
+ return (titleMatchScore * TITLE_MATCH_WEIGHT + timeMatchScore * TIME_MATCH_WEIGHT)
+ * watchDurationScore * multiplierForOldProgram;
+ }
+
+ private double calculateTitleMatchScore(String title1, String title2) {
+ List<String> wordList1 = splitTextToWords(title1);
+ List<String> wordList2 = splitTextToWords(title2);
+
+ int maxMatchedWordSeqLen = calculateMaximumMatchedWordSequenceLength(
+ wordList1, wordList2);
+
+ // F-measure score
+ double precision = (double) maxMatchedWordSeqLen / wordList1.size();
+ double recall = (double) maxMatchedWordSeqLen / wordList2.size();
+ return 2.0 * precision * recall / (precision + recall);
+ }
+
+ @VisibleForTesting
+ int calculateMaximumMatchedWordSequenceLength(
+ List<String> toSearchWords, List<String> toMatchWords) {
+ int[] matchedWordSeqLen = new int[toMatchWords.size()];
+ int maxMatchedWordSeqLen = 0;
+ for (String word : toSearchWords) {
+ for (int j = toMatchWords.size() - 1; j >= 0; --j) {
+ if (word.equals(toMatchWords.get(j))) {
+ matchedWordSeqLen[j] = j > 0 ? matchedWordSeqLen[j - 1] + 1 : 1;
+ } else {
+ maxMatchedWordSeqLen = Math.max(maxMatchedWordSeqLen, matchedWordSeqLen[j]);
+ matchedWordSeqLen[j] = 0;
+ }
+ }
+ }
+ for (int len : matchedWordSeqLen) {
+ maxMatchedWordSeqLen = Math.max(maxMatchedWordSeqLen, len);
+ }
+
+ return maxMatchedWordSeqLen;
+ }
+
+ private double calculateTimeMatchScore(Program p1, Program p2) {
+ ProgramTime t1 = ProgramTime.createFromProgram(p1);
+ ProgramTime t2 = ProgramTime.createFromProgram(p2);
+
+ double dupTimeScore = calculateOverlappedIntervalScore(t1, t2);
+
+ // F-measure score
+ double precision = dupTimeScore / (t1.endTimeOfDayInSec - t1.startTimeOfDayInSec);
+ double recall = dupTimeScore / (t2.endTimeOfDayInSec - t2.startTimeOfDayInSec);
+ return 2.0 * precision * recall / (precision + recall);
+ }
+
+ @VisibleForTesting
+ double calculateOverlappedIntervalScore(ProgramTime t1, ProgramTime t2) {
+ if (t1.dayChanged && !t2.dayChanged) {
+ // Swap two values.
+ return calculateOverlappedIntervalScore(t2, t1);
+ }
+
+ 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));
+ 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);
+ // 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));
+ }
+
+ if (!sameDay) {
+ score *= MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK;
+ }
+ return score;
+ }
+
+ private double calculateWatchDurationScore(Program program, long durationMs) {
+ return (double) durationMs
+ / (program.getEndTimeUtcMillis() - program.getStartTimeUtcMillis());
+ }
+
+ @VisibleForTesting
+ static int getTimeOfDayInSec(Calendar time) {
+ return time.get(Calendar.HOUR_OF_DAY) * 60 * 60
+ + time.get(Calendar.MINUTE) * 60
+ + time.get(Calendar.SECOND);
+ }
+
+ @VisibleForTesting
+ static List<String> splitTextToWords(String text) {
+ List<String> wordList = new ArrayList<>();
+ BreakIterator boundary = BreakIterator.getWordInstance();
+ boundary.setText(text);
+ int start = boundary.first();
+ 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))) {
+ wordList.add(word);
+ }
+ }
+ return wordList;
+ }
+
+ @VisibleForTesting
+ static class ProgramTime {
+ final int startTimeOfDayInSec;
+ final int endTimeOfDayInSec;
+ final int weekDay;
+ final boolean dayChanged;
+
+ public static ProgramTime createFromProgram(Program p) {
+ Calendar time = Calendar.getInstance();
+
+ time.setTimeInMillis(p.getStartTimeUtcMillis());
+ int weekDay = time.get(Calendar.DAY_OF_WEEK);
+ int startTimeOfDayInSec = getTimeOfDayInSec(time);
+
+ 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;
+
+ return new ProgramTime(startTimeOfDayInSec, endTimeOfDayInSec, weekDay, dayChanged);
+ }
+
+ private ProgramTime(int startTimeOfDayInSec, int endTimeOfDayInSec, int weekDay,
+ boolean dayChanged) {
+ this.startTimeOfDayInSec = startTimeOfDayInSec;
+ this.endTimeOfDayInSec = endTimeOfDayInSec;
+ this.weekDay = weekDay;
+ this.dayChanged = dayChanged;
+ }
+ }
+}
diff --git a/src/com/android/tv/recommendation/RoutineWatchRecommender.java b/src/com/android/tv/recommendation/RoutineWatchRecommender.java
deleted file mode 100644
index 0c7445e3..00000000
--- a/src/com/android/tv/recommendation/RoutineWatchRecommender.java
+++ /dev/null
@@ -1,107 +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.recommendation;
-
-import android.content.Context;
-import android.text.format.Time;
-
-import com.android.tv.data.Program;
-import com.android.tv.recommendation.TvRecommendation.ChannelRecord;
-import com.android.tv.recommendation.TvRecommendation.TvRecommender;
-import com.android.tv.util.Utils;
-
-public class RoutineWatchRecommender extends TvRecommender {
- // 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;
- private 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 final Context mContext;
-
- public RoutineWatchRecommender(Context context) {
- mContext = context;
- }
-
- @Override
- public double calculateScore(final ChannelRecord cr) {
- double maxScore = NOT_RECOMMENDED;
- Program curProgram = Utils.getCurrentProgram(mContext, cr.getChannelUri());
-
- if (curProgram != null) {
- for (Program program : cr.getWatchHistory()) {
- // TODO: need to consider the watched time.
- double score = calculateTitleMatchScore(curProgram, program) * (TITLE_MATCH_WEIGHT
- + calculateTimeMatchScore(program) * TIME_MATCH_WEIGHT);
- if (score >= REQUIRED_MIN_SCORE && score > maxScore) {
- maxScore = score;
- }
- }
- }
- return maxScore;
- }
-
- private int calculateTimeOfDay(Time time) {
- return time.hour * 60 * 60 + time.minute * 60 + time.second;
- }
-
- private double calculateTitleMatchScore(Program p1, Program p2) {
- // TODO: Use more proper algorithm for matching title.
- if (p1 == null | p2 == null || p1.getTitle() == null || p2.getTitle() == null) {
- return 0.0;
- }
- return p1.getTitle().equals(p2.getTitle()) ? 1.0 : 0.0;
- }
-
- private double calculateTimeMatchScore(Program program) {
- Time curTime = new Time();
- curTime.set(System.currentTimeMillis());
- int curTimeOfDay = calculateTimeOfDay(curTime);
- int curWeekDay = curTime.weekDay;
-
- Time startTime = new Time();
- startTime.set(program.getStartTimeUtcMillis());
- int startTimeOfDay = calculateTimeOfDay(startTime);
-
- Time endTime = new Time();
- endTime.set(program.getEndTimeUtcMillis());
- int endTimeOfDay = calculateTimeOfDay(endTime);
- if (startTimeOfDay > endTimeOfDay) {
- if (curTimeOfDay < endTimeOfDay) {
- curTimeOfDay += 24 * 60 * 60;
- curWeekDay = (curWeekDay + 6) % 7;
- }
- endTimeOfDay += 24 * 60 * 60;
- }
-
- double score = calculateTimeOfDayMatchScore(curTimeOfDay, startTimeOfDay, endTimeOfDay);
- if (curWeekDay != startTime.weekDay) {
- score *= MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK;
- }
- return score;
- }
-
- private double calculateTimeOfDayMatchScore(int curTimeOfDay, int startTimeOfDay,
- int endTimeOfDay) {
- // TODO: need to have intermediate values between 0.0 and 1.0
- if (curTimeOfDay >= startTimeOfDay && curTimeOfDay < endTimeOfDay) {
- return 1.0;
- }
- return 0.0;
- }
-}
diff --git a/src/com/android/tv/recommendation/SampleRecommender.java b/src/com/android/tv/recommendation/SampleRecommender.java
deleted file mode 100644
index ca155f64..00000000
--- a/src/com/android/tv/recommendation/SampleRecommender.java
+++ /dev/null
@@ -1,49 +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.recommendation;
-
-import android.content.Context;
-
-import com.android.tv.data.Program;
-import com.android.tv.recommendation.TvRecommendation.ChannelRecord;
-import com.android.tv.recommendation.TvRecommendation.TvRecommender;
-import com.android.tv.util.Utils;
-
-public class SampleRecommender extends TvRecommender {
- private final Context mContext;
-
- public SampleRecommender(Context context) {
- mContext = context;
- }
-
- @Override
- public double calculateScore(final ChannelRecord cr) {
- if (cr.getLastWatchedTimeMs() == 0l) {
- return NOT_RECOMMENDED;
- }
-
- Program program = Utils.getCurrentProgram(mContext, cr.getChannelUri());
- if (program == null) {
- return NOT_RECOMMENDED;
- }
-
- double ret = Math.random();
- ret *= program.getEndTimeUtcMillis() - System.currentTimeMillis();
- ret /= program.getEndTimeUtcMillis() - program.getStartTimeUtcMillis();
- return ret;
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/recommendation/TvRecommendation.java b/src/com/android/tv/recommendation/TvRecommendation.java
deleted file mode 100644
index 6483eb0d..00000000
--- a/src/com/android/tv/recommendation/TvRecommendation.java
+++ /dev/null
@@ -1,481 +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.recommendation;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.UriMatcher;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.media.tv.TvContract;
-import android.media.tv.TvContract.Channels;
-import android.net.Uri;
-import android.os.Handler;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.tv.data.Channel;
-import com.android.tv.data.Program;
-
-import java.util.ArrayList;
-import java.util.ArrayDeque;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class TvRecommendation {
- private static final String TAG = "TvRecommendation";
-
- private static final String PATH_INPUT = "input";
-
- 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_WATCHED_PROGRAM_ID = 3;
- private static final int MATCH_INPUT_ID_CHANNEL = 4;
-
- static {
- sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- sUriMatcher.addURI(TvContract.AUTHORITY, "channel", MATCH_CHANNEL);
- sUriMatcher.addURI(TvContract.AUTHORITY, "channel/#", MATCH_CHANNEL_ID);
- sUriMatcher.addURI(TvContract.AUTHORITY, "watched_program/#", MATCH_WATCHED_PROGRAM_ID);
- sUriMatcher.addURI(TvContract.AUTHORITY, "input/*/channel", MATCH_INPUT_ID_CHANNEL);
- }
-
- private final List<TvRecommenderWrapper> mTvRecommenders;
- // TODO: Consider to define each observer rather than the list or observers.
- private final Handler mHandler;
- private final ContentObserver mContentObserver;
- private final Context mContext;
- private final boolean mIncludeRecommendedOnly;
- private Map<Long, ChannelRecord> mChannelRecordMap;
-
- /**
- * Create a TV recommendation object.
- *
- * @param context The context to register {@link ContentObserver}s for
- * {@link android.media.tv.TvContract.Channels} and
- * {@link android.media.tv.TvContract.WatchedPrograms}.
- * @param handler The handler to run {@link android.database.ContentObserver#onChange(boolean)}
- * on, or null if none.
- * @param includeRecommendedOnly true to include only recommended results, or false.
- */
- public TvRecommendation(Context context, Handler handler, boolean includeRecommendedOnly) {
- mContext = context;
- mChannelRecordMap = new ConcurrentHashMap<Long, ChannelRecord>();
- mHandler = handler;
- mContentObserver = createContentObserver();
- mIncludeRecommendedOnly = includeRecommendedOnly;
- mTvRecommenders = new ArrayList<TvRecommenderWrapper>();
- registerContentObservers();
- buildChannelRecordMap();
- }
-
- private ContentObserver createContentObserver() {
- return new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- int match = sUriMatcher.match(uri);
- if (match == MATCH_CHANNEL) {
- Map<Long, ChannelRecord> channelRecordMap =
- new ConcurrentHashMap<Long, ChannelRecord>();
-
- Cursor c = null;
- try {
- c = mContext.getContentResolver().query(uri, null, null, null, null);
- if (c != null) {
- int channelIdIndex = c.getColumnIndex(Channels._ID);
- long channelId;
- while (c.moveToNext()) {
- channelId = c.getLong(channelIdIndex);
- ChannelRecord oldChannelRecord = mChannelRecordMap.get(channelId);
- ChannelRecord newChannelRecord =
- new ChannelRecord(mContext, Channel.fromCursor(c));
- newChannelRecord.mLastWatchedTimeMs = (oldChannelRecord == null)
- ? 0 : oldChannelRecord.mLastWatchedTimeMs;
- channelRecordMap.put(channelId, newChannelRecord);
- }
- }
- } finally {
- if (c != null) {
- c.close();
- }
- }
- mChannelRecordMap = channelRecordMap;
- } else if (match == MATCH_CHANNEL_ID) {
- long channelId = ContentUris.parseId(uri);
- Cursor cursor = null;
- try {
- cursor = mContext.getContentResolver().query(
- uri, null, null, null, null);
- if (cursor != null && cursor.moveToFirst()) {
- ChannelRecord oldChannelRecord = mChannelRecordMap.get(channelId);
- ChannelRecord newChannelRecord =
- new ChannelRecord(mContext, Channel.fromCursor(cursor));
- newChannelRecord.mLastWatchedTimeMs = (oldChannelRecord == null)
- ? 0 : oldChannelRecord.mLastWatchedTimeMs;
- mChannelRecordMap.put(channelId, newChannelRecord);
- } else {
- mChannelRecordMap.remove(channelId);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- } else if (match == MATCH_INPUT_ID_CHANNEL) {
- String inputId = TvContract.getInputId(uri);
-
- Set<Long> channelIdSet = new HashSet<Long>();
- for (ChannelRecord cr : mChannelRecordMap.values()) {
- if (inputId.equals(cr.mChannel.getInputId())) {
- channelIdSet.add(cr.mChannel.getId());
- }
- }
-
- Uri inputUri = TvContract.buildChannelsUriForInput(inputId, false);
- Cursor c = null;
- try {
- c = mContext.getContentResolver().query(inputUri, null, null, null, null);
- if (c != null) {
- int channelIdIndex = c.getColumnIndex(Channels._ID);
- long channelId;
- while (c.moveToNext()) {
- channelId = c.getLong(channelIdIndex);
- ChannelRecord oldChannelRecord = mChannelRecordMap.get(channelId);
- ChannelRecord newChannelRecord =
- new ChannelRecord(mContext, Channel.fromCursor(c));
- newChannelRecord.mLastWatchedTimeMs = (oldChannelRecord == null)
- ? 0 : oldChannelRecord.mLastWatchedTimeMs;
- mChannelRecordMap.put(channelId, newChannelRecord);
- channelIdSet.remove(channelId);
- }
- }
- } finally {
- if (c != null) {
- c.close();
- }
- }
-
- for (Long channelId : channelIdSet) {
- mChannelRecordMap.remove(channelId);
- }
- } else if (match == MATCH_WATCHED_PROGRAM_ID) {
- Cursor cursor = null;
- try {
- cursor = mContext.getContentResolver().query(
- uri, null, null, null, null);
- if (cursor != null && cursor.moveToFirst()) {
- ChannelRecord channelRecord =
- updateChannelRecordFromWatchedProgramCursor(cursor);
- for (TvRecommenderWrapper recommender : mTvRecommenders) {
- recommender.onNewWatchLog(channelRecord);
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- }
- };
- }
-
- public void release() {
- unregisterContentObservers();
- mChannelRecordMap.clear();
- }
-
- public void registerTvRecommender(TvRecommender recommender) {
- registerTvRecommender(recommender,
- TvRecommenderWrapper.DEFAULT_BASE_SCORE, TvRecommenderWrapper.DEFAULT_WEIGHT);
- }
-
- public void registerTvRecommender(TvRecommender recommender, double baseScore, double weight) {
- mTvRecommenders.add(new TvRecommenderWrapper(recommender, baseScore, weight));
- }
-
- /**
- * Get the channel list of recommendation up to {@code n} or the number of channels.
- *
- * @param size The number of channels that might be recommended.
- * @return Top {@code size} channels recommended. If {@code size} is bigger than the number of
- * channels, the number of results could be less than {@code size}.
- */
- // TODO: consider to change the return type from ChannelRecord[] to Channel[]
- public ChannelRecord[] getRecommendedChannelList(int size) {
- ArrayList<ChannelRecord> results = new ArrayList<ChannelRecord>();
- for (ChannelRecord cr : mChannelRecordMap.values()) {
- double maxScore = TvRecommender.NOT_RECOMMENDED;
- for (TvRecommenderWrapper recommender : mTvRecommenders) {
- double score = recommender.calculateScaledScore(cr);
- if (score > maxScore) {
- maxScore = score;
- }
- }
- cr.mScore = maxScore;
- if (!mIncludeRecommendedOnly || cr.mScore != TvRecommender.NOT_RECOMMENDED) {
- results.add(cr);
- }
- }
- ChannelRecord[] allChannelRecords = results.toArray(new ChannelRecord[0]);
- if (size > allChannelRecords.length) {
- size = allChannelRecords.length;
- }
- Arrays.sort(allChannelRecords);
- return Arrays.copyOfRange(allChannelRecords, 0, size);
- }
-
- public ChannelRecord[] getRecommendedChannelList() {
- return getRecommendedChannelList(mChannelRecordMap.size());
- }
-
- private void registerContentObservers() {
- mContext.getContentResolver().registerContentObserver(
- TvContract.WatchedPrograms.CONTENT_URI, true, mContentObserver);
- mContext.getContentResolver().registerContentObserver(
- TvContract.Channels.CONTENT_URI, true, mContentObserver);
- mContext.getContentResolver().registerContentObserver(
- new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
- .authority(TvContract.AUTHORITY).appendPath(PATH_INPUT).build(),
- true, mContentObserver);
- }
-
- private void unregisterContentObservers() {
- mContext.getContentResolver().unregisterContentObserver(mContentObserver);
- }
-
- private void buildChannelRecordMap() {
- // register channels into channel map.
- Cursor cursor = null;
- try {
- cursor = mContext.getContentResolver().query(
- TvContract.Channels.CONTENT_URI, null, null, null, null);
- if (cursor != null) {
- int indexId = cursor.getColumnIndex(TvContract.Channels._ID);
- while (cursor.moveToNext()) {
- ChannelRecord cr = new ChannelRecord(mContext, Channel.fromCursor(cursor));
- mChannelRecordMap.put(cursor.getLong(indexId), cr);
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- cursor = null;
- }
- }
-
- // update last watched time for channels.
- try {
- cursor = mContext.getContentResolver().query(
- TvContract.WatchedPrograms.CONTENT_URI, null, null, null, null);
- if (cursor != null) {
- while (cursor.moveToNext()) {
- updateChannelRecordFromWatchedProgramCursor(cursor);
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- private Program createProgramFromWatchedProgramCursor(Cursor cursor) {
- final int indexWatchChannelId = cursor.getColumnIndex(
- TvContract.WatchedPrograms.COLUMN_CHANNEL_ID);
- final int indexProgramTitle = cursor.getColumnIndex(
- TvContract.WatchedPrograms.COLUMN_TITLE);
- final int indexProgramStartTime = cursor.getColumnIndex(
- TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS);
- final int indexProgramEndTime = cursor.getColumnIndex(
- TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS);
- String title = cursor.getString(indexProgramTitle);
- return TextUtils.isEmpty(title) ? null : new Program.Builder()
- .setChannelId(cursor.getLong(indexWatchChannelId))
- .setTitle(title)
- .setStartTimeUtcMillis(cursor.getLong(indexProgramStartTime))
- .setStartTimeUtcMillis(cursor.getLong(indexProgramEndTime))
- .build();
- }
-
- private ChannelRecord updateChannelRecordFromWatchedProgramCursor(Cursor cursor) {
- final int indexWatchStartTime = cursor.getColumnIndex(
- TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS);
- final int indexWatchEndTime = cursor.getColumnIndex(
- TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS);
- final int indexWatchChannelId = cursor.getColumnIndex(
- TvContract.WatchedPrograms.COLUMN_CHANNEL_ID);
-
- long watchEndTimeMs = cursor.getLong(indexWatchEndTime);
- long watchDurationMs = watchEndTimeMs - cursor.getLong(indexWatchStartTime);
- ChannelRecord channelRecord = null;
- if (watchEndTimeMs != 0l) {
- channelRecord = mChannelRecordMap.get(
- cursor.getLong(indexWatchChannelId));
- if (channelRecord != null && channelRecord.mLastWatchedTimeMs < watchEndTimeMs) {
- channelRecord.mLastWatchedTimeMs = watchEndTimeMs;
- channelRecord.mLastWatchDurationMs = watchDurationMs;
- Program program = createProgramFromWatchedProgramCursor(cursor);
- if (program != null) {
- channelRecord.logWatchHistory(program);
- }
- }
- }
- return channelRecord;
- }
-
- public static class ChannelRecord
- implements Comparable<ChannelRecord>, Channel.LoadLogoCallback {
- // TODO: decide the value for max history size.
- private static final int MAX_HISTORY_SIZE = 100;
- private final Channel mChannel;
- private final Uri mChannelUri;
- private final Queue<Program> mWatchHistory;
- private long mLastWatchedTimeMs;
- private long mLastWatchDurationMs;
- private double mScore;
-
- public ChannelRecord(Context context, Channel channel) {
- mChannel = channel;
- mChannelUri = ContentUris.withAppendedId(TvContract.Channels.CONTENT_URI,
- channel.getId());
- mWatchHistory = new ArrayDeque<Program>();
- mLastWatchedTimeMs = 0l;
- mLastWatchDurationMs = 0;
- mChannel.loadLogo(context, this);
- }
-
- public Channel getChannel() {
- return mChannel;
- }
-
- public Uri getChannelUri() {
- return mChannelUri;
- }
-
- public long getLastWatchedTimeMs() {
- return mLastWatchedTimeMs;
- }
-
- public long getLastWatchDurationMs() {
- return mLastWatchDurationMs;
- }
-
- public double getRecommendationScore() {
- return mScore;
- }
-
- public final Program[] getWatchHistory() {
- return mWatchHistory.toArray(new Program[0]);
- }
-
- public void logWatchHistory(Program p) {
- mWatchHistory.offer(p);
- if (mWatchHistory.size() > MAX_HISTORY_SIZE) {
- mWatchHistory.poll();
- }
- }
-
- @Override
- public int compareTo(ChannelRecord another) {
- // Make Array.sort work in descending order.
- return (mScore == another.mScore) ? 0 : (mScore > another.mScore) ? -1 : 1;
- }
-
- @Override
- public void onLoadLogoFinished(Channel channel, Bitmap logo) {
- // do nothing
- }
- }
-
- public static abstract class TvRecommender {
- public static final double NOT_RECOMMENDED = -1.0;
-
- /**
- * This will be called when a new watch log comes into WatchedPrograms table.
- */
- protected void onNewWatchLog(ChannelRecord channelRecord) {
- }
-
- /**
- * The implementation should return the calculated score for the given channel record.
- * 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 cr The channel record which will be evaluated by this recommender.
- * @return The recommendation score
- */
- protected abstract double calculateScore(final ChannelRecord cr);
- }
-
- private static class TvRecommenderWrapper {
- private static final double DEFAULT_BASE_SCORE = 0.0;
- private static final double DEFAULT_WEIGHT = 1.0;
-
- private final TvRecommender mRecommender;
- // The minimum score of the TvRecommender unless it gives up to provide the score.
- private final double mBaseScore;
- // The weight of the recommender. The return-value of getScore() will be multiplied by
- // this value.
- private final double mWeight;
-
- public TvRecommenderWrapper(TvRecommender recommender, double baseScore, double weight) {
- mRecommender = recommender;
- mBaseScore = baseScore;
- mWeight = weight;
- }
-
- /**
- * This returns the scaled score for the given channel record based on the returned value
- * of calculateScore().
- *
- * @param channelRecord The channel record which will be evaluated by the recommender.
- * @return Returns the scaled score (mBaseScore + score * mWeight) when calculateScore() is
- * in the range of [0.0, 1.0]. If calculateScore() returns NOT_RECOMMENDED or any negative
- * numbers, it returns NOT_RECOMMENDED. If calculateScore() returns more than 1.0, it
- * returns (mBaseScore + mWeight).
- */
- public double calculateScaledScore(final ChannelRecord channelRecord) {
- double score = mRecommender.calculateScore(channelRecord);
- if (score < 0.0) {
- if (score != TvRecommender.NOT_RECOMMENDED) {
- Log.w(TAG, "Unexpected scroe (" + score + ") from the recommender"
- + mRecommender);
- }
- // If the recommender gives up to calculate the score, return 0.0
- return TvRecommender.NOT_RECOMMENDED;
- } else if (score > 1.0) {
- Log.w(TAG, "Unexpected scroe (" + score + ") from the recommender"
- + mRecommender);
- score = 1.0;
- }
- return mBaseScore + score * mWeight;
- }
-
- public void onNewWatchLog(ChannelRecord channelRecord) {
- mRecommender.onNewWatchLog(channelRecord);
- }
- }
-}
diff --git a/src/com/android/tv/recommendation/WatchedProgram.java b/src/com/android/tv/recommendation/WatchedProgram.java
new file mode 100644
index 00000000..239de1f2
--- /dev/null
+++ b/src/com/android/tv/recommendation/WatchedProgram.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.recommendation;
+
+import com.android.tv.data.Program;
+
+public final class WatchedProgram {
+ private final Program mProgram;
+ private final long mWatchStartTimeMs;
+ private final long mWatchEndTimeMs;
+
+ public WatchedProgram(Program program, long watchStartTimeMs, long watchEndTimeMs) {
+ mProgram = program;
+ mWatchStartTimeMs = watchStartTimeMs;
+ mWatchEndTimeMs = watchEndTimeMs;
+ }
+
+ public long getWatchStartTimeMs() {
+ return mWatchStartTimeMs;
+ }
+
+ public long getWatchEndTimeMs() {
+ return mWatchEndTimeMs;
+ }
+
+ public long getWatchedDurationMs() {
+ return mWatchEndTimeMs - mWatchStartTimeMs;
+ }
+
+ public Program getProgram() {
+ return mProgram;
+ }
+}
diff --git a/src/com/android/tv/search/LocalSearchProvider.java b/src/com/android/tv/search/LocalSearchProvider.java
index fa49ead0..e9cffa31 100644
--- a/src/com/android/tv/search/LocalSearchProvider.java
+++ b/src/com/android/tv/search/LocalSearchProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -23,54 +23,93 @@ import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.text.TextUtils;
+import android.util.Log;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
public class LocalSearchProvider extends ContentProvider {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "LocalSearchProvider";
+
+ public static final int PROGRESS_PERCENTAGE_HIDE = -1;
+
+ // 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_ICON_1, SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
- SearchManager.SUGGEST_COLUMN_INTENT_DATA
+ 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;
// The launcher passes 10 as a 'limit' parameter by default.
private static final int DEFAULT_SEARCH_LIMIT = 10;
+ 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 = TvProviderSearch.ACTION_TYPE_AMBIGUOUS;
+
+ private TvProviderSearch mTvProviderSearch;
+
@Override
public boolean onCreate() {
+ mTvProviderSearch = new TvProviderSearch(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ if (DEBUG) {
+ Log.d(TAG, "query(" + uri + ", " + Arrays.toString(projection) + ", " + selection + ", "
+ + Arrays.toString(selectionArgs) + ", " + sortOrder + ")");
+ }
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
}
- List<SearchResult> results = new ArrayList<SearchResult>();
+ List<SearchResult> results = new ArrayList<>();
if (!TextUtils.isEmpty(query)) {
- results.addAll(TvProviderSearch.search(getContext(), query, limit));
+ results.addAll(mTvProviderSearch.search(query, limit, action));
}
return createSuggestionsCursor(results);
}
private Cursor createSuggestionsCursor(List<SearchResult> results) {
MatrixCursor cursor = new MatrixCursor(SEARCHABLE_COLUMNS, results.size());
- List<String> row = new ArrayList<String>(SEARCHABLE_COLUMNS.length);
+ List<String> row = new ArrayList<>(SEARCHABLE_COLUMNS.length);
for (SearchResult result : results) {
row.clear();
- row.add(result.getTitle());
- row.add(result.getDescription());
- row.add(result.getImageUri());
- row.add(result.getIntentAction());
- row.add(result.getIntentData());
+ row.add(result.title);
+ row.add(result.description);
+ row.add(result.imageUri);
+ row.add(result.intentAction);
+ row.add(result.intentData);
+ row.add(result.contentType);
+ row.add(result.isLive ? LIVE_CONTENTS : NO_LIVE_CONTENTS);
+ row.add(result.videoWidth == 0 ? null : String.valueOf(result.videoWidth));
+ row.add(result.videoHeight == 0 ? null : String.valueOf(result.videoHeight));
+ row.add(result.duration == 0 ? null : String.valueOf(result.duration));
+ row.add(String.valueOf(result.progressPercentage));
cursor.addRow(row);
}
return cursor;
@@ -100,4 +139,30 @@ public class LocalSearchProvider extends ContentProvider {
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
-} \ No newline at end of file
+
+ /**
+ * A placeholder to a search result.
+ */
+ public static class SearchResult {
+ public long channelId;
+ public String channelNumber;
+ public String title;
+ public String description;
+ public String imageUri;
+ public String intentAction;
+ public String intentData;
+ public String contentType;
+ public boolean isLive;
+ public int videoWidth;
+ public int videoHeight;
+ public long duration;
+ public int progressPercentage;
+
+ @Override
+ public String toString() {
+ return "channelId: " + channelId +
+ ", channelNumber: " + channelNumber +
+ ", title: " + title;
+ }
+ }
+}
diff --git a/src/com/android/tv/search/ProgramGuideSearchFragment.java b/src/com/android/tv/search/ProgramGuideSearchFragment.java
new file mode 100644
index 00000000..bb6cdc69
--- /dev/null
+++ b/src/com/android/tv/search/ProgramGuideSearchFragment.java
@@ -0,0 +1,220 @@
+/*
+ * 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.search;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v17.leanback.app.SearchFragment;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SearchBar;
+import android.text.TextUtils;
+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 java.util.List;
+
+public class ProgramGuideSearchFragment extends SearchFragment {
+ private static final String TAG = "ProgramGuideSearch";
+ 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) {
+ final 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,
+ new ImageLoader.ImageLoaderCallback() {
+ @Override
+ public void onBitmapLoaded(Bitmap bitmap) {
+ cardView.setMainImage(
+ new BitmapDrawable(mMainActivity.getResources(), bitmap));
+ }
+ });
+ } else {
+ cardView.setMainImage(mMainActivity.getDrawable(R.drawable.ic_launcher));
+ }
+ }
+
+ @Override
+ public void onUnbindViewHolder(ViewHolder viewHolder) {
+ // Do nothing here.
+ }
+ };
+
+ 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());
+ private MainActivity mMainActivity;
+ private TvProviderSearch mTvProviderSearch;
+ private int mMainCardWidth;
+ private int mMainCardHeight;
+ private SearchTask mSearchTask;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mMainActivity = (MainActivity) getActivity();
+ mTvProviderSearch = new TvProviderSearch(mMainActivity);
+ Resources res = getResources();
+ mMainCardWidth = res.getDimensionPixelSize(R.dimen.card_image_layout_width);
+ mMainCardHeight = res.getDimensionPixelSize(R.dimen.card_image_layout_height);
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View v = super.onCreateView(inflater, container, savedInstanceState);
+ v.setBackgroundResource(R.color.program_guide_scrim);
+
+ setBadgeDrawable(mMainActivity.getDrawable(R.drawable.ic_launcher));
+ setSearchResultProvider(mSearchResultProvider);
+ setOnItemViewClickedListener(mItemClickedListener);
+ return v;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ SearchBar searchBar = (SearchBar) getView().findViewById(R.id.lb_search_bar);
+ searchBar.setSearchQuery("");
+ mResultAdapter.clear();
+ }
+
+ private void searchAndRefresh(String query) {
+ // TODO: Search directly from program data manager for performance.
+ // TODO: Search upcoming programs.
+ if (mSearchTask != null) {
+ mSearchTask.cancel(true);
+ }
+ mSearchTask = new SearchTask(query);
+ mSearchTask.execute();
+ }
+
+ private class SearchTask extends
+ AsyncTask<Void, Void, List<LocalSearchProvider.SearchResult>> {
+ private final String mQuery;
+
+ public SearchTask(String query) {
+ mQuery = query;
+ }
+
+ @Override
+ protected List<LocalSearchProvider.SearchResult> doInBackground(Void... params) {
+ return mTvProviderSearch.search(mQuery, SEARCH_RESULT_MAX,
+ TvProviderSearch.ACTION_TYPE_AMBIGUOUS);
+ }
+
+ @Override
+ protected void onPostExecute(List<LocalSearchProvider.SearchResult> results) {
+ super.onPostExecute(results);
+ mResultAdapter.clear();
+
+ if (DEBUG) {
+ 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));
+ ArrayObjectAdapter resultsAdapter = new ArrayObjectAdapter(mPresenter);
+ mResultAdapter.add(new ListRow(header, resultsAdapter));
+ } else {
+ HeaderItem header =
+ 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));
+ }
+ mSearchTask = null;
+ }
+ }
+}
diff --git a/src/com/android/tv/search/SearchResult.java b/src/com/android/tv/search/SearchResult.java
deleted file mode 100644
index 226f91c4..00000000
--- a/src/com/android/tv/search/SearchResult.java
+++ /dev/null
@@ -1,100 +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.search;
-
-public class SearchResult {
- private long mChannelId;
- private String mTitle;
- private String mDescription;
- private String mImageUri;
- private String mIntentAction;
- private String mIntentData;
-
- private SearchResult() {
- // do nothing
- }
-
- public long getChannelId() {
- return mChannelId;
- }
-
- public String getTitle() {
- return mTitle;
- }
-
- public String getDescription() {
- return mDescription;
- }
-
- public String getImageUri() {
- return mImageUri;
- }
-
- public String getIntentAction() {
- return mIntentAction;
- }
-
- public String getIntentData() {
- return mIntentData;
- }
-
- public static Builder builder() {
- return new Builder();
- }
-
- public static class Builder {
- private SearchResult result;
-
- private Builder() {
- result = new SearchResult();
- }
-
- public SearchResult build() {
- return result;
- }
-
- public Builder setChannelId(long ChannelId) {
- result.mChannelId = ChannelId;
- return this;
- }
-
- public Builder setTitle(String title) {
- result.mTitle = title;
- return this;
- }
-
- public Builder setDescription(String description) {
- result.mDescription = description;
- return this;
- }
-
- public Builder setImageUri(String imageUri) {
- result.mImageUri = imageUri;
- return this;
- }
-
- public Builder setIntentAction(String intentAction) {
- result.mIntentAction = intentAction;
- return this;
- }
-
- public Builder setIntentData(String intentData) {
- result.mIntentData = intentData;
- return this;
- }
- }
-}
diff --git a/src/com/android/tv/search/TvProviderSearch.java b/src/com/android/tv/search/TvProviderSearch.java
index 4d6f53f6..2548d34a 100644
--- a/src/com/android/tv/search/TvProviderSearch.java
+++ b/src/com/android/tv/search/TvProviderSearch.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,192 +16,487 @@
package com.android.tv.search;
-import android.content.ContentUris;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvContract;
import android.media.tv.TvContract.Channels;
import android.media.tv.TvContract.Programs;
+import android.media.tv.TvContract.WatchedPrograms;
+import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputManager;
import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
-import com.android.internal.util.Preconditions;
+import com.android.tv.search.LocalSearchProvider.SearchResult;
+import com.android.tv.util.Utils;
+
+import junit.framework.Assert;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
public class TvProviderSearch {
- public static List<SearchResult> search(Context context, String query, int limit) {
- List<SearchResult> results = new ArrayList<SearchResult>();
- results.addAll(searchChannels(context, query, new String[] {
- Channels.COLUMN_DISPLAY_NAME,
- Channels.COLUMN_DESCRIPTION
- }, limit));
- if (results.size() >= limit) {
- return results;
- }
+ private static final boolean DEBUG = false;
+ private static final String TAG = "TvProviderSearch";
- Set<Long> previousResults = getChannelIdSet(results);
- limit -= results.size();
- results.addAll(searchPrograms(context, query, new String[] {
- Programs.COLUMN_TITLE,
- Programs.COLUMN_SHORT_DESCRIPTION
- }, previousResults, limit));
+ private static final int NO_LIMIT = 0;
+
+ static final int ACTION_TYPE_AMBIGUOUS = 1;
+ static final int ACTION_TYPE_SWITCH_CHANNEL = 2;
+ static final int ACTION_TYPE_SWITCH_INPUT = 3;
+
+ private static final String SOURCE_TV_SEARCH = "TvSearch";
+
+ private final Context mContext;
+ private final ContentResolver mContentResolver;
+ private final TvInputManager mTvInputManager;
+
+ TvProviderSearch(Context context) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
+ }
+
+ /**
+ * 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},
+ */
+ public List<SearchResult> search(String query, int limit, int action) {
+ List<SearchResult> results = new ArrayList<>();
+ Set<Long> channelsFound = new HashSet<>();
+ if (action == ACTION_TYPE_SWITCH_CHANNEL) {
+ results.addAll(searchChannels(query, channelsFound, limit));
+ } else if (action == ACTION_TYPE_SWITCH_INPUT) {
+ results.addAll(searchInputs(query, limit));
+ } else {
+ // Search channels first.
+ results.addAll(searchChannels(query, channelsFound, limit));
+ if (results.size() >= limit) {
+ return results;
+ }
+
+ // In case the user wanted to perform the action "switch to XXX", which is indicated by
+ // setting the limit to 1, search inputs.
+ if (limit == 1) {
+ results.addAll(searchInputs(query, limit));
+ if (!results.isEmpty()) {
+ return results;
+ }
+ }
+
+ // Lastly, search programs.
+ limit -= results.size();
+ results.addAll(searchPrograms(query, null, new String[] {
+ Programs.COLUMN_TITLE, Programs.COLUMN_SHORT_DESCRIPTION },
+ channelsFound, limit));
+ }
return results;
}
- private static Set<Long> getChannelIdSet(List<SearchResult> results) {
- Set<Long> channelIdSet = new HashSet<Long>();
- for (SearchResult sr : results) {
- channelIdSet.add(sr.getChannelId());
+ private StringBuilder appendSelectionString(StringBuilder sb,
+ String[] columnForExactMatching, String[] columnForPartialMatching) {
+ boolean firstColumn = true;
+ if (columnForExactMatching != null) {
+ for (String column : columnForExactMatching) {
+ if (!firstColumn) {
+ sb.append(" OR ");
+ } else {
+ firstColumn = false;
+ }
+ sb.append(column).append("=?");
+ }
+ }
+ if (columnForPartialMatching != null) {
+ for (String column : columnForPartialMatching) {
+ if (!firstColumn) {
+ sb.append(" OR ");
+ } else {
+ firstColumn = false;
+ }
+ sb.append(column).append(" LIKE ?");
+ }
+ }
+ return sb;
+ }
+
+ 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) {
+ selectionArgs[pos] = query;
+ }
+ }
+ String selectionArg = "%" + query + "%";
+ if (columnForPartialMatching != null) {
+ int until = pos + columnForPartialMatching.length;
+ for (; pos < until; ++pos) {
+ selectionArgs[pos] = selectionArg;
+ }
+ }
+ }
+
+ private List<SearchResult> searchChannels(String query, Set<Long> channels, int limit) {
+ List<SearchResult> results = new ArrayList<>();
+ if (TextUtils.isDigitsOnly(query)) {
+ 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()));
}
- return channelIdSet;
+ if (results.size() > limit) {
+ results = results.subList(0, limit);
+ }
+ for (SearchResult result : results) {
+ fillProgramInfo(result);
+ }
+ return results;
}
- private static List<SearchResult> searchChannels(Context context, String query,
- String[] columnNames, int limit) {
- Preconditions.checkState(columnNames != null && columnNames.length > 0);
+ private List<SearchResult> searchChannels(String query, String[] columnForExactMatching,
+ String[] columnForPartialMatching, Set<Long> channelsFound, int limit) {
+ Assert.assertTrue(
+ (columnForExactMatching != null && columnForExactMatching.length > 0) ||
+ (columnForPartialMatching != null && columnForPartialMatching.length > 0));
String[] projection = {
Channels._ID,
+ Channels.COLUMN_DISPLAY_NUMBER,
Channels.COLUMN_DISPLAY_NAME,
- Channels.COLUMN_DESCRIPTION,
+ Channels.COLUMN_DESCRIPTION
};
StringBuilder sb = new StringBuilder();
- sb.append(Channels.COLUMN_BROWSABLE).append("=1 AND ");
- sb.append(Channels.COLUMN_SEARCHABLE).append("=1 AND (");
- sb.append(columnNames[0]).append(" like ?");
- for (int i = 1; i < columnNames.length; ++i) {
- sb.append(" OR ").append(columnNames[i]).append(" like ?");
+ 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");
}
+ sb.append(" AND (");
+ appendSelectionString(sb, columnForExactMatching, columnForPartialMatching);
sb.append(")");
String selection = sb.toString();
- String selectionArg = "%" + query + "%";
- String[] selectionArgs = new String[columnNames.length];
- for (int i = 0; i < selectionArgs.length; ++i) {
- selectionArgs[i] = selectionArg;
+ int len = (columnForExactMatching == null ? 0 : columnForExactMatching.length) +
+ (columnForPartialMatching == null ? 0 : columnForPartialMatching.length);
+ String[] selectionArgs = new String[len];
+ insertSelectionArgumentStrings(selectionArgs, 0, query, columnForExactMatching,
+ columnForPartialMatching);
+
+ List<SearchResult> searchResults = new ArrayList<>();
+
+ try (Cursor c = mContentResolver.query(Channels.CONTENT_URI, projection, selection,
+ selectionArgs, null)) {
+ if (c != null) {
+ int count = 0;
+ while (c.moveToNext()) {
+ long id = c.getLong(0);
+ // Filter out the channel which has been already searched.
+ if (channelsFound.contains(id)) {
+ continue;
+ }
+ channelsFound.add(id);
+
+ SearchResult result = new SearchResult();
+ result.channelId = id;
+ result.channelNumber = c.getString(1);
+ result.title = c.getString(2);
+ result.description = c.getString(3);
+ result.imageUri = TvContract.buildChannelLogoUri(result.channelId).toString();
+ result.intentAction = Intent.ACTION_VIEW;
+ result.intentData = buildIntentData(result.channelId);
+ result.contentType = Programs.CONTENT_ITEM_TYPE;
+ result.isLive = true;
+ result.progressPercentage = LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE;
+
+ searchResults.add(result);
+
+ if (limit != NO_LIMIT && ++count >= limit) {
+ break;
+ }
+ }
+ }
+ }
+ return searchResults;
+ }
+
+ /**
+ * Replaces the channel information - title, description, channel logo - with the current
+ * program information of the channel if the current program information exists and it is not
+ * blocked.
+ */
+ 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
+ };
+
+ try (Cursor c = mContentResolver.query(uri, projection, null, null, null)) {
+ if (c != null && c.moveToNext() && !isRatingBlocked(c.getString(2))) {
+ String channelName = result.title;
+ long startUtcMillis = c.getLong(5);
+ long endUtcMillis = c.getLong(6);
+ result.title = c.getString(0);
+ result.description = buildProgramDescription(result.channelNumber, channelName,
+ startUtcMillis, endUtcMillis);
+ String imageUri = c.getString(1);
+ if (imageUri != null) {
+ result.imageUri = imageUri;
+ }
+ result.videoWidth = c.getInt(3);
+ result.videoHeight = c.getInt(4);
+ result.duration = endUtcMillis - startUtcMillis;
+ result.progressPercentage = getProgressPercentage(startUtcMillis, endUtcMillis);
+ }
}
+ }
+
+ private String buildProgramDescription(String channelNumber, String channelName,
+ long programStartUtcMillis, long programEndUtcMillis) {
+ return Utils.getDurationString(mContext, programStartUtcMillis, programEndUtcMillis, false)
+ + System.lineSeparator() + channelNumber + " " + channelName;
+ }
- return search(context, Channels.CONTENT_URI, projection, selection, selectionArgs, limit,
- null);
+ private int getProgressPercentage(long startUtcMillis, long endUtcMillis) {
+ long current = System.currentTimeMillis();
+ if (startUtcMillis > current || endUtcMillis <= current) {
+ return LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE;
+ }
+ return (int)(100 * (current - startUtcMillis) / (endUtcMillis - startUtcMillis));
}
- private static List<SearchResult> searchPrograms(final Context context, String query,
- String[] columnNames, final Set<Long> previousResults, int limit) {
- Preconditions.checkState(columnNames != null && columnNames.length > 0);
+ private List<SearchResult> searchPrograms(String query, String[] columnForExactMatching,
+ String[] columnForPartialMatching, Set<Long> channelsFound, int limit) {
+ Assert.assertTrue(
+ (columnForExactMatching != null && columnForExactMatching.length > 0) ||
+ (columnForPartialMatching != null && columnForPartialMatching.length > 0));
String[] projection = {
Programs.COLUMN_CHANNEL_ID,
Programs.COLUMN_TITLE,
- Programs.COLUMN_SHORT_DESCRIPTION,
+ 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();
// Search among the programs which are now being on the air.
sb.append(Programs.COLUMN_START_TIME_UTC_MILLIS).append("<=? AND ");
sb.append(Programs.COLUMN_END_TIME_UTC_MILLIS).append(">=? AND (");
- sb.append(columnNames[0]).append(" like ?");
- for (int i = 1; i < columnNames.length; ++i) {
- sb.append(" OR ").append(columnNames[0]).append(" like ?");
- }
+ appendSelectionString(sb, columnForExactMatching, columnForPartialMatching);
sb.append(")");
String selection = sb.toString();
- String selectionArg = "%" + query + "%";
- String[] selectionArgs = new String[columnNames.length+2];
+
+ 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());
- for (int i = 2; i < selectionArgs.length; ++i) {
- selectionArgs[i] = selectionArg;
- }
+ insertSelectionArgumentStrings(selectionArgs, 2, query, columnForExactMatching,
+ columnForPartialMatching);
- return search(context, Programs.CONTENT_URI, projection, selection, selectionArgs, limit,
- new ResultFilter() {
- private Map<Long, Boolean> searchableMap = new HashMap<Long, Boolean>();
+ List<SearchResult> searchResults = new ArrayList<>();
- @Override
- public boolean filter(Cursor c) {
- long id = c.getLong(0);
- // Filter out the program whose channel is already searched.
- if (previousResults.contains(id)) {
- return false;
- }
- // The channel is cached.
- Boolean isSearchable = searchableMap.get(id);
- if (isSearchable != null) {
- return isSearchable;
- }
+ try (Cursor c = mContentResolver.query(Programs.CONTENT_URI, projection, selection,
+ selectionArgs, null)) {
+ if (c != null) {
+ int count = 0;
+ while (c.moveToNext()) {
+ long id = c.getLong(0);
+ // Filter out the program whose channel is already searched.
+ if (channelsFound.contains(id)) {
+ continue;
+ }
+ channelsFound.add(id);
- // Don't know whether the channel is searchable or not.
- String selection = Channels._ID + "=? AND "
- + Channels.COLUMN_BROWSABLE + "=1 AND "
- + Channels.COLUMN_SEARCHABLE + "=1";
- Cursor cursor = null;
- try {
- // Don't need to fetch all the columns.
- cursor = context.getContentResolver().query(Channels.CONTENT_URI,
- new String[] { Channels._ID }, selection,
- new String[] { String.valueOf(id) }, null);
- boolean isSearchableChannel = cursor != null && cursor.getCount() > 0;
- searchableMap.put(id, isSearchableChannel);
- return isSearchableChannel;
- } finally {
- if (cursor != null) {
- cursor.close();
+ // Don't know whether the channel is searchable or not.
+ String[] channelProjection = {
+ 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");
+ 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()
+ && !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.imageUri = c.getString(2);
+ result.intentAction = Intent.ACTION_VIEW;
+ result.intentData = buildIntentData(id);
+ result.contentType = Programs.CONTENT_ITEM_TYPE;
+ result.isLive = true;
+ result.videoWidth = c.getInt(4);
+ result.videoHeight = c.getInt(5);
+ result.duration = endUtcMillis - startUtcMillis;
+ result.progressPercentage = getProgressPercentage(startUtcMillis,
+ endUtcMillis);
+ searchResults.add(result);
+
+ if (limit != NO_LIMIT && ++count >= limit) {
+ break;
}
}
}
- });
+ }
+ }
+ }
+ return searchResults;
}
- private static List<SearchResult> search(Context context, Uri uri, String[] projection,
- String selection, String[] selectionArgs, int limit, ResultFilter resultFilter) {
- List<SearchResult> results = new ArrayList<SearchResult>();
-
- Cursor cursor = null;
- try {
- cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
- null);
- if (cursor != null) {
- // TODO: Need to add image when available.
- int count = 0;
- while (cursor.moveToNext()) {
- if (resultFilter != null && !resultFilter.filter(cursor)) {
- continue;
- }
+ private String buildIntentData(long channelId) {
+ return TvContract.buildChannelUri(channelId).buildUpon()
+ .appendQueryParameter(Utils.PARAM_SOURCE, SOURCE_TV_SEARCH)
+ .build().toString();
+ }
- long id = cursor.getLong(0);
- String title = cursor.getString(1);
- String description = cursor.getString(2);
-
- SearchResult result = SearchResult.builder()
- .setChannelId(id)
- .setTitle(title)
- .setDescription(description)
- .setIntentAction(Intent.ACTION_VIEW)
- .setIntentData(ContentUris.withAppendedId(Channels.CONTENT_URI, id)
- .toString())
- .build();
- results.add(result);
-
- if (++count >= limit) {
- break;
- }
+ private boolean isRatingBlocked(String ratings) {
+ if (ratings == null) {
+ return false;
+ }
+ for (String rating : ratings.split("\\s*,\\s*")) {
+ try {
+ if (mTvInputManager.isParentalControlsEnabled() && mTvInputManager.isRatingBlocked(
+ TvContentRating.unflattenFromString(rating))) {
+ return true;
}
+ } catch (IllegalArgumentException e) {
+ // Do nothing.
}
- } finally {
- if (cursor != null) {
- cursor.close();
+ }
+ return false;
+ }
+
+ private List<SearchResult> searchInputs(String query, int limit) {
+ if (DEBUG) {
+ Log.d(TAG, "searchInputs(" + query + ", limit=" + limit + ")");
+ }
+
+ query = canonicalizeLabel(query);
+ List<TvInputInfo> inputList = mTvInputManager.getTvInputList();
+ List<SearchResult> results = new ArrayList<>();
+
+ // Find exact matches first.
+ for (TvInputInfo input : inputList) {
+ String label = canonicalizeLabel(input.loadLabel(mContext));
+ String customLabel = canonicalizeLabel(input.loadCustomLabel(mContext));
+ if (TextUtils.equals(query, label) || TextUtils.equals(query, customLabel)) {
+ results.add(buildSearchResultForInput(input.getId()));
+ if (results.size() >= limit) {
+ return results;
+ }
}
}
+ // Then look for partial matches.
+ for (TvInputInfo input : inputList) {
+ String label = canonicalizeLabel(input.loadLabel(mContext));
+ String customLabel = canonicalizeLabel(input.loadCustomLabel(mContext));
+ if ((label != null && label.contains(query)) ||
+ (customLabel != null && customLabel.contains(query))) {
+ results.add(buildSearchResultForInput(input.getId()));
+ if (results.size() >= limit) {
+ return results;
+ }
+ }
+ }
return results;
}
- private interface ResultFilter {
- boolean filter(Cursor c);
+ private String canonicalizeLabel(CharSequence cs) {
+ Locale locale = mContext.getResources().getConfiguration().locale;
+ return cs != null ? cs.toString().replaceAll("[ -]", "").toLowerCase(locale) : null;
+ }
+
+ private SearchResult buildSearchResultForInput(String inputId) {
+ SearchResult result = new SearchResult();
+ result.intentAction = Intent.ACTION_VIEW;
+ result.intentData = TvContract.buildChannelUriForPassthroughInput(inputId).toString();
+ return result;
+ }
+
+ private class ChannelComparatorWithSameDisplayNumber implements Comparator<SearchResult> {
+ private final Map<Long, Long> mMaxWatchStartTimeMap = new HashMap<>();
+
+ @Override
+ public int compare(SearchResult lhs, SearchResult rhs) {
+ // Show recently watched channel first
+ Long lhsMaxWatchStartTime = mMaxWatchStartTimeMap.get(lhs.channelId);
+ if (lhsMaxWatchStartTime == null) {
+ lhsMaxWatchStartTime = getMaxWatchStartTime(lhs.channelId);
+ mMaxWatchStartTimeMap.put(lhs.channelId, lhsMaxWatchStartTime);
+ }
+ Long rhsMaxWatchStartTime = mMaxWatchStartTimeMap.get(rhs.channelId);
+ if (rhsMaxWatchStartTime == null) {
+ rhsMaxWatchStartTime = getMaxWatchStartTime(rhs.channelId);
+ mMaxWatchStartTimeMap.put(rhs.channelId, rhsMaxWatchStartTime);
+ }
+ if (!Objects.equals(lhsMaxWatchStartTime, rhsMaxWatchStartTime)) {
+ return Long.compare(rhsMaxWatchStartTime, lhsMaxWatchStartTime);
+ }
+ // Show recently added channel first if there's no watch history.
+ return Long.compare(rhs.channelId, lhs.channelId);
+ }
+
+ 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 selection = WatchedPrograms.COLUMN_CHANNEL_ID + "=?";
+ 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);
+ }
+ }
+ return -1;
+ }
}
}
diff --git a/src/com/android/tv/ui/ActionTileView.java b/src/com/android/tv/ui/ActionTileView.java
deleted file mode 100644
index 8f1edd91..00000000
--- a/src/com/android/tv/ui/ActionTileView.java
+++ /dev/null
@@ -1,124 +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.ui;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.support.v17.leanback.widget.BaseCardView;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.util.Preconditions;
-import com.android.tv.R;
-
-/**
- * A view to render an item of TV options.
- */
-public class ActionTileView extends BaseCardView implements ItemListView.TileView {
- private static final String TAG = "ActionTileView";
- private static final boolean DEBUG = false;
-
- private View mCircle;
- private final int mAnimDuration;
- private final float mZoomFactor;
- private TextView mLabelView;
- private ImageView mIconView;
- private ViewPropertyAnimator mAnimator;
-
- public ActionTileView(Context context) {
- this(context, null, 0);
- }
-
- public ActionTileView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ActionTileView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mAnimDuration = context.getResources().getInteger(R.integer.action_item_anim_duration);
- mZoomFactor = context.getResources().getFraction(
- R.fraction.lb_focus_zoom_factor_large, 1, 1);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mCircle = findViewById(R.id.action_tile_selection_circle);
- mAnimator = mCircle.animate();
- }
-
- @Override
- public void loadViews() {
- mLabelView = (TextView) findViewById(R.id.action_tile_label);
- mIconView = (ImageView) findViewById(R.id.action_tile_icon);
- }
-
- @Override
- public void populateViews(View.OnClickListener listener, Object item) {
- Preconditions.checkNotNull(item);
-
- MenuAction action = (MenuAction) item;
- setOnClickListener(listener);
- setTag(MainMenuView.MenuTag.buildTag(action));
-
- mLabelView.setText(action.getActionName(getContext()));
- mIconView.setImageDrawable(getContext().getDrawable(action.getDrawableResId()));
- }
-
- @Override
- protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-
- setSelected(gainFocus);
- }
-
- @Override
- public void setSelected(boolean selected) {
- super.setSelected(selected);
-
- if (mCircle != null) {
- if (selected) {
- animate().scaleX(mZoomFactor)
- .scaleY(mZoomFactor)
- .setDuration(mAnimDuration);
-
- mAnimator.alpha(1.0f)
- .setDuration(mAnimDuration)
- .setStartDelay(0)
- .start();
- } else {
- setScaleX(mZoomFactor);
- setScaleY(mZoomFactor);
- animate().scaleX(1.f)
- .scaleY(1.f)
- .setDuration(mAnimDuration);
-
- // When D-pad is long pressed, key down events are triggered too fast. So focus
- // movement is not shown without the animation cancel and setting alpha to 1.0.
- mAnimator.cancel();
- mCircle.setAlpha(1.0f);
- mAnimator.alpha(0.f)
- .setDuration(mAnimDuration)
- .setStartDelay(0)
- .start();
- }
- }
- }
-}
diff --git a/src/com/android/tv/ui/AppLayerTvView.java b/src/com/android/tv/ui/AppLayerTvView.java
new file mode 100644
index 00000000..c7b94a15
--- /dev/null
+++ b/src/com/android/tv/ui/AppLayerTvView.java
@@ -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.
+ */
+
+package com.android.tv.ui;
+
+import android.content.Context;
+import android.media.tv.TvView;
+import android.util.AttributeSet;
+
+/**
+ * A TvView class for application layer when multiple windows are being used in the app.
+ * <p>
+ * 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.
+ * </p>
+ */
+public class AppLayerTvView extends TvView {
+ public AppLayerTvView(Context context) {
+ super(context);
+ }
+
+ public AppLayerTvView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AppLayerTvView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public boolean hasWindowFocus() {
+ return true;
+ }
+}
diff --git a/src/com/android/tv/ui/ChannelBannerView.java b/src/com/android/tv/ui/ChannelBannerView.java
index 89cc50e2..277ccb0a 100644
--- a/src/com/android/tv/ui/ChannelBannerView.java
+++ b/src/com/android/tv/ui/ChannelBannerView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,73 +16,151 @@
package com.android.tv.ui;
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+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.graphics.Paint;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.media.tv.TvContentRating;
import android.media.tv.TvContract;
import android.media.tv.TvInputInfo;
-import android.media.tv.TvInputManager;
import android.net.Uri;
import android.os.Handler;
+import android.text.Spannable;
+import android.text.SpannableString;
import android.text.TextUtils;
-import android.text.format.DateFormat;
+import android.text.style.TextAppearanceSpan;
import android.util.AttributeSet;
-import android.util.LruCache;
import android.util.TypedValue;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
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.data.Channel;
-import com.android.tv.data.ChannelMap;
import com.android.tv.data.Program;
import com.android.tv.data.StreamInfo;
+import com.android.tv.util.ImageCache;
+import com.android.tv.util.ImageLoader;
+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.
*/
-public class ChannelBannerView extends RelativeLayout implements Channel.LoadLogoCallback {
- private static final int CACHE_SIZE = 10;
- private TextView mChannelTextView;
+public class ChannelBannerView extends FrameLayout implements Channel.LoadImageCallback,
+ TvTransitionManager.TransitionLayout {
+
+ /**
+ * 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.
+ */
+ 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.
+ */
+ public static final int LOCK_CHANNEL_INFO = 2;
+
+ private static final String EMPTY_STRING = "";
+
+ private static Program sNoProgram;
+ private static Program sLockedChannelProgram;
+ private static String sClosedCaptionMark;
+
+ private final MainActivity mMainActivity;
+ private final Resources mResources;
+ private View mChannelView;
+
+ private TextView mChannelNumberTextView;
private ImageView mChannelLogoImageView;
private TextView mProgramTextView;
private ImageView mTvInputLogoImageView;
private TextView mChannelNameTextView;
private TextView mProgramTimeTextView;
private ProgressBar mRemainingTimeView;
- private TextView mVideoStatus;
- // TODO: Need to get UX design for how to show ratings in the channel banner.
- private TextView mRatingTextView;
private TextView mClosedCaptionTextView;
private TextView mAspectRatioTextView;
private TextView mResolutionTextView;
private TextView mAudioChannelTextView;
- private TextView mProgrameDescriptionTextView;
+ private TextView mProgramDescriptionTextView;
+ private String mProgramDescriptionText;
private View mAnchorView;
- private Uri mCurrentChannelUri;
- private final LruCache<TvInputInfo, Drawable> mChannelInfoLogoCache =
- new LruCache<TvInputInfo, Drawable> (CACHE_SIZE) {
- @Override
- protected Drawable create(TvInputInfo info) {
- return info.loadIcon(getContext());
- }
- };
+ private Channel mCurrentChannel;
+ private Program mLastUpdatedProgram;
+ private final Handler mHandler = new Handler();
+
+ private int mLockType;
+
+ private Animator mResizeAnimator;
+ private int mCurrentHeight;
+ private boolean mProgramInfoUpdatePendingByResizing;
+
+ private final Animator mProgramDescriptionFadeInAnimator;
+ private final Animator mProgramDescriptionFadeOutAnimator;
+
+ private final Runnable mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mCurrentHeight = 0;
+ mMainActivity.goToEmptyScene(true);
+ }
+ };
+ private final long mShowDurationMillis;
+ private final int mChannelLogoImageViewWidth;
+ private final int mChannelLogoImageViewHeight;
+ private final int mChannelLogoImageViewMarginStart;
+ private final int mProgramDescriptionTextViewWidth;
+ private final int mChannelBannerTextColor;
+ private final int mChannelBannerDimTextColor;
+ private final int mResizeAnimDuration;
+ private final Interpolator mResizeInterpolator;
+
+ private final AnimatorListenerAdapter mResizeAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ mProgramInfoUpdatePendingByResizing = false;
+ }
- private final ContentObserver mProgramUpdateObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mProgramDescriptionTextView.setAlpha(1f);
+ mResizeAnimator = null;
+ if (mProgramInfoUpdatePendingByResizing) {
+ mProgramInfoUpdatePendingByResizing = false;
+ updateProgramInfo(mLastUpdatedProgram);
+ }
+ }
+ };
+
+ 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.
- post(mProgramUpdateRunnable);
+ mHandler.post(mProgramUpdateRunnable);
}
};
@@ -90,7 +168,7 @@ public class ChannelBannerView extends RelativeLayout implements Channel.LoadLog
@Override
public void run() {
removeCallbacks(this);
- updateProgramInfo();
+ updateViews(null);
}
};
@@ -99,11 +177,52 @@ public class ChannelBannerView extends RelativeLayout implements Channel.LoadLog
}
public ChannelBannerView(Context context, AttributeSet attrs) {
- super(context, attrs, 0);
+ this(context, attrs, 0);
}
public ChannelBannerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ mResources = getResources();
+
+ 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);
+ mChannelBannerTextColor = mResources.getColor(R.color.channel_banner_text_color);
+ mChannelBannerDimTextColor = mResources.getColor(R.color.channel_banner_dim_text_color);
+ mResizeAnimDuration = mResources.getInteger(R.integer.channel_banner_fast_anim_duration);
+
+ 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);
+
+ 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();
+ }
+ if (sClosedCaptionMark == null) {
+ sClosedCaptionMark = context.getString(R.string.closed_caption);
+ }
}
@Override
@@ -123,102 +242,189 @@ public class ChannelBannerView extends RelativeLayout implements Channel.LoadLog
protected void onFinishInflate() {
super.onFinishInflate();
- mChannelTextView = (TextView) findViewById(R.id.channel_text);
+ mChannelView = findViewById(R.id.channel_banner_view);
+
+ mChannelNumberTextView = (TextView) findViewById(R.id.channel_number);
mChannelLogoImageView = (ImageView) findViewById(R.id.channel_logo);
mProgramTextView = (TextView) findViewById(R.id.program_text);
mTvInputLogoImageView = (ImageView) findViewById(R.id.tvinput_logo);
mChannelNameTextView = (TextView) findViewById(R.id.channel_name);
mProgramTimeTextView = (TextView) findViewById(R.id.program_time_text);
mRemainingTimeView = (ProgressBar) findViewById(R.id.remaining_time);
- mVideoStatus = (TextView) findViewById(R.id.video_status);
- mRatingTextView = (TextView) findViewById(R.id.rating);
mClosedCaptionTextView = (TextView) findViewById(R.id.closed_caption);
mAspectRatioTextView = (TextView) findViewById(R.id.aspect_ratio);
mResolutionTextView = (TextView) findViewById(R.id.resolution);
mAudioChannelTextView = (TextView) findViewById(R.id.audio_channel);
- mProgrameDescriptionTextView = (TextView) findViewById(R.id.program_description);
+ mProgramDescriptionTextView = (TextView) findViewById(R.id.program_description);
mAnchorView = findViewById(R.id.anchor);
+
+ mProgramDescriptionFadeInAnimator.setTarget(mProgramDescriptionTextView);
+ mProgramDescriptionFadeOutAnimator.setTarget(mProgramDescriptionTextView);
+ mProgramDescriptionFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mProgramDescriptionTextView.setText(mProgramDescriptionText);
+ }
+ });
}
- public void updateViews(ChannelMap channelMap, StreamInfo info) {
- if (channelMap == null || !channelMap.isLoadFinished()) {
+ @Override
+ public void onEnterAction(boolean fromEmptyScene) {
+ resetAnimationEffects();
+ if (fromEmptyScene) {
+ ViewUtils.setTransitionAlpha(mChannelView, 1f);
+ }
+ scheduleHide();
+ }
+
+ @Override
+ public void onExitAction() {
+ mCurrentHeight = 0;
+ cancelHide();
+ }
+
+ private void scheduleHide() {
+ cancelHide();
+ mHandler.postDelayed(mHideRunnable, mShowDurationMillis);
+ }
+
+ private void cancelHide() {
+ mHandler.removeCallbacks(mHideRunnable);
+ }
+
+ private void resetAnimationEffects() {
+ setAlpha(1f);
+ setScaleX(1f);
+ setScaleY(1f);
+ setTranslationX(0);
+ setTranslationY(0);
+ }
+
+ /**
+ * 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
+ * @throws IllegalArgumentException if lockType is invalid.
+ */
+ public boolean 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;
+ }
+
+ /**
+ * Update channel banner view.
+ * Note that this only updates the channel banner contents,
+ * and use onBeforeShow() or onAfterHide() for showing/hiding.
+ *
+ * @param info A StreamInfo that includes stream information.
+ * If it's {@code null}, only program information will be updated.
+ */
+ public void updateViews(StreamInfo info) {
+ resetAnimationEffects();
+ Channel channel = mMainActivity.getCurrentChannel();
+ if (!Objects.equals(mCurrentChannel, channel) && isShown()) {
+ scheduleHide();
+ }
+ mCurrentChannel = channel;
+ if (channel == null) {
+ mLastUpdatedProgram = null;
+ updateProgramInfo(null);
return;
}
+ 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.equals(info.getCurrentChannel()) ? info : null);
+ updateChannelInfo();
+ }
+ updateProgramInfo(mMainActivity.getCurrentProgram());
+ }
+
+ private 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,
+ Utils.getAspectRatioString(info.getVideoWidth(), info.getVideoHeight()));
+ updateText(mResolutionTextView,
+ Utils.getVideoDefinitionLevelString(
+ mMainActivity, info.getVideoDefinitionLevel()));
+ updateText(mAudioChannelTextView,
+ Utils.getAudioChannelString(mMainActivity, info.getAudioChannelCount()));
+ } else {
+ // Channel change has been requested. But, StreamInfo hasn't been updated yet.
+ mClosedCaptionTextView.setVisibility(View.GONE);
+ mAspectRatioTextView.setVisibility(View.GONE);
+ mResolutionTextView.setVisibility(View.GONE);
+ mAudioChannelTextView.setVisibility(View.GONE);
+ }
+ }
- String displayNumber = channelMap.getCurrentDisplayNumber();
+ private void updateChannelInfo() {
+ // Update static information for a channel.
+ String displayNumber = mCurrentChannel.getDisplayNumber();
if (displayNumber == null) {
- displayNumber = "";
+ displayNumber = EMPTY_STRING;
}
if (displayNumber.length() <= 3) {
updateTextView(
- mChannelTextView,
- R.dimen.channel_banner_title_large_text_size,
- R.dimen.channel_banner_title_large_margin_top);
+ mChannelNumberTextView,
+ R.dimen.channel_banner_channel_number_large_text_size,
+ R.dimen.channel_banner_channel_number_large_margin_top);
} else if (displayNumber.length() <= 4) {
updateTextView(
- mChannelTextView,
- R.dimen.channel_banner_title_medium_text_size,
- R.dimen.channel_banner_title_medium_margin_top);
+ mChannelNumberTextView,
+ R.dimen.channel_banner_channel_number_medium_text_size,
+ R.dimen.channel_banner_channel_number_medium_margin_top);
} else {
updateTextView(
- mChannelTextView,
- R.dimen.channel_banner_title_small_text_size,
- R.dimen.channel_banner_title_small_margin_top);
+ mChannelNumberTextView,
+ R.dimen.channel_banner_channel_number_small_text_size,
+ R.dimen.channel_banner_channel_number_small_margin_top);
}
- mChannelTextView.setText(displayNumber);
-
- TvInputInfo inputInfo = (info == null) ? null : info.getCurrentTvInputInfo();
- Drawable tvInputLogo = (inputInfo == null) ? null : mChannelInfoLogoCache.get(inputInfo);
- if (tvInputLogo != null) {
- mTvInputLogoImageView.setVisibility(View.VISIBLE);
- mTvInputLogoImageView.setImageDrawable(tvInputLogo);
- } else {
- mTvInputLogoImageView.setVisibility(View.GONE);
- }
-
- String displayName = channelMap.getCurrentDisplayName();
+ mChannelNumberTextView.setText(displayNumber);
+ String displayName = mCurrentChannel.getDisplayName();
if (displayName == null) {
- displayName = "";
+ displayName = EMPTY_STRING;
}
mChannelNameTextView.setText(displayName);
- int resId = 0;
- if (!info.isVideoAvailable()) {
- switch (info.getVideoUnavailableReason()) {
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING:
- // We don't need to tell we're tuning.
- break;
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
- resId = R.string.channel_banner_video_unavailable_weak_signal;
- break;
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
- resId = R.string.channel_banner_video_unavailable_buffering;
- break;
- default:
- resId = R.string.channel_banner_video_unavailable_unknown;
- break;
- }
- }
- updateText(mVideoStatus, (resId == 0) ? null : getContext().getString(resId));
-
- updateText(mClosedCaptionTextView, info.hasClosedCaption() ? "CC" : "");
-
- updateText(mAspectRatioTextView,
- Utils.getAspectRatioString(info.getVideoWidth(), info.getVideoHeight()));
-
- updateText(mResolutionTextView,
- Utils.getVideoDefinitionLevelString(info.getVideoDefinitionLevel()));
-
- updateText(mAudioChannelTextView,
- Utils.getAudioChannelString(info.getAudioChannelCount()));
-
- mCurrentChannelUri = channelMap.getCurrentChannelUri();
- if (channelMap.getCurrentChannel() != null) {
- channelMap.getCurrentChannel().loadLogo(getContext(), this);
+ TvInputInfo info = mMainActivity.getTvInputManagerHelper().getTvInputInfo(
+ mCurrentChannel.getInputId());
+ if (info == null ||
+ !ImageLoader.loadBitmap(createTvInputLogoLoaderCallback(info),
+ new LoadTvInputLogoTask(getContext(), ImageCache.getInstance(), info))) {
+ mTvInputLogoImageView.setVisibility(View.GONE);
+ mTvInputLogoImageView.setImageDrawable(null);
}
+ mChannelLogoImageView.setImageBitmap(null);
+ mChannelLogoImageView.setVisibility(View.GONE);
+ mCurrentChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO,
+ mChannelLogoImageViewWidth, mChannelLogoImageViewHeight, this);
+ }
- updateProgramInfo();
+ private ImageLoader.ImageLoaderCallback createTvInputLogoLoaderCallback(
+ final TvInputInfo info) {
+ return new ImageLoader.ImageLoaderCallback() {
+ @Override
+ public void onBitmapLoaded(Bitmap bitmap) {
+ if (bitmap != null && info.getId().equals(mCurrentChannel.getInputId())) {
+ mTvInputLogoImageView.setVisibility(View.VISIBLE);
+ mTvInputLogoImageView.setImageBitmap(bitmap);
+ }
+ }
+ };
}
private void updateText(TextView view, String text) {
@@ -231,7 +437,7 @@ public class ChannelBannerView extends RelativeLayout implements Channel.LoadLog
}
private void updateTextView(TextView textView, int sizeRes, int marginTopRes) {
- float textSize = getContext().getResources().getDimension(sizeRes);
+ float textSize = mResources.getDimension(sizeRes);
if (textView.getTextSize() != textSize) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}
@@ -239,8 +445,8 @@ public class ChannelBannerView extends RelativeLayout implements Channel.LoadLog
}
private void updateTopMargin(View view, int marginTopRes) {
- LayoutParams lp = (LayoutParams) view.getLayoutParams();
- int topMargin = (int) getContext().getResources().getDimension(marginTopRes);
+ RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) view.getLayoutParams();
+ int topMargin = (int) mResources.getDimension(marginTopRes);
if (lp.topMargin != topMargin) {
lp.topMargin = topMargin;
view.setLayoutParams(lp);
@@ -248,125 +454,201 @@ public class ChannelBannerView extends RelativeLayout implements Channel.LoadLog
}
@Override
- public void onLoadLogoFinished(Channel channel, Bitmap logo) {
+ public void onLoadImageFinished(Channel channel, int type, Bitmap logo) {
+ if (channel != mCurrentChannel) {
+ // The logo is obsolete.
+ return;
+ }
if (logo == null) {
- mChannelLogoImageView.setVisibility(View.GONE);
+ // Need to update the text size of the program text view depending on the channel logo.
+ updateProgramTextView(mLastUpdatedProgram);
+ return;
+ }
+
+ mChannelLogoImageView.setImageBitmap(logo);
+ mChannelLogoImageView.setVisibility(View.VISIBLE);
+ updateProgramTextView(mLastUpdatedProgram);
+
+ if (mResizeAnimator == null) {
+ String description = mProgramDescriptionTextView.getText().toString();
+ boolean needFadeAnimation = !description.equals(mProgramDescriptionText);
+ updateBannerHeight(needFadeAnimation);
} else {
- mChannelLogoImageView.setImageBitmap(logo);
- mChannelLogoImageView.setVisibility(View.VISIBLE);
+ mProgramInfoUpdatePendingByResizing = true;
}
}
- private String getFormattedTimeString(long time) {
- return DateFormat.format(
- getContext().getString(R.string.channel_banner_time_format), time).toString();
- }
+ private void updateProgramInfo(Program program) {
+ if (mLockType == LOCK_CHANNEL_INFO) {
+ program = sLockedChannelProgram;
+ } else if (!Program.isValid(program) || TextUtils.isEmpty(program.getTitle())) {
+ program = sNoProgram;
+ }
- private void updateProgramInfo() {
- if (mCurrentChannelUri == null) {
- handleNoProgramInformation();
- return;
+ if (mLastUpdatedProgram == null
+ || !TextUtils.equals(program.getTitle(), mLastUpdatedProgram.getTitle())
+ || !TextUtils.equals(program.getEpisodeDisplayTitle(getContext()),
+ mLastUpdatedProgram.getEpisodeDisplayTitle(getContext()))) {
+ updateProgramTextView(program);
+ }
+ updateProgramTimeInfo(program);
+
+ // When the program is changed, but the previous resize animation has not ended yet,
+ // cancel the animation.
+ boolean isProgramChanged = !Objects.equals(mLastUpdatedProgram, program);
+ if (mResizeAnimator != null && isProgramChanged) {
+ mLastUpdatedProgram = program;
+ mProgramInfoUpdatePendingByResizing = true;
+ mResizeAnimator.cancel();
+ } else if (mResizeAnimator == null) {
+ if (mLockType != LOCK_NONE || TextUtils.isEmpty(program.getDescription())) {
+ mProgramDescriptionTextView.setVisibility(GONE);
+ mProgramDescriptionText = "";
+ } else {
+ mProgramDescriptionTextView.setVisibility(VISIBLE);
+ mProgramDescriptionText = program.getDescription();
+ }
+ String description = mProgramDescriptionTextView.getText().toString();
+ boolean needFadeAnimation = isProgramChanged
+ || !description.equals(mProgramDescriptionText);
+ updateBannerHeight(needFadeAnimation);
+ } else {
+ mProgramInfoUpdatePendingByResizing = true;
}
+ mLastUpdatedProgram = program;
+ }
- Program program = Utils.getCurrentProgram(getContext(), mCurrentChannelUri);
+ private void updateProgramTextView(Program program) {
if (program == null) {
- handleNoProgramInformation();
return;
}
- String title = program.getTitle();
- if (!TextUtils.isEmpty(title)) {
- int width = mProgramTextView.getWidth();
- if (width == 0) {
- post(mProgramUpdateRunnable);
- }
- float largeTextSize = getContext().getResources().getDimension(
- R.dimen.channel_banner_program_large_text_size);
- Typeface font = mProgramTextView.getTypeface();
- int estimatedLineCount = estimateLineCount(title, font, largeTextSize, width);
- boolean oneline = true;
- if (estimatedLineCount > 1) {
- updateTextView(
- mProgramTextView,
- R.dimen.channel_banner_program_medium_text_size,
- R.dimen.channel_banner_program_medium_margin_top);
- float mediumTextSize = getContext().getResources().getDimension(
- R.dimen.channel_banner_program_medium_text_size);
- if (estimateLineCount(title, font, mediumTextSize, width) > 1) {
- oneline = false;
- }
- } else {
- updateTextView(
- mProgramTextView,
- R.dimen.channel_banner_program_large_text_size,
- R.dimen.channel_banner_program_large_margin_top);
- }
- updateTopMargin(mAnchorView, oneline
- ? R.dimen.channel_banner_anchor_one_line_y
- : R.dimen.channel_banner_anchor_two_line_y);
- mProgramTextView.setText(title);
-
- long startTime = program.getStartTimeUtcMillis();
- long endTime = program.getEndTimeUtcMillis();
- if (startTime > 0 && endTime > 0) {
- mProgramTimeTextView.setVisibility(View.VISIBLE);
- mRemainingTimeView.setVisibility(View.VISIBLE);
-
- String startTimeText = getFormattedTimeString(startTime);
- String endTimeText = getFormattedTimeString(endTime);
-
- mProgramTimeTextView.setText(getContext().getString(
- R.string.channel_banner_program_time_format, startTimeText, endTimeText));
-
- long currTime = System.currentTimeMillis();
- if (currTime <= startTime) {
- mRemainingTimeView.setProgress(0);
- } else if (currTime >= endTime) {
- mRemainingTimeView.setProgress(100);
- } else {
- mRemainingTimeView.setProgress(
- (int) (100 *(currTime - startTime) / (endTime - startTime)));
- }
+ mProgramTextView.setVisibility(View.VISIBLE);
+ if (program == sLockedChannelProgram) {
+ mProgramTextView.setTextColor(mChannelBannerDimTextColor);
+ } else {
+ mProgramTextView.setTextColor(mChannelBannerTextColor);
+ }
+ updateTextView(mProgramTextView,
+ R.dimen.channel_banner_program_large_text_size,
+ R.dimen.channel_banner_program_large_margin_top);
+ if (TextUtils.isEmpty(program.getEpisodeTitle())) {
+ mProgramTextView.setText(program.getTitle());
+ } else {
+ String title = program.getTitle();
+ String episodeTitle = program.getEpisodeDisplayTitle(getContext());
+ String fullTitle = title + " " + episodeTitle;
+
+ SpannableString text = new SpannableString(fullTitle);
+ text.setSpan(new TextAppearanceSpan(getContext(),
+ R.style.text_appearance_channel_banner_episode_title),
+ fullTitle.length() - episodeTitle.length(), fullTitle.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ mProgramTextView.setText(text);
+ }
+ int width = mProgramDescriptionTextViewWidth
+ - ((mChannelLogoImageView.getVisibility() != View.VISIBLE)
+ ? 0 : mChannelLogoImageViewWidth + mChannelLogoImageViewMarginStart);
+ ViewGroup.LayoutParams lp = mProgramTextView.getLayoutParams();
+ lp.width = width;
+ mProgramTextView.setLayoutParams(lp);
+ mProgramTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+
+ boolean oneline = (mProgramTextView.getLineCount() == 1);
+ if (!oneline) {
+ updateTextView(
+ mProgramTextView,
+ R.dimen.channel_banner_program_medium_text_size,
+ R.dimen.channel_banner_program_medium_margin_top);
+ 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);
+ }
+
+ private void updateProgramTimeInfo(Program program) {
+ long startTime = program.getStartTimeUtcMillis();
+ long endTime = program.getEndTimeUtcMillis();
+ if (mLockType != LOCK_CHANNEL_INFO && startTime > 0 && endTime > startTime) {
+ mProgramTimeTextView.setVisibility(View.VISIBLE);
+ mRemainingTimeView.setVisibility(View.VISIBLE);
+
+ mProgramTimeTextView.setText(Utils.getDurationString(
+ getContext(), startTime, endTime, true));
+
+ long currTime = mMainActivity.getCurrentPlayingPosition();
+ if (currTime <= startTime) {
+ mRemainingTimeView.setProgress(0);
+ } else if (currTime >= endTime) {
+ mRemainingTimeView.setProgress(100);
} else {
- mProgramTimeTextView.setVisibility(View.GONE);
- mRemainingTimeView.setVisibility(View.GONE);
+ mRemainingTimeView.setProgress(
+ (int) (100 * (currTime - startTime) / (endTime - startTime)));
}
} else {
- mProgramTextView.setText(getContext().getString(R.string.channel_banner_no_title));
mProgramTimeTextView.setVisibility(View.GONE);
mRemainingTimeView.setVisibility(View.GONE);
}
+ }
- updateText(mRatingTextView, Utils.contentRatingsToString(program.getContentRatings()));
-
- if (!TextUtils.isEmpty(program.getDescription())) {
- mProgrameDescriptionTextView.setVisibility(View.VISIBLE);
- mProgrameDescriptionTextView.setText(program.getDescription());
- } else {
- mProgrameDescriptionTextView.setVisibility(View.GONE);
+ private void updateBannerHeight(boolean needFadeAnimation) {
+ Assert.assertNull(mResizeAnimator);
+ // Need to measure the layout height with the new description text.
+ CharSequence oldDescription = mProgramDescriptionTextView.getText();
+ mProgramDescriptionTextView.setText(mProgramDescriptionText);
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ int targetHeight = getMeasuredHeight();
+
+ if (mCurrentHeight == 0 || !isShown()) {
+ // Do not add the resize animation when the banner has not been shown before.
+ mCurrentHeight = targetHeight;
+ LayoutParams layoutParams = (LayoutParams) getLayoutParams();
+ if (targetHeight != layoutParams.height) {
+ layoutParams.height = targetHeight;
+ setLayoutParams(layoutParams);
+ }
+ } else if (mCurrentHeight != targetHeight || needFadeAnimation) {
+ // Restore description text for fade in/out animation.
+ if (needFadeAnimation) {
+ mProgramDescriptionTextView.setText(oldDescription);
+ }
+ mResizeAnimator = createResizeAnimator(targetHeight, needFadeAnimation);
+ mResizeAnimator.start();
}
}
- private void handleNoProgramInformation() {
- mProgramTextView.setText(getContext().getString(R.string.channel_banner_no_title));
- mProgramTimeTextView.setVisibility(View.GONE);
- mRemainingTimeView.setVisibility(View.GONE);
- mProgrameDescriptionTextView.setVisibility(View.GONE);
- }
+ 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.setDuration(mResizeAnimDuration);
+ heightAnimator.setInterpolator(mResizeInterpolator);
- private int estimateLineCount(String str, Typeface font, float textSize, int width) {
- if (width == 0) {
- return -1;
+ if (!addFadeAnimation) {
+ heightAnimator.addListener(mResizeAnimatorListener);
+ return heightAnimator;
}
- Paint paint = new Paint();
- paint.setTypeface(font);
- paint.setTextSize(textSize);
- // Add +1 to measured size, because number of lines becomes 2
- // when measured size equals width.
- return divideRoundUp((int) paint.measureText(str) + 1, width);
- }
- private int divideRoundUp(int x, int y) {
- return (x + y - 1) / y;
+ AnimatorSet fadeOutAndHeightAnimator = new AnimatorSet();
+ fadeOutAndHeightAnimator.playTogether(mProgramDescriptionFadeOutAnimator, heightAnimator);
+ AnimatorSet animator = new AnimatorSet();
+ animator.playSequentially(fadeOutAndHeightAnimator, mProgramDescriptionFadeInAnimator);
+ animator.addListener(mResizeAnimatorListener);
+ return animator;
}
}
diff --git a/src/com/android/tv/ui/ChannelListAdapter.java b/src/com/android/tv/ui/ChannelListAdapter.java
deleted file mode 100644
index cbebe94c..00000000
--- a/src/com/android/tv/ui/ChannelListAdapter.java
+++ /dev/null
@@ -1,140 +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.ui;
-
-import android.content.Context;
-import android.os.Handler;
-import android.view.View;
-
-import com.android.tv.R;
-import com.android.tv.data.Channel;
-import com.android.tv.data.ChannelMap;
-
-/*
- * An adapter of channel list.
- */
-public class ChannelListAdapter extends ItemListView.ItemListAdapter {
- private ChannelMap mChannelMap;
- private Channel[] mChannelList;
- private ItemListView mListView;
- private final boolean mGuideIncluded;
- private final boolean mBrowsableOnly;
- private final String mFixedTitle;
- private String mTitle;
- private final int mTileHeight;
-
- public ChannelListAdapter(Context context, Handler handler,
- View.OnClickListener onClickListener, boolean guideIncluded, boolean browsableOnly,
- String title,
- int tileHeight) {
- super(context, handler, R.layout.channel_tile, onClickListener);
- mGuideIncluded = guideIncluded;
- mBrowsableOnly = browsableOnly;
- mFixedTitle = title;
- mTileHeight = tileHeight;
- }
-
- @Override
- public int getTileHeight() {
- return mTileHeight;
- }
-
- @Override
- public String getTitle() {
- return mFixedTitle != null ? mFixedTitle : mTitle;
- }
-
- @Override
- public void update(ChannelMap channelMap) {
- update(channelMap, mListView);
- }
-
- @Override
- public void update(ChannelMap channelMap, ItemListView listView) {
- mChannelMap = channelMap;
- mListView = listView;
-
- mChannelList = mChannelMap == null ? null : mChannelMap.getChannelList(mBrowsableOnly);
- if (mGuideIncluded) {
- Channel[] channels = mChannelList == null ? new Channel[0] : mChannelList;
- Channel guideChannel = new Channel.Builder()
- .setType(R.integer.channel_type_guide)
- .build();
- mChannelList = new Channel[channels.length + 1];
- mChannelList[0] = guideChannel;
- for (int i = 0; i < channels.length; ++i) {
- mChannelList[i + 1] = channels[i];
- }
- }
- setItemList(mChannelList);
-
- updateTitle();
- selectGuideChannel();
- }
-
- @Override
- public void onBeforeShowing() {
- updateTitle();
- selectGuideChannel();
-
- if (mListView != null && mChannelMap != null) {
- for (int i = 0; i < mListView.getChildCount(); i++) {
- View v = mListView.getChildAt(i);
- if (v instanceof ChannelTileView) {
- ((ChannelTileView) v).updateProgramInformation();
- }
- }
- }
- }
-
- private void updateTitle() {
- if (mFixedTitle == null) {
- mTitle = null;
- if (mChannelMap != null) {
- mTitle = mChannelMap.getTvInput().getDisplayName();
- }
-
- if (mListView != null) {
- mListView.setTitle(mTitle);
- }
- }
- }
-
- private void selectGuideChannel() {
- if (mListView == null) {
- return;
- }
- if (mGuideIncluded) {
- mListView.setSelectedPosition(0);
- } else {
- selectCurrentChannel();
- }
- }
-
- private void selectCurrentChannel() {
- long id = mChannelMap == null ? Channel.INVALID_ID : mChannelMap.getCurrentChannelId();
- if (mListView == null || mChannelList == null || id == Channel.INVALID_ID) {
- return;
- }
- for (int i = 0; i < mChannelList.length; i++) {
- if (id == mChannelList[i].getId()) {
- mListView.setSelectedPosition(i);
- break;
- }
- }
- }
-}
diff --git a/src/com/android/tv/ui/ChannelNumberView.java b/src/com/android/tv/ui/ChannelNumberView.java
deleted file mode 100644
index d224c68c..00000000
--- a/src/com/android/tv/ui/ChannelNumberView.java
+++ /dev/null
@@ -1,353 +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.ui;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.util.Preconditions;
-import com.android.tv.R;
-import com.android.tv.TvActivity;
-import com.android.tv.data.Channel;
-
-import java.util.ArrayList;
-
-public class ChannelNumberView extends LinearLayout {
- private static final String TAG = "ChannelNumberView";
- private static final int MSG_HIDE_VIEW = 0;
-
- private static final int HIDE_VIEW_DELAY_MS = 2000;
- private static final int MAX_CHANNEL_NUMBER_DIGIT = 4;
- private static final int MAX_MINOR_CHANNEL_NUMBER_DIGIT = 3;
- private static final String PRIMARY_CHANNEL_DELIMETER = "-";
- private static final String[] CHANNEL_DELIMETERS = {"-", ".", " "};
- private static final int[] CHANNEL_DELIMETER_KEYCODES = {
- KeyEvent.KEYCODE_MINUS, KeyEvent.KEYCODE_NUMPAD_SUBTRACT, KeyEvent.KEYCODE_PERIOD,
- KeyEvent.KEYCODE_NUMPAD_DOT, KeyEvent.KEYCODE_SPACE
- };
-
- private TvActivity mTvActivity;
- private Channel[] mChannels;
- private TextView mTypedChannelNumberView;
- private VerticalGridView mChannelItemListView;
- private final ChannelNumber mTypedChannelNumber = new ChannelNumber();
- private final ArrayList<Channel> mChannelCandidates = new ArrayList<Channel>();
- protected final ChannelItemAdapter mAdapter = new ChannelItemAdapter();
- private final LayoutInflater mLayoutInflater;
- private int mItemBgColor;
- private int mItemFocusedBgColor;
- private long mSelectedChannelId = Channel.INVALID_ID;
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_HIDE_VIEW) {
- if (mSelectedChannelId != Channel.INVALID_ID) {
- mTvActivity.moveToChannel(mSelectedChannelId);
- }
- hide();
- }
- }
- };
-
- public ChannelNumberView(Context context) {
- this(context, null, 0);
- }
-
- public ChannelNumberView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ChannelNumberView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mLayoutInflater = LayoutInflater.from(context);
- }
-
- @Override
- protected void onFinishInflate(){
- mTypedChannelNumberView = (TextView) findViewById(R.id.typed_channel_number);
- mChannelItemListView = (VerticalGridView) findViewById(R.id.channel_list);
- mChannelItemListView.setAdapter(mAdapter);
- mItemBgColor = getContext().getResources().getColor(R.color.option_item_background);
- mItemFocusedBgColor = getContext().getResources().getColor(
- R.color.option_item_focused_background);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- Preconditions.checkState(mChannels != null);
- mHandler.removeMessages(MSG_HIDE_VIEW);
- mHandler.sendEmptyMessageDelayed(MSG_HIDE_VIEW, HIDE_VIEW_DELAY_MS);
-
- if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
- onNumberKeyUp(keyCode - KeyEvent.KEYCODE_0);
- return true;
- }
- if (isChannelNumberDelimiterKey(keyCode)) {
- onDelimeterKeyUp();
- return true;
- }
- return super.onKeyUp(keyCode, event);
- }
-
- public void show() {
- setVisibility(View.VISIBLE);
- mHandler.removeMessages(MSG_HIDE_VIEW);
- mHandler.sendEmptyMessageDelayed(MSG_HIDE_VIEW, HIDE_VIEW_DELAY_MS);
- }
-
- public void hide() {
- setVisibility(View.INVISIBLE);
- mTypedChannelNumber.reset();
- mSelectedChannelId = Channel.INVALID_ID;
- mChannelCandidates.clear();
- mAdapter.notifyDataSetChanged();
- }
-
- public void setChannels(Channel[] channels) {
- mChannels = channels;
- }
-
- public void setTvActivity(TvActivity activity) {
- mTvActivity = activity;
- }
-
- public static boolean isChannelNumberKey(int keyCode) {
- if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
- return true;
- }
- if (isChannelNumberDelimiterKey(keyCode)) {
- return true;
- }
- return false;
- }
-
- public static boolean isChannelNumberDelimiterKey(int keyCode) {
- for (int i = 0; i < CHANNEL_DELIMETER_KEYCODES.length; ++i) {
- if (CHANNEL_DELIMETER_KEYCODES[i] == keyCode) {
- return true;
- }
- }
- return false;
- }
-
- private void onNumberKeyUp(int num) {
- // Reset typed channel number in some cases.
- if (mTypedChannelNumber.mMajor == null) {
- mTypedChannelNumber.reset();
- } else if (!mTypedChannelNumber.mHasDelimeter
- && mTypedChannelNumber.mMajor.length() >= MAX_CHANNEL_NUMBER_DIGIT) {
- mTypedChannelNumber.reset();
- } else if (mTypedChannelNumber.mHasDelimeter
- && mTypedChannelNumber.mMinor != null
- && mTypedChannelNumber.mMinor.length() >= MAX_MINOR_CHANNEL_NUMBER_DIGIT) {
- mTypedChannelNumber.reset();
- }
-
- if (!mTypedChannelNumber.mHasDelimeter) {
- mTypedChannelNumber.mMajor += String.valueOf(num);
- } else {
- mTypedChannelNumber.mMinor += String.valueOf(num);
- }
- updateView();
- }
-
- private void onDelimeterKeyUp() {
- if (mTypedChannelNumber.mHasDelimeter || mTypedChannelNumber.mMajor.length() == 0) {
- return;
- }
- mTypedChannelNumber.mHasDelimeter = true;
- updateView();
- }
-
- private void updateView() {
- mTypedChannelNumberView.setText(mTypedChannelNumber.toString());
-
- mChannelCandidates.clear();
- ArrayList<Channel> secondaryChannelCandidates = new ArrayList<Channel>();
- for (Channel channel : mChannels) {
- ChannelNumber chNumber = parseChannelNumber(channel.getDisplayNumber());
- if (chNumber == null) {
- Log.i(TAG, "Malformed channel number (name=" + channel.getDisplayName()
- + ", number=" + channel.getDisplayNumber() + ")");
- continue;
- }
- if (matchChannelNumber(mTypedChannelNumber, chNumber)) {
- mChannelCandidates.add(channel);
- }
- if (!mTypedChannelNumber.mHasDelimeter) {
- // 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 delimeter, it should be matched to '111', '1-11' and '11-1'.
- String inputMajor = mTypedChannelNumber.mMajor;
- int length = inputMajor.length();
- ChannelNumber expectedInputChNumber = new ChannelNumber();
- for (int i = 0; i < length - 1 && i < MAX_MINOR_CHANNEL_NUMBER_DIGIT; ++i) {
- expectedInputChNumber.setChannelNumber(inputMajor.substring(0, length - i - 1),
- true, inputMajor.substring(length - i - 1));
- if (matchChannelNumber(expectedInputChNumber, chNumber)) {
- secondaryChannelCandidates.add(channel);
- }
- }
- }
- }
- mChannelCandidates.addAll(secondaryChannelCandidates);
- mAdapter.notifyDataSetChanged();
- if (mAdapter.getItemCount() > 0) {
- mChannelItemListView.setSelectedPosition(0);
- }
- }
-
- private static boolean matchChannelNumber(ChannelNumber typedChNumber, ChannelNumber chNumber) {
- if (!chNumber.mMajor.equals(typedChNumber.mMajor)) {
- return false;
- }
- if (typedChNumber.mHasDelimeter) {
- if (!chNumber.mHasDelimeter) {
- return false;
- }
- if (!chNumber.mMinor.startsWith(typedChNumber.mMinor)) {
- return false;
- }
- }
- return true;
- }
-
- private static ChannelNumber parseChannelNumber(String number) {
- ChannelNumber ret = new ChannelNumber();
- int indexOfDelimeter = -1;
- for (int i = 0; i < CHANNEL_DELIMETERS.length; ++i) {
- indexOfDelimeter = number.indexOf(CHANNEL_DELIMETERS[i]);
- if (indexOfDelimeter >= 0) {
- break;
- }
- }
- if (indexOfDelimeter == 0 || indexOfDelimeter == number.length() - 1) {
- return null;
- }
- if (indexOfDelimeter < 0) {
- ret.mMajor = number;
- return ret;
- }
- ret.mHasDelimeter = true;
- ret.mMajor = number.substring(0, indexOfDelimeter);
- if (number.length() > indexOfDelimeter + 1) {
- ret.mMinor = number.substring(indexOfDelimeter + 1);
- }
- return ret;
- }
-
- private static class ChannelNumber {
- ChannelNumber() {
- reset();
- }
-
- void reset() {
- setChannelNumber("", false, "");
- }
-
- void setChannelNumber(String major, boolean hasDelimeter, String minor) {
- mMajor = major;
- mHasDelimeter = hasDelimeter;
- mMinor = minor;
- }
-
- String mMajor;
- boolean mHasDelimeter;
- String mMinor;
-
- @Override
- public String toString() {
- return "" + mMajor + (mHasDelimeter ? PRIMARY_CHANNEL_DELIMETER + mMinor : "");
- }
- }
-
- class ChannelItemAdapter extends RecyclerView.Adapter<ChannelItemAdapter.MyViewHolder> {
- @Override
- public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View v = mLayoutInflater.inflate(R.layout.channel_item, parent, false);
- v.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Channel channel = (Channel) v.getTag();
- mTvActivity.moveToChannel(channel.getId());
- hide();
- }
- });
- v.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean focusGained) {
- if (focusGained) {
- mSelectedChannelId = ((Channel) v.getTag()).getId();
- } else {
- mSelectedChannelId = Channel.INVALID_ID;
- }
- v.setBackgroundColor(focusGained ? mItemFocusedBgColor : mItemBgColor);
- }
- });
- return new MyViewHolder(v);
- }
-
- @Override
- public void onBindViewHolder(MyViewHolder baseHolder, int position) {
- View v = baseHolder.itemView;
- Channel channel = mChannelCandidates.get(position);
- v.setTag(channel);
- TextView channelNumberView = (TextView) v.findViewById(R.id.channel_number);
- channelNumberView.setText(channel.getDisplayName() + " " + channel.getDisplayNumber());
- if (v instanceof ViewGroup) {
- final ViewGroup viewGroup = (ViewGroup) v;
- mHandler.post(new Runnable() {
- void requestLayout(ViewGroup v) {
- for (int i = 0; i < v.getChildCount(); i++) {
- v.getChildAt(i).requestLayout();
- if (v.getChildAt(i) instanceof ViewGroup) {
- requestLayout((ViewGroup) v.getChildAt(i));
- }
- }
- }
-
- @Override
- public void run() {
- requestLayout(viewGroup);
- }
- });
- }
- }
-
- @Override
- public int getItemCount() {
- return mChannelCandidates.size();
- }
-
- private class MyViewHolder extends RecyclerView.ViewHolder {
- MyViewHolder(View view) {
- super(view);
- }
- }
- }
-}
diff --git a/src/com/android/tv/ui/ChannelTileView.java b/src/com/android/tv/ui/ChannelTileView.java
deleted file mode 100644
index 6e755071..00000000
--- a/src/com/android/tv/ui/ChannelTileView.java
+++ /dev/null
@@ -1,189 +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.ui;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.util.Preconditions;
-import com.android.tv.R;
-import com.android.tv.data.Channel;
-import com.android.tv.data.Program;
-import com.android.tv.util.BitmapUtils;
-import com.android.tv.util.Utils;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-/**
- * A view to render channel tile.
- */
-public class ChannelTileView extends ShadowContainer
- implements ItemListView.TileView, Channel.LoadLogoCallback {
- private static final String TAG = "ChannelTileView";
-
- private float mRoundRadius;
- private float mPosterArtWidth;
- private float mPosterArtHeight;
- private LinearLayout mChannelInfosLayout;
- private ImageView mProgramPosterArtView;
- private ImageView mChannelLogoView;
- private TextView mChannelNameView;
- private TextView mChannelNumberView;
- private TextView mProgramNameView;
- private Channel mChannel;
- private Drawable mNormalBackgroud;
- private Drawable mBackgroundOnImage;
-
- public ChannelTileView(Context context) {
- super(context);
- }
-
- public ChannelTileView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public ChannelTileView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- public void loadViews() {
- mChannelInfosLayout = (LinearLayout) findViewById(R.id.channel_infos);
- mChannelLogoView = (ImageView) findViewById(R.id.channel_logo);
- mChannelNameView = (TextView) findViewById(R.id.channel_name);
- mChannelNumberView = (TextView) findViewById(R.id.channel_number);
- mProgramNameView = (TextView) findViewById(R.id.program_name);
- mProgramPosterArtView = (ImageView) findViewById(R.id.program_poster_art);
- mChannelNameView.setVisibility(INVISIBLE);
-
- mNormalBackgroud = getResources().getDrawable(R.drawable.channel_tile_top);
- mBackgroundOnImage = getResources().getDrawable(R.drawable.channel_tile_top_on_image);
- mRoundRadius = getResources().getDimension(R.dimen.channel_tile_round_radius);
- mPosterArtWidth = getResources().getDimension(R.dimen.channel_tile_poster_art_width);
- mPosterArtHeight = getResources().getDimension(R.dimen.channel_tile_poster_art_height);
- }
-
- @Override
- public void populateViews(View.OnClickListener listener, Object item) {
- Preconditions.checkNotNull(item);
-
- mChannel = (Channel) item;
- setOnClickListener(listener);
- setTag(MainMenuView.MenuTag.buildTag(mChannel));
-
- if (mChannel.getType() == R.integer.channel_type_guide) {
- mChannelInfosLayout.setBackgroundResource(R.drawable.channel_tile_guide_top);
- mChannelNumberView.setVisibility(INVISIBLE);
- mChannelNameView.setVisibility(INVISIBLE);
- mProgramPosterArtView.setVisibility(INVISIBLE);
- mChannelLogoView.setImageResource(R.drawable.ic_channel_guide);
- mChannelLogoView.setVisibility(VISIBLE);
- mProgramNameView.setText(R.string.menu_program_guide);
- mProgramNameView.setBackgroundResource(R.drawable.channel_tile_guide_bottom);
- } else {
- mChannelNumberView.setText(mChannel.getDisplayNumber());
- mChannelNumberView.setVisibility(VISIBLE);
- if (!mChannel.isLogoLoaded()) {
- showName();
- }
- mChannel.loadLogo(getContext(), this);
- updateProgramInformation();
- }
- }
-
- @Override
- public void onLoadLogoFinished(Channel channel, Bitmap logo) {
- if (channel.getId() != mChannel.getId()) {
- return;
- }
- if (logo == null) {
- showName();
- } else {
- showLogo(logo);
- }
- }
-
- private void showName() {
- mChannelNameView.setText(mChannel.getDisplayName());
- mChannelNameView.setVisibility(VISIBLE);
- mChannelLogoView.setVisibility(INVISIBLE);
- }
-
- private void showLogo(Bitmap logo) {
- mChannelLogoView.setImageBitmap(logo);
- mChannelLogoView.setVisibility(VISIBLE);
- mChannelNameView.setVisibility(INVISIBLE);
- }
-
- private void setProgramPosterArt(Bitmap bm) {
- mProgramPosterArtView.setImageBitmap(BitmapUtils.getRoundedCornerBitmap(bm,
- mRoundRadius, mPosterArtWidth, mPosterArtHeight));
- mProgramPosterArtView.setVisibility(VISIBLE);
- mChannelInfosLayout.setBackground(mBackgroundOnImage);
- }
-
- public void updateProgramInformation() {
- if (mProgramNameView == null || mProgramPosterArtView == null || mChannel == null
- || mChannel.getType() == R.integer.channel_type_guide) {
- return;
- }
-
- Program program = Utils.getCurrentProgram(getContext(),
- Utils.getChannelUri(mChannel.getId()));
- if (program == null || TextUtils.isEmpty(program.getTitle())) {
- mProgramNameView.setText(getContext().getText(R.string.no_program_information));
- } else {
- mProgramNameView.setText(program.getTitle());
- }
-
- String posterArtUri = program.getPosterArtUri();
- if (!TextUtils.isEmpty(posterArtUri)) {
- mProgramPosterArtView.setVisibility(INVISIBLE);
- mChannelInfosLayout.setBackground(mNormalBackgroud);
- new AsyncTask<String, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(String... params) {
- return BitmapUtils.decodeSampledBitmapFromUriString(getContext(), params[0],
- (int) mPosterArtWidth, (int) mPosterArtHeight);
- }
-
- @Override
- protected void onPostExecute(Bitmap bm) {
- if (bm != null) {
- setProgramPosterArt(bm);
- }
- }
- }.execute(posterArtUri);
- } else {
- mProgramPosterArtView.setVisibility(INVISIBLE);
- mChannelInfosLayout.setBackground(mNormalBackgroud);
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/ui/FullscreenDialogView.java b/src/com/android/tv/ui/FullscreenDialogView.java
new file mode 100644
index 00000000..9cda1f3a
--- /dev/null
+++ b/src/com/android/tv/ui/FullscreenDialogView.java
@@ -0,0 +1,202 @@
+/*
+ * 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;
+
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.View;
+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;
+
+public class FullscreenDialogView extends FrameLayout
+ implements FullscreenDialogFragment.DialogView {
+ private static final int FADE_IN_DURATION_MS = 400;
+ private static final int FADE_OUT_DURATION_MS = 300;
+ private static final int TRANSITION_INTERVAL_MS = 300;
+
+ private MainActivity mActivity;
+ private Dialog mDialog;
+ private boolean mSkipEnterAlphaAnimation;
+ private boolean mSkipExitAlphaAnimation;
+ private boolean mUseTranslationAnimation;
+
+ private final int mEnterTranslationX;
+ private final int mExitTranslationX;
+ private final TimeInterpolator mLinearOutSlowIn;
+ private final TimeInterpolator mFastOutLinearIn;
+
+ public FullscreenDialogView(Context context) {
+ this(context, null, 0);
+ }
+
+ public FullscreenDialogView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FullscreenDialogView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mEnterTranslationX = context.getResources().getInteger(
+ R.integer.fullscreen_dialog_enter_translation_x);
+ mExitTranslationX = context.getResources().getInteger(
+ R.integer.fullscreen_dialog_exit_translation_x);
+ mLinearOutSlowIn = AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.linear_out_slow_in);
+ mFastOutLinearIn = AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.fast_out_linear_in);
+ }
+
+ public void setTransitionAnimationEnabled(boolean enable) {
+ mUseTranslationAnimation = enable;
+ }
+
+ protected MainActivity getActivity() {
+ return mActivity;
+ }
+
+ /**
+ * Gets the host {@link Dialog}.
+ */
+ protected Dialog getDialog() {
+ return mDialog;
+ }
+
+ /**
+ * Dismisses the host {@link Dialog}.
+ */
+ protected void dismiss() {
+ startExitAnimation(new Runnable() {
+ @Override
+ public void run() {
+ mDialog.dismiss();
+ }
+ });
+ }
+
+ @Override
+ public void initialize(MainActivity activity, Dialog dialog) {
+ mActivity = activity;
+ mDialog = dialog;
+ }
+
+ @Override
+ public void onBackPressed() { }
+
+ @Override
+ public void onDestroy() { }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ startEnterAnimation();
+ }
+
+ /**
+ * 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() {
+ @Override
+ public void run() {
+ v.initialize(getActivity(), getDialog());
+ getDialog().setContentView(v);
+ }
+ }, TRANSITION_INTERVAL_MS);
+ }
+ });
+ }
+
+ private void startEnterAnimation() {
+ View v = findViewById(R.id.container);
+ if (mUseTranslationAnimation) {
+ v.setTranslationX(mEnterTranslationX);
+ v.animate()
+ .translationX(0)
+ .setInterpolator(mLinearOutSlowIn)
+ .setDuration(FADE_IN_DURATION_MS)
+ .setListener(new HardwareLayerAnimatorListenerAdapter(this))
+ .start();
+ }
+ if (!mSkipEnterAlphaAnimation) {
+ setAlpha(0);
+ animate()
+ .alpha(1.0f)
+ .setInterpolator(mLinearOutSlowIn)
+ .setDuration(FADE_IN_DURATION_MS)
+ .setListener(new HardwareLayerAnimatorListenerAdapter(this))
+ .start();
+ } else {
+ v.setAlpha(0);
+ v.animate()
+ .alpha(1.0f)
+ .setInterpolator(mLinearOutSlowIn)
+ .setDuration(FADE_IN_DURATION_MS)
+ .setListener(new HardwareLayerAnimatorListenerAdapter(this))
+ .start();
+ }
+ }
+
+ private void startExitAnimation(final Runnable onAnimationEnded) {
+ View v = findViewById(R.id.container);
+ if (mUseTranslationAnimation) {
+ v.animate()
+ .translationX(mExitTranslationX)
+ .setInterpolator(mFastOutLinearIn)
+ .setDuration(FADE_OUT_DURATION_MS)
+ .setListener(new HardwareLayerAnimatorListenerAdapter(this))
+ .start();
+ }
+ if (!mSkipExitAlphaAnimation) {
+ animate()
+ .alpha(0.0f)
+ .setInterpolator(mFastOutLinearIn)
+ .setDuration(FADE_OUT_DURATION_MS)
+ .setListener(new HardwareLayerAnimatorListenerAdapter(this) {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onAnimationEnded.run();
+ }
+ })
+ .start();
+ } else {
+ v.animate()
+ .alpha(0.0f)
+ .setInterpolator(mFastOutLinearIn)
+ .setDuration(FADE_OUT_DURATION_MS)
+ .setListener(new HardwareLayerAnimatorListenerAdapter(this) {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onAnimationEnded.run();
+ }
+ })
+ .start();
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/HardwareLayerAnimatorListenerAdapter.java b/src/com/android/tv/ui/HardwareLayerAnimatorListenerAdapter.java
new file mode 100644
index 00000000..126e1061
--- /dev/null
+++ b/src/com/android/tv/ui/HardwareLayerAnimatorListenerAdapter.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.view.View;
+
+/**
+ * An AnimatorListenerAdapter subclass that conveniently sets the layer type to hardware during the
+ * animation.
+ */
+public class HardwareLayerAnimatorListenerAdapter extends AnimatorListenerAdapter {
+ private final View mView;
+ private boolean mLayerTypeChanged;
+
+ public HardwareLayerAnimatorListenerAdapter(View view) {
+ mView = view;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animator) {
+ if (mView.hasOverlappingRendering() && mView.getLayerType() == View.LAYER_TYPE_NONE) {
+ mLayerTypeChanged = true;
+ mView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (mLayerTypeChanged) {
+ mLayerTypeChanged = false;
+ mView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/InputBannerView.java b/src/com/android/tv/ui/InputBannerView.java
new file mode 100644
index 00000000..cc7a9443
--- /dev/null
+++ b/src/com/android/tv/ui/InputBannerView.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.ui;
+
+import android.content.Context;
+import android.media.tv.TvInputInfo;
+import android.text.TextUtils;
+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;
+
+public class InputBannerView extends LinearLayout implements TvTransitionManager.TransitionLayout {
+ private final long mShowDurationMillis;
+
+ private final Runnable mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ ((MainActivity) getContext()).goToEmptyScene(true);
+ }
+ };
+
+ private TextView mInputLabelTextView;
+ private TextView mSecondaryInputLabelTextView;
+
+ public InputBannerView(Context context) {
+ this(context, null, 0);
+ }
+
+ public InputBannerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public InputBannerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mShowDurationMillis = context.getResources().getInteger(
+ R.integer.select_input_show_duration);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mInputLabelTextView = (TextView) findViewById(R.id.input_label);
+ mSecondaryInputLabelTextView = (TextView) findViewById(R.id.secondary_input_label);
+ }
+
+ public void updateLabel() {
+ MainActivity mainActivity = (MainActivity) getContext();
+ Channel channel = mainActivity.getCurrentChannel();
+ if (channel == null || !channel.isPassthrough()) {
+ return;
+ }
+ TvInputInfo input = mainActivity.getTvInputManagerHelper().getTvInputInfo(
+ channel.getInputId());
+ CharSequence customLabel = input.loadCustomLabel(getContext());
+ CharSequence label = input.loadLabel(getContext());
+ if (TextUtils.isEmpty(customLabel) || customLabel.equals(label)) {
+ mInputLabelTextView.setText(label);
+ mSecondaryInputLabelTextView.setVisibility(View.GONE);
+ } else {
+ mInputLabelTextView.setText(customLabel);
+ mSecondaryInputLabelTextView.setText(label);
+ mSecondaryInputLabelTextView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onEnterAction(boolean fromEmptyScene) {
+ removeCallbacks(mHideRunnable);
+ postDelayed(mHideRunnable, mShowDurationMillis);
+ }
+
+ @Override
+ public void onExitAction() {
+ removeCallbacks(mHideRunnable);
+ }
+}
diff --git a/src/com/android/tv/ui/IntroView.java b/src/com/android/tv/ui/IntroView.java
new file mode 100644
index 00000000..4266b30d
--- /dev/null
+++ b/src/com/android/tv/ui/IntroView.java
@@ -0,0 +1,84 @@
+/*
+ * 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;
+
+import android.content.Context;
+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.MenuView;
+
+public class IntroView extends FullscreenDialogView {
+ private AnimationDrawable mRippleDrawable;
+ private boolean mOpenMenu;
+
+ public IntroView(Context context) {
+ this(context, null, 0);
+ }
+
+ public IntroView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public IntroView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setTransitionAnimationEnabled(false);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ENTER:
+ dismiss();
+ mOpenMenu = true;
+ return true;
+ }
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public void onBackPressed() {
+ dismiss();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ View v = findViewById(R.id.welcome_ripple);
+ mRippleDrawable = (AnimationDrawable) v.getBackground();
+ mRippleDrawable.start();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ mRippleDrawable.stop();
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mOpenMenu) {
+ getActivity().getOverlayManager().showMenu(MenuView.REASON_GUIDE);
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/ItemListView.java b/src/com/android/tv/ui/ItemListView.java
deleted file mode 100644
index db4bbea3..00000000
--- a/src/com/android/tv/ui/ItemListView.java
+++ /dev/null
@@ -1,196 +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.ui;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.support.v17.leanback.widget.HorizontalGridView;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.util.Preconditions;
-import com.android.tv.R;
-import com.android.tv.data.ChannelMap;
-
-/*
- * A subclass of LinearLayout that shows a title and list view.
- */
-public class ItemListView extends LinearLayout {
- private static final long DURATION_OF_SELECTION_CHANGE_ANIMATION_MS = 200;
-
- public interface TileView {
- void loadViews();
- void populateViews(View.OnClickListener onClickListener, Object item);
- }
-
- private TextView mTitleView;
- private HorizontalGridView mListView;
-
- public ItemListView(Context context) {
- this(context, null, 0);
- }
-
- public ItemListView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ItemListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public void loadViews() {
- mTitleView = (TextView) findViewById(R.id.title);
- mListView = (HorizontalGridView) findViewById(R.id.list_view);
- }
-
- public void populateViews(String title, ItemListAdapter adapter) {
- mTitleView.setText(title);
- mListView.setAdapter(adapter);
-
- ViewGroup.LayoutParams lp = mListView.getLayoutParams();
- lp.height = adapter.getTileHeight();
-
- onDeselected();
- }
-
- public void setTitle(String title) {
- mTitleView.setText(title);
- }
-
- public void setSelectedPosition(int position) {
- Preconditions.checkNotNull(mListView.getAdapter());
-
- if (mListView.getAdapter().getItemCount() > position) {
- mListView.setSelectedPosition(position);
- }
- }
-
- public void onDeselected() {
- mListView.setVisibility(View.GONE);
- updateTitle(false);
- }
-
- public void onSelected() {
- mListView.setVisibility(View.VISIBLE);
- mListView.requestFocus();
-
- mListView.setAlpha(0f);
- mListView.animate()
- .alpha(1f)
- .setDuration(DURATION_OF_SELECTION_CHANGE_ANIMATION_MS);
-
- updateTitle(true);
- }
-
- private void updateTitle(boolean focused) {
- int colorResId = focused ? R.color.focused_list_title :
- R.color.unfocused_list_title;
- int textSizeResId = focused ? R.dimen.focused_list_title_text_size :
- R.dimen.unfocused_list_title_text_size;
-
- Resources res = getContext().getResources();
- mTitleView.setTextColor(res.getColor(colorResId));
- mTitleView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- res.getDimensionPixelOffset(textSizeResId));
- }
-
- public static abstract class ItemListAdapter extends
- RecyclerView.Adapter<ItemListAdapter.MyViewHolder> {
- private final LayoutInflater mLayoutInflater;
- private final View.OnClickListener mOnClickListener;
- private final int mLayoutResId;
- private Object[] mItemList;
- private final Handler mHandler;
-
- public abstract int getTileHeight();
- public abstract String getTitle();
- public abstract void update(ChannelMap channelMap);
- public abstract void update(ChannelMap channelMap, ItemListView list);
- public void onBeforeShowing() {}
-
- public ItemListAdapter(Context context, Handler handler, int layoutResId,
- View.OnClickListener onClickListener) {
- mHandler = handler;
- mLayoutResId = layoutResId;
- mLayoutInflater = LayoutInflater.from(context);
- mOnClickListener = onClickListener;
- }
-
- public void setItemList(Object[] itemList) {
- mItemList = itemList;
- notifyDataSetChanged();
- }
-
- @Override
- public int getItemViewType(int position) {
- return 0;
- }
-
- private class MyViewHolder extends RecyclerView.ViewHolder {
- MyViewHolder(View view) {
- super(view);
- }
- }
-
- @Override
- public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = mLayoutInflater.inflate(mLayoutResId, parent, false);
- ((TileView) view).loadViews();
- return new MyViewHolder(view);
- }
-
- @Override
- public void onBindViewHolder(MyViewHolder baseHolder, int position) {
- TileView view = (TileView) baseHolder.itemView;
- Object[] itemList = mItemList;
- if (itemList != null && position >= 0 && itemList.length > position) {
- view.populateViews(mOnClickListener, itemList[position]);
- if (view instanceof ViewGroup) {
- final ViewGroup viewGroup = (ViewGroup) view;
- mHandler.post(new Runnable() {
- void requestLayout(ViewGroup v) {
- for (int i = 0; i < v.getChildCount(); i++) {
- v.getChildAt(i).requestLayout();
- if (v.getChildAt(i) instanceof ViewGroup) {
- requestLayout((ViewGroup) v.getChildAt(i));
- }
- }
- }
-
- @Override
- public void run() {
- requestLayout(viewGroup);
- }
- });
- }
- }
- }
-
- @Override
- public int getItemCount() {
- Object[] itemList = mItemList;
- return itemList == null ? 0 : itemList.length;
- }
- }
-}
diff --git a/src/com/android/tv/ui/KeypadChannelSwitchView.java b/src/com/android/tv/ui/KeypadChannelSwitchView.java
new file mode 100644
index 00000000..bfbb6e9c
--- /dev/null
+++ b/src/com/android/tv/ui/KeypadChannelSwitchView.java
@@ -0,0 +1,393 @@
+/*
+ * 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;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.AdapterView;
+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.analytics.DurationTimer;
+import com.android.tv.analytics.Tracker;
+import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelNumber;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class KeypadChannelSwitchView extends LinearLayout implements
+ TvTransitionManager.TransitionLayout {
+ private static final String TAG = "KeypadChannelSwitchView";
+
+ private static final int MAX_CHANNEL_NUMBER_DIGIT = 4;
+ private static final int MAX_MINOR_CHANNEL_NUMBER_DIGIT = 3;
+ private static final int MAX_CHANNEL_ITEM = 8;
+ private static final String CHANNEL_DELIMITERS_REGEX = "[-\\.\\s]";
+ public static final String SCREEN_NAME = "Channel switch";
+
+ private final MainActivity mMainActivity;
+ private final Tracker mTracker;
+ private final DurationTimer mViewDurationTimer = new DurationTimer();
+ private boolean mNavigated = false;
+ private List<Channel> mChannels;
+ private TextView mChannelNumberView;
+ private ListView mChannelItemListView;
+ private final ChannelNumber mTypedChannelNumber = new ChannelNumber();
+ private final ArrayList<Channel> mChannelCandidates = new ArrayList<>();
+ protected final ChannelItemAdapter mAdapter = new ChannelItemAdapter();
+ 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.goToEmptyScene(true);
+ }
+ }
+ };
+ private final long mShowDurationMillis;
+ private final long mRippleAnimDurationMillis;
+ private final int mBaseViewHeight;
+ private final int mItemHeight;
+ private final int mResizeAnimDuration;
+ private Animator mResizeAnimator;
+ private final Interpolator mResizeInterpolator;
+ // NOTE: getHeight() will be updated after layout() is called. mCurrentHeight is needed for
+ // getting the latest updated value of the view height before layout().
+ private int mCurrentHeight;
+
+ public KeypadChannelSwitchView(Context context) {
+ this(context, null, 0);
+ }
+
+ public KeypadChannelSwitchView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeypadChannelSwitchView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ mMainActivity = (MainActivity) context;
+ mTracker = ((TvApplication) mMainActivity.getApplication()).getTracker();
+ 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);
+ 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);
+ }
+
+ @Override
+ protected void 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() {
+ @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();
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ mSelectedChannel = null;
+ }
+ });
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ scheduleHide();
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ Assert.assertNotNull(mChannels);
+ if (isChannelNumberKey(keyCode)) {
+ onNumberKeyUp(keyCode - KeyEvent.KEYCODE_0);
+ return true;
+ }
+ if (ChannelNumber.isChannelNumberDelimiterKey(keyCode)) {
+ onDelimiterKeyUp();
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public void onEnterAction(boolean fromEmptyScene) {
+ reset();
+ if (fromEmptyScene) {
+ ViewUtils.setTransitionAlpha(mChannelItemListView, 1f);
+ }
+ mNavigated = false;
+ mViewDurationTimer.start();
+ mTracker.sendShowChannelSwitch();
+ mTracker.sendScreenView(SCREEN_NAME);
+ updateView();
+ scheduleHide();
+ }
+
+ @Override
+ public void onExitAction() {
+ mCurrentHeight = 0;
+ mTracker.sendHideChannelSwitch(mViewDurationTimer.reset());
+ cancelHide();
+ }
+
+ private void scheduleHide() {
+ cancelHide();
+ postDelayed(mHideRunnable, mShowDurationMillis);
+ }
+
+ private void cancelHide() {
+ removeCallbacks(mHideRunnable);
+ }
+
+ private void reset() {
+ mTypedChannelNumber.reset();
+ mSelectedChannel = null;
+ mChannelCandidates.clear();
+ mAdapter.notifyDataSetChanged();
+ }
+
+ public void setChannels(List<Channel> channels) {
+ mChannels = channels;
+ }
+
+ public static boolean isChannelNumberKey(int keyCode) {
+ return keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9;
+ }
+
+ public void onNumberKeyUp(int num) {
+ // Reset typed channel number in some cases.
+ if (mTypedChannelNumber.majorNumber == null) {
+ mTypedChannelNumber.reset();
+ } else if (!mTypedChannelNumber.hasDelimiter
+ && mTypedChannelNumber.majorNumber.length() >= MAX_CHANNEL_NUMBER_DIGIT) {
+ mTypedChannelNumber.reset();
+ } else if (mTypedChannelNumber.hasDelimiter
+ && mTypedChannelNumber.minorNumber != null
+ && mTypedChannelNumber.minorNumber.length() >= MAX_MINOR_CHANNEL_NUMBER_DIGIT) {
+ mTypedChannelNumber.reset();
+ }
+
+ if (!mTypedChannelNumber.hasDelimiter) {
+ mTypedChannelNumber.majorNumber += String.valueOf(num);
+ } else {
+ mTypedChannelNumber.minorNumber += String.valueOf(num);
+ }
+ mTracker.sendChannelNumberInput();
+ updateView();
+ }
+
+ private void onDelimiterKeyUp() {
+ if (mTypedChannelNumber.hasDelimiter || mTypedChannelNumber.majorNumber.length() == 0) {
+ return;
+ }
+ mTypedChannelNumber.hasDelimiter = true;
+ mTracker.sendChannelNumberInput();
+ updateView();
+ }
+
+ private void updateView() {
+ mChannelNumberView.setText(mTypedChannelNumber.toString() + "_");
+ mChannelCandidates.clear();
+ ArrayList<Channel> secondaryChannelCandidates = new ArrayList<>();
+ 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() + ")");
+ continue;
+ }
+ if (matchChannelNumber(mTypedChannelNumber, chNumber)) {
+ mChannelCandidates.add(channel);
+ } else if (!mTypedChannelNumber.hasDelimiter) {
+ // 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, "")
+ .startsWith(mTypedChannelNumber.majorNumber)) {
+ secondaryChannelCandidates.add(channel);
+ }
+ }
+ }
+ mChannelCandidates.addAll(secondaryChannelCandidates);
+ mAdapter.notifyDataSetChanged();
+ if (mAdapter.getCount() > 0) {
+ mChannelItemListView.requestFocus();
+ mChannelItemListView.setSelection(0);
+ mSelectedChannel = mChannelCandidates.get(0);
+ }
+
+ updateViewHeight();
+ }
+
+ private void updateViewHeight() {
+ int itemListHeight = mItemHeight * Math.min(MAX_CHANNEL_ITEM, mAdapter.getCount());
+ int targetHeight = mBaseViewHeight + itemListHeight;
+ if (mResizeAnimator != null) {
+ mResizeAnimator.cancel();
+ mResizeAnimator = null;
+ }
+
+ if (mCurrentHeight == 0) {
+ // Do not add the resize animation when the banner has not been shown before.
+ mCurrentHeight = targetHeight;
+ setViewHeight(this, targetHeight);
+ } else if (mCurrentHeight != targetHeight){
+ mResizeAnimator = createResizeAnimator(targetHeight);
+ mResizeAnimator.start();
+ }
+ }
+
+ 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.setDuration(mResizeAnimDuration);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mResizeAnimator = null;
+ }
+ });
+ animator.setInterpolator(mResizeInterpolator);
+ return animator;
+ }
+
+ private void setViewHeight(View view, int height) {
+ ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
+ if (height != layoutParams.height) {
+ layoutParams.height = height;
+ view.setLayoutParams(layoutParams);
+ }
+ }
+
+ private static boolean matchChannelNumber(ChannelNumber typedChNumber, ChannelNumber chNumber) {
+ if (!chNumber.majorNumber.equals(typedChNumber.majorNumber)) {
+ return false;
+ }
+ if (typedChNumber.hasDelimiter) {
+ if (!chNumber.hasDelimiter) {
+ return false;
+ }
+ if (!chNumber.minorNumber.startsWith(typedChNumber.minorNumber)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ class ChannelItemAdapter extends BaseAdapter {
+ @Override
+ public int getCount() {
+ return mChannelCandidates.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mChannelCandidates.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final Channel channel = mChannelCandidates.get(position);
+ View v = convertView;
+ if (v == null) {
+ v = mLayoutInflater.inflate(R.layout.keypad_channel_switch_item, parent, false);
+ }
+
+ TextView channelNumberView = (TextView) v.findViewById(R.id.number);
+ channelNumberView.setText(channel.getDisplayNumber());
+
+ TextView channelNameView = (TextView) v.findViewById(R.id.name);
+ channelNameView.setText(channel.getDisplayName());
+ return v;
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/KeypadView.java b/src/com/android/tv/ui/KeypadView.java
deleted file mode 100644
index 1eef81ee..00000000
--- a/src/com/android/tv/ui/KeypadView.java
+++ /dev/null
@@ -1,130 +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.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.RelativeLayout;
-import android.widget.Toast;
-
-import com.android.tv.R;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class KeypadView extends RelativeLayout implements View.OnClickListener,
- View.OnFocusChangeListener {
- private static final IdToKeyCodeBiMap sIdToKeyCodeBiMap;
- static {
- sIdToKeyCodeBiMap = new IdToKeyCodeBiMap();
- sIdToKeyCodeBiMap.put(R.id.key_red, KeyEvent.KEYCODE_PROG_RED);
- sIdToKeyCodeBiMap.put(R.id.key_green, KeyEvent.KEYCODE_PROG_GREEN);
- sIdToKeyCodeBiMap.put(R.id.key_yellow, KeyEvent.KEYCODE_PROG_YELLOW);
- sIdToKeyCodeBiMap.put(R.id.key_blue, KeyEvent.KEYCODE_PROG_BLUE);
- sIdToKeyCodeBiMap.put(R.id.key_1, KeyEvent.KEYCODE_1);
- sIdToKeyCodeBiMap.put(R.id.key_2, KeyEvent.KEYCODE_2);
- sIdToKeyCodeBiMap.put(R.id.key_3, KeyEvent.KEYCODE_3);
- sIdToKeyCodeBiMap.put(R.id.key_4, KeyEvent.KEYCODE_4);
- sIdToKeyCodeBiMap.put(R.id.key_5, KeyEvent.KEYCODE_5);
- sIdToKeyCodeBiMap.put(R.id.key_6, KeyEvent.KEYCODE_6);
- sIdToKeyCodeBiMap.put(R.id.key_7, KeyEvent.KEYCODE_7);
- sIdToKeyCodeBiMap.put(R.id.key_8, KeyEvent.KEYCODE_8);
- sIdToKeyCodeBiMap.put(R.id.key_9, KeyEvent.KEYCODE_9);
- sIdToKeyCodeBiMap.put(R.id.key_l, KeyEvent.KEYCODE_SPACE); // TODO find better mapping
- sIdToKeyCodeBiMap.put(R.id.key_0, KeyEvent.KEYCODE_0);
- sIdToKeyCodeBiMap.put(R.id.key_r, KeyEvent.KEYCODE_MINUS); // TODO find better mapping
- }
-
- public KeypadView(Context context) {
- this(context, null);
- }
-
- public KeypadView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.keypadViewStyle);
- }
-
- public KeypadView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public KeypadView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- inflate(getContext(), R.layout.keypad_view, this);
- }
-
- public boolean wantKeys() {
- return isShown();
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- for (int i = 0; i < getChildCount(); ++i) {
- getChildAt(i).setOnFocusChangeListener(this);
- getChildAt(i).setOnClickListener(this);
- }
- }
-
- @Override
- public void onFocusChange(View view, boolean hasFocus) {
- if (hasFocus && view.isInTouchMode()) {
- view.performClick();
- }
- }
-
- @Override
- public void onClick(View view) {
- int keyCode = getKeyCodeForView(view);
- if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
- Toast.makeText(getContext(), R.string.not_implemented_yet, Toast.LENGTH_SHORT).show();
- }
- }
-
- private int getKeyCodeForView(View view) {
- return sIdToKeyCodeBiMap.getKeyCode(view.getId());
- }
-
- private static class IdToKeyCodeBiMap {
- private Map<Integer, Integer> idToKeyCodeMap = new HashMap<>();
- private Map<Integer, Integer> keyCodeToIdMap = new HashMap<>();
-
- public void put(int id, int keyCode){
- if (id == 0 || id == View.NO_ID) {
- throw new IllegalArgumentException("Invalid id");
- }
- if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
- throw new IllegalArgumentException("Invalid key code");
- }
- if (idToKeyCodeMap.put(id, keyCode) != null) {
- throw new IllegalArgumentException("The given id is already added");
- }
- if (keyCodeToIdMap.put(keyCode, id) != null) {
- throw new IllegalArgumentException("The given key code is already added");
- }
- }
-
- public int getKeyCode(int id){
- Integer keyCode = idToKeyCodeMap.get(id);
- if (keyCode == null) {
- return View.NO_ID;
- }
- return keyCode;
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/ui/LocaleAwareTextClock.java b/src/com/android/tv/ui/LocaleAwareTextClock.java
new file mode 100644
index 00000000..d28c95a5
--- /dev/null
+++ b/src/com/android/tv/ui/LocaleAwareTextClock.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.widget.TextClock;
+
+public class LocaleAwareTextClock extends TextClock {
+ private static final String TAG = "LocaleAwareTextClock";
+
+ public LocaleAwareTextClock(Context context) {
+ this(context, null);
+ }
+
+ public LocaleAwareTextClock(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LocaleAwareTextClock(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ // Note: This assumes that locale cannot be changed while TV is showing.
+ String pattern12 = DateFormat.getBestDateTimePattern(getTextLocale(), "hm MMMd");
+ String pattern24 = DateFormat.getBestDateTimePattern(getTextLocale(), "Hm MMMd");
+ setFormat12Hour(pattern12);
+ setFormat24Hour(pattern24);
+ }
+}
diff --git a/src/com/android/tv/ui/MainMenuView.java b/src/com/android/tv/ui/MainMenuView.java
deleted file mode 100644
index 89e4346a..00000000
--- a/src/com/android/tv/ui/MainMenuView.java
+++ /dev/null
@@ -1,421 +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.ui;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.support.v17.leanback.widget.HorizontalGridView;
-import android.support.v17.leanback.widget.OnChildSelectedListener;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import com.android.internal.util.Preconditions;
-import com.android.tv.R;
-import com.android.tv.TvActivity;
-import com.android.tv.data.Channel;
-import com.android.tv.data.ChannelMap;
-import com.android.tv.recommendation.RecentChannelRecommender;
-import com.android.tv.recommendation.TvRecommendation;
-import com.android.tv.ui.sidepanel.BaseSideFragment;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/*
- * A subclass of VerticalGridView that shows tv main menu.
- */
-public class MainMenuView extends FrameLayout implements View.OnClickListener,
- OnChildSelectedListener {
- private static final int DUMMY_TYPE = 0;
- private static final int CHANNEL_LIST_TYPE = 1;
- private static final int OPTIONS_TYPE = 2;
-
- private static final int MAX_COUNT_FOR_RECOMMENDATION = 10;
-
- private final LayoutInflater mLayoutInflater;
- private VerticalGridView mMenuList;
- private final MainMenuAdapter mAdapter = new MainMenuAdapter();
- private ChannelMap mChannelMap;
- private TvActivity mTvActivity;
- private TvRecommendation mTvRecommendation;
- private ItemListView mSelectedList;
- private OptionsAdapter mOptionsAdapter;
-
- private final Handler mHandler = new Handler();
-
- private final Runnable mChannelMapUpdateApplyer = new Runnable() {
- @Override
- public void run() {
- updateAdapters(true);
- }
- };
-
- private final ArrayList<ItemListView.ItemListAdapter> mAllAdapterList =
- new ArrayList<ItemListView.ItemListAdapter>();
-
- public MainMenuView(Context context) {
- this(context, null, 0);
- }
-
- public MainMenuView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public MainMenuView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- mLayoutInflater = LayoutInflater.from(context);
- }
-
- @Override
- protected void onFinishInflate(){
- mMenuList = (VerticalGridView) findViewById(R.id.menu_list);
- mMenuList.setOnChildSelectedListener(this);
- mMenuList.setAnimateChildLayout(false);
- mMenuList.setAdapter(mAdapter);
- mMenuList.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
- mMenuList.setWindowAlignmentOffset(getContext().getResources().getDimensionPixelOffset(
- R.dimen.selected_row_alignment));
- mMenuList.setWindowAlignmentOffsetPercent(
- VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
- mMenuList.setItemAlignmentOffset(0);
- mMenuList.setItemAlignmentOffsetPercent(
- VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
- }
-
- @Override
- protected void onAttachedToWindow() {
- Context context = getContext();
-
- // List for guide + recent channels
- mTvRecommendation = new TvRecommendation(context, mHandler, true);
- mTvRecommendation.registerTvRecommender(new RecentChannelRecommender());
- mAllAdapterList.add(new RecommendationListAdapter(context, mHandler, this,
- mTvRecommendation, true, MAX_COUNT_FOR_RECOMMENDATION, R.layout.channel_tile,
- context.getString(R.string.channel_list_title),
- context.getResources().getDimensionPixelOffset(R.dimen.channel_list_view_height)));
-
- // List for options
- mOptionsAdapter = new OptionsAdapter(context, mHandler, this);
- mAllAdapterList.add(mOptionsAdapter);
-
- // Keep all items for the main menu
- mMenuList.setItemViewCacheSize(mAllAdapterList.size());
- updateAdapters(true);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- mOptionsAdapter = null;
-
- mAllAdapterList.clear();
- updateAdapters(false);
-
- mTvRecommendation.release();
- mTvRecommendation = null;
- }
-
- public void setTvActivity(TvActivity activity) {
- mTvActivity = activity;
- }
-
- public void setChannelMap(ChannelMap channelMap) {
- mChannelMap = channelMap;
- if (mHandler.getLooper() == Looper.myLooper()) {
- mChannelMapUpdateApplyer.run();
- } else {
- mHandler.removeCallbacks(mChannelMapUpdateApplyer);
- mHandler.post(mChannelMapUpdateApplyer);
- }
- }
-
- private void updateAdapters(boolean channelMapUpdateRequired) {
- Preconditions.checkState(mHandler.getLooper() == Looper.myLooper());
-
- ArrayList<ItemListView.ItemListAdapter> availableAdapterList =
- new ArrayList<ItemListView.ItemListAdapter>();
- for (ItemListView.ItemListAdapter adapter : mAllAdapterList) {
- if (channelMapUpdateRequired) {
- adapter.update(mChannelMap);
- }
- if (adapter.getItemCount() > 0) {
- availableAdapterList.add(adapter);
- }
- }
-
- mAdapter.setItemListAdapters(
- availableAdapterList.toArray(new ItemListView.ItemListAdapter[0]));
- }
-
- private void show() {
- Preconditions.checkState(mHandler.getLooper() == Looper.myLooper());
- if (mChannelMap != null) {
- boolean adapterVisibilityChanged = false;
-
- for (ItemListView.ItemListAdapter adapter : mAllAdapterList) {
- int prevCount = adapter.getItemCount();
- adapter.onBeforeShowing();
- int currCount = adapter.getItemCount();
- if ((prevCount == 0 && currCount != 0) || (prevCount != 0 && currCount == 0)) {
- adapterVisibilityChanged = true;
- }
- }
-
- if (adapterVisibilityChanged) {
- updateAdapters(false);
- }
- }
-
- requestFocus();
- bringToFront();
- }
-
- public void resetSelectedItemPosition() {
- Preconditions.checkState(mHandler.getLooper().isCurrentThread());
- resetAllSelectedItemPositions(MainMenuView.this);
- }
-
- private static void resetAllSelectedItemPositions(ViewGroup parent) {
- int count = parent.getChildCount();
- for (int i = 0; i < count; ++i) {
- View v = parent.getChildAt(i);
- if (v instanceof HorizontalGridView) {
- HorizontalGridView gridView = (HorizontalGridView) v;
- if (gridView.getAdapter() != null && gridView.getAdapter().getItemCount() > 0) {
- gridView.setSelectedPosition(0);
- }
- } else if (v instanceof VerticalGridView) {
- VerticalGridView gridView = (VerticalGridView) v;
- if (gridView.getAdapter() != null && gridView.getAdapter().getItemCount() > 0) {
- gridView.setSelectedPosition(0);
- }
- }
- if (v instanceof ViewGroup) {
- resetAllSelectedItemPositions((ViewGroup) v);
- }
- }
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- if (changedView == this && visibility == View.VISIBLE) {
- show();
- }
- }
-
- public static class MenuTag {
- static final int CHANNEL_TAG_TYPE = 0;
- static final int MENU_ACTION_TAG_TYPE = 1;
-
- private final int mType;
- private final Object mObj;
-
- private MenuTag(int type, Object obj) {
- mType = type;
- mObj = obj;
- }
-
- private MenuTag(Channel channel) {
- this(CHANNEL_TAG_TYPE, channel);
- }
-
- private MenuTag(MenuAction action) {
- this(MENU_ACTION_TAG_TYPE, action);
- }
-
- public static Object buildTag(Channel channel) {
- return new MenuTag(channel);
- }
-
- public static Object buildTag(MenuAction action) {
- return new MenuTag(action);
- }
- }
-
- @Override
- public void onClick(View v) {
- final MenuTag tag = (MenuTag) v.getTag();
- boolean hideChannelBanner = true;
- boolean withAnimation = true;
- if (tag != null) {
- switch (tag.mType) {
- case MenuTag.CHANNEL_TAG_TYPE:
- Channel channel = (Channel) tag.mObj;
- if (channel.getType() != R.integer.channel_type_guide) {
- hideChannelBanner = false;
- }
- break;
-
- case MenuTag.MENU_ACTION_TAG_TYPE:
- MenuAction action = (MenuAction) tag.mObj;
- switch (action.getType()) {
- case MenuAction.AUTO_SCAN_CHANNELS_TYPE:
- case MenuAction.INPUT_SETTING_TYPE:
- withAnimation = false;
- break;
- }
- break;
- }
-
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- switch (tag.mType) {
- case MenuTag.CHANNEL_TAG_TYPE:
- Channel channel = (Channel) tag.mObj;
- if (channel.getType() == R.integer.channel_type_guide) {
- mTvActivity.showSimpleGuide(BaseSideFragment.INITIATOR_MENU);
- } else {
- mTvActivity.moveToChannel(channel.getId());
- }
- break;
-
- case MenuTag.MENU_ACTION_TAG_TYPE:
- MenuAction action = (MenuAction) tag.mObj;
- switch (action.getType()) {
- case MenuAction.SELECT_CLOSED_CAPTION:
- mTvActivity.showClosedCaptionOption(
- BaseSideFragment.INITIATOR_MENU);
- break;
-
- case MenuAction.SELECT_DISPLAY_MODE:
- mTvActivity.showDisplayModeOption(
- BaseSideFragment.INITIATOR_MENU);
- break;
-
- case MenuAction.SELECT_TV_INPUT_TYPE:
- mTvActivity.showInputPicker(BaseSideFragment.INITIATOR_MENU);
- break;
-
- case MenuAction.TOGGLE_PIP_TYPE:
- mTvActivity.togglePipView();
- break;
-
- case MenuAction.SELECT_PIP_LOCATION:
- mTvActivity.showPipLocationOption(
- BaseSideFragment.INITIATOR_MENU);
- break;
-
- case MenuAction.EDIT_CHANNEL_LIST_TYPE:
- mTvActivity.showEditChannelsFragment(
- BaseSideFragment.INITIATOR_MENU);
- break;
-
- case MenuAction.AUTO_SCAN_CHANNELS_TYPE:
- mTvActivity.startSetupActivity();
- break;
-
- case MenuAction.INPUT_SETTING_TYPE:
- mTvActivity.startSettingsActivity();
- break;
- }
- break;
- }
- }
- });
- }
-
- mTvActivity.hideOverlays(true, hideChannelBanner, false, withAnimation);
- }
-
- @Override
- public void onChildSelected(ViewGroup parent, View child, int position, long id) {
- if (mSelectedList == child) {
- return;
- }
- for (int i = 0; i < mMenuList.getChildCount(); i++) {
- ItemListView v = (ItemListView) mMenuList.getChildAt(i);
- if (v != child) {
- v.onDeselected();
- }
- }
- mSelectedList = (ItemListView) child;
- if (mSelectedList != null) {
- mSelectedList.onSelected();
- }
- }
-
- class MainMenuAdapter extends RecyclerView.Adapter<MainMenuAdapter.MyViewHolder> {
- private ItemListView.ItemListAdapter[] mAdapters;
-
- public void setItemListAdapters(ItemListView.ItemListAdapter[] adapters) {
- if (!Arrays.equals(mAdapters, adapters)) {
- mAdapters = adapters;
- notifyDataSetChanged();
- }
- }
-
- @Override
- public int getItemViewType(int position) {
- ItemListView.ItemListAdapter[] adapters = mAdapters;
- if (adapters != null && position < adapters.length) {
- if (adapters[position] instanceof ChannelListAdapter) {
- return CHANNEL_LIST_TYPE;
- }
- if (adapters[position] instanceof OptionsAdapter) {
- return OPTIONS_TYPE;
- }
- }
-
- return DUMMY_TYPE;
- }
-
- private class MyViewHolder extends RecyclerView.ViewHolder {
- MyViewHolder(View view) {
- super(view);
- }
- }
-
- @Override
- public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = mLayoutInflater.inflate(R.layout.item_list, parent, false);
- if (viewType == OPTIONS_TYPE) {
- int interCardSpacing = getContext().getResources().getDimensionPixelSize(
- R.dimen.action_tile_inter_card_spacing);
- ((HorizontalGridView) view.findViewById(R.id.list_view)).setHorizontalMargin(
- interCardSpacing);
- }
- ((ItemListView) view).loadViews();
-
- return new MyViewHolder(view);
- }
-
- @Override
- public void onBindViewHolder(MyViewHolder baseHolder, int position) {
- ItemListView.ItemListAdapter[] adapters = mAdapters;
- if (adapters != null && position < adapters.length) {
- ItemListView listView = (ItemListView) baseHolder.itemView;
- ItemListView.ItemListAdapter adapter = mAdapters[position];
- listView.populateViews(adapter.getTitle(), adapter);
- adapter.update(mChannelMap, listView);
- }
- }
-
- @Override
- public int getItemCount() {
- ItemListView.ItemListAdapter[] adapters = mAdapters;
- return adapters == null ? 0 : adapters.length;
- }
- }
-}
diff --git a/src/com/android/tv/ui/MenuAction.java b/src/com/android/tv/ui/MenuAction.java
deleted file mode 100644
index 5a3895e6..00000000
--- a/src/com/android/tv/ui/MenuAction.java
+++ /dev/null
@@ -1,82 +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.ui;
-
-import android.content.Context;
-
-import com.android.tv.R;
-
-/**
- * A class to define possible actions from main menu.
- */
-public class MenuAction {
- public static final int SELECT_CLOSED_CAPTION = 0;
- public static final int SELECT_DISPLAY_MODE = 1;
- public static final int SELECT_TV_INPUT_TYPE = 2;
- public static final int TOGGLE_PIP_TYPE = 3;
- public static final int SELECT_PIP_LOCATION = 4;
- public static final int EDIT_CHANNEL_LIST_TYPE = 5;
- public static final int AUTO_SCAN_CHANNELS_TYPE = 6;
- public static final int INPUT_SETTING_TYPE = 7;
-
- public static final MenuAction SELECT_CLOSED_CAPTION_ACTION =
- new MenuAction(R.string.menu_closed_caption, SELECT_CLOSED_CAPTION,
- R.drawable.ic_tvoption_cc);
- public static final MenuAction SELECT_DISPLAY_MODE_ACTION =
- new MenuAction(R.string.menu_display_mode, SELECT_DISPLAY_MODE,
- R.drawable.ic_tvoption_display_mode);
- public static final MenuAction SELECT_TV_INPUT_ACTION =
- new MenuAction(R.string.menu_select_input, SELECT_TV_INPUT_TYPE,
- R.drawable.ic_tvoptions_input);
- public static final MenuAction TOGGLE_PIP_ACTION =
- new MenuAction(R.string.menu_toggle_pip, TOGGLE_PIP_TYPE,
- R.drawable.ic_tvoption_pip);
- public static final MenuAction SELECT_PIP_LOCATION_ACTION =
- new MenuAction(R.string.menu_select_pip_location, SELECT_PIP_LOCATION,
- R.drawable.ic_tvoption_locate_pip);
- public static final MenuAction EDIT_CHANNEL_LIST_ACTION =
- new MenuAction(R.string.menu_edit_channels, EDIT_CHANNEL_LIST_TYPE,
- R.drawable.ic_tvoptions_edit_channels);
- public static final MenuAction AUTO_SCAN_CHANNELS_ACTION =
- new MenuAction(R.string.menu_auto_scan, AUTO_SCAN_CHANNELS_TYPE,
- R.drawable.ic_tvoptions_scan);
- public static final MenuAction INPUT_SETTING_ACTION =
- new MenuAction(R.string.menu_input_setting, INPUT_SETTING_TYPE,
- R.drawable.ic_tvoptions_input_settings);
-
- private final int mActionNameResId;
- private final int mType;
- private final int mDrawableResId;
-
- public MenuAction(int actionNameResId, int type, int drawableResId) {
- mActionNameResId = actionNameResId;
- mType = type;
- mDrawableResId = drawableResId;
- }
-
- public String getActionName(Context context) {
- return context.getString(mActionNameResId);
- }
-
- public int getType() {
- return mType;
- }
-
- public int getDrawableResId() {
- return mDrawableResId;
- }
-}
diff --git a/src/com/android/tv/ui/OptionsAdapter.java b/src/com/android/tv/ui/OptionsAdapter.java
deleted file mode 100644
index 93a4b8a4..00000000
--- a/src/com/android/tv/ui/OptionsAdapter.java
+++ /dev/null
@@ -1,83 +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.ui;
-
-import android.content.Context;
-import android.os.Handler;
-import android.view.View;
-
-import com.android.tv.R;
-import com.android.tv.data.ChannelMap;
-import com.android.tv.input.TvInput;
-
-import java.util.ArrayList;
-
-/*
- * An adapter of options.
- */
-public class OptionsAdapter extends ItemListView.ItemListAdapter {
- private final String mTitle;
- private final int mTileHeight;
- private ChannelMap mChannelMap;
-
- public OptionsAdapter(Context context, Handler handler, View.OnClickListener onClickListener) {
- super(context, handler, R.layout.action_tile, onClickListener);
-
- mTitle = context.getString(R.string.menu_title);
- mTileHeight = context.getResources().getDimensionPixelOffset(
- R.dimen.action_list_view_height);
- }
-
- @Override
- public int getTileHeight() {
- return mTileHeight;
- }
-
- @Override
- public String getTitle() {
- return mTitle;
- }
-
- @Override
- public void update(ChannelMap channelMap) {
- mChannelMap = channelMap;
- TvInput tvInput = channelMap == null ? null : channelMap.getTvInput();
-
- ArrayList<MenuAction> actionList = new ArrayList<MenuAction>();
- actionList.add(MenuAction.SELECT_CLOSED_CAPTION_ACTION);
- actionList.add(MenuAction.SELECT_DISPLAY_MODE_ACTION);
- actionList.add(MenuAction.SELECT_TV_INPUT_ACTION);
- actionList.add(MenuAction.TOGGLE_PIP_ACTION);
- actionList.add(MenuAction.SELECT_PIP_LOCATION_ACTION);
- if (channelMap != null && channelMap.size() != 0) {
- actionList.add(MenuAction.EDIT_CHANNEL_LIST_ACTION);
- }
- if (channelMap != null && tvInput.getIntentForSetupActivity() != null) {
- actionList.add(MenuAction.AUTO_SCAN_CHANNELS_ACTION);
- }
- if (channelMap != null && tvInput.getIntentForSettingsActivity() != null) {
- actionList.add(MenuAction.INPUT_SETTING_ACTION);
- }
-
- setItemList(actionList.toArray(new MenuAction[0]));
- }
-
- @Override
- public void update(ChannelMap channelMap, ItemListView list) {
- update(channelMap);
- }
-}
diff --git a/src/com/android/tv/ui/OverlayRootView.java b/src/com/android/tv/ui/OverlayRootView.java
new file mode 100644
index 00000000..f6dc2537
--- /dev/null
+++ b/src/com/android/tv/ui/OverlayRootView.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.FrameLayout;
+
+import com.android.tv.MainActivity;
+
+public class OverlayRootView extends FrameLayout {
+
+ private final MainActivity mMainActivity;
+
+ public OverlayRootView(Context context) {
+ this(context, null, 0, 0);
+ }
+
+ public OverlayRootView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0, 0);
+ }
+
+ public OverlayRootView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public OverlayRootView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mMainActivity = (MainActivity) context;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return mMainActivity.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
+ }
+}
diff --git a/src/com/android/tv/ui/RecommendationListAdapter.java b/src/com/android/tv/ui/RecommendationListAdapter.java
deleted file mode 100644
index 93647a2c..00000000
--- a/src/com/android/tv/ui/RecommendationListAdapter.java
+++ /dev/null
@@ -1,147 +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.ui;
-
-import android.content.Context;
-import android.os.Handler;
-import android.view.View;
-
-import com.android.tv.R;
-import com.android.tv.data.Channel;
-import com.android.tv.data.ChannelMap;
-import com.android.tv.recommendation.TvRecommendation;
-
-import java.util.ArrayList;
-
-/*
- * An adapter of recommended channel list.
- */
-public class RecommendationListAdapter extends ItemListView.ItemListAdapter {
- private ChannelMap mChannelMap;
- private Channel[] mChannelList;
- private ItemListView mListView;
- private final String mTitle;
- private final int mTileHeight;
- private final int mMaxCount;
- private final TvRecommendation mRecommendationEngine;
- private final boolean mGuideIncluded;
-
- public RecommendationListAdapter(Context context, Handler handler,
- View.OnClickListener onClickListener, TvRecommendation recommendationEngine,
- boolean guideIncluded, int maxCount, int tileResId, String title, int tileHeight) {
- super(context, handler, tileResId, onClickListener);
- mRecommendationEngine = recommendationEngine;
- mGuideIncluded = guideIncluded;
- mMaxCount = maxCount;
- mTitle = title;
- mTileHeight = tileHeight;
- }
-
- @Override
- public int getTileHeight() {
- return mTileHeight;
- }
-
- @Override
- public String getTitle() {
- return mTitle;
- }
-
- @Override
- public void update(ChannelMap channelMap) {
- update(channelMap, mListView);
- }
-
- @Override
- public void update(ChannelMap channelMap, ItemListView listView) {
- mChannelMap = channelMap;
- mListView = listView;
-
- updateChannelList();
- selectGuideChannel();
- }
-
- @Override
- public void onBeforeShowing() {
- updateChannelList();
- selectGuideChannel();
-
- if (mListView != null && mChannelMap != null) {
- for (int i = 0; i < mListView.getChildCount(); i++) {
- View v = mListView.getChildAt(i);
- if (v instanceof ChannelTileView) {
- ((ChannelTileView) v).updateProgramInformation();
- }
- }
- }
- }
-
- private void updateChannelList() {
- TvRecommendation.ChannelRecord[] records =
- mRecommendationEngine.getRecommendedChannelList(mMaxCount);
- mChannelList = null;
- if (mChannelMap != null && mChannelMap.size() > 0 && records != null
- && records.length > 0) {
- ArrayList<Channel> results = new ArrayList<Channel>();
- for (int i = 0; i < records.length; i++) {
- Channel channel = records[i].getChannel();
- if (mChannelMap.containsAndIsBrowsable(channel)) {
- results.add(channel);
- }
- }
- mChannelList = results.toArray(new Channel[0]);
- }
-
- if (mGuideIncluded) {
- Channel[] channels = mChannelList == null ? new Channel[0] : mChannelList;
- Channel guideChannel = new Channel.Builder()
- .setType(R.integer.channel_type_guide)
- .build();
- mChannelList = new Channel[channels.length + 1];
- mChannelList[0] = guideChannel;
- for (int i = 0; i < channels.length; ++i) {
- mChannelList[i + 1] = channels[i];
- }
- }
-
- setItemList(mChannelList);
- }
-
- private void selectGuideChannel() {
- if (mListView == null) {
- return;
- }
- if (mGuideIncluded) {
- mListView.setSelectedPosition(0);
- } else {
- selectCurrentChannel();
- }
- }
-
- private void selectCurrentChannel() {
- long id = mChannelMap == null ? Channel.INVALID_ID : mChannelMap.getCurrentChannelId();
- if (mListView == null || mChannelList == null || id == Channel.INVALID_ID) {
- return;
- }
- for (int i = 0; i < mChannelList.length; i++) {
- if (id == mChannelList[i].getId()) {
- mListView.setSelectedPosition(i);
- break;
- }
- }
- }
-}
diff --git a/src/com/android/tv/ui/SelectInputView.java b/src/com/android/tv/ui/SelectInputView.java
new file mode 100644
index 00000000..54889d8d
--- /dev/null
+++ b/src/com/android/tv/ui/SelectInputView.java
@@ -0,0 +1,437 @@
+/*
+ * 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;
+
+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;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+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.TvApplication;
+import com.android.tv.analytics.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;
+
+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";
+
+ private final MainActivity mMainActivity;
+ private final TvInputManagerHelper mTvInputManagerHelper;
+ private final List<TvInputInfo> mInputList = new ArrayList<>();
+ private final InputsComparator mComparator = new InputsComparator();
+ private final Tracker mTracker;
+ private final DurationTimer mViewDurationTimer = new DurationTimer();
+
+ private final Runnable mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // Just dismiss the view when no action is required.
+ if (mSelectedInput == null
+ || TextUtils.equals(mSelectedInput.getId(), mCurrentInputId)
+ || (!mSelectedInput.isPassthroughInput() && mCurrentInputId == null)) {
+ mMainActivity.goToEmptyScene(true);
+ return;
+ }
+ // TODO: pass english label to tracker http://b/22355024
+ final String label = mSelectedInput.loadLabel(getContext()).toString();
+ mTracker.sendInputSelected(label);
+ if (mSelectedInput.isPassthroughInput()) {
+ mMainActivity.tuneToChannel(
+ Channel.createPassthroughChannel(mSelectedInput.getId()));
+ } else {
+ mMainActivity.tuneToLastWatchedChannelForTunerInput();
+ }
+ }
+ };
+
+ private final int mInputItemHeight;
+ private final long mShowDurationMillis;
+ private final long mRippleAnimDurationMillis;
+ private final int mTextColorPrimary;
+ private final int mTextColorSecondary;
+ private final int mTextColorDisabled;
+ private final View mItemViewForMeasure;
+
+ private boolean mResetTransitionAlpha;
+ private TvInputInfo mSelectedInput;
+ // The ID of the currently selected pass-through input. The null value means that the currently
+ // selected input is a tuner.
+ private String mCurrentInputId;
+ private int mMaxItemWidth;
+
+ public SelectInputView(Context context) {
+ this(context, null, 0);
+ }
+
+ public SelectInputView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SelectInputView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setAdapter(new InputListAdapter());
+
+ mMainActivity = (MainActivity) context;
+ mTracker = ((TvApplication) context.getApplicationContext()).getTracker();
+ mTvInputManagerHelper = mMainActivity.getTvInputManagerHelper();
+ mTvInputManagerHelper.addCallback(new TvInputCallback() {
+ @Override
+ public void onInputAdded(String inputId) {
+ buildInputListAndNotify();
+ updateSelectedPositionIfNeeded();
+ }
+
+ @Override
+ public void onInputRemoved(String inputId) {
+ buildInputListAndNotify();
+ updateSelectedPositionIfNeeded();
+ }
+
+ @Override
+ public void onInputUpdated(String inputId) {
+ buildInputListAndNotify();
+ updateSelectedPositionIfNeeded();
+ }
+
+ @Override
+ public void onInputStateChanged(String inputId, int state) {
+ buildInputListAndNotify();
+ updateSelectedPositionIfNeeded();
+ }
+
+ private void updateSelectedPositionIfNeeded() {
+ if (!isFocusable() || mSelectedInput == null) {
+ return;
+ }
+ if (!isInputEnabled(mSelectedInput)) {
+ setSelectedPosition(0);
+ return;
+ }
+ if (getInputPosition(mSelectedInput.getId()) != getSelectedPosition()) {
+ setSelectedPosition(getInputPosition(mSelectedInput.getId()));
+ }
+ }
+ });
+
+ 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);
+ mTextColorPrimary = resources.getColor(R.color.select_input_text_color_primary);
+ mTextColorSecondary = resources.getColor(R.color.select_input_text_color_secondary);
+ mTextColorDisabled = resources.getColor(R.color.select_input_text_color_disabled);
+
+ mItemViewForMeasure = LayoutInflater.from(context).inflate(
+ R.layout.select_input_item, null, false);
+ buildInputListAndNotify();
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (DEBUG) Log.d(TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
+ scheduleHide();
+
+ if (keyCode == KeyEvent.KEYCODE_TV_INPUT) {
+ // Go down to the next available input.
+ int currentPosition = mInputList.indexOf(mSelectedInput);
+ int nextPosition = currentPosition;
+ while (true) {
+ nextPosition = (nextPosition + 1) % mInputList.size();
+ if (isInputEnabled(mInputList.get(nextPosition))) {
+ break;
+ }
+ if (nextPosition == currentPosition) {
+ nextPosition = 0;
+ break;
+ }
+ }
+ setSelectedPosition(nextPosition);
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public void onEnterAction(boolean fromEmptyScene) {
+ mTracker.sendShowInputSelection();
+ mTracker.sendScreenView(SCREEN_NAME);
+ mViewDurationTimer.start();
+ scheduleHide();
+
+ mResetTransitionAlpha = fromEmptyScene;
+ buildInputListAndNotify();
+ Channel channel = mMainActivity.getCurrentChannel();
+ mCurrentInputId = channel != null && channel.isPassthrough() ? channel.getInputId() : null;
+ if (mCurrentInputId != null
+ && !isInputEnabled(mTvInputManagerHelper.getTvInputInfo(mCurrentInputId))) {
+ // If current input is disabled, the first item will be focused. The tuner input
+ // is usually the first item.
+ setSelectedPosition(0);
+ } else {
+ setSelectedPosition(getInputPosition(mCurrentInputId));
+ }
+ setFocusable(true);
+ requestFocus();
+ }
+
+ private int getInputPosition(String inputId) {
+ if (inputId != null) {
+ for (int i = 0; i < mInputList.size(); ++i) {
+ if (TextUtils.equals(mInputList.get(i).getId(), inputId)) {
+ return i;
+ }
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public void onExitAction() {
+ mTracker.sendHideInputSelection(mViewDurationTimer.reset());
+ removeCallbacks(mHideRunnable);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int height = mInputItemHeight * mInputList.size();
+ super.onMeasure(MeasureSpec.makeMeasureSpec(mMaxItemWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ }
+
+ private void scheduleHide() {
+ removeCallbacks(mHideRunnable);
+ postDelayed(mHideRunnable, mShowDurationMillis);
+ }
+
+ private void buildInputListAndNotify() {
+ mInputList.clear();
+ Map<String, TvInputInfo> inputMap = new HashMap<>();
+ boolean foundTuner = false;
+ for (TvInputInfo input : mTvInputManagerHelper.getTvInputInfos(false, false)) {
+ if (input.isPassthroughInput()) {
+ mInputList.add(input);
+ inputMap.put(input.getId(), input);
+ } else if (!foundTuner) {
+ foundTuner = true;
+ mInputList.add(input);
+ }
+ }
+ // Do not show an AVR if an HDMI device is connected to it.
+ for (TvInputInfo input : inputMap.values()) {
+ if (input.getParentId() != null && !input.isConnectedToHdmiSwitch()) {
+ mInputList.remove(inputMap.get(input.getParentId()));
+ }
+ }
+ Collections.sort(mInputList, mComparator);
+
+ // Update the max item width.
+ mMaxItemWidth = 0;
+ for (TvInputInfo input : mInputList) {
+ setItemViewText(mItemViewForMeasure, input);
+ mItemViewForMeasure.measure(0, 0);
+ int width = mItemViewForMeasure.getMeasuredWidth();
+ if (width > mMaxItemWidth) {
+ mMaxItemWidth = width;
+ }
+ }
+
+ getAdapter().notifyDataSetChanged();
+ }
+
+ private void setItemViewText(View v, TvInputInfo input) {
+ TextView inputLabelView = (TextView) v.findViewById(R.id.input_label);
+ TextView secondaryInputLabelView = (TextView) v.findViewById(R.id.secondary_input_label);
+ CharSequence customLabel = input.loadCustomLabel(getContext());
+ CharSequence label = input.loadLabel(getContext());
+ if (TextUtils.isEmpty(customLabel) || customLabel.equals(label)) {
+ inputLabelView.setText(label);
+ secondaryInputLabelView.setVisibility(View.GONE);
+ } else {
+ inputLabelView.setText(customLabel);
+ secondaryInputLabelView.setText(label);
+ secondaryInputLabelView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private boolean isInputEnabled(TvInputInfo input) {
+ return mTvInputManagerHelper.getInputState(input)
+ != TvInputManager.INPUT_STATE_DISCONNECTED;
+ }
+
+ class InputListAdapter extends RecyclerView.Adapter<InputListAdapter.ViewHolder> {
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext()).inflate(
+ R.layout.select_input_item, parent, false);
+ return new ViewHolder(v);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, final int position) {
+ TvInputInfo input = mInputList.get(position);
+ if (input.isPassthroughInput()) {
+ if (isInputEnabled(input)) {
+ holder.itemView.setFocusable(true);
+ holder.inputLabelView.setTextColor(mTextColorPrimary);
+ holder.secondaryInputLabelView.setTextColor(mTextColorSecondary);
+ } else {
+ holder.itemView.setFocusable(false);
+ holder.inputLabelView.setTextColor(mTextColorDisabled);
+ holder.secondaryInputLabelView.setTextColor(mTextColorDisabled);
+ }
+ setItemViewText(holder.itemView, input);
+ } else {
+ holder.itemView.setFocusable(true);
+ holder.inputLabelView.setTextColor(mTextColorPrimary);
+ holder.inputLabelView.setText(R.string.input_long_label_for_tuner);
+ 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);
+ }
+ }
+ });
+
+ if (mResetTransitionAlpha) {
+ ViewUtils.setTransitionAlpha(holder.itemView, 1f);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mInputList.size();
+ }
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+ final TextView inputLabelView;
+ final TextView secondaryInputLabelView;
+
+ ViewHolder(View v) {
+ super(v);
+ inputLabelView = (TextView) v.findViewById(R.id.input_label);
+ secondaryInputLabelView = (TextView) v.findViewById(R.id.secondary_input_label);
+ }
+ }
+ }
+
+ private class InputsComparator implements Comparator<TvInputInfo> {
+ @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;
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/SetupView.java b/src/com/android/tv/ui/SetupView.java
new file mode 100644
index 00000000..ba90dcfe
--- /dev/null
+++ b/src/com/android/tv/ui/SetupView.java
@@ -0,0 +1,307 @@
+/*
+ * 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;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.media.tv.TvInputInfo;
+import android.support.annotation.VisibleForTesting;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+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.data.ChannelDataManager;
+import com.android.tv.util.SetupUtils;
+import com.android.tv.util.TvInputManagerHelper;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class SetupView extends FullscreenDialogView {
+ private static final int FINISH_ACTIVITY_DELAY_MS = 200;
+ private static final int REFRESH_DELAY_MS_AFTER_WINDOW_FOCUS_GAINED = 200;
+
+ private VerticalGridView mInputView;
+ private ChannelDataManager mChannelDataManager;
+ private List<TvInputInfo> mInputList;
+ // mInputList[0:mKnownInputStartIndex - 1] are new inputs.
+ // And mInputList[mKnownInputStartIndex:end] are inputs which have been shown in SetupView.
+ private int mKnownInputStartIndex;
+ private boolean mShowDivider;
+ private SetupAdapter mAdapter;
+ private boolean mClosing;
+ private boolean mInitialized;
+ private SetupUtils mSetupUtils;
+ private boolean mNeedIntroDialog;
+
+ private final ChannelDataManager.Listener mChannelDataListener = new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() { }
+
+ @Override
+ public void onChannelListUpdated() {
+ if (mAdapter != null) {
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void onChannelBrowsableChanged() {
+ if (mAdapter != null) {
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+ };
+
+ public SetupView(Context context) {
+ this(context, null, 0);
+ }
+
+ public SetupView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SetupView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setTransitionAnimationEnabled(true);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ TextView titleView = (TextView) findViewById(R.id.setup_title);
+ titleView.setText(R.string.setup_title);
+ TextView descriptionView = (TextView) findViewById(R.id.setup_description);
+ descriptionView.setText(R.string.setup_description);
+ mInputView = (VerticalGridView) findViewById(R.id.input_list);
+ TypedValue outValue = new TypedValue();
+ getResources().getValue(R.dimen.setup_item_window_alignment_offset_percent, outValue, true);
+ mInputView.setWindowAlignmentOffsetPercent(outValue.getFloat());
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return mClosing || super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ if (hasWindowFocus && mAdapter != null) {
+ // Without the following delay, the channel count description is sometimes
+ // changed twice by this method and mChannelDataListener.
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ // When channel count is still 0 after setup, the description should be changed
+ // from "Not set up" to "No channels".
+ if (mAdapter.getItemCount() != 0) {
+ mAdapter.notifyItemRangeChanged(0, mAdapter.getItemCount());
+ }
+ }
+ }, REFRESH_DELAY_MS_AFTER_WINDOW_FOCUS_GAINED);
+ }
+ }
+
+ /**
+ * Initializes SetupView.
+ */
+ @Override
+ public void initialize(MainActivity activity, Dialog dialog) {
+ super.initialize(activity, dialog);
+ if (mInitialized) {
+ throw new IllegalStateException("initialize() is called more than once");
+ }
+ mInitialized = true;
+ final TvInputManagerHelper inputManager = getActivity().getTvInputManagerHelper();
+ mChannelDataManager = getActivity().getChannelDataManager();
+ mSetupUtils = SetupUtils.getInstance(activity);
+ mKnownInputStartIndex = 0;
+ mInputList = inputManager.getTvInputInfos(true, true);
+ Collections.sort(mInputList, new TvInputInfoComparator(mSetupUtils, inputManager));
+ for (TvInputInfo input : mInputList) {
+ if (mSetupUtils.isNewInput(input.getId())) {
+ mSetupUtils.markAsKnownInput(input.getId());
+ ++mKnownInputStartIndex;
+ }
+ }
+ mShowDivider = mKnownInputStartIndex != 0 && mKnownInputStartIndex != mInputList.size();
+ mAdapter = new SetupAdapter();
+ mInputView.setAdapter(mAdapter);
+ mChannelDataManager.addListener(mChannelDataListener);
+ mNeedIntroDialog = mSetupUtils.isFirstTune();
+ }
+
+ /**
+ * Called when the DialogFragment including this view is destroyed.
+ */
+ @Override
+ public void onDestroy() {
+ mChannelDataManager.removeListener(mChannelDataListener);
+ }
+
+ @Override
+ protected void dismiss() {
+ mClosing = true;
+ if (mNeedIntroDialog) {
+ LayoutInflater inflater = LayoutInflater.from(getActivity());
+ IntroView v = (IntroView) inflater.inflate(R.layout.intro_dialog, null);
+ transitionTo(v);
+ } else {
+ super.dismiss();
+ }
+ }
+ /**
+ * Called when the back key is pressed.
+ */
+ @Override
+ public void onBackPressed() {
+ if (mChannelDataManager.getChannelCount() == 0) {
+ // If there is no channel, we finish the activity rather than closing just the view.
+ getActivity().finish();
+ }
+ dismiss();
+ }
+
+ private class SetupAdapter extends RecyclerView.Adapter<MyViewHolder> {
+ @Override
+ public int getItemViewType(int position) {
+ if (mShowDivider && position == mKnownInputStartIndex) {
+ return R.layout.setup_item_divider;
+ } else if (position == getItemCount() - 1) {
+ return R.layout.setup_item_action;
+ } else {
+ return R.layout.setup_item_input;
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ if (mInputList == null) {
+ return 1;
+ }
+ return mInputList.size() + 1 + (mShowDivider ? 1 : 0);
+ }
+
+ @Override
+ public void onBindViewHolder(final MyViewHolder viewHolder, int position) {
+ if (position == getItemCount() - 1) {
+ final boolean closeActivity = mChannelDataManager.getChannelCount() == 0;
+ viewHolder.mTitle.setText(R.string.setup_done_button_label);
+ viewHolder.itemView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mClosing = true;
+ if (closeActivity) {
+ // To wait completing ripple animation, finish() is called
+ // FINISH_ACTIVITY_DELAY_MS later.
+ mNeedIntroDialog = false;
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().finish();
+ }
+ }, FINISH_ACTIVITY_DELAY_MS);
+ } else {
+ dismiss();
+ }
+ }
+ });
+ } else {
+ if (mShowDivider) {
+ if (position == mKnownInputStartIndex) {
+ // This view is a divider.
+ return;
+ } else if (position > mKnownInputStartIndex) {
+ --position;
+ }
+ }
+ final TvInputInfo input = mInputList.get(position);
+ viewHolder.mTitle.setText(input.loadLabel(getContext()));
+ int channelCount = mChannelDataManager.getChannelCountForInput(input.getId());
+ if (mSetupUtils.hasSetupLaunched(input.getId())) {
+ if (channelCount == 0) {
+ viewHolder.mDescription.setText(R.string.setup_input_no_channels);
+ } else {
+ viewHolder.mDescription.setText(getResources().getQuantityString(
+ R.plurals.setup_input_channels, channelCount, channelCount));
+ }
+ } else if (position >= mKnownInputStartIndex) {
+ viewHolder.mDescription.setText(R.string.channel_description_setup_now);
+ } else {
+ viewHolder.mDescription.setText(R.string.setup_input_new);
+ }
+ viewHolder.itemView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ getActivity().startSetupActivity(input, true);
+ }
+ });
+ }
+ }
+
+ @Override
+ public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View itemView = LayoutInflater.from(parent.getContext()).inflate(viewType, parent,
+ false);
+ return new MyViewHolder(itemView);
+ }
+ }
+
+ private static class MyViewHolder extends RecyclerView.ViewHolder {
+ final TextView mTitle;
+ final TextView mDescription;
+
+ public MyViewHolder(View itemView) {
+ super(itemView);
+ mTitle = (TextView) itemView.findViewById(R.id.title);
+ mDescription = (TextView) itemView.findViewById(R.id.description);
+ }
+ }
+
+ @VisibleForTesting
+ static class TvInputInfoComparator implements Comparator<TvInputInfo> {
+ private SetupUtils mSetupUtils;
+ private TvInputManagerHelper mInputManager;
+
+ public TvInputInfoComparator(SetupUtils setupUtils, TvInputManagerHelper inputManager) {
+ mSetupUtils = setupUtils;
+ mInputManager = inputManager;
+ }
+
+ @Override
+ public int compare(TvInputInfo lhs, TvInputInfo rhs) {
+ boolean lhsIsNewInput = mSetupUtils.isNewInput(lhs.getId());
+ boolean rhsIsNewInput = mSetupUtils.isNewInput(rhs.getId());
+ if (lhsIsNewInput != rhsIsNewInput) {
+ return lhsIsNewInput ? -1 : 1;
+ }
+ return mInputManager.getDefaultTvInputInfoComparator().compare(lhs, rhs);
+ }
+ };
+}
diff --git a/src/com/android/tv/ui/ShadowContainer.java b/src/com/android/tv/ui/ShadowContainer.java
deleted file mode 100644
index 27fb7905..00000000
--- a/src/com/android/tv/ui/ShadowContainer.java
+++ /dev/null
@@ -1,122 +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.ui;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import com.android.tv.R;
-
-public class ShadowContainer extends FrameLayout {
-
- private static final float UNSELECTED_SCALE = 1.0f;
- private static final int SCALE_ANIM_DURATION = 100;
-
- private float mSelectedScale;
-
- private final View mShadowViewNormal;
- private final View mShadowViewFocused;
-
- public ShadowContainer(Context context) {
- this(context, null);
- }
-
- public ShadowContainer(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ShadowContainer(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- LayoutInflater inflater = (LayoutInflater) context.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- mShadowViewNormal = inflater.inflate(R.layout.shadow_normal, this, false);
- mShadowViewFocused = inflater.inflate(R.layout.shadow_focused, this, false);
- addView(mShadowViewNormal, 0);
- addView(mShadowViewFocused, 1);
- }
-
- @Override
- public void onFinishInflate() {
- final Context ctx = getContext();
- final Resources res = ctx.getResources();
-
- ViewGroup.LayoutParams lp = getLayoutParams();
- int width = lp.width;
- int height = lp.height;
- if (width < 0 && height < 0) {
- measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- height = getMeasuredHeight();
- }
- mSelectedScale = UNSELECTED_SCALE * getScalingFactor(res, width, height);
- }
-
- @Override
- protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-
- if (isAttachedToWindow() && getVisibility() == View.VISIBLE) {
- if (gainFocus) {
- mShadowViewNormal.animate().alpha(0f).setDuration(SCALE_ANIM_DURATION);
- mShadowViewFocused.animate().alpha(1f).setDuration(SCALE_ANIM_DURATION);
-
- animate().scaleX(mSelectedScale)
- .scaleY(mSelectedScale)
- .setDuration(SCALE_ANIM_DURATION);
- } else {
- setScaleX(mSelectedScale);
- setScaleY(mSelectedScale);
- mShadowViewNormal.animate().alpha(1f).setDuration(SCALE_ANIM_DURATION);
- mShadowViewFocused.animate().alpha(0f).setDuration(SCALE_ANIM_DURATION);
-
- animate().scaleX(UNSELECTED_SCALE)
- .scaleY(UNSELECTED_SCALE)
- .setDuration(SCALE_ANIM_DURATION);
- }
- } else {
- clearAnimation();
- if (gainFocus) {
- setScaleX(mSelectedScale);
- setScaleY(mSelectedScale);
- } else {
- setScaleX(UNSELECTED_SCALE);
- setScaleY(UNSELECTED_SCALE);
- }
- }
- }
-
- private static float getScalingFactor(Resources res, float width, float height) {
- // for now, just return the appropriate zoom level based on the item size.
- // Ideally, we'll eventually just move to using the leanback focus selector
- // which will take care of this.
- float sizeMedium = res.getDimension(R.dimen.item_size_medium_threshold);
- float sizeLarge = res.getDimension(R.dimen.item_size_large_threshold);
-
- if (height < sizeMedium) {
- return res.getFraction(R.fraction.lb_focus_zoom_factor_large, 1, 1);
- } else if (height < sizeLarge) {
- return res.getFraction(R.fraction.lb_focus_zoom_factor_medium, 1, 1);
- } else {
- return res.getFraction(R.fraction.lb_focus_zoom_factor_small, 1, 1);
- }
- }
-}
diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java
index 3f5978c1..cbf61304 100644
--- a/src/com/android/tv/ui/TunableTvView.java
+++ b/src/com/android/tv/ui/TunableTvView.java
@@ -1,82 +1,194 @@
+/*
+ * 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;
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.media.PlaybackParams;
+import android.media.tv.TvContentRating;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
import android.media.tv.TvView.OnUnhandledInputEventListener;
-import android.media.tv.TvView.TvInputListener;
+import android.media.tv.TvView.TvInputCallback;
import android.net.Uri;
import android.os.Bundle;
+import android.support.annotation.IntDef;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
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.TextView;
import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.analytics.DurationTimer;
+import com.android.tv.analytics.Tracker;
+import com.android.tv.common.TvCommonConstants;
import com.android.tv.data.Channel;
import com.android.tv.data.StreamInfo;
+import com.android.tv.parental.ContentRatingsManager;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
-import java.util.HashMap;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
-import java.util.Map;
public class TunableTvView extends FrameLayout implements StreamInfo {
- // STOPSHIP: Turn debugging off
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static final String TAG = "TunableTvView";
public static final String PERMISSION_RECEIVE_INPUT_EVENT =
- "android.permission.RECEIVE_INPUT_EVENT";
+ "com.android.tv.permission.RECEIVE_INPUT_EVENT";
+
+ public static final int VIDEO_UNAVAILABLE_REASON_NOT_TUNED = -1;
+
+ 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;
+
+ @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 })
+ public @interface TimeShiftState {}
+ public static final int TIME_SHIFT_STATE_NONE = 0;
+ public static final int TIME_SHIFT_STATE_PLAY = 1;
+ public static final int TIME_SHIFT_STATE_PAUSE = 2;
+ public static final int TIME_SHIFT_STATE_REWIND = 3;
+ public static final int TIME_SHIFT_STATE_FAST_FORWARD = 4;
+
+ private static final int FADED_IN = 0;
+ private static final int FADED_OUT = 1;
+ private static final int FADING_IN = 2;
+ private static final int FADING_OUT = 3;
+
+ private static final long INVALID_TIME = -1;
+
+ // 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 final TvView mTvView;
- private long mChannelId = Channel.INVALID_ID;
+ private AppLayerTvView mTvView;
+ private Channel mCurrentChannel;
private TvInputManagerHelper mInputManagerHelper;
+ private ContentRatingsManager mContentRatingsManager;
private boolean mStarted;
private TvInputInfo mInputInfo;
private OnTuneListener mOnTuneListener;
private int mVideoWidth;
private int mVideoHeight;
private int mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
+ private float mVideoFrameRate;
private int mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
private boolean mHasClosedCaption = false;
- private boolean mIsVideoAvailable;
- private int mVideoUnavailableReason;
+ private boolean mVideoAvailable;
+ private boolean mScreenBlocked;
+ private OnScreenBlockingChangedListener mOnScreenBlockedListener;
+ private TvContentRating mBlockedContentRating;
+ private int mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NOT_TUNED;
private boolean mCanReceiveInputEvent;
private boolean mIsMuted;
private float mVolume;
- private final Map<Long, String> mChannelToInputMap = new HashMap<Long, String>();
+ private boolean mParentControlEnabled;
+ private int mFixedSurfaceWidth;
+ private int mFixedSurfaceHeight;
+ private boolean mIsPip;
+ private int mScreenHeight;
+ private int mShrunkenTvViewHeight;
+
+ @TimeShiftState private int mTimeShiftState = TIME_SHIFT_STATE_NONE;
+ private TimeShiftListener mTimeShiftListener;
+ private boolean mTimeShiftAvailable;
+ private long mTimeShiftCurrentPositionMs = INVALID_TIME;
+
+ private final Tracker mTracker;
+ private final DurationTimer mChannelViewTimer = new DurationTimer();
+
+ // A block screen view which has lock icon with black background.
+ // This indicates that user's action is needed to play video.
+ private final View mBlockScreenView;
+
+ private final View mBlockScreenDescriptionView;
+ private final View mBlockScreenIconView;
+ private final View mBlockScreenShrunkenIconView;
+ private final TextView mBlockScreenTextView;
+
+ // Animators used for fade in/out of block screen icon.
+ private final Animator mBlockScreenDescriptionFadeIn;
+ private final Animator mBlockScreenDescriptionFadeOut;
+
+ // A View to hide screen when there's problem in video playback.
+ private final View mHideScreenView;
+
+ // A View to block screen until onContentAllowed is received if parental control is on.
+ private final View mBlockScreenForTuneView;
- private final TvInputListener mListener =
- new TvInputListener() {
+ // 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;
+
+ private int mBlockScreenType;
+
+ private final TvInputCallback mCallback =
+ new TvInputCallback() {
@Override
- public void onError(String inputId, int errorCode) {
- if (errorCode == TvView.ERROR_INPUT_NOT_CONNECTED) {
- Log.w(TAG, "Failed to bind an input");
- long channelId = mChannelId;
- mChannelId = Channel.INVALID_ID;
- mInputInfo = null;
- mCanReceiveInputEvent = false;
- if (mOnTuneListener != null) {
- mOnTuneListener.onTuned(false, channelId);
- mOnTuneListener = null;
- }
- } else if (errorCode == TvView.ERROR_INPUT_DISCONNECTED) {
- Log.w(TAG, "Session is released by crash");
- long channelId = mChannelId;
- mChannelId = Channel.INVALID_ID;
- mInputInfo = null;
- mCanReceiveInputEvent = false;
- if (mOnTuneListener != null) {
- mOnTuneListener.onUnexpectedStop(channelId);
- mOnTuneListener = null;
- }
+ public void onConnectionFailed(String inputId) {
+ Log.w(TAG, "Failed to bind an input");
+ 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");
+ Channel channel = mCurrentChannel;
+ mCurrentChannel = null;
+ mInputInfo = null;
+ mCanReceiveInputEvent = false;
+ if (mOnTuneListener != null) {
+ OnTuneListener listener = mOnTuneListener;
+ mOnTuneListener = null;
+ listener.onUnexpectedStop(channel);
}
}
@@ -86,9 +198,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
Log.d(TAG, "onChannelRetuned(inputId=" + inputId + ", channelUri="
+ channelUri + ")");
}
- // TODO: update {@code mChannelId}.
if (mOnTuneListener != null) {
- mOnTuneListener.onChannelChanged(channelUri);
+ mOnTuneListener.onChannelRetuned(channelUri);
}
}
@@ -114,6 +225,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mVideoWidth = 0;
mVideoHeight = 0;
mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
+ mVideoFrameRate = 0f;
} else if (type == TvTrackInfo.TYPE_AUDIO) {
mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
}
@@ -128,6 +240,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mVideoHeight = track.getVideoHeight();
mVideoFormat = Utils.getVideoDefinitionLevelFromSize(
mVideoWidth, mVideoHeight);
+ mVideoFrameRate = track.getVideoFrameRate();
} else if (type == TvTrackInfo.TYPE_AUDIO) {
mAudioChannelCount = track.getAudioChannelCount();
}
@@ -147,8 +260,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
public void onVideoAvailable(String inputId) {
- mIsVideoAvailable = true;
- unblock();
+ unhideScreenByVideoAvailability();
if (mOnTuneListener != null) {
mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
}
@@ -156,13 +268,33 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
public void onVideoUnavailable(String inputId, int reason) {
- mIsVideoAvailable = false;
- mVideoUnavailableReason = reason;
- block(reason);
+ hideScreenByVideoAvailability(reason);
if (mOnTuneListener != null) {
mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
}
}
+
+ @Override
+ public void onContentAllowed(String inputId) {
+ mBlockScreenForTuneView.setVisibility(View.GONE);
+ unblockScreenByContentRating();
+ if (mOnTuneListener != null) {
+ mOnTuneListener.onContentAllowed();
+ }
+ }
+
+ @Override
+ public void onContentBlocked(String inputId, TvContentRating rating) {
+ blockScreenByContentRating(rating);
+ if (mOnTuneListener != null) {
+ mOnTuneListener.onContentBlocked();
+ }
+ }
+
+ @Override
+ public void onTimeShiftStatusChanged(String inputId, int status) {
+ setTimeShiftAvailable(status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE);
+ }
};
public TunableTvView(Context context) {
@@ -181,11 +313,81 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
super(context, attrs, defStyleAttr, defStyleRes);
inflate(getContext(), R.layout.tunable_tv_view, this);
- mTvView = (TvView) findViewById(R.id.tv_view);
+ mTracker = ((TvApplication) context.getApplicationContext()).getTracker();
+ mBlockScreenType = BLOCK_SCREEN_TYPE_NORMAL;
+ mBlockScreenView = findViewById(R.id.block_screen);
+ mBlockScreenDescriptionView = findViewById(R.id.block_screen_description);
+
+ mBlockScreenIconView = mBlockScreenView.findViewById(R.id.block_screen_icon);
+ mBlockScreenShrunkenIconView = mBlockScreenView.findViewById(
+ R.id.block_screen_shrunken_icon);
+ mBlockScreenTextView = (TextView) mBlockScreenView.findViewById(R.id.block_screen_text);
+
+ mBlockScreenDescriptionFadeIn = AnimatorInflater.loadAnimator(context,
+ R.animator.tvview_block_screen_fade_in);
+ mBlockScreenDescriptionFadeIn.setTarget(mBlockScreenDescriptionView);
+ mBlockScreenDescriptionFadeIn.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ switch (mBlockScreenType) {
+ case BLOCK_SCREEN_TYPE_NORMAL:
+ mBlockScreenIconView.setVisibility(VISIBLE);
+ mBlockScreenShrunkenIconView.setVisibility(GONE);
+ break;
+ case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW:
+ mBlockScreenIconView.setVisibility(GONE);
+ mBlockScreenShrunkenIconView.setVisibility(VISIBLE);
+ break;
+ }
+ mBlockScreenDescriptionView.setVisibility(VISIBLE);
+ }
+ });
+ mBlockScreenDescriptionFadeOut = AnimatorInflater.loadAnimator(context,
+ R.animator.tvview_block_screen_fade_out);
+ mBlockScreenDescriptionFadeOut.setTarget(mBlockScreenDescriptionView);
+ mBlockScreenDescriptionFadeOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBlockScreenDescriptionView.setVisibility(GONE);
+ mBlockScreenDescriptionView.setAlpha(1f);
+ updateBlockScreenTextView();
+ }
+ });
+
+ mHideScreenView = findViewById(R.id.hide_screen);
+ mBufferingSpinnerView = findViewById(R.id.buffering_spinner);
+ mBlockScreenForTuneView = findViewById(R.id.block_screen_for_tune);
+ mDimScreenView = findViewById(R.id.dim);
+ 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();
+ }
+ }
+ });
+ }
+
+ public void initialize(AppLayerTvView tvView, boolean isPip, int screenHeight,
+ int shrunkenTvViewHeight) {
+ mTvView = tvView;
+ mIsPip = isPip;
+ mScreenHeight = screenHeight;
+ mShrunkenTvViewHeight = shrunkenTvViewHeight;
+ mTvView.setZOrderOnTop(isPip);
+ copyLayoutParamsToTvView();
}
public void start(TvInputManagerHelper tvInputManagerHelper) {
mInputManagerHelper = tvInputManagerHelper;
+ mContentRatingsManager = tvInputManagerHelper.getContentRatingsManager();
if (mStarted) {
return;
}
@@ -197,68 +399,120 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
return;
}
mStarted = false;
+ if (mCurrentChannel != null) {
+ mTracker.sendChannelViewStop(mCurrentChannel, mChannelViewTimer.reset());
+ }
+ reset();
+ }
+
+ public void reset() {
mTvView.reset();
- mChannelId = Channel.INVALID_ID;
+ mCurrentChannel = null;
mInputInfo = null;
mCanReceiveInputEvent = false;
mOnTuneListener = null;
+ setTimeShiftAvailable(false);
+ hideScreenByVideoAvailability(VIDEO_UNAVAILABLE_REASON_NOT_TUNED);
+ }
+
+ public void setMain() {
+ mTvView.setMain();
}
public boolean isPlaying() {
return mStarted;
}
- public boolean tuneTo(long channelId, Bundle params, OnTuneListener listener) {
+ /**
+ * Called when parental control is changed.
+ */
+ public void onParentalControlChanged(boolean enabled) {
+ mParentControlEnabled = enabled;
+ if (!mParentControlEnabled) {
+ mBlockScreenForTuneView.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public boolean tuneTo(Channel channel, Bundle params, OnTuneListener listener) {
if (!mStarted) {
throw new IllegalStateException("TvView isn't started");
}
- if (DEBUG) Log.d(TAG, "tuneTo " + channelId);
- mVideoWidth = 0;
- mVideoHeight = 0;
- mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
- mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
- mHasClosedCaption = false;
- String inputId = null;
- inputId = mChannelToInputMap.get(channelId);
- if (inputId == null) {
- inputId = Utils.getInputIdForChannel(getContext(), channelId);
- mChannelToInputMap.put(channelId, inputId);
- }
- TvInputInfo inputInfo = mInputManagerHelper.getTvInputInfo(inputId);
- if (inputInfo == null
- || mInputManagerHelper.getInputState(inputInfo) ==
- TvInputManager.INPUT_STATE_DISCONNECTED) {
+ if (DEBUG) Log.d(TAG, "tuneTo " + channel);
+ TvInputInfo inputInfo = mInputManagerHelper.getTvInputInfo(channel.getInputId());
+ if (inputInfo == null) {
return false;
}
+ if (mCurrentChannel != null) {
+ mTracker.sendChannelViewStop(mCurrentChannel, mChannelViewTimer.reset());
+ }
mOnTuneListener = listener;
- mChannelId = channelId;
+ mCurrentChannel = channel;
+ mTracker.sendChannelViewStart(mCurrentChannel);
+ mChannelViewTimer.start();
+ boolean needSurfaceSizeUpdate = false;
if (!inputInfo.equals(mInputInfo)) {
- mTvView.reset();
mInputInfo = inputInfo;
mCanReceiveInputEvent = getContext().getPackageManager().checkPermission(
- PERMISSION_RECEIVE_INPUT_EVENT, mInputInfo.getComponent().getPackageName())
+ PERMISSION_RECEIVE_INPUT_EVENT, mInputInfo.getServiceInfo().packageName)
== PackageManager.PERMISSION_GRANTED;
+ if (DEBUG) {
+ Log.d(TAG, "Input \'" + mInputInfo.getId() + "\' can receive input event: "
+ + mCanReceiveInputEvent);
+ }
+ needSurfaceSizeUpdate = true;
+ }
+ mVideoWidth = 0;
+ mVideoHeight = 0;
+ mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
+ mVideoFrameRate = 0f;
+ mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
+ mHasClosedCaption = false;
+ mTvView.setCallback(mCallback);
+ mTimeShiftCurrentPositionMs = INVALID_TIME;
+ if (TvCommonConstants.HAS_TIME_SHIFT_API) {
+ // To reduce the IPCs, unregister the callback here and register it when necessary.
+ mTvView.setTimeShiftPositionCallback(null);
+ }
+ setTimeShiftAvailable(false);
+ mTvView.tune(mInputInfo.getId(), mCurrentChannel.getUri(), params);
+ if (needSurfaceSizeUpdate && mFixedSurfaceWidth > 0 && mFixedSurfaceHeight > 0) {
+ // When the input is changed, TvView recreates its SurfaceView internally.
+ // So we need to call SurfaceHolder.setFixedSize for the new SurfaceView.
+ getSurfaceView().getHolder().setFixedSize(mFixedSurfaceWidth, mFixedSurfaceHeight);
+ }
+ hideScreenByVideoAvailability(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
+ unblockScreenByContentRating();
+ if (channel.isPassthrough()) {
+ mBlockScreenForTuneView.setVisibility(View.GONE);
+ } else if (mParentControlEnabled) {
+ mBlockScreenForTuneView.setVisibility(View.VISIBLE);
}
- mTvView.setTvInputListener(mListener);
- mTvView.tune(mInputInfo.getId(), Utils.getChannelUri(mChannelId), params);
if (mOnTuneListener != null) {
- // TODO: Add a callback for tune complete and call onTuned when it was successful.
- mOnTuneListener.onTuned(true, mChannelId);
+ mOnTuneListener.onStreamInfoChanged(this);
}
return true;
}
@Override
- public TvInputInfo getCurrentTvInputInfo() {
- return mInputInfo;
+ public Channel getCurrentChannel() {
+ return mCurrentChannel;
}
- public long getCurrentChannelId() {
- return mChannelId;
- }
-
- public void setPip(boolean isPip) {
- mTvView.setZOrderOnTop(isPip);
+ /**
+ * Sets the current channel. Call this method only when setting the current channel without
+ * actually tuning to it.
+ *
+ * @param currentChannel The new current channel to set to.
+ */
+ public void setCurrentChannel(Channel currentChannel) {
+ mCurrentChannel = currentChannel;
}
public void setStreamVolume(float volume) {
@@ -272,6 +526,31 @@ 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.
+ *
+ * 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 {@link android.view.SurfaceView#updateWindow}
+ * .
+ */
+ public void setFixedSurfaceSize(int width, int height) {
+ mFixedSurfaceWidth = width;
+ mFixedSurfaceHeight = height;
+ if (mFixedSurfaceWidth > 0 && mFixedSurfaceHeight > 0) {
+ // When the input is changed, TvView recreates its SurfaceView internally.
+ // So we need to call SurfaceHolder.setFixedSize for the new SurfaceView.
+ SurfaceView surfaceView = (SurfaceView) mTvView.getChildAt(0);
+ surfaceView.getHolder().setFixedSize(mFixedSurfaceWidth, mFixedSurfaceHeight);
+ } else {
+ SurfaceView surfaceView = (SurfaceView) mTvView.getChildAt(0);
+ surfaceView.getHolder().setSizeFromLayout();
+ }
+ }
+
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return mCanReceiveInputEvent && mTvView.dispatchKeyEvent(event);
@@ -293,10 +572,16 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
public interface OnTuneListener {
- void onTuned(boolean success, long channelId);
- void onUnexpectedStop(long channelId);
+ void onTuneFailed(Channel channel);
+ void onUnexpectedStop(Channel channel);
void onStreamInfoChanged(StreamInfo info);
- void onChannelChanged(Uri channel);
+ void onChannelRetuned(Uri channel);
+ void onContentBlocked();
+ void onContentAllowed();
+ }
+
+ public void requestUnblockContent(TvContentRating rating) {
+ mTvView.requestUnblockContent(rating);
}
@Override
@@ -315,6 +600,11 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
@Override
+ public float getVideoFrameRate() {
+ return mVideoFrameRate;
+ }
+
+ @Override
public int getAudioChannelCount() {
return mAudioChannelCount;
}
@@ -326,7 +616,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
public boolean isVideoAvailable() {
- return mIsVideoAvailable;
+ return mVideoAvailable;
}
@Override
@@ -334,41 +624,310 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
return mVideoUnavailableReason;
}
+ /**
+ * Returns the {@link android.view.SurfaceView} of the {@link android.media.tv.TvView}.
+ */
+ public SurfaceView getSurfaceView() {
+ return (SurfaceView) mTvView.getChildAt(0);
+ }
+
public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) {
mTvView.setOnUnhandledInputEventListener(listener);
}
+ public void setClosedCaptionEnabled(boolean enabled) {
+ mTvView.setCaptionEnabled(enabled);
+ }
+
public List<TvTrackInfo> getTracks(int type) {
return mTvView.getTracks(type);
}
+ public String getSelectedTrack(int type) {
+ return mTvView.getSelectedTrack(type);
+ }
+
public void selectTrack(int type, String trackId) {
mTvView.selectTrack(type, trackId);
}
- private void block(int reason) {
- hideBlock();
+ /**
+ * Returns if the screen is blocked by {@link #blockScreen()}.
+ */
+ public boolean isScreenBlocked() {
+ return mScreenBlocked;
+ }
+
+ public void setOnScreenBlockedListener(OnScreenBlockingChangedListener listener) {
+ mOnScreenBlockedListener = listener;
+ }
+
+ /**
+ * Returns currently blocked content rating. {@code null} if it's not blocked.
+ */
+ public TvContentRating getBlockedContentRating() {
+ return mBlockedContentRating;
+ }
+
+ /**
+ * Locks 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.
+ */
+ 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.
+ mBlockScreenDescriptionView.setScaleX(scale);
+ mBlockScreenDescriptionView.setScaleY(scale);
+ }
+ }
+
+ @Override
+ public void setLayoutParams(ViewGroup.LayoutParams params) {
+ super.setLayoutParams(params);
+ if (mTvView != null) {
+ copyLayoutParamsToTvView();
+ }
+ }
+
+ 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) {
+ // 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.
+ 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);
+ }
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if (mTvView != null) {
+ mTvView.setVisibility(visibility);
+ }
+ }
+
+ /**
+ * 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
+ * {@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).
+ *
+ * @param type The type of block screen to set.
+ */
+ public void setBlockScreenType(int type) {
+ // TODO: need to support the transition from NORMAL to SHRUNKEN and vice verse.
+ if (mBlockScreenType != type) {
+ mBlockScreenType = type;
+ updateBlockScreenUI(true);
+ }
+ }
+
+ private void updateBlockScreenUI(boolean animation) {
+ if (mBlockScreenDescriptionFadeIn.isRunning()) {
+ mBlockScreenDescriptionFadeIn.end();
+ }
+ if (mBlockScreenDescriptionFadeOut.isRunning()) {
+ mBlockScreenDescriptionFadeOut.end();
+ }
+
+ if (!mScreenBlocked && mBlockedContentRating == null) {
+ mBlockScreenView.setVisibility(GONE);
+ return;
+ }
+
+ mBlockScreenView.setVisibility(VISIBLE);
+ if (!animation) {
+ updateBlockScreenTextView();
+ switch (mBlockScreenType) {
+ case BLOCK_SCREEN_TYPE_NO_UI:
+ mBlockScreenIconView.setVisibility(GONE);
+ mBlockScreenShrunkenIconView.setVisibility(GONE);
+ mBlockScreenDescriptionView.setVisibility(GONE);
+ break;
+ case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW:
+ mBlockScreenIconView.setVisibility(GONE);
+ mBlockScreenShrunkenIconView.setVisibility(VISIBLE);
+ mBlockScreenDescriptionView.setVisibility(VISIBLE);
+ break;
+ case BLOCK_SCREEN_TYPE_NORMAL:
+ mBlockScreenIconView.setVisibility(VISIBLE);
+ mBlockScreenShrunkenIconView.setVisibility(GONE);
+ mBlockScreenDescriptionView.setVisibility(VISIBLE);
+ break;
+ }
+ } else {
+ switch (mBlockScreenType) {
+ case BLOCK_SCREEN_TYPE_NO_UI:
+ if (mBlockScreenDescriptionView.getVisibility() == VISIBLE) {
+ mBlockScreenDescriptionFadeOut.start();
+ }
+ break;
+ case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW:
+ case BLOCK_SCREEN_TYPE_NORMAL:
+ updateBlockScreenTextView();
+ if (mBlockScreenDescriptionView.getVisibility() == GONE) {
+ mBlockScreenDescriptionFadeIn.start();
+ }
+ break;
+ }
+ }
+ }
+
+ private void updateBlockScreenTextView() {
+ // TODO: need to add animation for padding change when the block screen type is changed
+ // NORMAL to SHRUNKEN and vice verse.
+ mBlockScreenTextView.setPadding(0,
+ getResources().getDimensionPixelOffset(
+ mBlockScreenType == BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW
+ ? R.dimen.shrunken_tvview_block_text_padding_top
+ : R.dimen.tvview_block_text_padding_top),
+ 0, 0);
+
+ if (mScreenBlocked) {
+ switch (mBlockScreenType) {
+ case BLOCK_SCREEN_TYPE_NO_UI:
+ case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW:
+ mBlockScreenTextView.setText("");
+ break;
+ case BLOCK_SCREEN_TYPE_NORMAL:
+ mBlockScreenTextView.setText(R.string.tvview_channel_locked);
+ break;
+ }
+ } else if (mBlockedContentRating != null) {
+ String name = mContentRatingsManager.getDisplayNameForRating(mBlockedContentRating);
+ switch (mBlockScreenType) {
+ case BLOCK_SCREEN_TYPE_NO_UI:
+ mBlockScreenTextView.setText("");
+ break;
+ case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW:
+ if (TextUtils.isEmpty(name)) {
+ mBlockScreenTextView.setText(R.string.shrunken_tvview_content_locked);
+ } else {
+ mBlockScreenTextView.setText(getContext().getString(
+ R.string.shrunken_tvview_content_locked_format, name));
+ }
+ break;
+ case BLOCK_SCREEN_TYPE_NORMAL:
+ if (TextUtils.isEmpty(name)) {
+ mBlockScreenTextView.setText(R.string.tvview_content_locked);
+ } else {
+ mBlockScreenTextView.setText(getContext().getString(
+ R.string.tvview_content_locked_format, name));
+ }
+ break;
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+
+ public void unblockScreen() {
+ mScreenBlocked = false;
+ checkBlockScreenAndMuteNeeded();
+ if (mOnScreenBlockedListener != null) {
+ mOnScreenBlockedListener.onScreenBlockingChanged(false);
+ }
+ }
+
+ private void unblockScreenByContentRating() {
+ mBlockedContentRating = null;
+ checkBlockScreenAndMuteNeeded();
+ }
+
+ private void hideScreenByVideoAvailability(int reason) {
switch (reason) {
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
- default:
- findViewById(R.id.block_reason_unknown).setVisibility(VISIBLE);
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
+ mBufferingSpinnerView.setVisibility(VISIBLE);
break;
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING:
- findViewById(R.id.block_reason_tune).setVisibility(VISIBLE);
- break;
case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
- findViewById(R.id.block_reason_weak_signal).setVisibility(VISIBLE);
- break;
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
- findViewById(R.id.block_reason_buffering).setVisibility(VISIBLE);
+ case VIDEO_UNAVAILABLE_REASON_NOT_TUNED:
+ default:
+ mHideScreenView.setVisibility(VISIBLE);
+ mBufferingSpinnerView.setVisibility(GONE);
break;
}
+ mVideoAvailable = false;
+ mVideoUnavailableReason = reason;
mute();
}
- private void unblock() {
- hideBlock();
- unmute();
+ private void unhideScreenByVideoAvailability() {
+ mVideoAvailable = true;
+ mHideScreenView.setVisibility(GONE);
+ mBufferingSpinnerView.setVisibility(GONE);
+ unmuteIfPossible();
+ }
+
+ private void unmuteIfPossible() {
+ if (mVideoAvailable && !mScreenBlocked && mBlockedContentRating == null) {
+ unmute();
+ }
}
private void mute() {
@@ -381,10 +940,256 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mTvView.setStreamVolume(mVolume);
}
- private void hideBlock() {
- findViewById(R.id.block_reason_unknown).setVisibility(GONE);
- findViewById(R.id.block_reason_tune).setVisibility(GONE);
- findViewById(R.id.block_reason_weak_signal).setVisibility(GONE);
- findViewById(R.id.block_reason_buffering).setVisibility(GONE);
+ /** Returns true if this view is faded out. */
+ public boolean isFadedOut() {
+ return mFadeState == FADED_OUT;
+ }
+
+ /** Fade out this TunableTvView. Fade out by increasing the dimming. */
+ public void fadeOut(int durationMillis, TimeInterpolator interpolator,
+ final Runnable actionAfterFade) {
+ mDimScreenView.setAlpha(0f);
+ mDimScreenView.setVisibility(View.VISIBLE);
+ 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;
+ }
+ });
+ }
+
+ /** Fade in this TunableTvView. Fade in by decreasing the dimming. */
+ public void fadeIn(int durationMillis, TimeInterpolator interpolator,
+ final Runnable actionAfterFade) {
+ mDimScreenView.setAlpha(1f);
+ mDimScreenView.setVisibility(View.VISIBLE);
+ 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);
+ }
+ });
+ }
+
+ /** Remove the fade effect. */
+ public void removeFadeEffect() {
+ mDimScreenView.animate().cancel();
+ mDimScreenView.setVisibility(View.GONE);
+ mFadeState = FADED_IN;
+ }
+
+ /**
+ * Sets the TimeShiftListener
+ *
+ * @param listener The instance of {@link TimeShiftListener}.
+ */
+ public void setTimeShiftListener(TimeShiftListener listener) {
+ mTimeShiftListener = listener;
+ }
+
+ private void setTimeShiftAvailable(boolean isTimeShiftAvailable) {
+ if (!TvCommonConstants.HAS_TIME_SHIFT_API || mTimeShiftAvailable == isTimeShiftAvailable) {
+ return;
+ }
+ 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);
+ }
+ }
+
+ @Override
+ public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) {
+ mTimeShiftCurrentPositionMs = timeMs;
+ }
+ });
+ } else {
+ mTvView.setTimeShiftPositionCallback(null);
+ }
+ if (mTimeShiftListener != null) {
+ mTimeShiftListener.onAvailabilityChanged();
+ }
+ }
+
+ /**
+ * Returns if the time shift is available for the current channel.
+ */
+ public boolean isTimeShiftAvailable() {
+ return mTimeShiftAvailable;
+ }
+
+ /**
+ * Returns the current time-shift state. It returns one of {@link #TIME_SHIFT_STATE_NONE},
+ * {@link #TIME_SHIFT_STATE_PLAY}, {@link #TIME_SHIFT_STATE_PAUSE},
+ * {@link #TIME_SHIFT_STATE_REWIND}, {@link #TIME_SHIFT_STATE_FAST_FORWARD}
+ * or {@link #TIME_SHIFT_STATE_PAUSE}.
+ */
+ @TimeShiftState public int getTimeShiftState() {
+ return mTimeShiftState;
+ }
+
+ /**
+ * Plays the media, if the current input supports time-shifting.
+ */
+ public void timeshiftPlay() {
+ if (!TvCommonConstants.HAS_TIME_SHIFT_API) {
+ Log.w(TAG, "Time shifting is not supported in this platform.");
+ return;
+ }
+ if (!isTimeShiftAvailable()) {
+ throw new IllegalStateException("Time-shift is not supported for the current channel");
+ }
+ if (mTimeShiftState == TIME_SHIFT_STATE_PLAY) {
+ return;
+ }
+ mTvView.timeShiftResume();
+ }
+
+ /**
+ * Pauses the media, if the current input supports time-shifting.
+ */
+ public void timeshiftPause() {
+ if (!TvCommonConstants.HAS_TIME_SHIFT_API) {
+ Log.w(TAG, "Time shifting is not supported in this platform.");
+ return;
+ }
+ if (!isTimeShiftAvailable()) {
+ throw new IllegalStateException("Time-shift is not supported for the current channel");
+ }
+ if (mTimeShiftState == TIME_SHIFT_STATE_PAUSE) {
+ return;
+ }
+ mTvView.timeShiftPause();
+ }
+
+ /**
+ * Rewinds the media with the given speed, if the current input supports time-shifting.
+ *
+ * @param speed The speed to rewind the media. e.g. 2 for 2x, 3 for 3x and 4 for 4x.
+ */
+ public void timeshiftRewind(int speed) {
+ if (!TvCommonConstants.HAS_TIME_SHIFT_API) {
+ Log.w(TAG, "Time shifting is not supported in this platform.");
+ return;
+ }
+ if (!isTimeShiftAvailable()) {
+ throw new IllegalStateException("Time-shift is not supported for the current channel");
+ }
+ if (speed <= 0) {
+ throw new IllegalArgumentException("The speed should be a positive integer.");
+ }
+ mTimeShiftState = TIME_SHIFT_STATE_REWIND;
+ PlaybackParams params = new PlaybackParams();
+ params.setSpeed(speed * -1);
+ mTvView.timeShiftSetPlaybackParams(params);
+ }
+
+ /**
+ * Fast-forwards the media with the given speed, if the current input supports time-shifting.
+ *
+ * @param speed The speed to forward the media. e.g. 2 for 2x, 3 for 3x and 4 for 4x.
+ */
+ public void timeshiftFastForward(int speed) {
+ if (!TvCommonConstants.HAS_TIME_SHIFT_API) {
+ Log.w(TAG, "Time shifting is not supported in this platform.");
+ return;
+ }
+ if (!isTimeShiftAvailable()) {
+ throw new IllegalStateException("Time-shift is not supported for the current channel");
+ }
+ if (speed <= 0) {
+ throw new IllegalArgumentException("The speed should be a positive integer.");
+ }
+ mTimeShiftState = TIME_SHIFT_STATE_FAST_FORWARD;
+ PlaybackParams params = new PlaybackParams();
+ params.setSpeed(speed);
+ mTvView.timeShiftSetPlaybackParams(params);
+ }
+
+ /**
+ * Seek to the given time position.
+ *
+ * @param timeMs The time in milliseconds to seek to.
+ */
+ public void timeshiftSeekTo(long timeMs) {
+ if (!TvCommonConstants.HAS_TIME_SHIFT_API) {
+ Log.w(TAG, "Time shifting is not supported in this platform.");
+ return;
+ }
+ if (!isTimeShiftAvailable()) {
+ throw new IllegalStateException("Time-shift is not supported for the current channel");
+ }
+ mTvView.timeShiftSeekTo(timeMs);
+ }
+
+ /**
+ * Returns the current playback position in milliseconds.
+ */
+ public long timeshiftGetCurrentPositionMs() {
+ if (!TvCommonConstants.HAS_TIME_SHIFT_API) {
+ Log.w(TAG, "Time shifting is not supported in this platform.");
+ return INVALID_TIME;
+ }
+ 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));
+ }
+ return mTimeShiftCurrentPositionMs;
+ }
+
+ /**
+ * Used to receive the time-shift events.
+ */
+ public static abstract class TimeShiftListener {
+ /**
+ * Called when the availability of the time-shift for the current channel has been changed.
+ * It should be guaranteed that this is called only when the availability is really changed.
+ */
+ public abstract void onAvailabilityChanged();
+
+ /**
+ * Called when the record start time has been changed..
+ */
+ 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.
+ */
+ public abstract void onScreenBlockingChanged(boolean blocked);
}
}
diff --git a/src/com/android/tv/ui/TvOverlayManager.java b/src/com/android/tv/ui/TvOverlayManager.java
new file mode 100644
index 00000000..28f16980
--- /dev/null
+++ b/src/com/android/tv/ui/TvOverlayManager.java
@@ -0,0 +1,595 @@
+/*
+ * 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;
+
+import android.support.annotation.IntDef;
+import android.util.Log;
+import android.view.KeyEvent;
+
+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.analytics.Tracker;
+import com.android.tv.dialog.FullscreenDialogFragment;
+import com.android.tv.dialog.PinDialogFragment;
+import com.android.tv.dialog.RecentlyWatchedDialogFragment;
+import com.android.tv.dialog.SafeDismissDialogFragment;
+import com.android.tv.guide.ProgramGuide;
+import com.android.tv.menu.MenuView;
+import com.android.tv.menu.MenuView.MenuShowReason;
+import com.android.tv.search.ProgramGuideSearchFragment;
+import com.android.tv.ui.TvTransitionManager.SceneType;
+import com.android.tv.ui.sidepanel.AboutFragment;
+import com.android.tv.ui.sidepanel.SideFragmentManager;
+import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A class responsible for the life cycle and event handling of the pop-ups over TV view.
+ */
+// TODO: Put TvTransitionManager into this class.
+public class TvOverlayManager {
+ private static final String TAG = "TvOverlayManager";
+ private static final boolean DEBUG = false;
+ public static final String SETUP_TRACKER_LABEL = "Setup dialog";
+ public 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})
+ public @interface HideOverlayFlag {}
+ // FLAG_HIDE_OVERLAYs must be bitwise exclusive.
+ public static final int FLAG_HIDE_OVERLAYS_DEFAULT = 0b00000000;
+ public static final int FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION = 0b00000010;
+ public static final int FLAG_HIDE_OVERLAYS_KEEP_SCENE = 0b00000100;
+ public static final int FLAG_HIDE_OVERLAYS_KEEP_DIALOG = 0b00001000;
+ public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS = 0b00010000;
+ public static final int FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY = 0b00100000;
+ public static final int FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE = 0b01000000;
+
+ @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})
+ private @interface TvOverlayType {}
+ // OVERLAY_TYPEs must be bitwise exclusive.
+ private static final int OVERLAY_TYPE_NONE = 0b00000000;
+ private static final int OVERLAY_TYPE_MENU = 0b00000001;
+ private static final int OVERLAY_TYPE_SIDE_FRAGMENT = 0b00000010;
+ private static final int OVERLAY_TYPE_DIALOG = 0b00000100;
+ private static final int OVERLAY_TYPE_GUIDE = 0b00001000;
+ private static final int OVERLAY_TYPE_SCENE_CHANNEL_BANNER = 0b00010000;
+ private static final int OVERLAY_TYPE_SCENE_INPUT_BANNER = 0b00100000;
+ private static final int OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH = 0b01000000;
+ private static final int OVERLAY_TYPE_SCENE_SELECT_INPUT = 0b10000000;
+
+ private static final Set<String> AVAILABLE_DIALOG_TAGS = new HashSet<>();
+ static {
+ AVAILABLE_DIALOG_TAGS.add(RecentlyWatchedDialogFragment.DIALOG_TAG);
+ AVAILABLE_DIALOG_TAGS.add(PinDialogFragment.DIALOG_TAG);
+ AVAILABLE_DIALOG_TAGS.add(FullscreenDialogFragment.DIALOG_TAG);
+ AVAILABLE_DIALOG_TAGS.add(AboutFragment.LicenseActionItem.DIALOG_TAG);
+ AVAILABLE_DIALOG_TAGS.add(RatingsFragment.AttributionItem.DIALOG_TAG);
+ }
+
+ private final MainActivity mMainActivity;
+ private final ChannelTuner mChannelTuner;
+ private final TvTransitionManager mTransitionManager;
+ private final MenuView mMenuView;
+ private final SideFragmentManager mSideFragmentManager;
+ private final ProgramGuide mProgramGuide;
+ private final KeypadChannelSwitchView mKeypadChannelSwitchView;
+ private final SelectInputView mSelectInputView;
+ private final ProgramGuideSearchFragment mSearchFragment;
+ private final Tracker mTracker;
+ private SafeDismissDialogFragment mCurrentDialog;
+
+ private @TvOverlayType int mOpenedOverlays;
+
+ public TvOverlayManager(MainActivity mainActivity, ChannelTuner channelTuner,
+ TvTransitionManager transitionManager, KeypadChannelSwitchView keypadChannelSwitchView,
+ SelectInputView selectInputView, ProgramGuideSearchFragment searchFragment) {
+ mMainActivity = mainActivity;
+ mChannelTuner = channelTuner;
+ mTransitionManager = transitionManager;
+ mKeypadChannelSwitchView = keypadChannelSwitchView;
+ mSelectInputView = selectInputView;
+ mSearchFragment = searchFragment;
+ mTracker = ((TvApplication) mainActivity.getApplication()).getTracker();
+ transitionManager.setListener(new TvTransitionManager.Listener() {
+ @Override
+ public void onSceneChanged(int fromScene, int toScene) {
+ // Call notifyOverlayOpened first so that the listener can know that a new scene
+ // will be opened when the notifyOverlayClosed is called.
+ if (toScene != TvTransitionManager.SCENE_TYPE_EMPTY) {
+ onOverlayOpened(convertSceneToOverlayType(toScene));
+ }
+ if (fromScene != TvTransitionManager.SCENE_TYPE_EMPTY) {
+ onOverlayClosed(convertSceneToOverlayType(fromScene));
+ }
+ }
+ });
+ // Menu
+ mMenuView = (MenuView) mainActivity.findViewById(R.id.menu);
+ mMenuView.setPreShowCallback(new Runnable() {
+ @Override
+ public void run() {
+ onOverlayOpened(OVERLAY_TYPE_MENU);
+ }
+ });
+ mMenuView.setPostHideCallback(new Runnable() {
+ @Override
+ public void run() {
+ onOverlayClosed(OVERLAY_TYPE_MENU);
+ }
+ });
+ // Side Fragment
+ 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() {
+ mMainActivity.showChannelBannerIfHiddenBySideFragment();
+ onOverlayClosed(OVERLAY_TYPE_SIDE_FRAGMENT);
+ }
+ });
+ // Program Guide
+ mProgramGuide = new ProgramGuide(mainActivity, channelTuner,
+ mainActivity.getTvInputManagerHelper(), mainActivity.getChannelDataManager(),
+ mainActivity.getProgramDataManager(),
+ ((TvApplication) mainActivity.getApplication()).getTracker(),
+ new Runnable() {
+ @Override
+ public void run() {
+ onOverlayOpened(OVERLAY_TYPE_GUIDE);
+ }
+ },
+ new Runnable() {
+ @Override
+ public void run() {
+ onOverlayClosed(OVERLAY_TYPE_GUIDE);
+ }
+ });
+ }
+
+ /**
+ * A method to follow the lifecycle of the {@link MainActivity}.
+ * This is called from {@link MainActivity#onStart}.
+ */
+ public void onStart() {
+ mMenuView.onStart();
+ }
+
+ /**
+ * A method to follow the lifecycle of the {@link MainActivity}.
+ * This is called from {@link MainActivity#onStop}.
+ */
+ public void onStop() {
+ mMenuView.onStop();
+ }
+
+ /**
+ * Returns the instance of {@link MenuView}.
+ */
+ public MenuView getMenuView() {
+ return mMenuView;
+ }
+
+ /**
+ * Returns the instance of {@link SideFragmentManager}.
+ */
+ public SideFragmentManager getSideFragmentManager() {
+ return mSideFragmentManager;
+ }
+
+ /**
+ * Returns the currently opened dialog.
+ */
+ public SafeDismissDialogFragment getCurrentDialog() {
+ return mCurrentDialog;
+ }
+
+ /**
+ * Returns the instance of {@link ProgramGuide}.
+ */
+ public ProgramGuide getProgramGuide() {
+ return mProgramGuide;
+ }
+
+ /**
+ * Shows the main menu.
+ */
+ public void showMenu(@MenuShowReason int reason) {
+ if (mChannelTuner != null && mChannelTuner.areAllChannelsLoaded()) {
+ mMenuView.show(reason);
+ }
+ }
+
+ /**
+ * Shows the play controller of the menu if the playback is paused.
+ */
+ public boolean showMenuWithTimeShiftPauseIfNeeded() {
+ if (mMainActivity.getTimeShiftManager().isPaused()) {
+ showMenu(MenuView.REASON_PLAY_CONTROLS_PAUSE);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Shows the given dialog.
+ */
+ public void showDialogFragment(String tag, SafeDismissDialogFragment dialog,
+ boolean keepSidePanelHistory) {
+ int flags = FLAG_HIDE_OVERLAYS_KEEP_DIALOG;
+ if (keepSidePanelHistory) {
+ flags |= FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY;
+ }
+ hideOverlays(flags);
+ // A tag for dialog must be added to AVAILABLE_DIALOG_TAGS to make it launchable from TV.
+ if (!AVAILABLE_DIALOG_TAGS.contains(tag)) {
+ return;
+ }
+
+ // TODO: Consider showing multiple dialog at once.
+ if (mCurrentDialog != null && mCurrentDialog.isAdded()) {
+ return;
+ }
+
+ mCurrentDialog = dialog;
+ dialog.show(mMainActivity.getFragmentManager(), tag);
+
+ // Calling this from SafeDismissDialogFragment.onCreated() might be late
+ // because it takes time for onCreated to be called
+ // and next key events can be handled by MainActivity, not Dialog.
+ onOverlayOpened(OVERLAY_TYPE_DIALOG);
+ }
+
+ /**
+ * Shows setup dialog.
+ */
+ public void showSetupDialog() {
+ if (DEBUG) Log.d(TAG,"showSetupDialog");
+ showDialogFragment(FullscreenDialogFragment.DIALOG_TAG,
+ new FullscreenDialogFragment(R.layout.setup_dialog, SETUP_TRACKER_LABEL), false);
+ }
+
+ /**
+ * Shows intro dialog.
+ */
+ public void showIntroDialog() {
+ if (DEBUG) Log.d(TAG,"showIntroDialog");
+ showDialogFragment(FullscreenDialogFragment.DIALOG_TAG,
+ new FullscreenDialogFragment(R.layout.intro_dialog, INTRO_TRACKER_LABEL), false);
+ }
+
+ /**
+ * Shows recently watched dialog.
+ */
+ public void showRecentlyWatchedDialog() {
+ showDialogFragment(RecentlyWatchedDialogFragment.DIALOG_TAG,
+ new RecentlyWatchedDialogFragment(), false);
+ }
+
+ /**
+ * It is called when a SafeDismissDialogFragment is destroyed.
+ */
+ public void onDialogDestroyed() {
+ mCurrentDialog = null;
+ onOverlayClosed(OVERLAY_TYPE_DIALOG);
+ }
+
+ /**
+ * Shows the program guide.
+ */
+ public void showProgramGuide() {
+ hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE);
+ mProgramGuide.show();
+ }
+
+ /**
+ * Hides all the opened overlays according to the flags.
+ */
+ // TODO: Add test for this method.
+ public void hideOverlays(@HideOverlayFlag int flags) {
+ if (mMainActivity.needToKeepDialogWhenHidingOverlay()) {
+ flags |= FLAG_HIDE_OVERLAYS_KEEP_DIALOG;
+ }
+ if ((flags & FLAG_HIDE_OVERLAYS_KEEP_DIALOG) != 0) {
+ // Keeps the dialog.
+ } 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);
+ }
+ mCurrentDialog.dismiss();
+ }
+ mCurrentDialog = null;
+ }
+
+ boolean withAnimation = (flags & FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION) == 0;
+
+ mMenuView.hide(withAnimation);
+ if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SCENE) != 0) {
+ // Keeps the current scene.
+ } else {
+ mTransitionManager.goToEmptyScene(withAnimation);
+ }
+ if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS) != 0) {
+ // Keeps side panels.
+ } else if (mSideFragmentManager.isSidePanelVisible()) {
+ if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY) != 0) {
+ mSideFragmentManager.hideSidePanel(withAnimation);
+ } else {
+ mSideFragmentManager.hideAll(withAnimation);
+ }
+ }
+ if ((flags & FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE) != 0) {
+ // Keep the program guide.
+ } else {
+ mProgramGuide.hide();
+ }
+ }
+
+ @TvOverlayType private int convertSceneToOverlayType(@SceneType int sceneType) {
+ switch (sceneType) {
+ case TvTransitionManager.SCENE_TYPE_CHANNEL_BANNER:
+ return OVERLAY_TYPE_SCENE_CHANNEL_BANNER;
+ case TvTransitionManager.SCENE_TYPE_INPUT_BANNER:
+ return OVERLAY_TYPE_SCENE_INPUT_BANNER;
+ case TvTransitionManager.SCENE_TYPE_KEYPAD_CHANNEL_SWITCH:
+ return OVERLAY_TYPE_SCENE_KEYPAD_CHANNEL_SWITCH;
+ case TvTransitionManager.SCENE_TYPE_SELECT_INPUT:
+ return OVERLAY_TYPE_SCENE_SELECT_INPUT;
+ case TvTransitionManager.SCENE_TYPE_EMPTY:
+ default:
+ return OVERLAY_TYPE_NONE;
+ }
+ }
+
+ private void onOverlayOpened(@TvOverlayType int overlayType) {
+ if (DEBUG) Log.d(TAG, "Overlay opened: 0b" + Integer.toBinaryString(overlayType));
+ mOpenedOverlays |= overlayType;
+ if (DEBUG) Log.d(TAG, "Opened overlays: 0b" + Integer.toBinaryString(mOpenedOverlays));
+ mMainActivity.updateKeyInputFocus();
+ }
+
+ private void onOverlayClosed(@TvOverlayType int overlayType) {
+ if (DEBUG) Log.d(TAG, "Overlay closed: 0b" + Integer.toBinaryString(overlayType));
+ mOpenedOverlays &= ~overlayType;
+ if (DEBUG) Log.d(TAG, "Opened overlays: 0b" + Integer.toBinaryString(mOpenedOverlays));
+ mMainActivity.updateKeyInputFocus();
+ boolean onlyBannerOrNoneOpened = (mOpenedOverlays & ~OVERLAY_TYPE_SCENE_CHANNEL_BANNER
+ & ~OVERLAY_TYPE_SCENE_INPUT_BANNER) == 0;
+ // Show the main menu again if there are no pop-ups or banners only.
+ // The main menu should not be shown when the activity is in paused state.
+ boolean wasMenuShown = false;
+ if (mMainActivity.isActivityResumed() && onlyBannerOrNoneOpened) {
+ wasMenuShown = showMenuWithTimeShiftPauseIfNeeded();
+ }
+ // Don't set screen name to main if the overlay closing is a banner
+ // or if a non banner overlay is still open
+ // or if we just opened the menu
+ if (overlayType != OVERLAY_TYPE_SCENE_CHANNEL_BANNER
+ && overlayType != OVERLAY_TYPE_SCENE_INPUT_BANNER
+ && onlyBannerOrNoneOpened
+ && !wasMenuShown) {
+ mTracker.sendScreenView(MainActivity.SCREEN_NAME);
+ }
+ }
+
+ /**
+ * Handles the onUserInteraction event of the {@link MainActivity}.
+ */
+ public void onUserInteraction() {
+ if (mSideFragmentManager.isActive()) {
+ mSideFragmentManager.scheduleHideAll();
+ } else if (mMenuView.isActive()) {
+ mMenuView.scheduleHide();
+ } else if (mProgramGuide.isActive()) {
+ mProgramGuide.scheduleHide();
+ }
+ }
+
+ /**
+ * 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;
+ }
+ // Handle media key here because it is related to the menu.
+ if (isMediaStartKey(keyCode)) {
+ // Consumes the keys which may trigger system's default music player.
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
+ }
+ if (mMenuView.isActive() || mSideFragmentManager.isActive() || mProgramGuide.isActive()) {
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY;
+ }
+ if (mTransitionManager.isKeypadChannelSwitchActive()) {
+ 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
+ : 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) {
+ // 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()
+ || mSideFragmentManager.isActive()
+ || mSearchFragment.isVisible()
+ || mTransitionManager.isKeypadChannelSwitchActive()
+ || mTransitionManager.isSelectInputActive()) {
+ // Do not handle media key when any pop-ups which can handle keys are active.
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
+ }
+ TimeShiftManager timeShiftManager = mMainActivity.getTimeShiftManager();
+ if (!timeShiftManager.isAvailable()) {
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
+ }
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ timeShiftManager.play();
+ showMenu(MenuView.REASON_PLAY_CONTROLS_PLAY);
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ timeShiftManager.pause();
+ showMenu(MenuView.REASON_PLAY_CONTROLS_PAUSE);
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ timeShiftManager.togglePlayPause();
+ showMenu(MenuView.REASON_PLAY_CONTROLS_PLAY_PAUSE);
+ break;
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ timeShiftManager.rewind();
+ showMenu(MenuView.REASON_PLAY_CONTROLS_REWIND);
+ break;
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ timeShiftManager.fastForward();
+ showMenu(MenuView.REASON_PLAY_CONTROLS_FAST_FORWARD);
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ timeShiftManager.jumpToPrevious();
+ showMenu(MenuView.REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS);
+ break;
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ timeShiftManager.jumpToNext();
+ showMenu(MenuView.REASON_PLAY_CONTROLS_JUMP_TO_NEXT);
+ break;
+ default:
+ // Does nothing.
+ break;
+ }
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
+ }
+ if (keyCode == KeyEvent.KEYCODE_I || keyCode == KeyEvent.KEYCODE_TV_INPUT) {
+ if (mTransitionManager.isSelectInputActive()) {
+ mSelectInputView.onKeyUp(keyCode, event);
+ } else {
+ mMainActivity.showSelectInputView();
+ }
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
+ }
+ if (mCurrentDialog != null) {
+ // Consumes the keys while a Dialog is showing.
+ // This can be happen while a Dialog isn't created yet.
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
+ }
+ if (mProgramGuide.isActive()) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ mProgramGuide.onBackPressed();
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
+ }
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY;
+ }
+ if (mSideFragmentManager.isActive()) {
+ if (keyCode == KeyEvent.KEYCODE_BACK
+ || mSideFragmentManager.isHideKeyForCurrentPanel(keyCode)) {
+ mSideFragmentManager.popSideFragment();
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
+ }
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY;
+ }
+ if (mMenuView.isActive() || mTransitionManager.isSceneActive()) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ TimeShiftManager timeShiftManager = mMainActivity.getTimeShiftManager();
+ if (timeShiftManager.isPaused()) {
+ timeShiftManager.play();
+ }
+ hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG);
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
+ }
+ if (mMenuView.isActive()) {
+ if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) {
+ mMainActivity.showKeypadChannelSwitchView(keyCode);
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
+ }
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY;
+ }
+ }
+ if (mTransitionManager.isKeypadChannelSwitchActive()) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ mTransitionManager.goToEmptyScene(true);
+ return 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()) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ mTransitionManager.goToEmptyScene(true);
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
+ }
+ return mSelectInputView.onKeyUp(keyCode, event) ?
+ MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED
+ : MainActivity.KEY_EVENT_HANDLER_RESULT_NOT_HANDLED;
+ }
+ return MainActivity.KEY_EVENT_HANDLER_RESULT_PASSTHROUGH;
+ }
+
+ /**
+ * 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:
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/tv/ui/TvTransitionManager.java b/src/com/android/tv/ui/TvTransitionManager.java
new file mode 100644
index 00000000..7096893f
--- /dev/null
+++ b/src/com/android/tv/ui/TvTransitionManager.java
@@ -0,0 +1,315 @@
+/*
+ * 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;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.support.annotation.IntDef;
+import android.transition.Fade;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionInflater;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.transition.TransitionValues;
+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 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})
+ 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;
+ public static final int SCENE_TYPE_KEYPAD_CHANNEL_SWITCH = 3;
+ public static final int SCENE_TYPE_SELECT_INPUT = 4;
+
+ private final MainActivity mMainActivity;
+ private final ViewGroup mSceneContainer;
+ private final ChannelBannerView mChannelBannerView;
+ private final InputBannerView mInputBannerView;
+ private final KeypadChannelSwitchView mKeypadChannelSwitchView;
+ private final SelectInputView mSelectInputView;
+ private final FrameLayout mEmptyView;
+ private ViewGroup mCurrentSceneView;
+ private Animator mEnterAnimator;
+ private Animator mExitAnimator;
+
+ private boolean mInitialized;
+ private Scene mEmptyScene;
+ private Scene mChannelBannerScene;
+ private Scene mInputBannerScene;
+ private Scene mKeypadChannelSwitchScene;
+ private Scene mSelectInputScene;
+ private Scene mCurrentScene;
+
+ private Listener mListener;
+
+ 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);
+ mCurrentSceneView = mEmptyView;
+ }
+
+ public void goToEmptyScene(boolean withAnimation) {
+ if (mCurrentScene == mEmptyScene) {
+ return;
+ }
+ initIfNeeded();
+ if (withAnimation) {
+ transitionTo(mEmptyScene);
+ } else {
+ TransitionManager.go(mEmptyScene, null);
+ }
+ }
+
+ public void goToChannelBannerScene() {
+ initIfNeeded();
+ if (mMainActivity.getCurrentChannel().isPassthrough()) {
+ if (mCurrentScene != mInputBannerScene) {
+ // Show the input banner instead.
+ LayoutParams lp = (LayoutParams) mInputBannerView.getLayoutParams();
+ lp.width = mCurrentScene == mSelectInputScene ? mSelectInputView.getWidth()
+ : FrameLayout.LayoutParams.WRAP_CONTENT;
+ mInputBannerView.setLayoutParams(lp);
+ mInputBannerView.updateLabel();
+ transitionTo(mInputBannerScene);
+ }
+ } else if (mCurrentScene != mChannelBannerScene) {
+ transitionTo(mChannelBannerScene);
+ }
+ }
+
+ public void goToKeypadChannelSwitchScene() {
+ initIfNeeded();
+ if (mCurrentScene != mKeypadChannelSwitchScene) {
+ transitionTo(mKeypadChannelSwitchScene);
+ }
+ }
+
+ public void goToSelectInputScene() {
+ initIfNeeded();
+ if (mCurrentScene != mSelectInputScene) {
+ transitionTo(mSelectInputScene);
+ }
+ }
+
+ public boolean isSceneActive() {
+ return mCurrentScene != mEmptyScene;
+ }
+
+ public boolean isKeypadChannelSwitchActive() {
+ return mCurrentScene != null && mCurrentScene == mKeypadChannelSwitchScene;
+ }
+
+ public boolean isSelectInputActive() {
+ return mCurrentScene != null && mCurrentScene == mSelectInputScene;
+ }
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ public void initIfNeeded() {
+ if (mInitialized) {
+ return;
+ }
+ mEnterAnimator = AnimatorInflater.loadAnimator(mMainActivity,
+ R.animator.channel_banner_enter);
+ mExitAnimator = AnimatorInflater.loadAnimator(mMainActivity,
+ R.animator.channel_banner_exit);
+
+ mEmptyScene = new Scene(mSceneContainer, 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();
+ }
+ });
+
+ mChannelBannerScene = buildScene(mSceneContainer, mChannelBannerView);
+ mInputBannerScene = buildScene(mSceneContainer, mInputBannerView);
+ mKeypadChannelSwitchScene = buildScene(mSceneContainer, mKeypadChannelSwitchView);
+ mSelectInputScene = buildScene(mSceneContainer, mSelectInputView);
+ mCurrentScene = mEmptyScene;
+
+ // Enter transitions
+ 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));
+ setTransition(mChannelBannerScene, mEmptyScene, exit);
+ setTransition(mInputBannerScene, mEmptyScene, exit);
+ setTransition(mKeypadChannelSwitchScene, mEmptyScene, exit);
+ setTransition(mSelectInputScene, mEmptyScene, exit);
+
+ // All other possible transitions between scenes
+ TransitionInflater ti = TransitionInflater.from(mMainActivity);
+ Transition transition = ti.inflateTransition(R.transition.transition_between_scenes);
+ setTransition(mChannelBannerScene, mKeypadChannelSwitchScene, transition);
+ setTransition(mChannelBannerScene, mSelectInputScene, transition);
+ setTransition(mInputBannerScene, mSelectInputScene, transition);
+ setTransition(mKeypadChannelSwitchScene, mChannelBannerScene, transition);
+ setTransition(mKeypadChannelSwitchScene, mSelectInputScene, transition);
+ setTransition(mSelectInputScene, mChannelBannerScene, transition);
+ setTransition(mSelectInputScene, mInputBannerScene, transition);
+
+ mInitialized = true;
+ }
+
+ /**
+ * 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) {
+ return SCENE_TYPE_INPUT_BANNER;
+ } else if (scene == mKeypadChannelSwitchScene) {
+ return SCENE_TYPE_KEYPAD_CHANNEL_SWITCH;
+ } else if (scene == mSelectInputScene) {
+ return SCENE_TYPE_SELECT_INPUT;
+ }
+ return SCENE_TYPE_EMPTY;
+ }
+
+ private void setCurrentScene(Scene scene, ViewGroup sceneView) {
+ if (mListener != null) {
+ mListener.onSceneChanged(getSceneType(mCurrentScene), getSceneType(scene));
+ }
+ mCurrentScene = scene;
+ mCurrentSceneView = sceneView;
+ // TODO: Is this a still valid call?
+ mMainActivity.updateKeyInputFocus();
+ }
+
+ public interface TransitionLayout {
+ // TODO: remove the parameter fromEmptyScene once a bug regarding transition alpha
+ // is fixed. The bug is that the transition alpha is not reset after the transition is
+ // canceled.
+ void onEnterAction(boolean fromEmptyScene);
+
+ void onExitAction();
+ }
+
+ 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();
+ }
+ });
+ return scene;
+ }
+
+ private void removeAllViewsFromOverlay() {
+ // Clean up all the animations which can be still running.
+ mSceneContainer.getOverlay().remove(mChannelBannerView);
+ mSceneContainer.getOverlay().remove(mInputBannerView);
+ mSceneContainer.getOverlay().remove(mKeypadChannelSwitchView);
+ mSceneContainer.getOverlay().remove(mSelectInputView);
+ }
+
+ private class SceneTransition extends Transition {
+ static final int ENTER = 0;
+ static final int EXIT = 1;
+
+ private final Animator mAnimator;
+
+ SceneTransition(int mode) {
+ mAnimator = mode == ENTER ? mEnterAnimator : mExitAnimator;
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ }
+
+ @Override
+ public Animator createAnimator(
+ ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
+ Animator animator = mAnimator.clone();
+ animator.setTarget(sceneRoot);
+ animator.addListener(new HardwareLayerAnimatorListenerAdapter(sceneRoot));
+ return animator;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ void onSceneChanged(@SceneType int fromSceneType, @SceneType int toSceneType);
+ }
+}
diff --git a/src/com/android/tv/ui/TvViewUiManager.java b/src/com/android/tv/ui/TvViewUiManager.java
new file mode 100644
index 00000000..3793d245
--- /dev/null
+++ b/src/com/android/tv/ui/TvViewUiManager.java
@@ -0,0 +1,797 @@
+/*
+ * 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;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.SuppressLint;
+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.Handler;
+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;
+import android.view.animation.AnimationUtils;
+import android.widget.FrameLayout;
+
+import com.android.tv.R;
+import com.android.tv.TvOptionsManager;
+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.
+ */
+public class TvViewUiManager {
+ private static final String TAG = "TvViewManager";
+ private static final boolean DEBUG = false;
+
+ private static final float DISPLAY_MODE_EPSILON = 0.001f;
+
+ private final Context mContext;
+ 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 final int mScreenWidth;
+ private final int mScreenHeight;
+ 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();
+ private int mDisplayMode;
+ // Used to restore the previous state from ShrunkenTvView state.
+ private int mTvViewStartMarginBeforeShrunken;
+ private int mTvViewEndMarginBeforeShrunken;
+ private int mDisplayModeBeforeShrunken;
+ 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 ObjectAnimator mBackgroundAnimator;
+ private int mBackgroundColor;
+
+ public TvViewUiManager(Context context, TunableTvView tvView, TunableTvView pipView,
+ FrameLayout contentView, TvOptionsManager tvOptionManager) {
+ mContext = context;
+ mResources = context.getResources();
+ mTvView = tvView;
+ mPipView = pipView;
+ mContentView = contentView;
+ mTvOptionsManager = tvOptionManager;
+
+ DisplayManager displayManager = (DisplayManager) context
+ .getSystemService(Context.DISPLAY_SERVICE);
+ Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ Point size = new Point();
+ display.getSize(size);
+ mScreenWidth = size.x;
+ mScreenHeight = size.y;
+
+ 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 = (mScreenWidth - papSpacing) / 2 - papMarginHorizontal;
+ mTvViewPapStartMargin = papMarginHorizontal + mTvViewPapWidth + papSpacing;
+ mTvViewPapEndMargin = papMarginHorizontal;
+ mTvViewFrame = createMarginLayoutParams(0, 0, 0, 0);
+
+ 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);
+
+ mPipViewHorizontalMargin = mResources
+ .getDimensionPixelOffset(R.dimen.pipview_margin_horizontal);
+ mPipViewTopMargin = mResources.getDimensionPixelOffset(R.dimen.pipview_margin_top);
+ mPipViewBottomMargin = mResources.getDimensionPixelOffset(R.dimen.pipview_margin_bottom);
+ }
+
+ /**
+ * 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();
+ initBackgroundAnimatorIfNeeded();
+ }
+
+ /**
+ * It is called when shrunken TvView is desired, such as EditChannelFragment and
+ * ChannelsLockedFragment.
+ */
+ public void startShrunkenTvView() {
+ mIsUnderShrunkenTvView = 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 / mScreenWidth;
+ int startMargin = (int) (mTvViewPapStartMargin * factor);
+ int endMargin = (int) (mTvViewPapEndMargin * factor + sidePanelWidth);
+ setTvViewMargin(startMargin, endMargin);
+ } else {
+ setTvViewMargin(mTvViewShrunkenStartMargin, mTvViewShrunkenEndMargin);
+ }
+ mDisplayModeBeforeShrunken = setDisplayMode(DisplayMode.MODE_NORMAL, false, true);
+ }
+
+ /**
+ * It is called when shrunken TvView is no longer desired, such as EditChannelFragment and
+ * ChannelsLockedFragment.
+ */
+ public void endShrunkenTvView() {
+ mIsUnderShrunkenTvView = false;
+ setTvViewMargin(mTvViewStartMarginBeforeShrunken, mTvViewEndMarginBeforeShrunken);
+ setDisplayMode(mDisplayModeBeforeShrunken, false, true);
+ }
+
+ /**
+ * 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.
+ */
+ public boolean isDisplayModeAvailable(int displayMode) {
+ if (displayMode == DisplayMode.MODE_NORMAL) {
+ return true;
+ }
+
+ int viewWidth = mContentView.getWidth();
+ int viewHeight = mContentView.getHeight();
+
+ int videoWidth = mTvView.getVideoWidth();
+ int videoHeight = mTvView.getVideoHeight();
+
+ if (viewWidth <= 0 || viewHeight <= 0 || videoWidth <= 0 || videoHeight <= 0) {
+ Log.w(TAG, "Video size is currently unavailable");
+ if (DEBUG) {
+ Log.d(TAG, "isDisplayModeAvailable: "
+ + "viewWidth=" + viewWidth
+ + ", viewHeight=" + viewHeight
+ + ", videoWidth=" + videoWidth
+ + ", videoHeight="+ videoHeight
+ );
+ }
+ return false;
+ }
+
+ float viewRatio = viewWidth / (float) viewHeight;
+ float videoRatio = videoWidth / (float) videoHeight;
+ return Math.abs(viewRatio - videoRatio) >= DISPLAY_MODE_EPSILON;
+ }
+
+ /**
+ * Returns a constant defined in DisplayMode.
+ */
+ public int getDisplayMode() {
+ if (isDisplayModeAvailable(mDisplayMode)) {
+ return mDisplayMode;
+ }
+ return DisplayMode.MODE_NORMAL;
+ }
+
+ /**
+ * Sets the display mode to the given value.
+ *
+ * @return the previous display mode.
+ */
+ public int setDisplayMode(int displayMode, boolean storeInPreference, boolean animate) {
+ int prev = mDisplayMode;
+ mDisplayMode = displayMode;
+ if (storeInPreference) {
+ mSharedPreferences.edit().putInt(TvSettings.PREF_DISPLAY_MODE, displayMode).apply();
+ }
+ applyDisplayMode(mTvView.getVideoWidth(), mTvView.getVideoHeight(), animate);
+ return prev;
+ }
+
+ /**
+ * 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);
+ setDisplayMode(displayMode, false, animate);
+ }
+
+ /**
+ * Updates TvView. It is called when video resolution is updated.
+ */
+ public void updateTvView() {
+ applyDisplayMode(mTvView.getVideoWidth(), mTvView.getVideoHeight(), false);
+ if (mTvView.isVideoAvailable() && mTvView.isFadedOut()) {
+ mTvView.fadeIn(mResources.getInteger(R.integer.tvview_fade_in_duration),
+ mFastOutLinearIn, null);
+ }
+ }
+
+ /**
+ * Fades in TvView.
+ */
+ public void fadeInTvView() {
+ if (mTvView.isFadedOut()) {
+ mTvView.fadeIn(mResources.getInteger(R.integer.tvview_fade_in_duration),
+ mFastOutLinearIn, null);
+ }
+ }
+
+ /**
+ * Fades out TvView.
+ */
+ public void fadeOutTvView(Runnable postAction) {
+ if (!mTvView.isFadedOut()) {
+ mTvView.fadeOut(mResources.getInteger(R.integer.tvview_fade_out_duration),
+ mLinearOutSlowIn, postAction);
+ }
+ }
+
+ /**
+ * 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.
+ */
+ private void setTvViewMargin(int tvViewStartMargin, int tvViewEndMargin) {
+ mTvViewStartMargin = tvViewStartMargin;
+ mTvViewEndMargin = tvViewEndMargin;
+ }
+
+ private boolean isTvViewFullScreen() {
+ return mTvViewStartMargin == 0 && mTvViewEndMargin == 0;
+ }
+
+ private void setBackgroundColor(int color, FrameLayout.LayoutParams targetLayoutParams,
+ boolean animate) {
+ if (animate) {
+ initBackgroundAnimatorIfNeeded();
+ if (mBackgroundAnimator.isStarted()) {
+ // Cancel the current animation and start new one.
+ mBackgroundAnimator.cancel();
+ }
+
+ 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);
+
+ if (!isTvViewFullScreen() && !hasPillarBox) {
+ // If there is no pillar box, no animation is needed.
+ mContentView.setBackgroundColor(color);
+ } else if (!isTvViewFullScreen() || willHavePillarBox) {
+ mBackgroundAnimator.setIntValues(mBackgroundColor, color);
+ mBackgroundAnimator.setEvaluator(new ArgbEvaluator());
+ mBackgroundAnimator.setInterpolator(mFastOutLinearIn);
+ mBackgroundAnimator.start();
+ }
+ // In the 'else'case (TV activity is getting out of the shrunken tv view mode and will
+ // have a pillar box), we keep the background color and don't show the animation.
+ } else {
+ mContentView.setBackgroundColor(color);
+ }
+ mBackgroundColor = color;
+ }
+
+ private void setTvViewPosition(final FrameLayout.LayoutParams layoutParams,
+ MarginLayoutParams 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;
+ mTvViewLayoutParams = layoutParams;
+ mTvViewFrame = tvViewFrame;
+ if (animate) {
+ initTvAnimatorIfNeeded();
+ if (mTvViewAnimator.isStarted()) {
+ // Cancel the current animation and start new one.
+ mTvViewAnimator.cancel();
+ mOldTvViewFrame = mLastAnimatedTvViewFrame;
+ } else {
+ mOldTvViewFrame = oldTvViewFrame;
+ }
+ mTvViewAnimator.setObjectValues(mTvView.getLayoutParams(), layoutParams);
+ mTvViewAnimator.setEvaluator(new TypeEvaluator<FrameLayout.LayoutParams>() {
+ 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;
+ }
+ interpolateMarginsRelative(lp, startValue, endValue, fraction);
+ return lp;
+ }
+ });
+ mTvViewAnimator
+ .setInterpolator(isTvViewFullScreen() ? mFastOutLinearIn : mLinearOutSlowIn);
+ mTvViewAnimator.start();
+ } else {
+ if (mTvViewAnimator != null && mTvViewAnimator.isStarted()) {
+ // Continue the current animation.
+ // layoutParams will be applied when animation ends.
+ return;
+ }
+ // This block is also called when animation ends.
+ if (isTvViewFullScreen()) {
+ // When this layout is for full screen, fix the surface size after layout to make
+ // resize animation smooth.
+ mTvView.post(new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "setFixedSize: w=" + mTvView.getWidth() + " h=" + mTvView
+ .getHeight());
+ }
+ mTvView.setLayoutParams(layoutParams);
+ mTvView.setFixedSurfaceSize(mTvView.getWidth(), mTvView.getHeight());
+ }
+ });
+ } 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;
+ int pipVideoWidth = mPipView.getVideoWidth();
+ int pipVideoHeight = mPipView.getVideoHeight();
+ if (pipVideoWidth > 0 && pipVideoHeight > 0) {
+ width = height * pipVideoWidth / pipVideoHeight;
+ if (width > tvViewFrame.width) {
+ width = tvViewFrame.width;
+ }
+ } else {
+ 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 / mScreenWidth;
+ float verticalScaleFactor = (float) tvViewHeight / mScreenHeight;
+
+ 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);
+ }
+ int pipVideoWidth = mPipView.getVideoWidth();
+ int pipVideoHeight = mPipView.getVideoHeight();
+ if (pipVideoWidth > 0 && pipVideoHeight > 0) {
+ width = height * pipVideoWidth / pipVideoHeight;
+ if (width > maxWidth) {
+ width = maxWidth;
+ }
+ } else {
+ 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);
+ }
+ }
+
+ 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() {
+ if (mTvViewAnimator != null) {
+ return;
+ }
+
+ // TvViewAnimator animates TvView by repeatedly re-layouting TvView.
+ // TvView includes a SurfaceView on which scale/translation effects do not work. Normally,
+ // SurfaceView can be animated by changing left/top/right/bottom directly using
+ // ObjectAnimator, although it would require calling getChildAt(0) against TvView (which is
+ // supposed to be opaque). More importantly, this method does not work in case of TvView,
+ // 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.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;
+
+ @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);
+ }
+ });
+ }
+ });
+ mTvViewAnimator.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ float fraction = animator.getAnimatedFraction();
+ mLastAnimatedTvViewFrame = new MarginLayoutParams(0, 0);
+ interpolateMarginsRelative(mLastAnimatedTvViewFrame,
+ mOldTvViewFrame, mTvViewFrame, fraction);
+ updatePipView(mLastAnimatedTvViewFrame);
+ }
+ });
+ }
+
+ private void initBackgroundAnimatorIfNeeded() {
+ if (mBackgroundAnimator != null) {
+ return;
+ }
+
+ 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() {
+ @Override
+ public void run() {
+ mContentView.setBackgroundColor(mBackgroundColor);
+ }
+ });
+ }
+ });
+ }
+
+ private void applyDisplayMode(int videoWidth, int videoHeight, boolean animate) {
+ int availableAreaWidth = mScreenWidth - mTvViewStartMargin - mTvViewEndMargin;
+ int availableAreaHeight = availableAreaWidth * mScreenHeight / mScreenWidth;
+ FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(0, 0,
+ ((FrameLayout.LayoutParams) mTvView.getLayoutParams()).gravity);
+ int displayMode = mDisplayMode;
+ double availableAreaRatio = 0;
+ double videoRatio = 0;
+ if (videoWidth <= 0 || videoHeight <= 0) {
+ videoWidth = mScreenWidth;
+ videoHeight = mScreenHeight;
+ }
+ 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 = (double) videoWidth / videoHeight;
+ }
+
+ int tvViewFrameTop = (mScreenHeight - availableAreaHeight) / 2;
+ MarginLayoutParams tvViewFrame = createMarginLayoutParams(
+ mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop);
+ layoutParams.width = availableAreaWidth;
+ layoutParams.height = availableAreaHeight;
+ switch (displayMode) {
+ case DisplayMode.MODE_FULL:
+ layoutParams.width = availableAreaWidth;
+ layoutParams.height = availableAreaHeight;
+ break;
+ case DisplayMode.MODE_ZOOM:
+ if (videoRatio < availableAreaRatio) {
+ // Y axis will be clipped.
+ layoutParams.width = availableAreaWidth;
+ layoutParams.height = (int) (availableAreaWidth / videoRatio);
+ } else {
+ // X axis will be clipped.
+ layoutParams.width = (int) (availableAreaHeight * videoRatio);
+ layoutParams.height = availableAreaHeight;
+ }
+ break;
+ case DisplayMode.MODE_NORMAL:
+ if (videoRatio < availableAreaRatio) {
+ // X axis has black area.
+ layoutParams.width = (int) (availableAreaHeight * videoRatio);
+ layoutParams.height = availableAreaHeight;
+ } else {
+ // Y axis has black area.
+ layoutParams.width = availableAreaWidth;
+ layoutParams.height = (int) (availableAreaWidth / videoRatio);
+ }
+ 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;
+ layoutParams.setMarginStart(marginStart);
+ // Set marginEnd as well because setTvViewPosition uses both start/end margin.
+ layoutParams.setMarginEnd(mScreenWidth - layoutParams.width - marginStart);
+
+ setBackgroundColor(mResources.getColor(isTvViewFullScreen() ? R.color.tvactivity_background
+ : R.color.tvactivity_background_on_shrunken_tvview), layoutParams, animate);
+ setTvViewPosition(layoutParams, tvViewFrame, animate);
+
+ // Update the current display mode.
+ mTvOptionsManager.onDisplayModeChanged(displayMode);
+ }
+
+ private static int interpolate(int start, int end, float fraction) {
+ return (int) (start + (end - start) * fraction);
+ }
+
+ private static void interpolateMarginsRelative(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.setMarginEnd(interpolate(startValue.getMarginEnd(), endValue.getMarginEnd(), fraction));
+ out.width = interpolate(startValue.width, endValue.width, fraction);
+ out.height = interpolate(startValue.height, endValue.height, fraction);
+ }
+
+ private MarginLayoutParams createMarginLayoutParams(
+ int startMargin, int endMargin, int topMargin, int bottomMargin) {
+ MarginLayoutParams lp = new MarginLayoutParams(0, 0);
+ lp.setMarginStart(startMargin);
+ lp.setMarginEnd(endMargin);
+ lp.topMargin = topMargin;
+ lp.bottomMargin = bottomMargin;
+ lp.width = mScreenWidth - startMargin - endMargin;
+ lp.height = mScreenHeight - topMargin - bottomMargin;
+ return lp;
+ }
+}
diff --git a/src/com/android/tv/ui/ViewUtils.java b/src/com/android/tv/ui/ViewUtils.java
new file mode 100644
index 00000000..5a853dcd
--- /dev/null
+++ b/src/com/android/tv/ui/ViewUtils.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.ui;
+
+import android.util.Log;
+import android.view.View;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * A class that includes convenience methods for view classes.
+ */
+public class ViewUtils {
+ private static final String TAG = "ViewUtils";
+
+ private ViewUtils() {
+ // Prevent instantiation.
+ }
+
+ public static void setTransitionAlpha(View v, float alpha) {
+ Method method;
+ try {
+ method = View.class.getDeclaredMethod("setTransitionAlpha", Float.TYPE);
+ method.invoke(v, alpha);
+ } catch (NoSuchMethodException|IllegalAccessException|IllegalArgumentException
+ |InvocationTargetException e) {
+ Log.e(TAG, "Fail to call View.setTransitionAlpha", e);
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/AboutFragment.java b/src/com/android/tv/ui/sidepanel/AboutFragment.java
new file mode 100644
index 00000000..5b7444b2
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/AboutFragment.java
@@ -0,0 +1,101 @@
+/*
+ * 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.view.View;
+import android.widget.TextView;
+
+import com.android.tv.MainActivity;
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.dialog.WebDialogFragment;
+import com.android.tv.license.LicenseUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shows version and optional license information.
+ */
+public class AboutFragment extends SideFragment {
+ private final static String TAG = "AboutFragment";
+ private static final String TRACKER_LABEL = "about";
+
+ /**
+ * Shows the application version name.
+ */
+ public static class VersionItem extends Item {
+ @Override
+ protected int getResourceId() {
+ return R.layout.option_item_simple;
+ }
+
+ @Override
+ protected void onBind(View view) {
+ super.onBind(view);
+ TextView titleView = (TextView) view.findViewById(R.id.title);
+ titleView.setText(R.string.about_menu_version);
+ TextView descriptionView = (TextView) view.findViewById(R.id.description);
+ descriptionView.setText(TvApplication.getVersionName());
+ }
+
+ @Override
+ protected void onSelected() {
+ }
+ }
+
+ /**
+ * Opens a dialog showing open source licenses.
+ */
+ public static 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.about_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_about);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ protected List<Item> getItemList() {
+ List<Item> items = new ArrayList<>();
+ items.add(new VersionItem());
+ if (LicenseUtils.hasLicenses(getActivity().getAssets())) {
+ items.add(new LicenseActionItem((MainActivity) getActivity()));
+ }
+ return items;
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/ActionItem.java b/src/com/android/tv/ui/sidepanel/ActionItem.java
index 1ef862cd..c75eff9b 100644
--- a/src/com/android/tv/ui/sidepanel/ActionItem.java
+++ b/src/com/android/tv/ui/sidepanel/ActionItem.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -17,16 +17,32 @@
package com.android.tv.ui.sidepanel;
import android.view.View;
+import android.widget.ImageView;
import android.widget.TextView;
import com.android.tv.R;
-public class ActionItem extends Item {
+public abstract class ActionItem extends Item {
private final String mTitle;
- private TextView mTitleView;
+ private final String mDescription;
+ private final int mIconId;
public ActionItem(String title) {
+ this(title, null, 0);
+ }
+
+ 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
@@ -35,13 +51,23 @@ public class ActionItem extends Item {
}
@Override
- protected void bind(View view) {
- mTitleView = (TextView) view.findViewById(R.id.title);
- mTitleView.setText(mTitle);
- }
-
- @Override
- protected void unbind() {
- mTitleView = null;
+ protected void onBind(View view) {
+ super.onBind(view);
+ TextView titleView = (TextView) view.findViewById(R.id.title);
+ titleView.setText(mTitle);
+ TextView descriptionView = (TextView) view.findViewById(R.id.description);
+ if (mDescription != null) {
+ descriptionView.setVisibility(View.VISIBLE);
+ descriptionView.setText(mDescription);
+ } 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/BaseOptionFragment.java b/src/com/android/tv/ui/sidepanel/BaseOptionFragment.java
deleted file mode 100644
index b8d45acc..00000000
--- a/src/com/android/tv/ui/sidepanel/BaseOptionFragment.java
+++ /dev/null
@@ -1,104 +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.ui.sidepanel;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.RadioButton;
-
-import com.android.tv.R;
-import com.android.tv.TvActivity;
-
-public class BaseOptionFragment extends BaseSideFragment {
- private static final String TAG = "ClosedCaptionOptionFragment";
- private static final boolean DEBUG = true;
-
- private View mMainView;
- private boolean mClosingByItemSelected;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mMainView = super.onCreateView(inflater, container, savedInstanceState);
- return mMainView;
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- if (!mClosingByItemSelected) {
- ((TvActivity) getActivity()).onSideFragmentCanceled(getInitiator());
- }
- ((TvActivity) getActivity()).hideOverlays(false, false, true);
- }
-
- @Override
- public final void initialize(String title, Object[] itemTags, int fragmentLayoutId,
- int itemLayoutId, int itemBgColor, int itemFocusedBgColor, int itemHeightResId) {
- throw new UnsupportedOperationException("Call initialize(String title, Object[] itemTags)");
- }
-
- public void initialize(String title, Object[] itemTags) {
- super.initialize(title, itemTags, R.layout.option_fragment, R.layout.option_item,
- R.color.option_item_background, R.color.option_item_focused_background,
- R.dimen.option_item_height);
- }
-
- public void initialize(String title, int itemLayoutResId, Object[] itemTags) {
- super.initialize(title, itemTags, R.layout.option_fragment, itemLayoutResId,
- R.color.option_item_background, R.color.option_item_focused_background,
- R.dimen.option_item_height);
- }
-
- @Override
- public void onItemSelected(View v, int position, Object tag) {
- RadioButton radioButton = (RadioButton) v.findViewById(R.id.option_item);
- uncheckAllRadioButtons((ViewGroup) mMainView);
- radioButton.setChecked(true);
- mClosingByItemSelected = true;
- ((TvActivity) getActivity()).popFragmentBackStack();
- }
-
- @Override
- public void onBindView(View v, int position, Object tag, boolean prevSelected) {
- RadioButton radioButton = (RadioButton) v.findViewById(R.id.option_item);
- if (prevSelected) {
- radioButton.setChecked(true);
- } else {
- radioButton.setChecked(false);
- }
- if (tag instanceof String) {
- radioButton.setText((String) tag);
- } else {
- radioButton.setText("");
- }
- }
-
- private static void uncheckAllRadioButtons(ViewGroup parent) {
- int count = parent.getChildCount();
- for (int i = 0; i < count; ++i) {
- View v = parent.getChildAt(i);
- if (v instanceof ViewGroup) {
- uncheckAllRadioButtons((ViewGroup) v);
- } else if (v instanceof RadioButton) {
- ((RadioButton) v).setChecked(false);
- }
- }
- }
-}
diff --git a/src/com/android/tv/ui/sidepanel/BaseSideFragment.java b/src/com/android/tv/ui/sidepanel/BaseSideFragment.java
deleted file mode 100644
index 5b5343b0..00000000
--- a/src/com/android/tv/ui/sidepanel/BaseSideFragment.java
+++ /dev/null
@@ -1,191 +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.ui.sidepanel;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v7.widget.RecyclerView;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.internal.util.Preconditions;
-import com.android.tv.R;
-
-public class BaseSideFragment extends Fragment {
- private static final String TAG = "BaseSideFragment";
-
- public static final String KEY_INITIATOR = "last_state";
- public static final int INITIATOR_UNKNOWN = 0;
- public static final int INITIATOR_SHORTCUT_KEY = 1;
- public static final int INITIATOR_MENU = 2;
-
- private String mTitle;
- private TextView mTitleView;
- private VerticalGridView mOptionItemListView;
- private LayoutInflater mLayoutInflater;
- private Object[] mItemTags;
- private int mPrevSelectedItemPosition;
- private int mFragmentLayoutId;
- private int mItemLayoutId;
- private int mInitiator;
- private Handler mHandler = new Handler();
- private int mItemBgColor;
- private int mItemFocusedBgColor;
- private int mItemHeight;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- // initialize should be called before onCreateView.
- Preconditions.checkState(!TextUtils.isEmpty(mTitle));
- Bundle arg = getArguments();
- if (arg != null && arg.containsKey(KEY_INITIATOR)) {
- mInitiator = arg.getInt(KEY_INITIATOR);
- } else {
- mInitiator = INITIATOR_UNKNOWN;
- }
-
- View fragView = inflater.inflate(mFragmentLayoutId, container, false);
- mTitleView = (TextView) fragView.findViewById(R.id.side_panel_title);
- mTitleView.setText(mTitle);
- mOptionItemListView = (VerticalGridView) fragView.findViewById(R.id.side_panel_list);
- mOptionItemListView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- int padding = ((v.getHeight() + mItemHeight) / 2) % mItemHeight + mItemHeight;
- v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), padding);
- }
- });
-
- mOptionItemListView.setAdapter(new OptionItemAdapter());
- mLayoutInflater = inflater;
- return fragView;
- }
-
- public int getInitiator() {
- return mInitiator;
- }
-
- public void notifyDataSetChanged() {
- if (mOptionItemListView != null) {
- mOptionItemListView.getAdapter().notifyDataSetChanged();
- }
- }
-
- public void setPrevSelectedItemPosition(int position) {
- mPrevSelectedItemPosition = position;
- notifyDataSetChanged();
- }
-
- public void onItemFocusChanged(View v, boolean focusGained, int position, Object tag) {
- v.setBackgroundColor(focusGained ? mItemFocusedBgColor : mItemBgColor);
- }
-
- public void onItemSelected(View v, int position, Object tag) {
- }
-
- public void onCreatedChildView(View v) {
- }
-
- public void onBindView(View v, int position, Object tag, boolean prevSelected) {
- }
-
- public void setSelectedPosition(int position) {
- mOptionItemListView.setSelectedPosition(position);
- mOptionItemListView.requestFocus();
- }
-
- protected void initialize(String title, Object[] itemTags, int fragmentLayoutId,
- int itemLayoutId, int itemBgColorResId, int itemFocusedBgColorResId,
- int itemHeightResId) {
- Preconditions.checkState(!TextUtils.isEmpty(title));
- mTitle = title;
- mItemTags = itemTags;
- mFragmentLayoutId = fragmentLayoutId;
- mItemLayoutId = itemLayoutId;
- mItemBgColor = getActivity().getResources().getColor(itemBgColorResId);
- mItemFocusedBgColor = getActivity().getResources().getColor(itemFocusedBgColorResId);
- mItemHeight = getActivity().getResources().getDimensionPixelOffset(itemHeightResId);
- notifyDataSetChanged();
- }
-
- class OptionItemAdapter extends RecyclerView.Adapter<OptionItemAdapter.MyViewHolder> {
- @Override
- public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View v = mLayoutInflater.inflate(mItemLayoutId, parent, false);
- v.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- int position = (Integer) v.getTag(R.id.TAG_OPTION_ITEM_POSITOIN);
- onItemSelected(v, position, mItemTags[position]);
- }
- });
- v.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean focusGained) {
- int position = (Integer) v.getTag(R.id.TAG_OPTION_ITEM_POSITOIN);
- onItemFocusChanged(v, focusGained, position, mItemTags[position]);
- }
- });
- onCreatedChildView(v);
- return new MyViewHolder(v);
- }
-
- @Override
- public void onBindViewHolder(MyViewHolder baseHolder, int position) {
- View v = baseHolder.itemView;
- v.setVisibility(View.VISIBLE);
- onBindView(v, position, mItemTags[position], position == mPrevSelectedItemPosition);
- v.setTag(R.id.TAG_OPTION_ITEM_POSITOIN, position);
- if (v instanceof ViewGroup) {
- final ViewGroup viewGroup = (ViewGroup) v;
- mHandler.post(new Runnable() {
- void requestLayout(ViewGroup v) {
- for (int i = 0; i < v.getChildCount(); i++) {
- v.getChildAt(i).requestLayout();
- if (v.getChildAt(i) instanceof ViewGroup) {
- requestLayout((ViewGroup) v.getChildAt(i));
- }
- }
- }
-
- @Override
- public void run() {
- requestLayout(viewGroup);
- }
- });
- }
- }
-
- @Override
- public int getItemCount() {
- return mItemTags == null ? 0 : mItemTags.length;
- }
-
- private class MyViewHolder extends RecyclerView.ViewHolder {
- MyViewHolder(View view) {
- super(view);
- }
- }
- }
-}
diff --git a/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java b/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java
new file mode 100644
index 00000000..8389675e
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java
@@ -0,0 +1,130 @@
+/*
+ * 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.text.TextUtils;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.tv.R;
+import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.ChannelDataManager.ChannelListener;
+import com.android.tv.data.OnCurrentProgramUpdatedListener;
+import com.android.tv.data.Program;
+import com.android.tv.data.ProgramDataManager;
+
+public abstract class ChannelCheckItem extends CompoundButtonItem {
+ private final ChannelDataManager mChannelDataManager;
+ private final ProgramDataManager mProgramDataManager;
+ 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,
+ ChannelDataManager channelDataManager,
+ ProgramDataManager programDataManager) {
+ super(channel.getDisplayName(), "");
+ mChannel = channel;
+ mChannelDataManager = channelDataManager;
+ mProgramDataManager = programDataManager;
+ }
+
+ protected Channel getChannel() {
+ return mChannel;
+ }
+
+ @Override
+ protected int getResourceId() {
+ return R.layout.option_item_channel_check;
+ }
+
+ @Override
+ protected int getCompoundButtonId() {
+ return R.id.check_box;
+ }
+
+ @Override
+ protected int getTitleViewId() {
+ return R.id.channel_name;
+ }
+
+ @Override
+ protected int getDescriptionViewId() {
+ return R.id.program_title;
+ }
+
+ @Override
+ protected void onBind(View view) {
+ super.onBind(view);
+ 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);
+ }
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ mChannelNumberView.setText(getChannel().getDisplayNumber());
+ updateProgramTitle(mProgramDataManager.getCurrentProgram(mChannel.getId()));
+ }
+
+ @Override
+ protected void onUnbind() {
+ mChannelDataManager.removeChannelListener(mChannel.getId(), mChannelListener);
+ mProgramDataManager.removeOnCurrentProgramUpdatedListener(mChannel.getId(),
+ mOnCurrentProgramUpdatedListener);
+ mProgramTitleView = null;
+ mChannelNumberView = null;
+ super.onUnbind();
+ }
+
+ @Override
+ protected void onSelected() {
+ setChecked(!isChecked());
+ }
+
+ private void updateProgramTitle(Program program) {
+ String title = null;
+ if (program != null) {
+ title = program.getTitle();
+ }
+ if (TextUtils.isEmpty(title)) {
+ title = mProgramTitleView.getContext().getString(R.string.no_program_information);
+ }
+ mProgramTitleView.setText(title);
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/ChannelSourcesFragment.java b/src/com/android/tv/ui/sidepanel/ChannelSourcesFragment.java
new file mode 100644
index 00000000..a1d527a5
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/ChannelSourcesFragment.java
@@ -0,0 +1,97 @@
+/*
+ * 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.view.View;
+import android.widget.Toast;
+
+import com.android.tv.MainActivity;
+import com.android.tv.R;
+import com.android.tv.util.SetupUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ChannelSourcesFragment extends SideFragment {
+ private static final String TRACKER_LABEL = "channel sources";
+ private final long mCurrentChannelId;
+
+ public ChannelSourcesFragment(long currentChannelId) {
+ mCurrentChannelId = currentChannelId;
+ }
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.side_panel_title_channel_sources);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ protected List<Item> getItemList() {
+ List<Item> items = new ArrayList<>();
+ final Item customizeChannelListItem = new SubMenuItem(
+ getString(R.string.channel_source_item_customize_channels),
+ getString(R.string.channel_source_item_customize_channels_description),
+ 0, getMainActivity().getOverlayManager().getSideFragmentManager()) {
+ @Override
+ protected SideFragment getFragment() {
+ return new CustomizeChannelListFragment(mCurrentChannelId);
+ }
+
+ @Override
+ protected void onBind(View view) {
+ super.onBind(view);
+ setEnabled(false);
+ }
+
+ @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.channel_source_item_setup),
+ hasNewInput ? getString(R.string.channel_source_item_setup_new_inputs)
+ : null) {
+ @Override
+ protected void onSelected() {
+ closeFragment();
+ activity.getOverlayManager().showSetupDialog();
+ }
+ });
+ return items;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (getChannelDataManager().areAllChannelsHidden()) {
+ Toast.makeText(getActivity(), R.string.msg_all_channels_hidden, Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/CheckBoxItem.java b/src/com/android/tv/ui/sidepanel/CheckBoxItem.java
index f61a5554..205f1bc8 100644
--- a/src/com/android/tv/ui/sidepanel/CheckBoxItem.java
+++ b/src/com/android/tv/ui/sidepanel/CheckBoxItem.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,52 +16,65 @@
package com.android.tv.ui.sidepanel;
+import android.util.TypedValue;
+import android.view.Gravity;
import android.view.View;
-import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
import com.android.tv.R;
-public class CheckBoxItem extends Item {
- private final String mTitle;
- private boolean mChecked;
- private CheckBox mCheckBox;
+public class CheckBoxItem extends CompoundButtonItem {
+ private final boolean mLayoutForLargeDescription;
public CheckBoxItem(String title) {
- mTitle = title;
+ this(title, null);
}
- @Override
- protected int getResourceId() {
- return R.layout.option_item_check_box;
+ public CheckBoxItem(String title, String description) {
+ this(title, description, false);
}
- @Override
- protected void bind(View view) {
- mCheckBox = (CheckBox) view.findViewById(R.id.check_box);
- mCheckBox.setText(mTitle);
- mCheckBox.setChecked(mChecked);
+ public CheckBoxItem(String title, String description, boolean layoutForLargeDescription) {
+ super(title, description);
+ mLayoutForLargeDescription = layoutForLargeDescription;
}
@Override
- protected void unbind() {
- mCheckBox = null;
+ protected void onBind(View view) {
+ super.onBind(view);
+ if (mLayoutForLargeDescription) {
+ CompoundButton checkBox = (CompoundButton) view.findViewById(getCompoundButtonId());
+ 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);
+ checkBox.setLayoutParams(lp);
+
+ TypedValue outValue = new TypedValue();
+ 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);
+ descriptionTextView.setLineSpacing(0, outValue.getFloat());
+ }
}
@Override
- protected void onSelected() {
- setChecked(!mChecked);
+ protected int getResourceId() {
+ return R.layout.option_item_check_box;
}
- public void setChecked(boolean checked) {
- if (mChecked != checked) {
- mChecked = checked;
- if (mCheckBox != null) {
- mCheckBox.setChecked(mChecked);
- }
- }
+ @Override
+ protected int getCompoundButtonId() {
+ return R.id.check_box;
}
- public boolean getChecked() {
- return mChecked;
+ @Override
+ 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 45693d1b..d6ccdf6b 100644
--- a/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java
+++ b/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java
@@ -1,41 +1,209 @@
+/*
+ * 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.widget.Toast;
+import android.media.tv.TvTrackInfo;
+import android.os.Bundle;
+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 com.android.tv.util.Utils;
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 boolean mResetClosedCaption;
+ private int mClosedCaptionOption;
+ private String mClosedCaptionLanguage;
+ private String mClosedCaptionTrackId;
+ private ClosedCaptionOptionItem mSelectedItem;
+ private List<Item> mItems;
+ private boolean mPaused;
+
+ public ClosedCaptionFragment() {
+ super(KeyEvent.KEYCODE_CAPTIONS, KeyEvent.KEYCODE_S);
+ }
+
@Override
protected String getTitle() {
- return getString(R.string.closed_caption_option_title);
+ return getString(R.string.side_panel_title_closed_caption);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
}
@Override
protected List<Item> getItemList() {
- ArrayList<Item> items = new ArrayList<>();
- items.add(new RadioButtonItem(getString(R.string.option_item_on)) {
- @Override
- protected void onSelected() {
- super.onSelected();
- getTvActivity().setClosedCaptionEnabled(false, true);
- setClosedCaptionEnabled(true);
+ CaptionSettings captionSettings = getMainActivity().getCaptionSettings();
+ mResetClosedCaption = true;
+ mClosedCaptionOption = captionSettings.getEnableOption();
+ mClosedCaptionLanguage = captionSettings.getLanguage();
+ mClosedCaptionTrackId = captionSettings.getTrackId();
+
+ mItems = new ArrayList<>();
+ mSelectedItem = null;
+
+ List<TvTrackInfo> tracks = getMainActivity().getTracks(TvTrackInfo.TYPE_SUBTITLE);
+ if (tracks != null && !tracks.isEmpty()) {
+ String trackId = 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) {
+ item.setChecked(true);
}
- });
- items.add(new RadioButtonItem(getString(R.string.option_item_off)) {
- @Override
- protected void onSelected() {
- super.onSelected();
- setClosedCaptionEnabled(false);
+ 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);
+ mSelectedItem = item;
+ }
+ mItems.add(item);
}
- });
- return items;
+ }
+ if (getMainActivity().hasCaptioningSettingsActivity()) {
+ mItems.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 (!mPaused && mSelectedItem != null) {
+ getMainActivity().selectSubtitleTrack(
+ mSelectedItem.mOption, mSelectedItem.mTrackId);
+ }
+ }
+ });
+ }
+ return mItems;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return super.onCreateView(inflater, container, savedInstanceState);
}
- private void setClosedCaptionEnabled(boolean enabled) {
- getTvActivity().setClosedCaptionEnabled(enabled, true);
- Toast.makeText(getActivity(), R.string.not_implemented_yet, Toast.LENGTH_SHORT).show();
+ @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) {
+ getMainActivity().selectSubtitleLanguage(mClosedCaptionOption, mClosedCaptionLanguage,
+ mClosedCaptionTrackId);
+ }
+ super.onDestroyView();
+ }
+
+ private String getLabel(TvTrackInfo track) {
+ if (track.getLanguage() != null) {
+ return new Locale(track.getLanguage()).getDisplayName();
+ }
+ return getString(R.string.default_language);
+ }
+
+ 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;
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ mSelectedItem = this;
+ getMainActivity().selectSubtitleTrack(mOption, mTrackId);
+ mResetClosedCaption = false;
+ closeFragment();
+ }
+
+ @Override
+ protected void onFocused() {
+ super.onFocused();
+ getMainActivity().selectSubtitleTrack(mOption, mTrackId);
+ }
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/ui/sidepanel/ClosedCaptionOptionFragment.java b/src/com/android/tv/ui/sidepanel/ClosedCaptionOptionFragment.java
deleted file mode 100644
index 3db185ff..00000000
--- a/src/com/android/tv/ui/sidepanel/ClosedCaptionOptionFragment.java
+++ /dev/null
@@ -1,92 +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.ui.sidepanel;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Toast;
-
-import com.android.tv.R;
-import com.android.tv.TvActivity;
-
-public class ClosedCaptionOptionFragment extends BaseOptionFragment {
- private static final String TAG = "ClosedCaptionOptionFragment";
- private static final boolean DEBUG = true;
-
- private static final int CC_ON = 0;
- private static final int CC_OFF = 1;
-
- private TvActivity mTvActivity;
- private boolean mIsFirstResume;
- private boolean mLastStoredCcEnabled;
- private boolean mCcEnabled;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mIsFirstResume = true;
- mTvActivity = (TvActivity) getActivity();
-
- Object[] items = new Object[2];
- items[0] = getString(R.string.option_item_on);
- items[1] = getString(R.string.option_item_off);
-
- initialize(getString(R.string.closed_caption_option_title), items);
- return super.onCreateView(inflater, container, savedInstanceState);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (mIsFirstResume) {
- mCcEnabled = mTvActivity.isClosedCaptionEnabled();
- mLastStoredCcEnabled = mCcEnabled;
- int initialPosition = mCcEnabled ? CC_ON : CC_OFF;
- setSelectedPosition(initialPosition);
- setPrevSelectedItemPosition(initialPosition);
- mIsFirstResume = false;
- }
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- if (mLastStoredCcEnabled != mCcEnabled) {
- mTvActivity.restoreClosedCaptionEnabled();
- }
- }
-
- @Override
- public void onItemFocusChanged(View v, boolean focusGained, int position, Object tag) {
- super.onItemFocusChanged(v, focusGained, position, tag);
- if (focusGained) {
- mCcEnabled = (position == CC_ON);
- mTvActivity.setClosedCaptionEnabled(mCcEnabled, false);
- }
- }
-
- @Override
- public void onItemSelected(View v, int position, Object tag) {
- mCcEnabled = (position == CC_ON);
- mTvActivity.setClosedCaptionEnabled(mCcEnabled, true);
- mLastStoredCcEnabled = mCcEnabled;
- super.onItemSelected(v, position, tag);
- Toast.makeText(getActivity(), R.string.not_implemented_yet, Toast.LENGTH_SHORT).show();
- }
-}
diff --git a/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java b/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java
new file mode 100644
index 00000000..7613a9a2
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java
@@ -0,0 +1,97 @@
+/*
+ * 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.view.View;
+import android.widget.CompoundButton;
+import android.widget.TextView;
+
+import com.android.tv.R;
+
+public abstract class CompoundButtonItem extends Item {
+ private final String mCheckedTitle;
+ private final String mUncheckedTitle;
+ private final String mDescription;
+ private boolean mChecked;
+ private TextView mTextView;
+ private CompoundButton mCompoundButton;
+
+ public CompoundButtonItem(String title, String description) {
+ this(title, title, description);
+ }
+
+ public CompoundButtonItem(String checkedTitle, String uncheckedTitle, String description) {
+ mCheckedTitle = checkedTitle;
+ mUncheckedTitle = uncheckedTitle;
+ mDescription = description;
+ }
+
+ protected abstract int getCompoundButtonId();
+
+ protected int getTitleViewId() {
+ return R.id.title;
+ }
+
+ protected int getDescriptionViewId() {
+ return R.id.description;
+ }
+
+ @Override
+ protected void onBind(View view) {
+ super.onBind(view);
+ mCompoundButton = (CompoundButton) view.findViewById(getCompoundButtonId());
+ mTextView = (TextView) view.findViewById(getTitleViewId());
+ TextView descriptionView = (TextView) view.findViewById(getDescriptionViewId());
+ if (mDescription != null) {
+ descriptionView.setVisibility(View.VISIBLE);
+ descriptionView.setText(mDescription);
+ } else {
+ descriptionView.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ protected void onUnbind() {
+ super.onUnbind();
+ mTextView = null;
+ mCompoundButton = null;
+ }
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ updateInternal();
+ }
+
+ public void setChecked(boolean checked) {
+ if (mChecked != checked) {
+ mChecked = checked;
+ updateInternal();
+ }
+ }
+
+ public boolean isChecked() {
+ return mChecked;
+ }
+
+ private void updateInternal() {
+ if (isBound()) {
+ mTextView.setText(mChecked ? mCheckedTitle : mUncheckedTitle);
+ mCompoundButton.setChecked(mChecked);
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java
new file mode 100644
index 00000000..15b9d8c0
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java
@@ -0,0 +1,359 @@
+/*
+ * 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.TvContract.Channels;
+import android.os.Bundle;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.view.KeyEvent;
+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.data.Channel;
+import com.android.tv.data.ChannelNumber;
+import com.android.tv.util.TvInputManagerHelper;
+import com.android.tv.util.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+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<Channel> mChannels = new ArrayList<>();
+ private final long mInitialChannelId;
+
+ private long mLastFocusedChannelId = Channel.INVALID_ID;
+
+ private int mGroupingType = GROUP_BY_SOURCE;
+ private TvInputManagerHelper mInputManager;
+ private Channel.DefaultComparator mChannelComparator;
+ private boolean mGroupByFragmentRunning;
+
+ private final List<Item> mItems = new ArrayList<>();
+
+ private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
+ new VerticalGridView.OnKeyInterceptListener() {
+ @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;
+ }
+ }
+ return false;
+ }
+ };
+
+ public CustomizeChannelListFragment() {
+ this(Channel.INVALID_ID);
+ }
+
+ public CustomizeChannelListFragment(long initialChannelId) {
+ mInitialChannelId = initialChannelId;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mInputManager = getMainActivity().getTvInputManagerHelper();
+ mChannelComparator = new Channel.DefaultComparator(getActivity(), mInputManager);
+ }
+
+ @Override
+ 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(mOnKeyInterceptListener);
+
+ if (!mGroupByFragmentRunning) {
+ getMainActivity().startShrunkenTvView(false, true);
+
+ int initialChannelPosition = INVALID_POSITION;
+ int i = 0;
+ for (Item item : mItems) {
+ if (item instanceof ChannelItem
+ && ((ChannelItem) item).getChannel().getId() == mInitialChannelId) {
+ initialChannelPosition = i;
+ break;
+ }
+ ++i;
+ }
+ if (initialChannelPosition != INVALID_POSITION) {
+ setSelectedPosition(initialChannelPosition);
+ } else {
+ setSelectedPosition(0);
+ }
+ mLastFocusedChannelId = mInitialChannelId;
+ MainActivity tvActivity = getMainActivity();
+ if (mLastFocusedChannelId != Channel.INVALID_ID &&
+ mLastFocusedChannelId != tvActivity.getCurrentChannelId()) {
+ tvActivity.tuneToChannel(getChannelDataManager().getChannel(mLastFocusedChannelId));
+ }
+ }
+ mGroupByFragmentRunning = false;
+ return view;
+ }
+
+ @Override
+ public void onDestroyView() {
+ getChannelDataManager().applyUpdatedValuesToDb();
+ if (!mGroupByFragmentRunning) {
+ getMainActivity().endShrunkenTvView();
+ }
+ super.onDestroyView();
+ }
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.side_panel_title_edit_channels_for_an_input);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ protected List<Item> getItemList() {
+ mItems.clear();
+ mChannels.clear();
+ mChannels.addAll(getChannelDataManager().getChannelList());
+ if (mGroupingType == GROUP_BY_SOURCE) {
+ addItemForGroupBySource(mItems);
+ } else {
+ // GROUP_BY_HD_SD
+ addItemForGroupByHdSd(mItems);
+ }
+ return mItems;
+ }
+
+ private void addItemForGroupBySource(List<Item> items) {
+ items.add(new GroupBySubMenu(getString(R.string.edit_channels_group_by_sources)));
+ SelectGroupItem selectGroupItem = null;
+ ArrayList<Channel> channels = new ArrayList<>(mChannels);
+ Collections.sort(channels, mChannelComparator);
+
+ String inputId = null;
+ for (Channel channel: channels) {
+ if (!channel.getInputId().equals(inputId)) {
+ inputId = channel.getInputId();
+ String inputLabel = Utils.loadLabel(getActivity(),
+ mInputManager.getTvInputInfo(inputId));
+ items.add(new DividerItem(inputLabel));
+ selectGroupItem = new SelectGroupItem();
+ items.add(selectGroupItem);
+ }
+ ChannelItem channelItem = new ChannelItem(channel, selectGroupItem);
+ items.add(channelItem);
+ selectGroupItem.addChannelItem(channelItem);
+ }
+ }
+
+ private void addItemForGroupByHdSd(List<Item> items) {
+ items.add(new GroupBySubMenu(getString(R.string.edit_channels_group_by_hd_sd)));
+ SelectGroupItem selectGroupItem = null;
+ ArrayList<Channel> channels = new ArrayList<>(mChannels);
+ Collections.sort(channels, new Comparator<Channel>() {
+ @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) {
+ 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)));
+ selectGroupItem = new SelectGroupItem();
+ items.add(selectGroupItem);
+ }
+ ChannelItem channelItem = new ChannelItem(channel, selectGroupItem);
+ items.add(channelItem);
+ selectGroupItem.addChannelItem(channelItem);
+ }
+ }
+
+ private static boolean isHdChannel(Channel channel) {
+ String videoFormat = channel.getVideoFormat();
+ 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)
+ || Channels.VIDEO_FORMAT_4320P.equals(videoFormat));
+ }
+
+ private class SelectGroupItem extends ActionItem {
+ private final List<ChannelItem> mChannelItemsInGroup = new ArrayList<>();
+ private TextView mTextView;
+ private boolean mAllChecked;
+
+ public SelectGroupItem() {
+ super(null);
+ }
+
+ private void addChannelItem(ChannelItem channelItem) {
+ mChannelItemsInGroup.add(channelItem);
+ }
+
+ @Override
+ protected void onBind(View view) {
+ super.onBind(view);
+ mTextView = (TextView) view.findViewById(R.id.title);
+ }
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ mAllChecked = true;
+ for (ChannelItem channelItem : mChannelItemsInGroup) {
+ if (!channelItem.getChannel().isBrowsable()) {
+ mAllChecked = false;
+ break;
+ }
+ }
+ mTextView.setText(getString(mAllChecked
+ ? R.string.edit_channels_item_deselect_group
+ : R.string.edit_channels_item_select_group));
+ }
+
+ @Override
+ protected void onSelected() {
+ for (ChannelItem channelItem : mChannelItemsInGroup) {
+ Channel channel = channelItem.getChannel();
+ if (channel.isBrowsable() == mAllChecked) {
+ getChannelDataManager().updateBrowsable(channel.getId(), !mAllChecked, true);
+ channelItem.notifyUpdated();
+ }
+ }
+ getChannelDataManager().notifyChannelBrowsableChanged();
+ mAllChecked = !mAllChecked;
+ mTextView.setText(getString(mAllChecked
+ ? R.string.edit_channels_item_deselect_group
+ : R.string.edit_channels_item_select_group));
+ }
+ }
+
+ private class ChannelItem extends ChannelCheckItem {
+ private final SelectGroupItem mSelectGroupItem;
+
+ public ChannelItem(Channel channel, SelectGroupItem selectGroupItem) {
+ super(channel, getChannelDataManager(), getProgramDataManager());
+ mSelectGroupItem = selectGroupItem;
+ }
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ setChecked(getChannel().isBrowsable());
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ getChannelDataManager().updateBrowsable(getChannel().getId(), isChecked());
+ mSelectGroupItem.notifyUpdated();
+ }
+
+ @Override
+ protected void onFocused() {
+ super.onFocused();
+ mLastFocusedChannelId = getChannel().getId();
+ }
+ }
+
+ 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,
+ getMainActivity().getOverlayManager().getSideFragmentManager());
+ }
+
+ @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<Item> getItemList() {
+ List<Item> 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;
+ }
+ };
+ }
+
+ @Override
+ protected void onSelected() {
+ mGroupByFragmentRunning = true;
+ super.onSelected();
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/DebugOptionFragment.java b/src/com/android/tv/ui/sidepanel/DebugOptionFragment.java
new file mode 100644
index 00000000..35cc18c2
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/DebugOptionFragment.java
@@ -0,0 +1,48 @@
+/*
+ * 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 com.android.tv.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DebugOptionFragment extends SideFragment {
+ private static final String TRACKER_LABEL = "debug options";
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.menu_debug_options);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ protected List<Item> getItemList() {
+ List<Item> items = new ArrayList<>();
+ items.add(new ActionItem(getString(R.string.item_watch_history)) {
+ @Override
+ protected void onSelected() {
+ getMainActivity().getOverlayManager().showRecentlyWatchedDialog();
+ }
+ });
+ return items;
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/DisplayModeFragment.java b/src/com/android/tv/ui/sidepanel/DisplayModeFragment.java
index 120ef9e3..b084a115 100644
--- a/src/com/android/tv/ui/sidepanel/DisplayModeFragment.java
+++ b/src/com/android/tv/ui/sidepanel/DisplayModeFragment.java
@@ -1,30 +1,97 @@
+/*
+ * 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.app.Activity;
+
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;
public class DisplayModeFragment extends SideFragment {
+ private static final String TRACKER_LABEL = "display mode";
+ private TvViewUiManager mTvViewUiManager;
+
@Override
protected String getTitle() {
- return getString(R.string.display_mode_option_title);
+ return getString(R.string.side_panel_title_display_mode);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
}
@Override
protected List<Item> getItemList() {
- ArrayList<Item> items = new ArrayList<>();
+ List<Item> items = new ArrayList<>();
for (int i = 0; i < DisplayMode.SIZE_OF_RATIO_TYPES; ++i) {
- final int displayMode = i;
- items.add(new RadioButtonItem(DisplayMode.getLabel(i, getActivity())) {
- @Override
- protected void onSelected() {
- super.onSelected();
- getTvActivity().setDisplayMode(displayMode, true);
- }
- });
+ items.add(new DisplayModeRadioItem(i));
}
return items;
}
-} \ No newline at end of file
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mTvViewUiManager = getMainActivity().getTvViewUiManager();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ setSelectedPosition(mTvViewUiManager.getDisplayMode());
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mTvViewUiManager.restoreDisplayMode(true);
+ }
+
+ private class DisplayModeRadioItem extends RadioButtonItem {
+ private final int mDisplayMode;
+
+ private DisplayModeRadioItem(int displayMode) {
+ super(DisplayMode.getLabel(displayMode, getActivity()));
+ mDisplayMode = displayMode;
+ }
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ setEnabled(mTvViewUiManager.isDisplayModeAvailable(mDisplayMode));
+ setChecked(mDisplayMode == mTvViewUiManager.getDisplayMode());
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ mTvViewUiManager.setDisplayMode(mDisplayMode, true, true);
+ closeFragment();
+ }
+
+ @Override
+ protected void onFocused() {
+ super.onFocused();
+ mTvViewUiManager.setDisplayMode(mDisplayMode, false, true);
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/DisplayModeOptionFragment.java b/src/com/android/tv/ui/sidepanel/DisplayModeOptionFragment.java
deleted file mode 100644
index 43b446d4..00000000
--- a/src/com/android/tv/ui/sidepanel/DisplayModeOptionFragment.java
+++ /dev/null
@@ -1,88 +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.ui.sidepanel;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.tv.R;
-import com.android.tv.TvActivity;
-import com.android.tv.data.DisplayMode;
-
-public class DisplayModeOptionFragment extends BaseOptionFragment {
- private static final String TAG = "AspectRatioOptionFragment";
- private static final boolean DEBUG = true;
-
- private TvActivity mTvActivity;
- private boolean mIsFirstResume;
- private int mLastStoredAspectRatio;
- private int mAspectRatio;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mIsFirstResume = true;
- mTvActivity = (TvActivity) getActivity();
-
- Object[] items = new Object[DisplayMode.SIZE_OF_RATIO_TYPES];
- for (int i = 0; i < DisplayMode.SIZE_OF_RATIO_TYPES; ++i) {
- items[i] = DisplayMode.getLabel(i, getActivity());
- }
- initialize(getString(R.string.display_mode_option_title), items);
- return super.onCreateView(inflater, container, savedInstanceState);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (mIsFirstResume) {
- mAspectRatio = mTvActivity.getDisplayMode();
- mLastStoredAspectRatio = mAspectRatio;
- int initialPosition = mAspectRatio;
- setSelectedPosition(initialPosition);
- setPrevSelectedItemPosition(initialPosition);
- mIsFirstResume = false;
- }
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- if (mLastStoredAspectRatio != mAspectRatio) {
- mTvActivity.restoreDisplayMode();
- }
- }
-
- @Override
- public void onItemFocusChanged(View v, boolean focusGained, int position, Object tag) {
- super.onItemFocusChanged(v, focusGained, position, tag);
- if (focusGained) {
- mAspectRatio = position;
- mTvActivity.setDisplayMode(position, false);
- }
- }
-
- @Override
- public void onItemSelected(View v, int position, Object tag) {
- mAspectRatio = position;
- mTvActivity.setDisplayMode(mAspectRatio, true);
- mLastStoredAspectRatio = mAspectRatio;
- super.onItemSelected(v, position, tag);
- }
-}
diff --git a/src/com/android/tv/ui/sidepanel/DividerItem.java b/src/com/android/tv/ui/sidepanel/DividerItem.java
index 019e6454..0edf8488 100644
--- a/src/com/android/tv/ui/sidepanel/DividerItem.java
+++ b/src/com/android/tv/ui/sidepanel/DividerItem.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,11 +16,46 @@
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(String title) {
+ mTitle = title;
+ }
+
@Override
protected int getResourceId() {
return R.layout.option_item_divider;
}
-} \ No newline at end of file
+
+ @Override
+ protected void onBind(View view) {
+ super.onBind(view);
+ mTitleView = (TextView) view.findViewById(R.id.title);
+ if (mTitle == null) {
+ mTitleView.setVisibility(View.GONE);
+ view.setMinimumHeight(0);
+ } else {
+ mTitleView.setVisibility(View.VISIBLE);
+ mTitleView.setText(mTitle);
+ view.setMinimumHeight(view.getContext().getResources().getDimensionPixelOffset(
+ R.dimen.option_item_height));
+ }
+ }
+
+ @Override
+ protected void onUnbind() {
+ mTitleView = null;
+ }
+
+ @Override
+ protected void onSelected() { }
+}
diff --git a/src/com/android/tv/ui/sidepanel/EditChannelsFragment.java b/src/com/android/tv/ui/sidepanel/EditChannelsFragment.java
deleted file mode 100644
index 48d21fd3..00000000
--- a/src/com/android/tv/ui/sidepanel/EditChannelsFragment.java
+++ /dev/null
@@ -1,209 +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.ui.sidepanel;
-
-import android.content.ContentValues;
-import android.media.tv.TvContract;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckBox;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.internal.util.Preconditions;
-import com.android.tv.R;
-import com.android.tv.TvActivity;
-import com.android.tv.data.Channel;
-import com.android.tv.input.TvInput;
-
-public class EditChannelsFragment extends BaseSideFragment {
- private static final int ACTION_SHOW_ALL = 0;
- private static final int ACTION_HIDE_ALL = 1;
-
- private TvInput mSelectedInput;
-
- private TvActivity mTvActivity;
- private Item[] mItems;
- private String[] mActions;
- private Channel[] mChannels;
- private int mBrowsableChannelCount;
-
- private static final class Item {
- private static final int TYPE_ACTION = 0;
- private static final int TYPE_CHANNEL = 1;
- private static final int TYPE_DIVIDER = 2;
-
- private Item(int type, int action, Channel channel) {
- mType = type;
- mAction = action;
- Preconditions.checkState(!(type == TYPE_CHANNEL && channel == null));
- mChannel = channel;
- }
-
- private int mType;
- private int mAction;
- private Channel mChannel;
- }
-
- public EditChannelsFragment(Channel[] channels) {
- mChannels = channels;
- mBrowsableChannelCount = 0;
- for (Channel channel : channels) {
- if (channel.isBrowsable()) {
- ++mBrowsableChannelCount;
- }
- }
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- mTvActivity.onSideFragmentCanceled(getInitiator());
- mTvActivity.hideOverlays(false, false, true);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mTvActivity = (TvActivity) getActivity();
- mSelectedInput = mTvActivity.getSelectedTvInput();
-
- mActions = getActivity().getResources().getStringArray(R.array.edit_channels_actions);
- mItems = new Item[mActions.length + mChannels.length + 1];
- int index = 0;
- for (; index < mActions.length; ++index) {
- mItems[index] = new Item(Item.TYPE_ACTION, index ,null);
- }
- mItems[index++] = new Item(Item.TYPE_DIVIDER, 0, null);
- for (Channel channel : mChannels) {
- mItems[index++] = new Item(Item.TYPE_CHANNEL, 0, channel);
- }
- String displayName = mSelectedInput.getDisplayName();
- String title = String.format(getString(R.string.edit_channels_title), displayName);
- initialize(title, mItems, R.layout.option_fragment, R.layout.edit_channels_item,
- R.color.option_item_background, R.color.option_item_focused_background,
- R.dimen.edit_channels_item_height);
-
- if (mBrowsableChannelCount <= 0) {
- Toast.makeText(getActivity(), R.string.all_the_channels_are_unchecked,
- Toast.LENGTH_SHORT).show();
- }
-
- return super.onCreateView(inflater, container, savedInstanceState);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- // TODO: the current channel should be initially focused.
- setSelectedPosition(0);
- }
-
- @Override
- public void onBindView(View v, int position, Object tag, boolean prevSelected) {
- super.onBindView(v, position, tag, prevSelected);
-
- CheckBox checkBox = (CheckBox) v.findViewById(R.id.check_box);
- TextView textView = (TextView) v.findViewById(R.id.channel_text_view);
- View itemContainer = v.findViewById(R.id.item_container);
- View divider = v.findViewById(R.id.divider);
-
- Item item = (Item) tag;
- if (item.mType == Item.TYPE_ACTION) {
- checkBox.setVisibility(View.GONE);
- textView.setText(mActions[item.mAction]);
- } else if (item.mType == Item.TYPE_CHANNEL) {
- checkBox.setVisibility(View.VISIBLE);
- checkBox.setChecked(item.mChannel.isBrowsable());
-
- String channelNumber = item.mChannel.getDisplayNumber();
- String channelName = item.mChannel.getDisplayName();
- String channelString;
- if (TextUtils.isEmpty(channelName)) {
- channelString = channelNumber;
- } else {
- channelString = String.format(getString(R.string.channel_item),
- channelNumber, channelName);
- }
- textView.setText(channelString);
- }
- divider.setVisibility(item.mType == Item.TYPE_DIVIDER ? View.VISIBLE : View.GONE);
- itemContainer.setVisibility(item.mType != Item.TYPE_DIVIDER ? View.VISIBLE : View.GONE);
- v.setFocusable(item.mType != Item.TYPE_DIVIDER);
- }
-
- @Override
- public void onItemSelected(View v, int position, Object tag) {
- Item item = (Item) tag;
- if (item.mType == Item.TYPE_ACTION) {
- if (item.mAction == ACTION_SHOW_ALL) {
- updateAllChannels(true);
- } else if (item.mAction == ACTION_HIDE_ALL) {
- updateAllChannels(false);
- }
- } else if (item.mType == Item.TYPE_CHANNEL) {
- CheckBox checkBox = (CheckBox) v.findViewById(R.id.check_box);
- boolean checked = checkBox.isChecked();
-
- Channel channel = item.mChannel;
- Uri uri = TvContract.buildChannelUri(channel.getId());
- ContentValues values = new ContentValues();
- values.put(TvContract.Channels.COLUMN_BROWSABLE, checked ? 0 : 1);
- getActivity().getContentResolver().update(uri, values, null, null);
- channel.setBrowsable(!checked);
-
- checkBox.setChecked(!checked);
- mBrowsableChannelCount += checked ? -1 : 1;
- if (mBrowsableChannelCount <= 0) {
- Toast.makeText(getActivity(), R.string.all_the_channels_are_unchecked,
- Toast.LENGTH_SHORT).show();
- }
- }
-
- super.onItemSelected(v, position, tag);
- }
-
- private void updateAllChannels(boolean browsable) {
- Uri uri = mSelectedInput.buildChannelsUri(null);
- ContentValues values = new ContentValues();
- values.put(TvContract.Channels.COLUMN_BROWSABLE, browsable ? 1 : 0);
-
- getActivity().getContentResolver().update(uri, values, null, null);
-
- for (Channel channel : mChannels) {
- channel.setBrowsable(browsable);
- }
- notifyDataSetChanged();
-
- if (browsable) {
- mBrowsableChannelCount = mChannels.length;
- } else {
- mBrowsableChannelCount = 0;
- Toast.makeText(getActivity(), R.string.all_the_channels_are_unchecked,
- Toast.LENGTH_SHORT).show();
- }
- }
-
- @Override
- public void onItemFocusChanged(View v, boolean focusGained, int position, Object tag) {
- super.onItemFocusChanged(v, focusGained, position, tag);
- }
-}
diff --git a/src/com/android/tv/ui/sidepanel/InputPickerFragment.java b/src/com/android/tv/ui/sidepanel/InputPickerFragment.java
deleted file mode 100644
index 8a05cc1e..00000000
--- a/src/com/android/tv/ui/sidepanel/InputPickerFragment.java
+++ /dev/null
@@ -1,115 +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.ui.sidepanel;
-
-import android.media.tv.TvInputInfo;
-import android.media.tv.TvInputManager;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.RadioButton;
-
-import com.android.internal.util.Preconditions;
-import com.android.tv.R;
-import com.android.tv.TvActivity;
-import com.android.tv.input.TisTvInput;
-import com.android.tv.input.TvInput;
-import com.android.tv.input.UnifiedTvInput;
-import com.android.tv.util.TvInputManagerHelper;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Comparator;
-
-public class InputPickerFragment extends BaseOptionFragment {
- private TvInput mSelectedInput;
-
- private TvActivity mTvActivity;
- private boolean mIsFirstResume;
- private int mInitialPosition;
-
- private TvInputManagerHelper mInputManager;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mIsFirstResume = true;
- mTvActivity = (TvActivity) getActivity();
- mInputManager = mTvActivity.getTvInputManagerHelper();
- mInputManager.update();
- Collection<TvInputInfo> inputInfos = mInputManager.getTvInputInfos(false);
- int inputSize = inputInfos.size();
- Preconditions.checkState(inputSize > 0);
- mSelectedInput = mTvActivity.getSelectedTvInput();
-
- Object[] items = new Object[inputSize + 1];
- // Unified TV input is always the first item.
- items[0] = new UnifiedTvInput(mInputManager, getActivity());
- int i = 1;
- for (TvInputInfo inputInfo : inputInfos) {
- items[i++] = new TisTvInput(mInputManager, inputInfo, mTvActivity);
- }
- Arrays.sort(items, 1, items.length, new Comparator<Object>() {
- @Override
- public int compare(Object lhs, Object rhs) {
- return ((TvInput) lhs).getDisplayName().compareTo(((TvInput) rhs).getDisplayName());
- }
- });
-
- mInitialPosition = 0;
- for (i = 0; i < items.length; ++i) {
- if (items[i].equals(mSelectedInput)) {
- mInitialPosition = i;
- break;
- }
- }
- initialize(getString(R.string.select_input_device), items);
- return super.onCreateView(inflater, container, savedInstanceState);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (mIsFirstResume) {
- setSelectedPosition(mInitialPosition);
- setPrevSelectedItemPosition(mInitialPosition);
- mIsFirstResume = false;
- }
- }
-
- @Override
- public void onBindView(View v, int position, Object tag, boolean prevSelected) {
- super.onBindView(v, position, tag, prevSelected);
- TvInput input = (TvInput) tag;
- boolean available = input.getInputState() != TvInputManager.INPUT_STATE_DISCONNECTED;
- v.setEnabled(available);
- v.setClickable(available);
-
- RadioButton radioButton = (RadioButton) v.findViewById(R.id.option_item);
- radioButton.setEnabled(available);
- radioButton.setText(input.getDisplayName());
- }
-
- @Override
- public void onItemSelected(View v, int position, Object tag) {
- if (!((TvInput) tag).equals(mSelectedInput)) {
- mTvActivity.onInputPicked((TvInput) tag);
- }
- super.onItemSelected(v, position, tag);
- }
-}
diff --git a/src/com/android/tv/ui/sidepanel/Item.java b/src/com/android/tv/ui/sidepanel/Item.java
index 53238fd7..da9b39b0 100644
--- a/src/com/android/tv/ui/sidepanel/Item.java
+++ b/src/com/android/tv/ui/sidepanel/Item.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -17,11 +17,73 @@
package com.android.tv.ui.sidepanel;
import android.view.View;
+import android.view.ViewGroup;
public abstract class Item {
+ private View mItemView;
+ private boolean mEnabled = true;
+
+ public void setEnabled(boolean enabled) {
+ if (mEnabled != enabled) {
+ mEnabled = enabled;
+ if (mItemView != null) {
+ setEnabledInternal(mItemView, enabled);
+ }
+ }
+ }
+
+ /**
+ * Returns whether this item is enabled.
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ public final void notifyUpdated() {
+ if (mItemView != null) {
+ onUpdate();
+ }
+ }
+
protected abstract int getResourceId();
- protected void bind(@SuppressWarnings("unused") View view) { }
- protected void unbind() { }
- protected void onSelected() { }
- protected void onFocused() { }
-} \ No newline at end of file
+
+ protected void onBind(View view) {
+ mItemView = view;
+ }
+
+ protected void onUnbind() {
+ mItemView = null;
+ }
+
+ /**
+ * 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);
+ }
+
+ protected abstract void onSelected();
+
+ protected void onFocused() {
+ }
+
+ /**
+ * Returns true if the item is bound, i.e., onBind is called.
+ */
+ protected boolean isBound() {
+ return mItemView != null;
+ }
+
+ private void setEnabledInternal(View view, boolean enabled) {
+ view.setEnabled(enabled);
+ if (view instanceof ViewGroup) {
+ ViewGroup parent = (ViewGroup) view;
+ int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; ++i) {
+ setEnabledInternal(parent.getChildAt(i), enabled);
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/MultiAudioFragment.java b/src/com/android/tv/ui/sidepanel/MultiAudioFragment.java
new file mode 100644
index 00000000..10c8c3e2
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/MultiAudioFragment.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.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;
+
+public class MultiAudioFragment extends SideFragment {
+ private static final String TRACKER_LABEL = "multi-audio";
+ private int mInitialSelectedPosition = INVALID_POSITION;
+ private String mSelectedTrackId;
+ private String mFocusedTrackId;
+
+ public MultiAudioFragment() {
+ super(KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK, KeyEvent.KEYCODE_A);
+ }
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.side_panel_title_multi_audio);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ protected List<Item> getItemList() {
+ List<TvTrackInfo> tracks = getMainActivity().getTracks(TvTrackInfo.TYPE_AUDIO);
+ mSelectedTrackId = getMainActivity().getSelectedTrack(TvTrackInfo.TYPE_AUDIO);
+
+ List<Item> items = new ArrayList<>();
+ if (tracks != null) {
+ 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());
+ if (track.getId().equals(mSelectedTrackId)) {
+ item.setChecked(true);
+ mInitialSelectedPosition = pos;
+ mSelectedTrackId = mFocusedTrackId = track.getId();
+ }
+ items.add(item);
+ ++pos;
+ }
+ }
+ return items;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mInitialSelectedPosition != INVALID_POSITION) {
+ setSelectedPosition(mInitialSelectedPosition);
+ }
+ }
+
+ private class MultiAudioOptionItem extends RadioButtonItem {
+ private final String mTrackId;
+
+ private MultiAudioOptionItem(String title, String trackId) {
+ super(title);
+ mTrackId = trackId;
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ mSelectedTrackId = mFocusedTrackId = mTrackId;
+ getMainActivity().selectAudioTrack(mTrackId);
+ closeFragment();
+ }
+
+ @Override
+ protected void onFocused() {
+ super.onFocused();
+ mFocusedTrackId = mTrackId;
+ getMainActivity().selectAudioTrack(mTrackId);
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ if (!TextUtils.equals(mSelectedTrackId, mFocusedTrackId)) {
+ getMainActivity().selectAudioTrack(mSelectedTrackId);
+ }
+ super.onDetach();
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java b/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java
new file mode 100644
index 00000000..06415c21
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java
@@ -0,0 +1,167 @@
+/*
+ * 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<Item> 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<Item> 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.)
+ TvInputInfo mainInputInfo = mPipInputManager.getPipInput(
+ getMainActivity().getCurrentChannel()).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/PipLocationFragment.java b/src/com/android/tv/ui/sidepanel/PipLocationFragment.java
deleted file mode 100644
index 23c97274..00000000
--- a/src/com/android/tv/ui/sidepanel/PipLocationFragment.java
+++ /dev/null
@@ -1,96 +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.ui.sidepanel;
-
-import android.os.Bundle;
-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.TvActivity;
-import com.android.tv.util.TvSettings;
-
-public class PipLocationFragment extends BaseOptionFragment {
- private static final String TAG = "PipLocationFragment";
- private static final boolean DEBUG = true;
-
- private TvActivity mTvActivity;
- private boolean mIsFirstResume;
- private int mPipLocation;
- private final int[] mLocationToItemPosition = new int[4];
- private final Object[] mItem = new Integer[] {
- TvSettings.PIP_LOCATION_BOTTOM_RIGHT,
- TvSettings.PIP_LOCATION_TOP_RIGHT,
- TvSettings.PIP_LOCATION_TOP_LEFT,
- TvSettings.PIP_LOCATION_BOTTOM_LEFT,
- };
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mIsFirstResume = true;
- mTvActivity = (TvActivity) getActivity();
-
- mLocationToItemPosition[TvSettings.PIP_LOCATION_BOTTOM_RIGHT] = 0;
- mLocationToItemPosition[TvSettings.PIP_LOCATION_TOP_RIGHT] = 1;
- mLocationToItemPosition[TvSettings.PIP_LOCATION_TOP_LEFT] = 2;
- mLocationToItemPosition[TvSettings.PIP_LOCATION_BOTTOM_LEFT] = 3;
-
- initialize(getString(R.string.pip_location_option_title),
- R.layout.pip_location_item, mItem);
- return super.onCreateView(inflater, container, savedInstanceState);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (mIsFirstResume) {
- mPipLocation = mTvActivity.getPipLocation();
- int initialPosition = mLocationToItemPosition[mPipLocation];
- setSelectedPosition(initialPosition);
- setPrevSelectedItemPosition(initialPosition);
- mIsFirstResume = false;
- }
- }
-
- @Override
- public void onBindView(View v, int position, Object tag, boolean prevSelected) {
- super.onBindView(v, position, tag, prevSelected);
- ImageView pipLocationImageView = (ImageView) v.findViewById(R.id.pip_location);
- int location = (Integer) tag;
- if (location == TvSettings.PIP_LOCATION_TOP_LEFT) {
- pipLocationImageView.setImageResource(R.drawable.ic_pip_loc_top_left);
- } else if (location == TvSettings.PIP_LOCATION_TOP_RIGHT) {
- pipLocationImageView.setImageResource(R.drawable.ic_pip_loc_top_right);
- } else if (location == TvSettings.PIP_LOCATION_BOTTOM_LEFT) {
- pipLocationImageView.setImageResource(R.drawable.ic_pip_loc_bottom_left);
- } else if (location == TvSettings.PIP_LOCATION_BOTTOM_RIGHT) {
- pipLocationImageView.setImageResource(R.drawable.ic_pip_loc_bottom_right);
- } else {
- throw new IllegalArgumentException("Invaild PIP location: " + location);
- }
- }
-
- @Override
- public void onItemSelected(View v, int position, Object tag) {
- int pipLocation = (Integer) tag;
- mTvActivity.setPipLocation(pipLocation, true);
- super.onItemSelected(v, position, tag);
- }
-}
diff --git a/src/com/android/tv/ui/sidepanel/RadioButtonItem.java b/src/com/android/tv/ui/sidepanel/RadioButtonItem.java
index c6d4e262..b6c36795 100644
--- a/src/com/android/tv/ui/sidepanel/RadioButtonItem.java
+++ b/src/com/android/tv/ui/sidepanel/RadioButtonItem.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,52 +16,29 @@
package com.android.tv.ui.sidepanel;
-import android.view.View;
-import android.widget.RadioButton;
-
import com.android.tv.R;
-public class RadioButtonItem extends Item {
- private final String mTitle;
- private boolean mChecked;
- private RadioButton mRadioButton;
-
+public class RadioButtonItem extends CompoundButtonItem {
public RadioButtonItem(String title) {
- mTitle = title;
+ super(title, null);
}
- @Override
- protected int getResourceId() {
- return R.layout.option_item_radio_button;
+ public RadioButtonItem(String title, String description) {
+ super(title, description);
}
@Override
- protected void bind(View view) {
- mRadioButton = (RadioButton) view.findViewById(R.id.radio_button);
- mRadioButton.setText(mTitle);
- mRadioButton.setChecked(mChecked);
+ protected int getResourceId() {
+ return R.layout.option_item_radio_button;
}
@Override
- protected void unbind() {
- mRadioButton = null;
+ protected int getCompoundButtonId() {
+ return R.id.radio_button;
}
@Override
protected void onSelected() {
setChecked(true);
}
-
- public void setChecked(boolean checked) {
- if (mChecked != checked) {
- mChecked = checked;
- if (mRadioButton != null) {
- mRadioButton.setChecked(mChecked);
- }
- }
- }
-
- public boolean getChecked() {
- return mChecked;
- }
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/ui/sidepanel/SideFragment.java b/src/com/android/tv/ui/sidepanel/SideFragment.java
index 6967a744..e7072572 100644
--- a/src/com/android/tv/ui/sidepanel/SideFragment.java
+++ b/src/com/android/tv/ui/sidepanel/SideFragment.java
@@ -1,109 +1,285 @@
+/*
+ * 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.app.Activity;
import android.app.Fragment;
+import android.content.Context;
+import android.graphics.drawable.RippleDrawable;
import android.os.Bundle;
import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v7.widget.RecyclerView;
+import android.view.KeyEvent;
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.TvActivity;
+import com.android.tv.TvApplication;
+import com.android.tv.analytics.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 java.util.List;
-public abstract class SideFragment extends Fragment {
- public SideFragment() { }
+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 = {
+ R.layout.option_item_radio_button,
+ R.layout.option_item_channel_lock,
+ R.layout.option_item_check_box,
+ R.layout.option_item_channel_check
+ };
+
+ private static RecyclerView.RecycledViewPool sRecycledViewPool;
+
+ private VerticalGridView mListView;
+ private ItemAdapter mAdapter;
+ private SideFragmentListener mListener;
+ private ChannelDataManager mChannelDataManager;
+ private ProgramDataManager mProgramDataManager;
+ private Tracker mTracker;
+ private final DurationTimer mSidePanelDurationTimer = new DurationTimer();
+
+ private final int mHideKey;
+ private final int mDebugHideKey;
+
+ public SideFragment() {
+ this(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.KEYCODE_UNKNOWN);
+ }
+
+ /**
+ * @param hideKey the KeyCode used to hide the fragment
+ * @param debugHideKey the KeyCode used to hide the fragment if
+ * {@link SystemProperties#USE_DEBUG_KEYS}.
+ */
+ public SideFragment(int hideKey, int debugHideKey) {
+ mHideKey = hideKey;
+ mDebugHideKey = debugHideKey;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mChannelDataManager = getMainActivity().getChannelDataManager();
+ mProgramDataManager = getMainActivity().getProgramDataManager();
+ mTracker = ((TvApplication) getMainActivity().getApplicationContext()).getTracker();
+ }
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.option_fragment, container, false);
+ 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);
TextView textView = (TextView) view.findViewById(R.id.side_panel_title);
textView.setText(getTitle());
- VerticalGridView listView = (VerticalGridView) view.findViewById(R.id.side_panel_list);
- listView.setAdapter(new ItemAdapter(inflater, getItemList()));
- listView.requestFocus();
+ mListView = (VerticalGridView) view.findViewById(R.id.side_panel_list);
+ mListView.setRecycledViewPool(sRecycledViewPool);
- // TODO find a better way to do this
- if (getFragmentManager().getBackStackEntryCount() != 0) {
- view.findViewById(R.id.side_panel_shadow).setVisibility(View.GONE);
- }
+ mAdapter = new ItemAdapter(inflater, getItemList());
+ mListView.setAdapter(mAdapter);
+ mListView.requestFocus();
return view;
}
@Override
+ public void onResume() {
+ super.onResume();
+ mTracker.sendShowSidePanel(this);
+ mTracker.sendScreenView(this.getTrackerLabel());
+ mSidePanelDurationTimer.start();
+ }
+
+ @Override
+ public void onPause() {
+ mTracker.sendHideSidePanel(this, mSidePanelDurationTimer.reset());
+ super.onPause();
+ }
+
+ @Override
public void onDetach() {
+ mTracker = null;
super.onDetach();
+ }
+
+ public final boolean isHideKeyForThisPanel(int keyCode) {
+ boolean debugKeysEnabled = SystemProperties.USE_DEBUG_KEYS.getValue();
+ return mHideKey != KeyEvent.KEYCODE_UNKNOWN &&
+ (mHideKey == keyCode || (debugKeysEnabled && mDebugHideKey == keyCode));
+ }
- // TODO find a better way to do this
- if (getFragmentManager().getBackStackEntryCount() == 0) {
- TvActivity tvActivity = getTvActivity();
- tvActivity.onSideFragmentCanceled(BaseSideFragment.INITIATOR_UNKNOWN);
- tvActivity.hideOverlays(false, false, true);
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ mListView.swapAdapter(null, true);
+ if (mListener != null) {
+ mListener.onSideFragmentViewDestroyed();
}
}
- protected TvActivity getTvActivity() {
- return (TvActivity) getActivity();
+ public final void setListener(SideFragmentListener listener) {
+ mListener = listener;
+ }
+
+ protected void setSelectedPosition(int position) {
+ mListView.setSelectedPosition(position);
+ }
+
+ protected int getSelectedPosition() {
+ return mListView.getSelectedPosition();
+ }
+
+ public void setItems(List<Item> items) {
+ mAdapter.reset(items);
+ }
+
+ protected void closeFragment() {
+ getMainActivity().getOverlayManager().getSideFragmentManager().popSideFragment();
+ }
+
+ protected MainActivity getMainActivity() {
+ return (MainActivity) getActivity();
+ }
+
+ protected ChannelDataManager getChannelDataManager() {
+ return mChannelDataManager;
+ }
+
+ protected ProgramDataManager getProgramDataManager() {
+ return mProgramDataManager;
}
protected void notifyDataSetChanged() {
- VerticalGridView listView = (VerticalGridView) getView().findViewById(R.id.side_panel_list);
- listView.getAdapter().notifyDataSetChanged();
+ mAdapter.notifyDataSetChanged();
+ }
+
+ /*
+ * HACK: The following methods bypass the updating mechanism of RecyclerView.Adapter and
+ * directly updates each item. This works around a bug in the base libraries where calling
+ * Adapter.notifyItemsChanged() causes the VerticalGridView to lose track of displayed item
+ * position.
+ */
+
+ protected void notifyItemChanged(int position) {
+ notifyItemChanged(mAdapter.getItem(position));
+ }
+
+ protected void notifyItemChanged(Item item) {
+ item.notifyUpdated();
+ }
+
+ /**
+ * 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
+ * <code>positionStart</code> to the end without structural changes.
+ */
+ protected void notifyItemsChanged(int positionStart) {
+ notifyItemsChanged(positionStart, mAdapter.getItemCount() - positionStart);
+ }
+
+ protected void notifyItemsChanged(int positionStart, int itemCount) {
+ while (itemCount-- != 0) {
+ notifyItemChanged(positionStart++);
+ }
+ }
+
+ /*
+ * END HACK
+ */
+
+ protected int getFragmentLayoutResourceId() {
+ return R.layout.option_fragment;
}
protected abstract String getTitle();
+ @Override
+ public abstract String getTrackerLabel();
protected abstract List<Item> getItemList();
+ public interface SideFragmentListener {
+ void onSideFragmentViewDestroyed();
+ }
+
+ 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);
+ }
+ }
+ }
+
private static class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ViewHolder> {
private final LayoutInflater mLayoutInflater;
- private final List<Item> mItems;
+ private List<Item> mItems;
private ItemAdapter(LayoutInflater layoutInflater, List<Item> items) {
mLayoutInflater = layoutInflater;
mItems = items;
}
+ private void reset(List<Item> items) {
+ mItems = items;
+ notifyDataSetChanged();
+ }
+
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = mLayoutInflater.inflate(viewType, parent, false);
- final ViewHolder holder = new ViewHolder(view);
- view.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (holder.item instanceof RadioButtonItem) {
- clearRadioGroup(holder.item);
- }
- holder.item.onSelected();
- }
- });
- view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View view, boolean focusGained) {
- if (focusGained) {
- holder.item.onFocused();
- }
- }
- });
- return holder;
+ return new ViewHolder(mLayoutInflater.inflate(viewType, parent, false));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
- holder.item = getItem(position);
- holder.item.bind(holder.itemView);
+ holder.onBind(this, getItem(position));
}
@Override
public void onViewRecycled(ViewHolder holder) {
- holder.item.unbind();
- holder.item = null;
+ holder.onUnbind();
}
@Override
@@ -138,12 +314,55 @@ public abstract class SideFragment extends Fragment {
}
}
- private static class ViewHolder extends RecyclerView.ViewHolder {
- public Item item;
+ 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);
+ }
+
+ 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;
+ }
+
+ @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();
+ }
+ }
+
+ @Override
+ public void onFocusChange(View view, boolean focusGained) {
+ if (focusGained) {
+ mItem.onFocused();
+ }
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/ui/sidepanel/SideFragmentManager.java b/src/com/android/tv/ui/sidepanel/SideFragmentManager.java
new file mode 100644
index 00000000..b1041629
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/SideFragmentManager.java
@@ -0,0 +1,222 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.app.Activity;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.view.View;
+
+import com.android.tv.R;
+
+public class SideFragmentManager {
+ private static final String FIRST_BACKSTACK_RECORD_NAME = "0";
+
+ private final Activity mActivity;
+ private final FragmentManager mFragmentManager;
+ private final Runnable mPreShowRunnable;
+ private final Runnable mPostHideRunnable;
+
+ // To get the count reliably while using popBackStack(),
+ // instead of using getBackStackEntryCount() with popBackStackImmediate().
+ private int mFragmentCount;
+
+ private final View mPanel;
+ private final Animator mShowAnimator;
+ private final Animator mHideAnimator;
+
+ private final Runnable mHideAllRunnable = new Runnable() {
+ @Override
+ public void run() {
+ hideAll(true);
+ }
+ };
+ private final long mShowDurationMillis;
+
+ public SideFragmentManager(Activity activity, Runnable preShowRunnable,
+ Runnable postHideRunnable) {
+ mActivity = activity;
+ mFragmentManager = mActivity.getFragmentManager();
+ mPreShowRunnable = preShowRunnable;
+ mPostHideRunnable = postHideRunnable;
+
+ mPanel = mActivity.findViewById(R.id.side_panel);
+ mShowAnimator = AnimatorInflater.loadAnimator(mActivity, R.animator.side_panel_enter);
+ 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();
+ }
+ });
+
+ mShowDurationMillis = mActivity.getResources().getInteger(
+ R.integer.side_panel_show_duration);
+ }
+
+ public int getCount() {
+ return mFragmentCount;
+ }
+
+ public boolean isActive() {
+ return mFragmentCount != 0 && !isHiding();
+ }
+
+ public boolean isHiding() {
+ return mHideAnimator.isStarted();
+ }
+
+ public void show(SideFragment sideFragment) {
+ 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(
+ R.animator.side_panel_fragment_enter,
+ R.animator.side_panel_fragment_exit,
+ R.animator.side_panel_fragment_pop_enter,
+ R.animator.side_panel_fragment_pop_exit);
+ }
+ ft.replace(R.id.side_fragment_container, sideFragment)
+ .addToBackStack(Integer.toString(mFragmentCount)).commit();
+ mFragmentCount++;
+
+ if (isFirst) {
+ mPanel.setVisibility(View.VISIBLE);
+ mShowAnimator.start();
+ }
+ scheduleHideAll();
+ }
+
+ public void popSideFragment() {
+ if (!isActive()) {
+ return;
+ } else if (mFragmentCount == 1) {
+ // Show closing animation with the last fragment.
+ hideAll(true);
+ return;
+ }
+ mFragmentManager.popBackStack();
+ mFragmentCount--;
+ }
+
+ public void hideAll(boolean withAnimation) {
+ if (withAnimation) {
+ if (!isHiding()) {
+ mHideAnimator.start();
+ }
+ return;
+ }
+ if (isHiding()) {
+ mHideAnimator.end();
+ return;
+ }
+ hideAllInternal();
+ }
+
+ private void hideAllInternal() {
+ if (mFragmentCount == 0) {
+ return;
+ }
+
+ mPanel.setVisibility(View.GONE);
+ mFragmentManager.popBackStack(FIRST_BACKSTACK_RECORD_NAME,
+ FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ mFragmentCount = 0;
+
+ if (mPostHideRunnable != null) {
+ mPostHideRunnable.run();
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public void showSidePanel(boolean withAnimation) {
+ SideFragment.preloadRecycledViews(mActivity);
+ if (mFragmentCount == 0) {
+ return;
+ }
+
+ mPanel.setVisibility(View.VISIBLE);
+ if (withAnimation) {
+ mShowAnimator.start();
+ }
+ scheduleHideAll();
+ }
+
+ /**
+ * 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) {
+ if (withAnimation) {
+ mPanel.removeCallbacks(mHideAllRunnable);
+ Animator hideAnimator =
+ 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);
+ }
+ });
+ } else {
+ mPanel.setVisibility(View.GONE);
+ }
+ }
+
+ public boolean isSidePanelVisible() {
+ return mPanel.getVisibility() == View.VISIBLE;
+ }
+
+ public void scheduleHideAll() {
+ mPanel.removeCallbacks(mHideAllRunnable);
+ mPanel.postDelayed(mHideAllRunnable, mShowDurationMillis);
+ }
+
+ /**
+ * Should {@code keyCode} hide the current panel.
+ */
+ public boolean isHideKeyForCurrentPanel(int keyCode) {
+ if (isActive()) {
+ 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/SidePanelContainer.java b/src/com/android/tv/ui/sidepanel/SidePanelContainer.java
deleted file mode 100644
index ada85f1b..00000000
--- a/src/com/android/tv/ui/sidepanel/SidePanelContainer.java
+++ /dev/null
@@ -1,51 +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.ui.sidepanel;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.widget.FrameLayout;
-
-public class SidePanelContainer extends FrameLayout {
-
- private boolean mKeyDispatchable = true;
-
- public SidePanelContainer(Context context) {
- super(context, null, 0);
- }
-
- public SidePanelContainer(Context context, AttributeSet attrs) {
- super(context, attrs, 0);
- }
-
- public SidePanelContainer(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public void setKeyDispatchable(boolean dispatchable) {
- mKeyDispatchable = dispatchable;
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (!mKeyDispatchable) {
- return true;
- }
- return super.dispatchKeyEvent(event);
- }
-}
diff --git a/src/com/android/tv/ui/sidepanel/SimpleGuideFragment.java b/src/com/android/tv/ui/sidepanel/SimpleGuideFragment.java
deleted file mode 100644
index 5f036a4e..00000000
--- a/src/com/android/tv/ui/sidepanel/SimpleGuideFragment.java
+++ /dev/null
@@ -1,281 +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.ui.sidepanel;
-
-import android.app.FragmentTransaction;
-import android.content.ContentUris;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.media.tv.TvContract;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.Layout;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import com.android.tv.R;
-import com.android.tv.TvActivity;
-import com.android.tv.data.Channel;
-import com.android.tv.data.Channel.LoadLogoCallback;
-import com.android.tv.data.ChannelMap;
-import com.android.tv.data.Program;
-import com.android.tv.data.GenreItems;
-import com.android.tv.util.Utils;
-
-public class SimpleGuideFragment extends BaseSideFragment {
- private static final String TAG = "SimpleGuideFragment";
- private static final boolean DEBUG = true;
-
- private final TvActivity mTvActivity;
- private final ChannelMap mChannelMap;
- private int mCurPosition;
- private boolean mClosingByItemSelected;
- // TODO: user shared preference for this.
- private String mSelectedShowOnlyItem;
-
- public SimpleGuideFragment(TvActivity tvActivity, ChannelMap channelMap) {
- mTvActivity = tvActivity;
- mChannelMap = channelMap;
- mSelectedShowOnlyItem = GenreItems.getCanonicalGenre(GenreItems.POSITION_ALL_CHANNELS);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- initialize();
- return super.onCreateView(inflater, container, savedInstanceState);
- }
-
- private void initialize() {
- Channel[] channels = new Channel[0];
- if (mSelectedShowOnlyItem == null) { // All Channels.
- channels = mChannelMap.getChannelList(true);
- } else {
- Uri uri = mTvActivity.getSelectedTvInput().buildChannelsUri(mSelectedShowOnlyItem);
- Cursor c = null;
- try {
- c = mTvActivity.getContentResolver().query(uri, null, null, null, null);
- if (c != null) {
- channels = new Channel[c.getCount()];
- int index = 0;
- while (c.moveToNext()) {
- channels[index++] = Channel.fromCursor(c);
- }
- }
- } finally {
- if (c != null) {
- c.close();
- }
- }
- }
-
- Object[] itemTags = new Object[channels.length + 1];
- itemTags[0] = new Object(); // a dummy object for the show only menu.
- for (int i = 0; i < channels.length; ++i) {
- itemTags[i + 1] = channels[i];
- }
-
- initialize(getString(R.string.simple_guide_title), itemTags,
- R.layout.option_fragment, R.layout.simple_guide_item,
- R.color.option_item_background, R.color.option_item_focused_background,
- R.dimen.simple_guide_item_height);
- mCurPosition = getCurrentChannelPosition(channels);
- setPrevSelectedItemPosition(mCurPosition);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- setSelectedPosition(mCurPosition);
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- if (!mClosingByItemSelected) {
- mTvActivity.onSideFragmentCanceled(getInitiator());
- }
- mTvActivity.hideOverlays(false, false, true);
- }
-
- @Override
- public void onItemFocusChanged(View v, boolean focusGained, int position, Object tag) {
- if (DEBUG) Log.d(TAG, "onItemFocusChanged " + focusGained + ": position=" + position
- + ", label=" + tag);
- super.onItemFocusChanged(v, focusGained, position, tag);
- mCurPosition = position;
- }
-
- @Override
- public void onItemSelected(View v, int position, Object tag) {
- if (DEBUG) Log.d(TAG, "onItemSelected: position=" + position + ", label=" + tag);
- if (tag instanceof Channel) {
- mTvActivity.moveToChannel(((Channel) tag).getId());
- mClosingByItemSelected = true;
- mTvActivity.popFragmentBackStack();
- } else {
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.add(R.id.right_panel, new SimpleGuideShowOnlyFragment(this, mSelectedShowOnlyItem));
- ft.addToBackStack(null);
- // TODO: add an animation.
- ft.commit();
- }
- }
-
- @Override
- public void onCreatedChildView(View v) {
- ((TextView) v.findViewById(R.id.resolution)).addOnLayoutChangeListener(
- new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- calibrateResolutionTextViewPosition((ViewGroup) v.getParent(),
- (TextView) v);
- }
- });
- }
-
- private void calibrateResolutionTextViewPosition(ViewGroup parent,
- TextView resolutionTextView) {
- if (resolutionTextView.getVisibility() == View.VISIBLE) {
- Layout layout = resolutionTextView.getLayout();
- if (layout.getLineCount() > 0
- && layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
- resolutionTextView.setVisibility(View.GONE);
- ((TextView) parent.findViewById(R.id.resolution_on_left_side)).setVisibility(
- View.VISIBLE);
- }
- }
- }
-
- @Override
- public void onBindView(View v, int position, Object tag, boolean prevSelected) {
- final ImageView channelLogoView = (ImageView) v.findViewById(R.id.channel_logo);
- final TextView channelNumberView = (TextView) v.findViewById(R.id.channel_number);
- final TextView channelNumberAloneView =
- (TextView) v.findViewById(R.id.channel_number_alone);
- TextView programTitleView = (TextView) v.findViewById(R.id.program_title);
- TextView channelNameView = (TextView) v.findViewById(R.id.channel_name);
- ProgressBar remainingTimeView = (ProgressBar) v.findViewById(R.id.remaining_time);
- TextView resolutionTextView = (TextView) v.findViewById(R.id.resolution);
- TextView resolutionOnLeftSideTextView =
- (TextView) v.findViewById(R.id.resolution_on_left_side);
- resolutionOnLeftSideTextView.setVisibility(View.GONE);
- if (tag instanceof Channel) {
- Channel channel = (Channel) tag;
- if (!channel.isLogoLoaded()) {
- channel.loadLogo(getActivity(), new LoadLogoCallback() {
- @Override
- public void onLoadLogoFinished(Channel channel, Bitmap logo) {
- if (logo != null) {
- channelNumberAloneView.setVisibility(View.GONE);
- channelLogoView.setVisibility(View.VISIBLE);
- channelNumberView.setVisibility(View.VISIBLE);
- channelLogoView.setImageBitmap(logo);
- channelNumberView.setText(channel.getDisplayNumber());
- } else {
- channelLogoView.setVisibility(View.GONE);
- channelNumberView.setVisibility(View.GONE);
- channelNumberAloneView.setVisibility(View.VISIBLE);
- channelNumberAloneView.setText(channel.getDisplayNumber());
- }
- }
- });
- } else {
- Bitmap logo = channel.getLogo();
- if (logo != null) {
- channelNumberAloneView.setVisibility(View.GONE);
- channelLogoView.setVisibility(View.VISIBLE);
- channelNumberView.setVisibility(View.VISIBLE);
- channelLogoView.setImageBitmap(logo);
- channelNumberView.setText(channel.getDisplayNumber());
- } else {
- channelLogoView.setVisibility(View.GONE);
- channelNumberView.setVisibility(View.GONE);
- channelNumberAloneView.setVisibility(View.VISIBLE);
- channelNumberAloneView.setText(channel.getDisplayNumber());
- }
- }
- Program program = Utils.getCurrentProgram(mTvActivity,
- ContentUris.withAppendedId(TvContract.Channels.CONTENT_URI, channel.getId()));
- String text = program.getTitle();
- if (TextUtils.isEmpty(text)) {
- text = getResources().getString(R.string.no_program_information);
- }
- programTitleView.setText(text);
- channelNameView.setText(channel.getDisplayName());
- // TODO: Handle when the text is too long.
-
- long startTime = program.getStartTimeUtcMillis();
- long endTime = program.getEndTimeUtcMillis();
- if (startTime > 0 && endTime > 0) {
- long currTime = System.currentTimeMillis();
- if (currTime <= startTime) {
- remainingTimeView.setProgress(0);
- } else if (currTime >= endTime) {
- remainingTimeView.setProgress(100);
- } else {
- remainingTimeView.setProgress(
- (int) (100 *(currTime - startTime) / (endTime - startTime)));
- }
- remainingTimeView.setVisibility(View.VISIBLE);
- } else {
- remainingTimeView.setVisibility(View.GONE);
- }
-
- String videoDefinitionLevel = program.getVideoDefinitionLevel();
- if (!TextUtils.isEmpty(videoDefinitionLevel)) {
- resolutionTextView.setVisibility(View.VISIBLE);
- resolutionTextView.setText(videoDefinitionLevel);
- resolutionOnLeftSideTextView.setText(videoDefinitionLevel);
- } else {
- resolutionTextView.setVisibility(View.GONE);
- }
- } else {
- channelLogoView.setVisibility(View.GONE);
- channelNumberView.setVisibility(View.GONE);
- channelNumberAloneView.setVisibility(View.INVISIBLE);
- remainingTimeView.setVisibility(View.GONE);
- programTitleView.setText(getString(R.string.show_only_title));
- channelNameView.setText(GenreItems.getLabel(mTvActivity, mSelectedShowOnlyItem));
- }
- }
-
- private int getCurrentChannelPosition(Channel[] channels) {
- long curChannelId = mChannelMap.getCurrentChannelId();
- int curChannelPos = 0;
- for (int i = 0; i < channels.length; ++i) {
- if (channels[i].getId() == curChannelId) {
- curChannelPos = i + 1;
- break;
- }
- }
- return curChannelPos;
- }
-
- public void setGenreOnGuide(String item) {
- mSelectedShowOnlyItem = item;
- initialize();
- setSelectedPosition(mCurPosition);
- }
-}
diff --git a/src/com/android/tv/ui/sidepanel/SimpleGuideShowOnlyFragment.java b/src/com/android/tv/ui/sidepanel/SimpleGuideShowOnlyFragment.java
deleted file mode 100644
index db3f88d3..00000000
--- a/src/com/android/tv/ui/sidepanel/SimpleGuideShowOnlyFragment.java
+++ /dev/null
@@ -1,118 +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.ui.sidepanel;
-
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.RadioButton;
-
-import com.android.tv.R;
-import com.android.tv.data.GenreItems;
-
-public class SimpleGuideShowOnlyFragment extends BaseSideFragment {
- private static final String TAG = "SimpleGuideShowOnlyFragment";
- private static final boolean DEBUG = true;
-
- private SimpleGuideFragment mSimpleGuideFragment;
-
- private View mMainView;
- private int mFocusedItemPosition;
-
- public SimpleGuideShowOnlyFragment(SimpleGuideFragment simpleGuideFragment,
- String selectedGenre) {
- super();
- mSimpleGuideFragment = simpleGuideFragment;
- mFocusedItemPosition = GenreItems.getPosition(selectedGenre);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- Object[] items = GenreItems.getItems(getActivity());
- initialize(getString(R.string.show_only_title), items,
- R.layout.option_fragment, R.layout.show_only_item,
- R.color.option_item_background, R.color.option_item_focused_background,
- R.dimen.simple_guide_item_height);
- setPrevSelectedItemPosition(mFocusedItemPosition);
- mMainView = super.onCreateView(inflater, container, savedInstanceState);
- // This fragment is always added on top of SimpleGuideFragment. So we need to make
- // this fragment shadow invisible.
- mMainView.findViewById(R.id.side_panel_shadow).setVisibility(View.INVISIBLE);
- return mMainView;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- setSelectedPosition(mFocusedItemPosition);
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- }
-
- @Override
- public void onItemFocusChanged(View v, boolean focusGained, int position, Object tag) {
- if (DEBUG) Log.d(TAG, "onItemFocusChanged " + focusGained + ": position=" + position
- + ", label=" + (String) tag);
- super.onItemFocusChanged(v, focusGained, position, tag);
- mFocusedItemPosition = position;
- }
-
- @Override
- public void onItemSelected(View v, int position, Object tag) {
- if (DEBUG) Log.d(TAG, "onItemSelected: position=" + position + ", label=" + (String) tag);
-
- mFocusedItemPosition = position;
-
- RadioButton radioButton = (RadioButton) v.findViewById(R.id.show_only_item);
- uncheckAllRadioButtons((ViewGroup) mMainView);
- radioButton.setChecked(true);
-
- mSimpleGuideFragment.setGenreOnGuide(GenreItems.getCanonicalGenre(getActivity(),
- (String) tag));
- getFragmentManager().popBackStack();
- }
-
- @Override
- public void onBindView(View v, int position, Object tag, boolean prevSelected) {
- if (DEBUG) Log.d(TAG, "onBindView: position=" + position + ", label=" + (String) tag);
- RadioButton radioButton = (RadioButton) v.findViewById(R.id.show_only_item);
- if (prevSelected) {
- radioButton.setChecked(true);
- } else {
- radioButton.setChecked(false);
- }
- radioButton.setText((String) tag);
- }
-
- private static void uncheckAllRadioButtons(ViewGroup parent) {
- int count = parent.getChildCount();
- for (int i = 0; i < count; ++i) {
- View v = parent.getChildAt(i);
- if (v instanceof ViewGroup) {
- uncheckAllRadioButtons((ViewGroup) v);
- } else if (v instanceof RadioButton) {
- ((RadioButton) v).setChecked(false);
- }
- }
- }
-}
diff --git a/src/com/android/tv/ui/sidepanel/SubMenuItem.java b/src/com/android/tv/ui/sidepanel/SubMenuItem.java
index e76eab2e..aa349dbd 100644
--- a/src/com/android/tv/ui/sidepanel/SubMenuItem.java
+++ b/src/com/android/tv/ui/sidepanel/SubMenuItem.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,66 +16,36 @@
package com.android.tv.ui.sidepanel;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.view.View;
-import android.widget.TextView;
-import com.android.tv.R;
+public abstract class SubMenuItem extends ActionItem {
+ private final SideFragmentManager mSideFragmentManager;
-import java.util.List;
-
-public class SubMenuItem extends Item {
- private final String mTitle;
- private final FragmentManager mFragmentManager;
- private TextView mTitleView;
-
- public SubMenuItem(String title, FragmentManager fragmentManager) {
- mTitle = title;
- mFragmentManager = fragmentManager;
+ public SubMenuItem(String title, SideFragmentManager fragmentManager) {
+ this(title, null, 0, fragmentManager);
}
- @Override
- protected int getResourceId() {
- return R.layout.option_item_sub_menu;
+ public SubMenuItem(String title, String description, SideFragmentManager fragmentManager) {
+ this(title, description, 0, fragmentManager);
}
- @Override
- protected void bind(View view) {
- mTitleView = (TextView) view.findViewById(R.id.title);
- mTitleView.setText(mTitle);
+ public SubMenuItem(String title, int iconId, SideFragmentManager fragmentManager) {
+ this(title, null, iconId, fragmentManager);
}
- @Override
- protected void unbind() {
- mTitleView = null;
+ public SubMenuItem(String title, String description, int iconId,
+ SideFragmentManager fragmentManager) {
+ super(title, description, iconId);
+ mSideFragmentManager = fragmentManager;
}
@Override
protected void onSelected() {
- mFragmentManager
- .beginTransaction()
- .add(R.id.right_panel, new SideFragment() {
- @Override
- protected String getTitle() {
- return SubMenuItem.this.getTitle();
- }
-
- @Override
- protected List<Item> getItemList() {
- return SubMenuItem.this.getItemList();
- }
- })
- .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
- .addToBackStack(null)
- .commit();
+ launchFragment();
}
- protected String getTitle() {
- return mTitle;
+ protected void launchFragment() {
+ mSideFragmentManager.show(getFragment());
}
- protected List<Item> getItemList() {
- return null;
- }
-} \ No newline at end of file
+ protected abstract SideFragment getFragment();
+}
diff --git a/src/com/android/tv/ui/sidepanel/SwitchItem.java b/src/com/android/tv/ui/sidepanel/SwitchItem.java
new file mode 100644
index 00000000..c04a72ac
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/SwitchItem.java
@@ -0,0 +1,44 @@
+/*
+ * 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 com.android.tv.R;
+
+public class SwitchItem extends CompoundButtonItem {
+ public SwitchItem(String title) {
+ super(title, null);
+ }
+
+ public SwitchItem(String checkedTitle, String uncheckedTitle) {
+ super(checkedTitle, uncheckedTitle, null);
+ }
+
+ @Override
+ protected int getResourceId() {
+ return R.layout.option_item_switch;
+ }
+
+ @Override
+ protected int getCompoundButtonId() {
+ return R.id.switch_button;
+ }
+
+ @Override
+ protected void onSelected() {
+ setChecked(!isChecked());
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java
new file mode 100644
index 00000000..41e2ec37
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java
@@ -0,0 +1,230 @@
+/*
+ * 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.parentalcontrols;
+
+import android.database.ContentObserver;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.view.KeyEvent;
+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;
+import com.android.tv.ui.sidepanel.ActionItem;
+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;
+import java.util.List;
+
+public class ChannelsBlockedFragment extends SideFragment {
+ private static final String TRACKER_LABEL = "Channels blocked";
+ private int mBlockedChannelCount;
+ private final List<Channel> 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 Item mLockAllItem = new BlockAllItem();
+ private final List<Item> mItems = new ArrayList<>();
+
+ private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
+ new VerticalGridView.OnKeyInterceptListener() {
+ @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;
+ }
+ }
+ return false;
+ }
+ };
+
+ @Override
+ 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(mOnKeyInterceptListener);
+ getActivity().getContentResolver().registerContentObserver(TvContract.Programs.CONTENT_URI,
+ true, mProgramUpdateObserver);
+ getMainActivity().startShrunkenTvView(true, true);
+ return view;
+ }
+
+ @Override
+ public void onDestroyView() {
+ getActivity().getContentResolver().unregisterContentObserver(mProgramUpdateObserver);
+ getChannelDataManager().applyUpdatedValuesToDb();
+ getMainActivity().endShrunkenTvView();
+ super.onDestroyView();
+ }
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.option_channels_locked);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ protected List<Item> getItemList() {
+ mItems.clear();
+ mItems.add(mLockAllItem);
+ mChannels.clear();
+ mChannels.addAll(getChannelDataManager().getChannelList());
+ Collections.sort(mChannels, new Comparator<Channel>() {
+ @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)));
+ hasHiddenChannels = true;
+ }
+ mItems.add(new ChannelBlockedItem(channel));
+ if (channel.isLocked()) {
+ ++mBlockedChannelCount;
+ }
+ if (channel.getId() == currentChannelId) {
+ mSelectedPosition = mItems.size() - 1;
+ }
+ }
+ return mItems;
+ }
+
+ private class BlockAllItem extends ActionItem {
+ private TextView mTextView;
+
+ public BlockAllItem() {
+ super(null);
+ }
+
+ @Override
+ protected void onBind(View view) {
+ super.onBind(view);
+ mTextView = (TextView) view.findViewById(R.id.title);
+ }
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ updateText();
+ }
+
+ @Override
+ protected void onUnbind() {
+ super.onUnbind();
+ mTextView = null;
+ }
+
+ @Override
+ protected void onSelected() {
+ boolean lock = !areAllChannelsBlocked();
+ for (Channel channel : mChannels) {
+ getChannelDataManager().updateLocked(channel.getId(), lock);
+ }
+ mBlockedChannelCount = lock ? mChannels.size() : 0;
+ notifyItemsChanged();
+ }
+
+ @Override
+ protected void onFocused() {
+ super.onFocused();
+ mLastFocusedChannelId = Channel.INVALID_ID;
+ }
+
+ private void updateText() {
+ mTextView.setText(getString(areAllChannelsBlocked() ?
+ R.string.option_channels_unlock_all : R.string.option_channels_lock_all));
+ }
+
+ private boolean areAllChannelsBlocked() {
+ return mBlockedChannelCount == mChannels.size();
+ }
+ }
+
+ private class ChannelBlockedItem extends ChannelCheckItem {
+ private ChannelBlockedItem(Channel channel) {
+ super(channel, getChannelDataManager(), getProgramDataManager());
+ }
+
+ @Override
+ protected int getResourceId() {
+ return R.layout.option_item_channel_lock;
+ }
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ setChecked(getChannel().isLocked());
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ getChannelDataManager().updateLocked(getChannel().getId(), isChecked());
+ mBlockedChannelCount += isChecked() ? 1 : -1;
+ notifyItemChanged(mLockAllItem);
+ }
+
+ @Override
+ protected void onFocused() {
+ super.onFocused();
+ mLastFocusedChannelId = getChannel().getId();
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java
new file mode 100644
index 00000000..da712924
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java
@@ -0,0 +1,159 @@
+/*
+ * 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.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;
+import com.android.tv.dialog.PinDialogFragment;
+import com.android.tv.ui.sidepanel.ActionItem;
+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;
+
+public class ParentalControlsFragment extends SideFragment {
+ private static final String TRACKER_LABEL = "Parental controls";
+ private List<ActionItem> mActionItems;
+
+ private final SideFragmentListener mSideFragmentListener = new SideFragmentListener() {
+ @Override
+ public void onSideFragmentViewDestroyed() {
+ notifyDataSetChanged();
+ }
+ };
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.menu_parental_controls);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ protected List<Item> getItemList() {
+ List<Item> 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());
+ }
+
+ @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;
+ }
+
+ @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;
+ }
+ }
+ 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 =
+ new PinDialogFragment(
+ PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN,
+ new PinDialogFragment.ResultListener() {
+ @Override
+ public void done(boolean success) {
+ tvActivity.getOverlayManager().getSideFragmentManager()
+ .showSidePanel(true);
+ }
+ });
+ tvActivity.getOverlayManager().showDialogFragment(PinDialogFragment.DIALOG_TAG,
+ fragment, true);
+ }
+ });
+ items.addAll(mActionItems);
+ enableActionItems(getMainActivity().getParentalControlSettings()
+ .isParentalControlsEnabled());
+ return items;
+ }
+
+ private void enableActionItems(boolean enabled) {
+ for (ActionItem actionItem : mActionItems) {
+ actionItem.setEnabled(enabled);
+ }
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java
new file mode 100644
index 00000000..c878900a
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java
@@ -0,0 +1,85 @@
+/*
+ * 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.parentalcontrols;
+
+import com.android.tv.MainActivity;
+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();
+ }
+ };
+
+ public static String getDescription(MainActivity tvActivity) {
+ return RatingsFragment.getDescription(tvActivity);
+ }
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.option_program_restrictions);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ protected List<Item> getItemList() {
+ List<Item> 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;
+ }
+ });
+ 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;
+ }
+ };
+ // When "None" is selected for rating systems, disable the Ratings option.
+ if (RatingSystemsFragment.getDescription(getMainActivity()).equals(
+ getString(R.string.option_no_enabled_rating_system))) {
+ ratingsItem.setEnabled(false);
+ }
+ items.add(ratingsItem);
+ return items;
+ }
+}
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingSystemsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingSystemsFragment.java
new file mode 100644
index 00000000..ba9adc50
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingSystemsFragment.java
@@ -0,0 +1,173 @@
+/*
+ * 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.parentalcontrols;
+
+import android.os.Bundle;
+
+import com.android.tv.MainActivity;
+import com.android.tv.R;
+import com.android.tv.parental.ContentRatingSystem;
+import com.android.tv.parental.ContentRatingsManager;
+import com.android.tv.parental.ParentalControlSettings;
+import com.android.tv.ui.sidepanel.ActionItem;
+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;
+import java.util.Locale;
+
+public class RatingSystemsFragment extends SideFragment {
+ private static final String TRACKER_LABEL = "Rating systems";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setDefaultRatingSystemsIfNeeded((MainActivity) getActivity());
+ }
+
+ public static String getDescription(MainActivity tvActivity) {
+ setDefaultRatingSystemsIfNeeded(tvActivity);
+
+ List<ContentRatingSystem> contentRatingSystems =
+ tvActivity.getContentRatingsManager().getContentRatingSystems();
+ Collections.sort(contentRatingSystems, ContentRatingSystem.DISPLAY_NAME_COMPARATOR);
+ StringBuilder builder = new StringBuilder();
+ for (ContentRatingSystem s : contentRatingSystems) {
+ if (!tvActivity.getParentalControlSettings().isContentRatingSystemEnabled(s)) {
+ continue;
+ }
+ builder.append(s.getDisplayName());
+ builder.append(", ");
+ }
+ return builder.length() > 0 ? builder.substring(0, builder.length() - 2)
+ : tvActivity.getString(R.string.option_no_enabled_rating_system);
+ }
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.option_country_rating_systems);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ protected List<Item> getItemList() {
+ ContentRatingsManager contentRatingsManager = getMainActivity().getContentRatingsManager();
+ ParentalControlSettings parentalControlSettings =
+ getMainActivity().getParentalControlSettings();
+ List<ContentRatingSystem> contentRatingSystems =
+ contentRatingsManager.getContentRatingSystems();
+ Collections.sort(contentRatingSystems, ContentRatingSystem.DISPLAY_NAME_COMPARATOR);
+ List<Item> items = new ArrayList<>();
+ List<Item> itemsHidden = new ArrayList<>();
+ List<Item> itemsHiddenMultipleCountries = new ArrayList<>();
+
+ // Add default, custom and preselected content rating systems to the "short" list.
+ for (ContentRatingSystem s : contentRatingSystems) {
+ if (!s.isCustom() && s.getCountries() != null
+ && s.getCountries().contains(Locale.getDefault().getCountry())) {
+ items.add(new RatingSystemItem(s));
+ } else if (s.isCustom() || parentalControlSettings.isContentRatingSystemEnabled(s)) {
+ items.add(new RatingSystemItem(s));
+ } else {
+ List<String> countries = s.getCountries();
+ if (countries.size() > 1) {
+ // Convert country codes to display names.
+ for (int i = 0; i < countries.size(); ++i) {
+ countries.set(i, new Locale("", countries.get(i)).getDisplayCountry());
+ }
+ Collections.sort(countries);
+ StringBuilder builder = new StringBuilder();
+ for (String country : countries) {
+ builder.append(country);
+ builder.append(", ");
+ }
+ itemsHiddenMultipleCountries.add(
+ new RatingSystemItem(s, builder.substring(0, builder.length() - 2)));
+ } else {
+ itemsHidden.add(new RatingSystemItem(s));
+ }
+ }
+ }
+
+ // Add the rest of the content rating systems to the "long" list.
+ final List<Item> allItems = new ArrayList<>(items);
+ allItems.addAll(itemsHidden);
+ 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);
+ }
+ });
+ return items;
+ }
+
+ private static void setDefaultRatingSystemsIfNeeded(MainActivity tvActivity) {
+ if (TvSettings.isContentRatingSystemSet(tvActivity)) {
+ return;
+ }
+ // Sets the default if the content rating system has never been set.
+ List<ContentRatingSystem> contentRatingSystems =
+ tvActivity.getContentRatingsManager().getContentRatingSystems();
+ ContentRatingsManager manager = tvActivity.getContentRatingsManager();
+ ParentalControlSettings settings = tvActivity.getParentalControlSettings();
+ for (ContentRatingSystem s : contentRatingSystems) {
+ if (!s.isCustom() && s.getCountries() != null
+ && s.getCountries().contains(Locale.getDefault().getCountry())) {
+ settings.setContentRatingSystemEnabled(manager, s, true);
+ }
+ }
+ }
+
+ private class RatingSystemItem extends CheckBoxItem {
+ private final ContentRatingSystem mContentRatingSystem;
+
+ RatingSystemItem(ContentRatingSystem contentRatingSystem) {
+ this(contentRatingSystem, null);
+ }
+
+ RatingSystemItem(ContentRatingSystem contentRatingSystem, String description) {
+ super(contentRatingSystem.getDisplayName(), description, description != null);
+ mContentRatingSystem = contentRatingSystem;
+ }
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ setChecked(getMainActivity().getParentalControlSettings()
+ .isContentRatingSystemEnabled(mContentRatingSystem));
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ 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
new file mode 100644
index 00000000..7ec28bb8
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java
@@ -0,0 +1,286 @@
+/*
+ * 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.parentalcontrols;
+
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+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.license.LicenseUtils;
+import com.android.tv.parental.ContentRatingSystem;
+import com.android.tv.parental.ContentRatingSystem.Rating;
+import com.android.tv.parental.ParentalControlSettings;
+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.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;
+
+public class RatingsFragment extends SideFragment {
+ private static final SparseIntArray sLevelResourceIdMap;
+ private static final SparseIntArray sDescriptionResourceIdMap;
+ private static final String TRACKER_LABEL = "Ratings";
+ private int mItemsSize;
+
+ 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);
+
+ 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);
+ }
+
+ private final List<RatingLevelItem> mRatingLevelItems = new ArrayList<>();
+
+ public static String getDescription(MainActivity tvActivity) {
+ @ContentRatingLevel int currentLevel =
+ tvActivity.getParentalControlSettings().getContentRatingLevel();
+ if (sLevelResourceIdMap.indexOfKey(currentLevel) >= 0) {
+ return tvActivity.getString(sLevelResourceIdMap.get(currentLevel));
+ }
+ return null;
+ }
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.option_ratings);
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ protected List<Item> getItemList() {
+ List<Item> items = new ArrayList<>();
+
+ mRatingLevelItems.clear();
+ for (int i = 0; i < sLevelResourceIdMap.size(); ++i) {
+ mRatingLevelItems.add(new RatingLevelItem(sLevelResourceIdMap.keyAt(i)));
+ }
+ updateRatingLevels();
+ items.addAll(mRatingLevelItems);
+
+ List<ContentRatingSystem> contentRatingSystems =
+ getMainActivity().getContentRatingsManager().getContentRatingSystems();
+ Collections.sort(contentRatingSystems, ContentRatingSystem.DISPLAY_NAME_COMPARATOR);
+
+ for (ContentRatingSystem s : contentRatingSystems) {
+ if (getMainActivity().getParentalControlSettings().isContentRatingSystemEnabled(s)) {
+ items.add(new DividerItem(s.getDisplayName()));
+ for (Rating rating : s.getRatings()) {
+ RatingItem item = rating.getSubRatings().size() == 0 ?
+ new RatingItem(s, rating) :
+ new RatingWithSubItem(s, rating);
+ items.add(item);
+ }
+ }
+ }
+ if (LicenseUtils.hasRatingAttribution(getMainActivity().getAssets())) {
+ // Display the attribution if our content rating system is selected.
+ items.add(new DividerItem());
+ items.add(new AttributionItem(getMainActivity()));
+ }
+ mItemsSize = items.size();
+ return items;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getMainActivity().getParentalControlSettings().loadRatings();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ // Although we set the attribution item at the end of the item list non-focusable, we do get
+ // its position when the fragment is resumed. This ensures that we do not select the
+ // non-focusable item at the end of the list. See b/17387103.
+ if (getSelectedPosition() >= mItemsSize) {
+ setSelectedPosition(mItemsSize - 1);
+ }
+ }
+
+ private void updateRatingLevels() {
+ @ContentRatingLevel int ratingLevel =
+ getMainActivity().getParentalControlSettings().getContentRatingLevel();
+ for (RatingLevelItem ratingLevelItem : mRatingLevelItems) {
+ ratingLevelItem.setChecked(ratingLevel == ratingLevelItem.mRatingLevel);
+ }
+ }
+
+ private class RatingLevelItem extends RadioButtonItem {
+ private final int mRatingLevel;
+
+ private RatingLevelItem(int ratingLevel) {
+ super(getString(sLevelResourceIdMap.get(ratingLevel)),
+ (sDescriptionResourceIdMap.indexOfKey(ratingLevel) >= 0) ?
+ getString(sDescriptionResourceIdMap.get(ratingLevel)) : null);
+ mRatingLevel = ratingLevel;
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ getMainActivity().getParentalControlSettings().setContentRatingLevel(
+ getMainActivity().getContentRatingsManager(), mRatingLevel);
+ notifyItemsChanged(mRatingLevelItems.size());
+ }
+ }
+
+ private class RatingItem extends CheckBoxItem {
+ protected final ContentRatingSystem mContentRatingSystem;
+ protected final Rating mRating;
+ private final Drawable mIcon;
+ private CompoundButton mCompoundButton;
+
+ private RatingItem(ContentRatingSystem contentRatingSystem, Rating rating) {
+ super(rating.getTitle(), rating.getDescription());
+ mContentRatingSystem = contentRatingSystem;
+ mRating = rating;
+ mIcon = rating.getIcon();
+ }
+
+ @Override
+ protected void onBind(View view) {
+ super.onBind(view);
+
+ mCompoundButton = (CompoundButton) view.findViewById(getCompoundButtonId());
+ mCompoundButton.setVisibility(View.VISIBLE);
+
+ ImageView imageView = (ImageView) view.findViewById(R.id.icon);
+ if (mIcon != null) {
+ imageView.setVisibility(View.VISIBLE);
+ imageView.setImageDrawable(mIcon);
+ } else {
+ imageView.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ protected void onUnbind() {
+ super.onUnbind();
+ mCompoundButton = null;
+ }
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ mCompoundButton.setButtonDrawable(getButtonDrawable());
+ setChecked(getMainActivity().getParentalControlSettings().isRatingBlocked(
+ mContentRatingSystem, mRating));
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ if (getMainActivity().getParentalControlSettings()
+ .setRatingBlocked(mContentRatingSystem, mRating, isChecked())) {
+ updateRatingLevels();
+ }
+ }
+
+ @Override
+ protected int getResourceId() {
+ return R.layout.option_item_rating;
+ }
+
+ protected int getButtonDrawable() {
+ return R.drawable.btn_lock_material_anim;
+ }
+ }
+
+ private class RatingWithSubItem extends RatingItem {
+ private RatingWithSubItem(ContentRatingSystem contentRatingSystem, Rating rating) {
+ super(contentRatingSystem, rating);
+ }
+
+ @Override
+ protected void onSelected() {
+ getMainActivity().getOverlayManager().getSideFragmentManager()
+ .show(new SubRatingsFragment(mContentRatingSystem, mRating));
+ }
+
+ @Override
+ protected int getButtonDrawable() {
+ int blockedStatus = getMainActivity().getParentalControlSettings().getBlockedStatus(
+ mContentRatingSystem, mRating);
+ if (blockedStatus == ParentalControlSettings.RATING_BLOCKED) {
+ return R.drawable.btn_lock_material;
+ } else if (blockedStatus == ParentalControlSettings.RATING_BLOCKED_PARTIAL) {
+ return R.drawable.btn_partial_lock_material;
+ }
+ return R.drawable.btn_unlock_material;
+ }
+ }
+
+ /**
+ * 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 TRACKER_LABEL = "Sources for content rating systems";
+ private final MainActivity mMainActivity;
+
+ public AttributionItem(MainActivity mainActivity) {
+ mMainActivity = mainActivity;
+ }
+
+ @Override
+ protected int getResourceId() {
+ return R.layout.option_item_attribution;
+ }
+
+ @Override
+ protected void onSelected() {
+ 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
new file mode 100644
index 00000000..f6612fdb
--- /dev/null
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java
@@ -0,0 +1,193 @@
+/*
+ * 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.parentalcontrols;
+
+import android.graphics.drawable.Drawable;
+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;
+import com.android.tv.parental.ContentRatingSystem.SubRating;
+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;
+
+public class SubRatingsFragment extends SideFragment {
+ private static final String TRACKER_LABEL = "Sub ratings";
+
+ private final ContentRatingSystem mContentRatingSystem;
+ private final Rating mRating;
+ private final List<SubRatingItem> mSubRatingItems = new ArrayList<>();
+
+ public SubRatingsFragment(ContentRatingSystem contentRatingSystem, Rating rating) {
+ mContentRatingSystem = contentRatingSystem;
+ mRating = rating;
+ }
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.option_subrating_title, mRating.getTitle());
+ }
+
+ @Override
+ public String getTrackerLabel() {
+ return TRACKER_LABEL;
+ }
+
+ @Override
+ protected List<Item> getItemList() {
+ List<Item> items = new ArrayList<>();
+ items.add(new RatingItem());
+ items.add(new DividerItem(getString(R.string.option_subrating_header)));
+ mSubRatingItems.clear();
+ for (SubRating subRating : mRating.getSubRatings()) {
+ mSubRatingItems.add(new SubRatingItem(subRating));
+ }
+ items.addAll(mSubRatingItems);
+ return items;
+ }
+
+ private class RatingItem extends CheckBoxItem {
+ private RatingItem() {
+ super(mRating.getTitle(), mRating.getDescription());
+ }
+
+ @Override
+ protected void onBind(View view) {
+ super.onBind(view);
+
+ CompoundButton button = (CompoundButton) view.findViewById(getCompoundButtonId());
+ button.setButtonDrawable(R.drawable.btn_lock_material_anim);
+ button.setVisibility(View.VISIBLE);
+
+ Drawable icon = mRating.getIcon();
+ ImageView imageView = (ImageView) view.findViewById(R.id.icon);
+ if (icon != null) {
+ imageView.setVisibility(View.VISIBLE);
+ imageView.setImageDrawable(icon);
+ } else {
+ imageView.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ setChecked(isRatingEnabled());
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ boolean checked = isChecked();
+ setRatingEnabled(checked);
+ if (checked) {
+ // If the rating is checked, check and disable all the sub rating items.
+ for (SubRating subRating : mRating.getSubRatings()) {
+ setSubRatingEnabled(subRating, true);
+ }
+ for (SubRatingItem item : mSubRatingItems) {
+ item.setChecked(true);
+ item.setEnabled(false);
+ }
+ } else {
+ // If the rating is unchecked, just enable all the sub rating items and do not
+ // change the check state.
+ for (SubRatingItem item : mSubRatingItems) {
+ item.setEnabled(true);
+ }
+ }
+ }
+
+ @Override
+ protected int getResourceId() {
+ return R.layout.option_item_rating;
+ }
+ }
+
+ private class SubRatingItem extends CheckBoxItem {
+ private final SubRating mSubRating;
+
+ private SubRatingItem(SubRating subRating) {
+ super(subRating.getTitle(), subRating.getDescription());
+ mSubRating = subRating;
+ }
+
+ @Override
+ protected void onBind(View view) {
+ super.onBind(view);
+
+ CompoundButton button = (CompoundButton) view.findViewById(getCompoundButtonId());
+ button.setButtonDrawable(R.drawable.btn_lock_material_anim);
+ button.setVisibility(View.VISIBLE);
+
+ Drawable icon = mSubRating.getIcon();
+ ImageView imageView = (ImageView) view.findViewById(R.id.icon);
+ if (icon != null) {
+ imageView.setVisibility(View.VISIBLE);
+ imageView.setImageDrawable(icon);
+ } else {
+ imageView.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ setChecked(isSubRatingEnabled(mSubRating));
+ setEnabled(!isRatingEnabled());
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ setSubRatingEnabled(mSubRating, isChecked());
+ }
+
+ @Override
+ protected int getResourceId() {
+ return R.layout.option_item_rating;
+ }
+ }
+
+ private boolean isRatingEnabled() {
+ return getMainActivity().getParentalControlSettings()
+ .isRatingBlocked(mContentRatingSystem, mRating);
+ }
+
+ private boolean isSubRatingEnabled(SubRating subRating) {
+ return getMainActivity().getParentalControlSettings()
+ .isSubRatingEnabled(mContentRatingSystem, mRating, subRating);
+ }
+
+ private void setRatingEnabled(boolean enabled) {
+ getMainActivity().getParentalControlSettings()
+ .setRatingBlocked(mContentRatingSystem, mRating, enabled);
+ }
+
+ private void setSubRatingEnabled(SubRating subRating, boolean enabled) {
+ getMainActivity().getParentalControlSettings()
+ .setSubRatingBlocked(mContentRatingSystem, mRating, subRating, enabled);
+ }
+}
diff --git a/src/com/android/tv/util/AsyncDbTask.java b/src/com/android/tv/util/AsyncDbTask.java
new file mode 100644
index 00000000..73f45638
--- /dev/null
+++ b/src/com/android/tv/util/AsyncDbTask.java
@@ -0,0 +1,275 @@
+/*
+ * 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.ContentResolver;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.support.annotation.NonNull;
+import android.util.Log;
+import android.util.Range;
+
+import com.android.tv.data.Channel;
+import com.android.tv.data.Program;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * {@link AsyncTask} that defaults to executing on its own single threaded Executor Service.
+ *
+ * <p>Instances of this class should only be executed this using {@link
+ * #executeOnDbThread(Object[])}.
+ */
+public abstract class AsyncDbTask<Params, Progress, Result>
+ extends AsyncTask<Params, Progress, Result> {
+ private static final String TAG = "AsyncDbTask";
+ private static final boolean DEBUG = false;
+
+ private static 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);
+ }
+ }
+
+ public 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.
+ */
+ public static ExecutorService getExecutor() {
+ return DB_EXECUTOR;
+ }
+
+ /**
+ * Executes the given command at some time in the future.
+ *
+ * <p>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
+ */
+ public static void execute(Runnable command) {
+ DB_EXECUTOR.execute(command);
+ }
+
+ /**
+ * Returns the result of a {@link ContentResolver#query(Uri, String[], String, String[],
+ * String)}.
+ *
+ * <p> {@link #doInBackground(Void...)} executes the query on call {@link #onQuery(Cursor)}
+ * which is implemented by subclasses.
+ *
+ * @param <Result> The type of result returned by {@link #onQuery(Cursor)}
+ */
+ public abstract static class AsyncQueryTask<Result> extends AsyncDbTask<Void, Void, Result> {
+ private final ContentResolver mContentResolver;
+ private final Uri mUri;
+ private final String[] mProjection;
+ private final String mSelection;
+ private final String[] mSelectionArgs;
+ private final String mOrderBy;
+
+
+ public AsyncQueryTask(ContentResolver contentResolver, Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String orderBy) {
+ mContentResolver = contentResolver;
+ mUri = uri;
+ mProjection = projection;
+ mSelection = selection;
+ mSelectionArgs = selectionArgs;
+ mOrderBy = orderBy;
+ }
+
+ @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());
+ Log.w(TAG, e);
+ if (DEBUG) {
+ throw e;
+ }
+ }
+
+ if (isCancelled()) {
+ // This is guaranteed to never call onPostExecute because the task is canceled.
+ return null;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Starting query for " + this);
+ }
+ try (Cursor c = mContentResolver
+ .query(mUri, mProjection, mSelection, mSelectionArgs, mOrderBy)) {
+ if (c != null && !isCancelled()) {
+ Result result = 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;
+ }
+ }
+ }
+
+ /**
+ * Return the result from the cursor.
+ *
+ * <p><b>Note</b> This is executed on the DB thread by {@link #doInBackground(Void...)}
+ */
+ protected abstract Result onQuery(Cursor c);
+
+ @Override
+ public String toString() {
+ return this.getClass().getSimpleName() + "(" + mUri + ")";
+ }
+ }
+
+ /**
+ * Returns the result of a query as an {@link List} of {@code T}.
+ *
+ * <p>Subclasses must implement {@link #fromCursor(Cursor)}.
+ */
+ public static abstract class AsyncQueryListTask<T> extends AsyncQueryTask<List<T>> {
+
+ public AsyncQueryListTask(ContentResolver contentResolver, Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String orderBy) {
+ super(contentResolver, uri, projection, selection, selectionArgs, orderBy);
+ }
+
+ @Override
+ protected final List<T> onQuery(Cursor c) {
+ List<T> result = new ArrayList<>();
+ while (c.moveToNext()) {
+ if (isCancelled()) {
+ // This is guaranteed to never call onPostExecute because the task is canceled.
+ return null;
+ }
+ T t = fromCursor(c);
+ result.add(t);
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Found " + result.size() + " for " + this);
+ }
+ return result;
+ }
+
+ /**
+ * Return a single instance of {@code T} from the cursor.
+ *
+ * <p><b>NOTE</b> Do not move the cursor or close it, that is handled by {@link
+ * #onQuery(Cursor)}.
+ *
+ * <p><b>Note</b> This is executed on the DB thread by {@link #onQuery(Cursor)}
+ *
+ * @param c The cursor with the values to create T from.
+ */
+ protected abstract T fromCursor(Cursor c);
+ }
+
+ /**
+ * Gets an {@link List} of {@link Channel}s from {@link TvContract.Channels#CONTENT_URI}.
+ */
+ public static abstract class AsyncChannelQueryTask extends AsyncQueryListTask<Channel> {
+
+ public AsyncChannelQueryTask(ContentResolver contentResolver) {
+ super(contentResolver, TvContract.Channels.CONTENT_URI, Channel.PROJECTION,
+ null, null, null);
+ }
+
+ @Override
+ protected final Channel fromCursor(Cursor c) {
+ return Channel.fromCursor(c);
+ }
+ }
+
+ /**
+ * Execute the task on the {@link #DB_EXECUTOR} thread.
+ */
+ @SafeVarargs
+ public final void executeOnDbThread(Params... params) {
+ executeOnExecutor(DB_EXECUTOR, params);
+ }
+
+ /**
+ * Gets an {@link List} of {@link Program}s for a given channel and period {@link
+ * TvContract#buildProgramsUriForChannel(long, long, long)}.
+ */
+ public static class LoadProgramsForChannelTask extends AsyncQueryListTask<Program> {
+ protected final Range<Long> mPeriod;
+ protected final long mChannelId;
+
+ public LoadProgramsForChannelTask(ContentResolver contentResolver, long channelId,
+ Range<Long> period) {
+ super(contentResolver, TvContract
+ .buildProgramsUriForChannel(channelId, period.getLower(), period.getUpper()),
+ Program.PROJECTION, null, null, null);
+ mPeriod = period;
+ mChannelId = channelId;
+ }
+
+ @Override
+ protected final Program fromCursor(Cursor c) {
+ return Program.fromCursor(c);
+ }
+
+ public long getChannelId() {
+ return mChannelId;
+ }
+
+ public final Range<Long> getPeriod() {
+ return mPeriod;
+ }
+ }
+}
diff --git a/src/com/android/tv/util/BitmapUtils.java b/src/com/android/tv/util/BitmapUtils.java
index 3ad55889..c06eac03 100644
--- a/src/com/android/tv/util/BitmapUtils.java
+++ b/src/com/android/tv/util/BitmapUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,59 +16,53 @@
package com.android.tv.util;
+import android.content.ContentResolver;
import android.content.Context;
+import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
-import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import java.io.BufferedInputStream;
-import java.io.FileNotFoundException;
+import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
-import java.net.MalformedURLException;
import java.net.URL;
+import java.net.URLConnection;
public class BitmapUtils {
private static final String TAG = "BitmapUtils";
+ private static final boolean DEBUG = false;
- private static final int MARK_READ_LIMIT = 10 * 1024; // 10K
+ // 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 BitmapUtils() { /* cannot be instantiated */ }
-
- public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, float roundPx, float targetWidth,
- float targetHeight) {
- Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),
- Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(output);
-
- final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
- final RectF rectF = new RectF(rect);
-
- final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
- paint.setColor(Color.BLACK);
+ private static final int CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION = 3000; // 3 sec
+ private static final int READ_TIMEOUT_MS_FOR_URLCONNECTION = 10000; // 10 sec
- canvas.drawARGB(0, 0, 0, 0);
- canvas.drawRoundRect(rectF, roundPx * bitmap.getWidth() / targetWidth,
- roundPx * bitmap.getHeight() / targetHeight, paint);
- paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
- canvas.drawBitmap(bitmap, rect, rect, paint);
+ private BitmapUtils() { /* cannot be instantiated */ }
- return output;
+ public static Bitmap scaleBitmap(Bitmap bm, int maxWidth, int maxHeight) {
+ Bitmap result;
+ Rect rect = calculateNewSize(bm, maxWidth, maxHeight);
+ result = Bitmap.createBitmap(rect.right, rect.bottom, bm.getConfig());
+ Canvas canvas = new Canvas(result);
+ canvas.drawBitmap(bm, null, rect, null);
+ return result;
}
- public static Bitmap scaleBitmap(Bitmap bm, int maxWidth, int maxHeight) {
+ private static Rect calculateNewSize(Bitmap bm, int maxWidth, int maxHeight) {
final double ratio = maxHeight / (double) maxWidth;
final double bmRatio = bm.getHeight() / (double) bm.getWidth();
- Bitmap result = null;
Rect rect = new Rect();
if (ratio > bmRatio) {
rect.right = maxWidth;
@@ -77,121 +71,196 @@ public class BitmapUtils {
rect.right = Math.round((float) bm.getWidth() * maxHeight / bm.getHeight());
rect.bottom = maxHeight;
}
- result = Bitmap.createBitmap(rect.right, rect.bottom, bm.getConfig());
- Canvas canvas = new Canvas(result);
- canvas.drawBitmap(bm, null, rect, null);
- return result;
+ return rect;
}
- /*
- * Decode large sized bitmap into required size.
+ 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 Bitmap decodeSampledBitmapFromUriString(Context context, String uriString,
- int reqWidth, int reqHeight) {
+ public static ScaledBitmapInfo decodeSampledBitmapFromUriString(Context context,
+ String uriString, int reqWidth, int reqHeight) {
if (TextUtils.isEmpty(uriString)) {
return null;
}
- InputStream is = null;
+ InputStream inputStream = null;
try {
- is = getInputStream(context, uriString);
- if (is == null) {
- return null;
- }
+ inputStream = new BufferedInputStream(getInputStream(context, uriString));
+ inputStream.mark(MARK_READ_LIMIT);
+
+ // Check the bitmap dimensions.
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(inputStream, null, options);
+
+ // Rewind the stream in order to restart bitmap decoding.
+ try {
+ inputStream.reset();
+ } catch (IOException e) {
+ if (DEBUG) {
+ Log.i(TAG, "Failed to rewind stream: " + uriString, e);
+ }
- // We doesn't trust TIS to provide us with proper sized image
- Bitmap bitmap = decodeSampledBitmapFromStream(is, reqWidth, reqHeight);
- if (bitmap != null) {
- return bitmap;
+ // Failed to rewind the stream, try to reopen it.
+ close(inputStream);
+ inputStream = getInputStream(context, uriString);
}
- closeInputStream(is);
- is = getInputStream(context, uriString);
- if (is == null) {
+ // Decode the bitmap possibly resizing it.
+ options.inJustDecodeBounds = false;
+ options.inPreferredConfig = Bitmap.Config.RGB_565;
+ options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+ Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
+ if (bitmap == null) {
return null;
}
- return BitmapFactory.decodeStream(is);
+ return new ScaledBitmapInfo(uriString, bitmap, options.inSampleSize);
+ } catch (IOException e) {
+ if (DEBUG) {
+ // It can happens in normal cases like when a channel doesn't have any logo.
+ Log.w(TAG, "Failed to open stream: " + uriString, e);
+ }
+ return null;
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Failed to open stream: " + uriString, e);
+ return null;
} finally {
- closeInputStream(is);
+ close(inputStream);
}
}
- /*
- * Decode large sized bitmap into required size.
- * If it returns null, the InputStream should be closed and re-opened.
- */
- public static Bitmap decodeSampledBitmapFromStream(InputStream is, int reqWidth,
+ private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth,
int reqHeight) {
- // The input stream is read two times, so BufferedInputStream which supports marking should
- // be used.
- BufferedInputStream bis = new BufferedInputStream(is);
- // 10K is the sufficient for the image header, because only the image header will be read
- // at the first time.
- bis.mark(MARK_READ_LIMIT);
-
- // First decode with inJustDecodeBounds=true to check dimensions
- final BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(bis, null, options);
-
- // Reset the input stream to read from the start.
- try {
- bis.reset();
- } catch (IOException e) {
- Log.i(TAG, "Failed to reset input stream.", e);
- return null;
- }
-
- // Calculate inSampleSize
- options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+ return calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight);
+ }
- // Decode bitmap with inSampleSize set
- options.inJustDecodeBounds = false;
- return BitmapFactory.decodeStream(bis, null, options);
+ private static int calculateInSampleSize(int width, int height, int reqWidth, int reqHeight) {
+ // Calculates the largest inSampleSize that, is a power of two and, keeps either width or
+ // height larger or equal to the requested width and height.
+ int ratio = Math.max(width / reqWidth, height / reqHeight);
+ return Math.max(1, Integer.highestOneBit(ratio));
}
- private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth,
- int reqHeight) {
- // Raw height and width of image
- // They are shifted right by one bit which causes an effect that inSampleSize is shifted
- // left by one bit.
- int width = options.outWidth >> 1;
- int height = options.outHeight >> 1;
- int inSampleSize = 1;
-
- // Calculate the largest inSampleSize value that is a power of 2 and keeps either
- // height and width larger than the requested height and width.
- while (width > reqWidth || height > reqHeight) {
- width >>= 1;
- height >>= 1;
- inSampleSize <<= 1;
+ private static InputStream getInputStream(Context context, String uriString)
+ throws IOException {
+ Uri uri = Uri.parse(uriString).normalizeScheme();
+ if (isContentResolverUri(uri)) {
+ return context.getContentResolver().openInputStream(uri);
+ } else {
+ // TODO We should disconnect() the URLConnection in order to allow connection reuse.
+ URLConnection urlConnection = new URL(uriString).openConnection();
+ urlConnection.setConnectTimeout(CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION);
+ urlConnection.setReadTimeout(READ_TIMEOUT_MS_FOR_URLCONNECTION);
+ return urlConnection.getInputStream();
}
+ }
- return inSampleSize;
+ private static boolean isContentResolverUri(Uri uri) {
+ String scheme = uri.getScheme();
+ return ContentResolver.SCHEME_CONTENT.equals(scheme)
+ || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
+ || ContentResolver.SCHEME_FILE.equals(scheme);
}
- private static InputStream getInputStream(Context context, String uriString) {
- try {
- return new URL(uriString).openStream();
- } catch (MalformedURLException e) {
+ private static void close(Closeable closeable) {
+ if (closeable != null) {
try {
- return context.getContentResolver().openInputStream(Uri.parse(uriString));
- } catch (FileNotFoundException ex) {
- Log.i(TAG, "Unable to load uri: " + uriString);
+ closeable.close();
+ } catch (IOException e) {
+ // Log and continue.
+ Log.w(TAG,"Error closing " + closeable, e);
}
- } catch (IOException e) {
- Log.i(TAG, "Failed to open stream: " + uriString);
}
- return null;
}
- private static void closeInputStream(InputStream is) {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- // Does nothing.
+ /**
+ * 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 loaded bitmap object.
+ */
+ @NonNull
+ public final Bitmap bitmap;
+
+ /**
+ * The scaling factor to the original bitmap. It should be an positive integer.
+ *
+ * @see android.graphics.BitmapFactory.Options#inSampleSize
+ */
+ public final int inSampleSize;
+
+ /**
+ * A constructor.
+ *
+ * @param bitmap The loaded bitmap object.
+ * @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;
+ this.bitmap = bitmap;
+ this.inSampleSize = inSampleSize;
+ }
+
+ /**
+ * 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
+ */
+ public boolean needToReload(int reqWidth, int reqHeight) {
+ if (inSampleSize <= 1) {
+ if (DEBUG) Log.d(TAG, "Reload not required " + this + " already full size.");
+ return false;
+ }
+ Rect size = calculateNewSize(this.bitmap, reqWidth, reqHeight);
+ boolean reload = (size.right >= bitmap.getWidth() * 2
+ || size.bottom >= bitmap.getHeight() * 2);
+ if (DEBUG) {
+ Log.d(TAG, "needToReload(" + reqWidth + ", " + reqHeight + ")=" + reload
+ + " becuase 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){
+ return needToReload(other.bitmap.getWidth(), other.bitmap.getHeight());
+ }
+
+ @Override
+ public String toString() {
+ return "ScaledBitmapInfo[" + id + "](in=" + inSampleSize + ", w=" + bitmap.getWidth()
+ + ", h=" + bitmap.getHeight() + ")";
+ }
+ }
+
+ /**
+ * Applies a color filter to the {@code drawable}. The color filter is made with the given
+ * {@code color} and {@link android.graphics.PorterDuff.Mode#SRC_ATOP}.
+ *
+ * @see Drawable#setColorFilter
+ */
+ public static void setColorFilterToDrawable(int color, Drawable drawable) {
+ if (drawable != null) {
+ drawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/tv/util/BooleanSystemProperty.java b/src/com/android/tv/util/BooleanSystemProperty.java
new file mode 100644
index 00000000..11dd5ab8
--- /dev/null
+++ b/src/com/android/tv/util/BooleanSystemProperty.java
@@ -0,0 +1,90 @@
+/*
+ * 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.util.Log;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Lazy loaded boolean system property.
+ * <p>
+ * Set with <code>adb shell setprop <em>key</em> <em>value</em></code> where value is
+ */
+public final class BooleanSystemProperty {
+ private final static String TAG = "BooleanSystemProperty";
+ private final static boolean DEBUG = false;
+ private static final List<BooleanSystemProperty> ALL_PROPERTIES = new ArrayList<>();
+ private final boolean mDefaultValue;
+ private final String mKey;
+ private Boolean mValue = null;
+
+ public BooleanSystemProperty(String key, boolean defaultValue) {
+ mDefaultValue = defaultValue;
+ mKey = key;
+ ALL_PROPERTIES.add(this);
+ }
+
+ public static void resetAll() {
+ for (BooleanSystemProperty prop : ALL_PROPERTIES) {
+ prop.reset();
+ }
+ }
+
+ /**
+ * Gets system properties set by <code>adb shell setprop <em>key</em> <em>value</em></code>
+ *
+ * @param key the property key.
+ * @param defaultValue the value to return if the property is undefined or empty.
+ * @return the system property value or the default value.
+ */
+ private static boolean getBoolean(String key, boolean defaultValue) {
+ try {
+ final Class<?> systemProperties = Class.forName("android.os.SystemProperties");
+ final Method get = systemProperties.getMethod("getBoolean", String.class, Boolean.TYPE);
+ return (boolean) get.invoke(null, key, defaultValue);
+ } catch (Exception e) {
+ Log.e(TAG, "Error getting boolean for " + key, e);
+ // This should never happen
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Clears the cached value. The next call to getValue will check {@code
+ * android.os.SystemProperties}.
+ */
+ public void reset() {
+ mValue = null;
+ }
+
+ /**
+ * Returns the value of the system property.
+ *
+ * <p>If the value is cached get the value from {@code android.os.SystemProperties} with the
+ * default set in the constructor.
+ */
+ public boolean getValue() {
+ if (mValue == null) {
+ mValue = getBoolean(mKey, mDefaultValue);
+ if (DEBUG) Log.d(TAG, mKey + "=" + mValue);
+ }
+ return mValue;
+ }
+}
diff --git a/src/com/android/tv/util/CaptionSettings.java b/src/com/android/tv/util/CaptionSettings.java
new file mode 100644
index 00000000..3b38905b
--- /dev/null
+++ b/src/com/android/tv/util/CaptionSettings.java
@@ -0,0 +1,100 @@
+/*
+ * 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.view.accessibility.CaptioningManager;
+
+import java.util.Locale;
+
+public class CaptionSettings {
+ public static final int OPTION_SYSTEM = 0;
+ public static final int OPTION_OFF = 1;
+ public static final int OPTION_ON = 2;
+
+ private final CaptioningManager mCaptioningManager;
+ private int mOption = OPTION_SYSTEM;
+ private String mLanguage;
+ private String mTrackId;
+
+ public CaptionSettings(Context context) {
+ mCaptioningManager = (CaptioningManager) context.getSystemService(
+ Context.CAPTIONING_SERVICE);
+ }
+
+ public final String getSystemLanguage() {
+ Locale l = mCaptioningManager.getLocale();
+ if (l != null) {
+ return l.getLanguage();
+ }
+ return null;
+ }
+
+ public final String getLanguage() {
+ switch (mOption) {
+ case OPTION_SYSTEM:
+ return getSystemLanguage();
+ case OPTION_OFF:
+ return null;
+ case OPTION_ON:
+ return mLanguage;
+ }
+ return null;
+ }
+
+ public final boolean isSystemSettingEnabled() {
+ return mCaptioningManager.isEnabled();
+ }
+
+ public final boolean isEnabled() {
+ switch (mOption) {
+ case OPTION_SYSTEM:
+ return isSystemSettingEnabled();
+ case OPTION_OFF:
+ return false;
+ case OPTION_ON:
+ return true;
+ }
+ return false;
+ }
+
+ public int getEnableOption() {
+ return mOption;
+ }
+
+ public void setEnableOption(int option) {
+ mOption = option;
+ }
+
+ public void setLanguage(String language) {
+ mLanguage = language;
+ }
+
+ /**
+ * 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.
+ */
+ public void setTrackId(String trackId) {
+ mTrackId = trackId;
+ }
+}
diff --git a/sample/res/xml/externalfiletvinputservice.xml b/src/com/android/tv/util/Clock.java
index bd34cde3..58653068 100644
--- a/sample/res/xml/externalfiletvinputservice.xml
+++ b/src/com/android/tv/util/Clock.java
@@ -1,13 +1,11 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-/**
- * Copyright (c) 2014, The Android Open Source Project
+/*
+ * 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
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,8 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
--->
-<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
- android:setupActivity="com.example.sampletvinput.ExternalFileTvInputSettingsActivity"
- android:settingsActivity="com.example.sampletvinput.ExternalFileTvInputSettingsActivity" />
+package com.android.tv.util;
+
+/**
+ * Interface to provide time APIs for test.
+ */
+public interface Clock {
+ /**
+ * Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC.
+ */
+ long currentTimeMillis();
+
+ Clock SYSTEM = new Clock() {
+ @Override
+ public long currentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+ };
+}
diff --git a/src/com/android/tv/util/ImageCache.java b/src/com/android/tv/util/ImageCache.java
new file mode 100644
index 00000000..67a63a59
--- /dev/null
+++ b/src/com/android/tv/util/ImageCache.java
@@ -0,0 +1,171 @@
+/*
+ * 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.VisibleForTesting;
+import android.util.Log;
+import android.util.LruCache;
+
+import com.android.tv.MainActivity;
+import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
+
+/**
+ * A convenience class for caching bitmap.
+ */
+public class ImageCache implements MainActivity.MemoryManageable {
+ private static final float MAX_CACHE_SIZE_PERCENT = 0.8f;
+ private static final float MIN_CACHE_SIZE_PERCENT = 0.05f;
+ private static final float DEFAULT_CACHE_SIZE_PERCENT = 0.1f;
+ private static final boolean DEBUG = false;
+ private static final String TAG = "ImageCache";
+ private static final int MIN_CACHE_SIZE_KBYTES = 1024;
+
+ private final LruCache<String, ScaledBitmapInfo> mMemoryCache;
+
+ /**
+ * Creates a new ImageCache object with a given cache size percent.
+ *
+ * @param memCacheSizePercent The cache size as a percent of available app memory.
+ */
+ private ImageCache(float memCacheSizePercent) {
+ int memCacheSize = calculateMemCacheSize(memCacheSizePercent);
+
+ // Set up memory cache
+ if (DEBUG) {
+ Log.d(TAG, "Memory cache created (size = " + memCacheSize + " Kbytes)");
+ }
+ mMemoryCache = new LruCache<String, ScaledBitmapInfo>(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;
+
+ /**
+ * Returns an existing ImageCache, if it doesn't exist, a new one is created using the supplied
+ * 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).
+ * @return An existing retained ImageCache object or a new one if one did not exist
+ */
+ public static synchronized ImageCache getInstance(float memCacheSizePercent) {
+ if (sImageCache == null) {
+ sImageCache = newInstance(memCacheSizePercent);
+ }
+ return sImageCache;
+ }
+
+ @VisibleForTesting
+ static ImageCache newInstance(float memCacheSizePercent) {
+ 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).
+ *
+ * @return An existing retained ImageCache object or a new one if one did not exist
+ */
+ public static ImageCache getInstance() {
+ return getInstance(DEFAULT_CACHE_SIZE_PERCENT);
+ }
+
+ /**
+ * Adds a bitmap to memory cache.
+ *
+ * <p>If there is an existing bitmap only replace it if
+ * {@link ScaledBitmapInfo#needToReload(ScaledBitmapInfo)} is true.
+ *
+ * @param bitmapInfo The {@link ScaledBitmapInfo} object to store
+ */
+ public void putIfNeeded(ScaledBitmapInfo bitmapInfo) {
+ if (bitmapInfo == null || bitmapInfo.id == null) {
+ throw new IllegalArgumentException("Neither bitmap nor bitmap.id should be null.");
+ }
+ String key = bitmapInfo.id;
+ // Add to memory cache
+ synchronized (mMemoryCache) {
+ ScaledBitmapInfo old = mMemoryCache.put(key, bitmapInfo);
+ 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 + ".");
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Add " + bitmapInfo + " to memory cache. Current size is " +
+ mMemoryCache.size() + " / " + mMemoryCache.maxSize() + " Kbytes");
+ }
+ }
+ }
+ }
+
+ /**
+ * Get from memory cache.
+ *
+ * @param key Unique identifier for which item to get
+ * @return The bitmap if found in cache, null otherwise
+ */
+ public ScaledBitmapInfo get(String key) {
+ ScaledBitmapInfo memBitmapInfo = mMemoryCache.get(key);
+ if (DEBUG) {
+ int hit = mMemoryCache.hitCount();
+ int miss = mMemoryCache.missCount();
+ String result = memBitmapInfo == null ? "miss" : "hit";
+ double ratio = ((double) hit) / (hit + miss) * 100;
+ Log.d(TAG, "Memory cache " + result + " for " + key);
+ Log.d(TAG, "Memory cache " + hit + "h:" + miss + "m " + ratio + "%");
+ }
+ return memBitmapInfo;
+ }
+
+ /**
+ * 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:
+ * 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)");
+ }
+ return Math.max(MIN_CACHE_SIZE_KBYTES,
+ Math.round(percent * Runtime.getRuntime().maxMemory() / 1024));
+ }
+
+ @Override
+ public void performTrimMemory(int level) {
+ mMemoryCache.evictAll();
+ }
+}
diff --git a/src/com/android/tv/util/ImageLoader.java b/src/com/android/tv/util/ImageLoader.java
new file mode 100644
index 00000000..8e901dd0
--- /dev/null
+++ b/src/com/android/tv/util/ImageLoader.java
@@ -0,0 +1,322 @@
+/*
+ * 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.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.tv.TvInputInfo;
+import android.os.AsyncTask;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
+import android.support.annotation.WorkerThread;
+import android.util.Log;
+
+import com.android.tv.R;
+import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * 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";
+ private static final boolean DEBUG = false;
+
+ /**
+ * Interface definition for a callback to be invoked when image loading is finished.
+ */
+ @UiThread
+ public interface ImageLoaderCallback {
+ /**
+ * Called when bitmap is loaded.
+ */
+ void onBitmapLoaded(@Nullable Bitmap bitmap);
+ }
+
+ private static final Map<String, LoadBitmapTask> sPendingListMap = new HashMap<>();
+
+ /**
+ * Preload a bitmap image into the cache.
+ *
+ * <p>Not to make heavy CPU load, AsyncTask.SERIAL_EXECUTOR is used for the image loading.
+ */
+ @UiThread
+ public static void prefetchBitmap(Context context, String uriString,
+ int maxWidth, int maxHeight) {
+ if (DEBUG) {
+ Log.d(TAG, "prefetchBitmap() " + uriString);
+ }
+ doLoadBitmap(context, uriString, maxWidth, maxHeight, null, AsyncTask.SERIAL_EXECUTOR);
+ }
+
+ /**
+ * Load a bitmap image with the cache using a ContentResolver.
+ *
+ * <p><b>Note</b> 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) {
+ return loadBitmap(context, uriString, Integer.MAX_VALUE, Integer.MAX_VALUE, callback);
+ }
+
+ /**
+ * Load a bitmap image with the cache and resize it with given params.
+ *
+ * <p><b>Note</b> 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,
+ ImageLoaderCallback callback) {
+ if (DEBUG) {
+ Log.d(TAG, "loadBitmap() " + uriString);
+ }
+ return doLoadBitmap(context, uriString, maxWidth, maxHeight, callback,
+ AsyncTask.THREAD_POOL_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();
+ ScaledBitmapInfo bitmapInfo = imageCache.get(uriString);
+ if (bitmapInfo != null && !bitmapInfo.needToReload(maxWidth, maxHeight)) {
+ if (callback != null) {
+ callback.onBitmapLoaded(bitmapInfo.bitmap);
+ }
+ return true;
+ }
+ return doLoadBitmap(callback, executor,
+ new LoadBitmapFromUriTask(context, imageCache, uriString, maxWidth, maxHeight));
+ }
+
+ /**
+ * Load a bitmap image with the cache and resize it with given params.
+ *
+ * <p>The LoadBitmapTask will be executed on a non ui thread.
+ *
+ * @return {@code true} if the load is complete and the callback is executed.
+ */
+ @UiThread
+ public static boolean loadBitmap(ImageLoaderCallback callback, LoadBitmapTask loadBitmapTask) {
+ if (DEBUG) {
+ Log.d(TAG, "loadBitmap() " + loadBitmapTask);
+ }
+ return doLoadBitmap(callback, AsyncTask.THREAD_POOL_EXECUTOR, loadBitmapTask);
+ }
+
+ /**
+ * @return {@code true} if the load is complete and the callback is executed.
+ */
+ private static boolean doLoadBitmap(ImageLoaderCallback callback, Executor executor,
+ LoadBitmapTask loadBitmapTask) {
+ ScaledBitmapInfo bitmapInfo = loadBitmapTask.getFromCache();
+ boolean needToReload = loadBitmapTask.isReloadNeeded();
+ if (bitmapInfo != null && !needToReload) {
+ if (callback != null) {
+ callback.onBitmapLoaded(bitmapInfo.bitmap);
+ }
+ return true;
+ }
+ LoadBitmapTask existingTask = sPendingListMap.get(loadBitmapTask.getKey());
+ if (existingTask != null && !loadBitmapTask.isReloadNeeded(existingTask) ) {
+ // The image loading is already scheduled and is large enough.
+ if (callback != null) {
+ existingTask.mCallbacks.add(callback);
+ }
+ } else {
+ if (callback != null) {
+ loadBitmapTask.mCallbacks.add(callback);
+ }
+ sPendingListMap.put(loadBitmapTask.getKey(), loadBitmapTask);
+ try {
+ loadBitmapTask.executeOnExecutor(executor);
+ } catch (RejectedExecutionException e) {
+ Log.e(TAG, "Failed to create new image loader", e);
+ sPendingListMap.remove(loadBitmapTask.getKey());
+ }
+ }
+ return false;
+ }
+
+/**
+ * Loads and caches a a possibly scaled down version of a bitmap.
+ *
+ * <p>Implement {@link #doGetBitmapInBackground()} to to the actual loading.
+ */
+ public static abstract class LoadBitmapTask extends AsyncTask<Void, Void, ScaledBitmapInfo> {
+ protected final int mMaxWidth;
+ protected final int mMaxHeight;
+ private final List<ImageLoader.ImageLoaderCallback> mCallbacks = new ArrayList<>();
+ private final ImageCache mImageCache;
+ private final String mKey;
+
+ /**
+ * Returns true if a reload is needed compared to current results in the cache or false if
+ * there is not match in the cache.
+ */
+ private boolean isReloadNeeded() {
+ ScaledBitmapInfo bitmapInfo = getFromCache();
+ 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);
+ }
+ }
+ return needToReload;
+ }
+
+ /**
+ * 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;
+ }
+
+ @Nullable
+ public final ScaledBitmapInfo getFromCache() {
+ return mImageCache.get(mKey);
+ }
+
+ public LoadBitmapTask(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 + "}");
+ }
+ mKey = key;
+ mImageCache = imageCache;
+ mMaxHeight = maxHeight;
+ mMaxWidth = maxWidth;
+ }
+
+ /**
+ * Loads the bitmap returning a possibly scaled down version.
+ */
+ @Nullable
+ @WorkerThread
+ public abstract ScaledBitmapInfo doGetBitmapInBackground();
+
+ @Override
+ @Nullable
+ public final ScaledBitmapInfo doInBackground(Void... params) {
+ ScaledBitmapInfo bitmapInfo = getFromCache();
+ if (bitmapInfo != null && !isReloadNeeded()) {
+ return bitmapInfo;
+ }
+ bitmapInfo = doGetBitmapInBackground();
+ if (bitmapInfo != null) {
+ mImageCache.putIfNeeded(bitmapInfo);
+ }
+ return bitmapInfo;
+ }
+
+ @Override
+ public final void onPostExecute(ScaledBitmapInfo scaledBitmapInfo) {
+ if (ImageLoader.DEBUG) {
+ Log.d(ImageLoader.TAG, "Bitmap is loaded " + mKey);
+ }
+ for (ImageLoader.ImageLoaderCallback callback : mCallbacks) {
+ callback.onBitmapLoaded(scaledBitmapInfo == null ? null : scaledBitmapInfo.bitmap);
+ }
+ ImageLoader.sPendingListMap.remove(mKey);
+ }
+
+ public final String getKey() {
+ return mKey;
+ }
+
+ @Override
+ public String toString() {
+ return this.getClass().getSimpleName() + "(" + mKey + " "
+ + mMaxWidth + "x" + mMaxHeight + ")";
+ }
+ }
+
+ private static final class LoadBitmapFromUriTask extends LoadBitmapTask {
+ private final Context mContext;
+ private LoadBitmapFromUriTask(Context context, ImageCache imageCache, String uriString,
+ int maxWidth, int maxHeight) {
+ super(imageCache, uriString, maxHeight, maxWidth);
+ mContext = context;
+ }
+
+ @Override
+ @Nullable
+ public final ScaledBitmapInfo doGetBitmapInBackground() {
+ return BitmapUtils
+ .decodeSampledBitmapFromUriString(mContext, getKey(), mMaxWidth, mMaxHeight);
+ }
+ }
+
+ /**
+ * Loads and caches the logo for a given {@link TvInputInfo}
+ */
+ public static final class LoadTvInputLogoTask extends LoadBitmapTask {
+ private final TvInputInfo mInfo;
+ private final Context mContext;
+
+ public LoadTvInputLogoTask(Context context, ImageCache cache, TvInputInfo info) {
+ super(cache,
+ info.getId() + "-logo",
+ context.getResources()
+ .getDimensionPixelSize(R.dimen.channel_banner_input_logo_size),
+ context.getResources()
+ .getDimensionPixelSize(R.dimen.channel_banner_input_logo_size)
+ );
+ mInfo = info;
+ mContext = context;
+ }
+
+ @Nullable
+ @Override
+ public ScaledBitmapInfo doGetBitmapInBackground() {
+ Drawable drawable = mInfo.loadIcon(mContext);
+ if (!(drawable instanceof BitmapDrawable)) {
+ return null;
+ }
+ Bitmap original = ((BitmapDrawable) drawable).getBitmap();
+ if (original == null) {
+ return null;
+ }
+ return BitmapUtils.createScaledBitmapInfo(getKey(), original, mMaxWidth, mMaxHeight);
+ }
+ }
+
+ private ImageLoader() {
+ }
+}
diff --git a/src/com/android/tv/util/PipInputManager.java b/src/com/android/tv/util/PipInputManager.java
new file mode 100644
index 00000000..d5817907
--- /dev/null
+++ b/src/com/android/tv/util/PipInputManager.java
@@ -0,0 +1,411 @@
+/*
+ * 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.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.HashSet;
+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.
+ */
+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<String, PipInput> mPipInputMap = new HashMap<>(); // inputId -> PipInput
+ private final Set<Listener> mListeners = new HashSet<>();
+
+ 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;
+ }
+ mInputManager.addCallback(mTvInputCallback);
+ mChannelTuner.addListener(mChannelTunerListener);
+ initializePipInputList();
+ }
+
+ /**
+ * Stops {@link PipInputManager}.
+ */
+ public void stop() {
+ if (!mStarted) {
+ return;
+ }
+ 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.
+ *
+ * @param availableOnly If true, it counts only available PIP inputs. Please see {@link
+ * PipInput#isAvailable()} for the details of availability.
+ */
+ public int getPipInputSize(boolean availableOnly) {
+ if (availableOnly) {
+ int count = 0;
+ for (PipInput pipInput : mPipInputMap.values()) {
+ if (pipInput.isAvailable()) {
+ ++count;
+ }
+ }
+ return count;
+ } else {
+ return mPipInputMap.size();
+ }
+ }
+
+ /**
+ * Gets the list of inputs for PIP.
+ *
+ * @param availableOnly If true, it returns only available PIP inputs. Please see {@link
+ * PipInput#isAvailable()} for the details of availability.
+ */
+ public List<PipInput> getPipInputList(boolean availableOnly) {
+ List<PipInput> pipInputs;
+ if (availableOnly) {
+ pipInputs = new ArrayList<>();
+ for (PipInput pipInput : mPipInputMap.values()) {
+ if (pipInput.mAvailable) {
+ pipInputs.add(pipInput);
+ }
+ }
+ } else {
+ pipInputs = new ArrayList<>(mPipInputMap.values());
+ }
+ Collections.sort(pipInputs, new Comparator<PipInput>() {
+ @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 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();
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/util/SearchManagerHelper.java b/src/com/android/tv/util/SearchManagerHelper.java
new file mode 100644
index 00000000..3a3f82f3
--- /dev/null
+++ b/src/com/android/tv/util/SearchManagerHelper.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.android.tv.common.TvCommonConstants;
+
+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 boolean launchAssistAction() {
+ try {
+ if (TvCommonConstants.IS_MNC_PREVIEW) {
+ return (boolean) SearchManager.class.getDeclaredMethod(
+ "launchAssistAction", String.class, Integer.TYPE, Bundle.class).invoke(
+ mSearchManager, null, UserHandle.myUserId(), null);
+ } else if (TvCommonConstants.IS_MNC_OR_HIGHER) {
+ return (boolean) SearchManager.class.getDeclaredMethod(
+ "launchLegacyAssist", String.class, Integer.TYPE, Bundle.class).invoke(
+ mSearchManager, null, UserHandle.myUserId(), null);
+ } else {
+ return (boolean) SearchManager.class.getDeclaredMethod(
+ "launchAssistAction", Integer.TYPE, String.class, Integer.TYPE).invoke(
+ mSearchManager, 0, null, UserHandle.myUserId());
+ }
+ } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException
+ | InvocationTargetException e) {
+ Log.e(TAG, "Fail to call SearchManager.launchAssistAction", e);
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/tv/util/SetupUtils.java b/src/com/android/tv/util/SetupUtils.java
new file mode 100644
index 00000000..683216a5
--- /dev/null
+++ b/src/com/android/tv/util/SetupUtils.java
@@ -0,0 +1,206 @@
+/*
+ * 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.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputManager;
+import android.os.Build;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A utility class related to input setup.
+ */
+public class SetupUtils {
+ private static final String TAG = "SetupUtils";
+ private static final boolean DEBUG = false;
+
+ // Known inputs are inputs which are shown in SetupView before. When a new input is installed,
+ // the input will not be included in "PREF_KEY_KNOWN_INPUTS".
+ private static final String PREF_KEY_KNOWN_INPUTS = "known_inputs";
+ // Set up inputs are inputs whose setup activity has been launched from Live channels app.
+ private static final String PREF_KEY_SET_UP_INPUTS = "set_up_inputs";
+ private static final String PREF_KEY_IS_FIRST_TUNE = "is_first_tune";
+ private static SetupUtils sSetupUtils;
+
+ private final SharedPreferences mSharedPreferences;
+ private final Set<String> mKnownInputs;
+ private final Set<String> mSetUpInputs;
+ private boolean mIsFirstTune;
+
+ private SetupUtils(Context context) {
+ mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ mSetUpInputs = new HashSet<>(mSharedPreferences.getStringSet(PREF_KEY_SET_UP_INPUTS,
+ new HashSet<String>()));
+ mKnownInputs = new HashSet<>(mSharedPreferences.getStringSet(PREF_KEY_KNOWN_INPUTS,
+ new HashSet<String>()));
+ mIsFirstTune = mSharedPreferences.getBoolean(PREF_KEY_IS_FIRST_TUNE, true);
+ }
+
+ /**
+ * Gets an instance of {@link SetupUtils}.
+ */
+ public static SetupUtils getInstance(Context context) {
+ if (sSetupUtils != null) {
+ return sSetupUtils;
+ }
+ sSetupUtils = new SetupUtils(context.getApplicationContext());
+ return sSetupUtils;
+ }
+
+ public boolean isFirstTune() {
+ return mIsFirstTune;
+ }
+
+ /**
+ * Returns true, if the input with {@code inputId} is newly installed.
+ */
+ public boolean isNewInput(String inputId) {
+ return !mKnownInputs.contains(inputId);
+ }
+
+ /**
+ * Marks an input with {@code inputId} as a known input. Once it is marked, {@link #isNewInput}
+ * will return false.
+ */
+ public void markAsKnownInput(String inputId) {
+ mKnownInputs.add(inputId);
+ mSharedPreferences.edit().putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs).apply();
+ }
+
+ /**
+ * Returns true, if {@code inputId}'s setup activity has been launched.
+ */
+ public boolean hasSetupLaunched(String inputId) {
+ boolean launched = mSetUpInputs.contains(inputId);
+ if (DEBUG) {
+ Log.d(TAG, "hasSetupLaunched: (input=" + inputId + ", result= " + launched + ")");
+ }
+ return launched;
+ }
+
+ /**
+ * 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())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Grants permission for writing EPG data to all verified packages.
+ *
+ * @param context The Context used for granting permission.
+ */
+ public static void grantEpgPermissionToSetUpPackages(Context context) {
+ // TvProvider allows granting of Uri permissions starting from MNC.
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
+ SharedPreferences sharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(context);
+ Set<String> setUpInputs = new HashSet<>(sharedPreferences.getStringSet(
+ PREF_KEY_SET_UP_INPUTS, new HashSet<String>()));
+ Set<String> setUpPackages = new HashSet<>();
+ for (String input : setUpInputs) {
+ setUpPackages.add(ComponentName.unflattenFromString(input).getPackageName());
+ }
+ for (String packageName : setUpPackages) {
+ grantEpgPermission(context, packageName);
+ }
+ }
+ }
+
+ /**
+ * Grants permission for writing EPG data to a given package.
+ *
+ * @param context The Context used for granting permission.
+ * @param packageName The name of the package to give permission.
+ */
+ public static void grantEpgPermission(Context context, String packageName) {
+ // TvProvider allows granting of Uri permissions starting from MNC.
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
+ if (DEBUG) {
+ Log.d(TAG, "grantEpgPermission(context=" + context + ", packageName=" + packageName
+ + ")");
+ }
+ try {
+ 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);
+ }
+ }
+ }
+
+ /**
+ * Called when Live channels app is launched. Once it is called, {@link
+ * #isFirstTune} will return false.
+ */
+ public void onTuned() {
+ if (!mIsFirstTune) {
+ return;
+ }
+ mIsFirstTune = false;
+ mSharedPreferences.edit().putBoolean(PREF_KEY_IS_FIRST_TUNE, false).apply();
+ }
+
+ /**
+ * Called when input list is changed. It mainly handles input removals.
+ */
+ public void onInputListUpdated(TvInputManager manager) {
+ // mKnownInputs is a super set of mSetUpInputs.
+ Set<String> removedInputList = new HashSet<>(mKnownInputs);
+ for (TvInputInfo input : manager.getTvInputList()) {
+ removedInputList.remove(input.getId());
+ }
+
+ if (!removedInputList.isEmpty()) {
+ for (String input : removedInputList) {
+ mSetUpInputs.remove(input);
+ mKnownInputs.remove(input);
+ }
+ mSharedPreferences.edit()
+ .putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs).apply();
+ mSharedPreferences.edit().putStringSet(PREF_KEY_KNOWN_INPUTS, mKnownInputs).apply();
+ }
+ }
+
+ /**
+ * Called when an setup activity is launched. Once it is called, {@link #hasSetupLaunched}
+ * will return true for {@code inputId}.
+ */
+ public void onSetupLaunched(String inputId) {
+ if (DEBUG) Log.d(TAG, "onSetupLaunched: input=" + inputId);
+ mSetUpInputs.add(inputId);
+ mSharedPreferences.edit()
+ .putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs).apply();
+ }
+}
diff --git a/src/com/android/tv/util/SystemProperties.java b/src/com/android/tv/util/SystemProperties.java
new file mode 100644
index 00000000..6f661976
--- /dev/null
+++ b/src/com/android/tv/util/SystemProperties.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+/**
+ * A convenience class for getting TV related system properties.
+ */
+public final class SystemProperties {
+ /**
+ * 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 search is available in the EPG. Defaults to false.
+ */
+ public static final BooleanSystemProperty USE_EPG_SEARCH = new BooleanSystemProperty(
+ "tv_use_epg_search", false); // TODO: remove this flag.
+
+ /**
+ * 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 6ce7a42c..63d62697 100644
--- a/src/com/android/tv/util/TvInputManagerHelper.java
+++ b/src/com/android/tv/util/TvInputManagerHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,20 +16,26 @@
package com.android.tv.util;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
-import android.media.tv.TvInputManager.TvInputListener;
-import android.net.Uri;
+import android.media.tv.TvInputManager.TvInputCallback;
import android.os.Handler;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
import android.util.Log;
+import com.android.tv.parental.ContentRatingsManager;
+import com.android.tv.parental.ParentalControlSettings;
+
import junit.framework.Assert;
import java.util.ArrayList;
-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.Set;
@@ -37,28 +43,78 @@ import java.util.Set;
public class TvInputManagerHelper {
private static final String TAG = "TvInputManagerHelper";
+ // Hardcoded list for known bundled inputs not written by OEM/SOCs.
+ // Bundled (system) inputs not in the list will get the high priority
+ // so they and their channels come first in the UI.
+ private static final Set<String> BUNDLED_PACKAGE_SET = new HashSet<>();
+ static {
+ BUNDLED_PACKAGE_SET.add("com.android.tv");
+ BUNDLED_PACKAGE_SET.add("com.android.tv");
+ };
+
+ private final Context mContext;
private final TvInputManager mTvInputManager;
- private final Map<String, Integer> mInputStateMap =
- new HashMap<String, Integer>();
- private final Map<String, TvInputInfo> mInputMap =
- new HashMap<String, TvInputInfo>();
- private final TvInputListener mInternalListener =
- new TvInputListener() {
+ private final Map<String, Integer> mInputStateMap = new HashMap<>();
+ private final Map<String, TvInputInfo> mInputMap = new HashMap<>();
+ private final Map<String, Boolean> mInputIdToPartnerInputMap = new HashMap<>();
+ private final TvInputCallback mInternalCallback =
+ new TvInputCallback() {
@Override
public void onInputStateChanged(String inputId, int state) {
mInputStateMap.put(inputId, state);
- for (TvInputListener listener : mListeners) {
- listener.onInputStateChanged(inputId, state);
+ for (TvInputCallback callback : mCallbacks) {
+ callback.onInputStateChanged(inputId, state);
+ }
+ }
+
+ @Override
+ public void onInputAdded(String inputId) {
+ TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
+ if (info != null) {
+ mInputMap.put(inputId, info);
+ 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) {
+ mInputMap.remove(inputId);
+ mInputStateMap.remove(inputId);
+ mInputIdToPartnerInputMap.remove(inputId);
+ mContentRatingsManager.update();
+ for (TvInputCallback callback : mCallbacks) {
+ callback.onInputRemoved(inputId);
+ }
+ }
+
+ @Override
+ public void onInputUpdated(String inputId) {
+ TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
+ mInputMap.put(inputId, info);
+ for (TvInputCallback callback : mCallbacks) {
+ callback.onInputUpdated(inputId);
}
}
};
+
private final Handler mHandler = new Handler();
private boolean mStarted;
- private final HashSet<TvInputListener> mListeners = new HashSet<TvInputListener>();
- private List<Uri> mTvContentRatingSystemXmls;
+ private final HashSet<TvInputCallback> mCallbacks = new HashSet<>();
+ private final ContentRatingsManager mContentRatingsManager;
+ private final ParentalControlSettings mParentalControlSettings;
+ private final Comparator<TvInputInfo> mTvInputInfoComparator;
- public TvInputManagerHelper(TvInputManager tvInputManager) {
- mTvInputManager = tvInputManager;
+ public TvInputManagerHelper(Context context) {
+ mContext = context;
+ mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
+ mContentRatingsManager = new ContentRatingsManager(context);
+ mParentalControlSettings = new ParentalControlSettings(context);
+ mTvInputInfoComparator = new TvInputInfoComparator(this);
}
public void start() {
@@ -70,16 +126,7 @@ public class TvInputManagerHelper {
if (inputs.size() < 1) {
return;
}
- mTvInputManager.registerListener(mInternalListener, mHandler);
- update();
- }
-
- // It updates newly installed or deleted TV input.
- // TODO: remove it when TIS package change can be notified from frameworks.
- public void update() {
- if (!mStarted) {
- throw new IllegalStateException("TvInputManagerHelper didn't start yet");
- }
+ mTvInputManager.registerCallback(mInternalCallback, mHandler);
mInputMap.clear();
mInputStateMap.clear();
for (TvInputInfo input : mTvInputManager.getTvInputList()) {
@@ -87,55 +134,116 @@ public class TvInputManagerHelper {
mInputMap.put(inputId, input);
int state = mTvInputManager.getInputState(inputId);
mInputStateMap.put(inputId, state);
+ mInputIdToPartnerInputMap.put(inputId, isPartnerInput(input));
}
Assert.assertEquals(mInputStateMap.size(), mInputMap.size());
- mTvContentRatingSystemXmls = mTvInputManager.getTvContentRatingSystemXmls();
+ mContentRatingsManager.update();
}
public void stop() {
if (!mStarted) {
return;
}
- mTvInputManager.unregisterListener(mInternalListener);
+ mTvInputManager.unregisterCallback(mInternalCallback);
mStarted = false;
mInputStateMap.clear();
mInputMap.clear();
}
- public Collection<TvInputInfo> getTvInputInfos(boolean availableOnly) {
- if (!availableOnly) {
- return mInputMap.values();
- } else {
- ArrayList<TvInputInfo> list = new ArrayList<TvInputInfo>();
- Iterator<Map.Entry<String, Integer>> it =
- mInputStateMap.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry<String, Integer> pair = it.next();
- if (pair.getValue() != TvInputManager.INPUT_STATE_DISCONNECTED) {
- list.add(getTvInputInfo(pair.getKey()));
- }
+ public List<TvInputInfo> getTvInputInfos(boolean availableOnly, boolean tunerOnly) {
+ ArrayList<TvInputInfo> list = new ArrayList<>();
+ for (Map.Entry<String, Integer> pair : mInputStateMap.entrySet()) {
+ if (availableOnly && pair.getValue() == TvInputManager.INPUT_STATE_DISCONNECTED) {
+ continue;
+ }
+ TvInputInfo input = getTvInputInfo(pair.getKey());
+ if (tunerOnly && input.getType() != TvInputInfo.TYPE_TUNER) {
+ continue;
}
- return list;
+ list.add(input);
}
+ Collections.sort(list, mTvInputInfoComparator);
+ return list;
+ }
+
+ /**
+ * Returns the default comparator for {@link TvInputInfo}.
+ * See {@link TvInputInfoComparator} for detail.
+ */
+ public Comparator<TvInputInfo> getDefaultTvInputInfoComparator() {
+ return mTvInputInfoComparator;
+ }
+
+ /**
+ * 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.
+ */
+ @VisibleForTesting
+ public boolean isPartnerInput(TvInputInfo inputInfo) {
+ return inputInfo != null
+ && (inputInfo.getServiceInfo().applicationInfo.flags
+ & ApplicationInfo.FLAG_SYSTEM) != 0
+ && !BUNDLED_PACKAGE_SET.contains(
+ inputInfo.getServiceInfo().applicationInfo.packageName);
+ }
+
+ /**
+ * 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);
+ return (isPartnerInput != null) ? isPartnerInput : false;
+ }
+
+ /**
+ * Loads label of {@param info}.
+ *
+ * It's visible for comparator test to mock TvInputInfo.
+ * Package private is enough for this method, but public is necessary to workaround mockito bug.
+ */
+ @VisibleForTesting
+ public String loadLabel(TvInputInfo info) {
+ return info.loadLabel(mContext).toString();
+ }
+
+ /**
+ * Returns if TV input exists with the input id.
+ */
+ public boolean hasTvInputInfo(String inputId) {
+ if (!mStarted) {
+ Log.w(TAG, "hasTvInputInfo() called before TvInputManagerHelper was started.");
+ return false;
+ }
+ return !TextUtils.isEmpty(inputId) && mInputMap.get(inputId) != null;
}
public TvInputInfo getTvInputInfo(String inputId) {
if (!mStarted) {
- throw new IllegalStateException("TvInputManagerHelper didn't start yet");
+ Log.w(TAG, "getTvInputInfo() called before TvInputManagerHelper was started.");
+ return null;
}
if (inputId == null) {
return null;
}
- TvInputInfo input = mInputMap.get(inputId);
- if (input == null) {
- update();
- input = mInputMap.get(inputId);
- }
- return input;
+ return mInputMap.get(inputId);
+ }
+
+ public ApplicationInfo getTvInputAppInfo(String inputId) {
+ TvInputInfo info = getTvInputInfo(inputId);
+ return info == null ? null : info.getServiceInfo().applicationInfo;
}
- public int getTvInputSize() {
- return mInputStateMap.size();
+ public int getTunerTvInputSize() {
+ int size = 0;
+ for (TvInputInfo input : mInputMap.values()) {
+ if (input.getType() == TvInputInfo.TYPE_TUNER) {
+ ++size;
+ }
+ }
+ return size;
}
public int getInputState(TvInputInfo inputInfo) {
@@ -148,25 +256,53 @@ public class TvInputManagerHelper {
}
Integer state = mInputStateMap.get(inputId);
if (state == null) {
- update();
- state = mInputStateMap.get(inputId);
- if (state == null) {
- Log.w(TAG, "getInputState: no such input (id=" + inputId + ")");
- return TvInputManager.INPUT_STATE_DISCONNECTED;
- }
+ Log.w(TAG, "getInputState: no such input (id=" + inputId + ")");
+ return TvInputManager.INPUT_STATE_DISCONNECTED;
}
return state;
}
- public List<Uri> getTvContentRatingSystemXmls() {
- return mTvContentRatingSystemXmls;
+ public void addCallback(TvInputCallback callback) {
+ mCallbacks.add(callback);
+ }
+
+ public void removeCallback(TvInputCallback callback) {
+ mCallbacks.remove(callback);
}
- public void addListener(TvInputListener listener) {
- mListeners.add(listener);
+ public ParentalControlSettings getParentalControlSettings() {
+ return mParentalControlSettings;
}
- public void removeListener(TvInputListener listener) {
- mListeners.remove(listener);
+ /**
+ * Returns a ContentRatingsManager instance for a given application context.
+ */
+ public ContentRatingsManager getContentRatingsManager() {
+ return mContentRatingsManager;
}
+
+ /**
+ * 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)
+ */
+ @VisibleForTesting
+ static class TvInputInfoComparator implements Comparator<TvInputInfo> {
+ private TvInputManagerHelper mInputManager;
+
+ public TvInputInfoComparator(TvInputManagerHelper inputManager) {
+ mInputManager = inputManager;
+ }
+
+ @Override
+ public int compare(TvInputInfo lhs, TvInputInfo rhs) {
+ if (mInputManager.isPartnerInput(lhs) != mInputManager.isPartnerInput(rhs)) {
+ return mInputManager.isPartnerInput(lhs) ? -1 : 1;
+ }
+ return mInputManager.loadLabel(lhs).compareTo(mInputManager.loadLabel(rhs));
+ }
+ };
}
diff --git a/src/com/android/tv/util/TvSettings.java b/src/com/android/tv/util/TvSettings.java
index 4ba9da1e..788a7d60 100644
--- a/src/com/android/tv/util/TvSettings.java
+++ b/src/com/android/tv/util/TvSettings.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,22 +16,243 @@
package com.android.tv.util;
+import android.content.Context;
+import android.preference.PreferenceManager;
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+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 PREFS_FILE = "settings";
public static final String PREF_TV_WATCH_LOGGING_ENABLED = "tv_watch_logging_enabled";
- public static final String PREF_DISPLAY_INPUT_NAME = "display_input_name_";
public static final String PREF_CLOSED_CAPTION_ENABLED = "is_cc_enabled"; // boolean value
public static final String PREF_DISPLAY_MODE = "display_mode"; // int value
- public static final String PREF_PIP_LOCATION = "pip_location"; // 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;
+ public static final int PIP_SOUND_LAST = PIP_SOUND_PIP_WINDOW;
+
+ // 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";
+
+ // 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";
+ private static final String PREF_DISABLE_PIN_UNTIL = "pref.disable_pin_until";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ 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;
+ 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.
+ */
+ @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.
+ */
+ @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();
+ }
+
+ // Multi-track audio settings
+ public static String getMultiAudioId(Context context) {
+ 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();
+ }
+
+ public static String getMultiAudioLanguage(Context context) {
+ 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();
+ }
+
+ public static int getMultiAudioChannelCount(Context context) {
+ 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();
+ }
+
+ // Parental Control settings
+ public static void addContentRatingSystems(Context context, Set<String> ids) {
+ Set<String> contentRatingSystemSet = getContentRatingSystemSet(context);
+ if (contentRatingSystemSet.addAll(ids)) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply();
+ }
+ }
+
+ public static void addContentRatingSystem(Context context, String id) {
+ Set<String> contentRatingSystemSet = getContentRatingSystemSet(context);
+ if (contentRatingSystemSet.add(id)) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply();
+ }
+ }
+
+ public static void removeContentRatingSystems(Context context, Set<String> ids) {
+ Set<String> contentRatingSystemSet = getContentRatingSystemSet(context);
+ if (contentRatingSystemSet.removeAll(ids)) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply();
+ }
+ }
+
+ public static void removeContentRatingSystem(Context context, String id) {
+ Set<String> contentRatingSystemSet = getContentRatingSystemSet(context);
+ if (contentRatingSystemSet.remove(id)) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply();
+ }
+ }
+
+ public static boolean hasContentRatingSystem(Context context, String id) {
+ return getContentRatingSystemSet(context).contains(id);
+ }
+
+ /**
+ * Returns whether the content rating system is ever set. Returns {@code false} only when the
+ * user changes parental control settings for the first time.
+ */
+ public static boolean isContentRatingSystemSet(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getStringSet(PREF_CONTENT_RATING_SYSTEMS, null) != null;
+ }
+
+ private static Set<String> getContentRatingSystemSet(Context context) {
+ return new HashSet<>(PreferenceManager.getDefaultSharedPreferences(context)
+ .getStringSet(PREF_CONTENT_RATING_SYSTEMS, Collections.<String>emptySet()));
+ }
+
+ @ContentRatingLevel
+ public static int getContentRatingLevel(Context context) {
+ 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();
+ }
+
+ /**
+ * Returns the time until we should disable the PIN dialog (because the user input wrong PINs
+ * repeatedly).
+ */
+ public static long getDisablePinUntil(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context).getLong(
+ PREF_DISABLE_PIN_UNTIL, 0);
+ }
- public static final int PIP_LOCATION_TOP_LEFT = 0;
- public static final int PIP_LOCATION_TOP_RIGHT = 1;
- public static final int PIP_LOCATION_BOTTOM_LEFT = 2;
- public static final int PIP_LOCATION_BOTTOM_RIGHT = 3;
+ /**
+ * Saves the time until we should disable the PIN dialog (because the user input wrong PINs
+ * repeatedly).
+ */
+ public static void setDisablePinUntil(Context context, long timeMillis) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit().putLong(
+ PREF_DISABLE_PIN_UNTIL, timeMillis).apply();
+ }
}
diff --git a/src/com/android/tv/util/TvTrackInfoUtils.java b/src/com/android/tv/util/TvTrackInfoUtils.java
new file mode 100644
index 00000000..3006f963
--- /dev/null
+++ b/src/com/android/tv/util/TvTrackInfoUtils.java
@@ -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.
+ */
+package com.android.tv.util;
+
+import android.media.tv.TvTrackInfo;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * 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 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.
+ */
+ public static Comparator<TvTrackInfo> createComparator(final String id, final String language,
+ final int channelCount) {
+ return new Comparator<TvTrackInfo>() {
+
+ @Override
+ public int compare(TvTrackInfo lhs, TvTrackInfo rhs) {
+ if (lhs == rhs) {
+ return 0;
+ }
+ if (lhs == null) {
+ return -1;
+ }
+ if (rhs == null) {
+ return 1;
+ }
+ boolean rhsLangMatch = Utils.isEqualLanguage(rhs.getLanguage(), language);
+ boolean lhsLangMatch = 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;
+ }
+ } else {
+ return -1;
+ }
+ } else {
+ return lhsLangMatch ? 1 : 0;
+ }
+ }
+ };
+ }
+
+ /**
+ * 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 channelCount The channel count to match.
+ * @return the best matching track or the first one if none matches.
+ */
+ public static TvTrackInfo getBestTrackInfo(List<TvTrackInfo> tracks, String id, String language,
+ int channelCount) {
+ if (tracks == null) {
+ return null;
+ }
+ Comparator<TvTrackInfo> comparator = createComparator(id, language, channelCount);
+ TvTrackInfo best = null;
+ for (TvTrackInfo track : tracks) {
+ if (comparator.compare(track, best) > 0) {
+ best = track;
+ }
+ }
+ return best;
+ }
+
+ private TvTrackInfoUtils() {
+ }
+}
diff --git a/src/com/android/tv/util/Utils.java b/src/com/android/tv/util/Utils.java
index d60ee325..b0822517 100644
--- a/src/com/android/tv/util/Utils.java
+++ b/src/com/android/tv/util/Utils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,62 +16,79 @@
package com.android.tv.util;
-import android.content.ContentUris;
+import android.annotation.SuppressLint;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.media.tv.TvContentRating;
import android.media.tv.TvContract;
+import android.media.tv.TvContract.Channels;
import android.media.tv.TvInputInfo;
+import android.media.tv.TvTrackInfo;
import android.net.Uri;
import android.preference.PreferenceManager;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
-import android.util.Base64;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.View;
+import com.android.tv.R;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
import com.android.tv.data.StreamInfo;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
/**
* A class that includes convenience methods for accessing TvProvider database.
*/
public class Utils {
- public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService";
- public static final String EXTRA_SERVICE_NAME = "serviceName";
- public static final String EXTRA_KEYCODE = "keycode";
-
- public static final String CHANNEL_SORT_ORDER_BY_DISPLAY_NUMBER =
- "CAST(" + TvContract.Channels.COLUMN_DISPLAY_NUMBER + " AS INTEGER), "
- + "CAST(SUBSTR(LTRIM(" + TvContract.Channels.COLUMN_DISPLAY_NUMBER
- + ",'0123456789'),2) AS INTEGER)";
-
- // preferences stored in the default preference.
- private static final String PREF_KEY_LAST_SELECTED_TV_INPUT = "last_selected_tv_input";
- private static final String PREF_KEY_LAST_SELECTED_PHYS_TV_INPUT =
- "last_selected_phys_tv_input";
-
- private static final String PREFIX_PREF_NAME = "com.android.tv.";
- // preferences stored in the preference of a specific tv input.
- private static final String PREF_KEY_LAST_WATCHED_CHANNEL_ID = "last_watched_channel_id";
+ private static final String TAG = "Utils";
+ private static final boolean DEBUG = false;
+
+ 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";
+
+ // Query parameter in the intent of starting MainActivity.
+ public static final String PARAM_SOURCE = "source";
- // STOPSHIP: Use the one defined in the contract class instead.
- private static final String TvContract_Programs_COLUMN_VIDEO_RESOLUTION = "video_resolution";
+ private static final String PATH_CHANNEL = "channel";
+ private static final String PATH_PROGRAM = "program";
- private static int VIDEO_SD_WIDTH = 704;
- private static int VIDEO_SD_HEIGHT = 480;
- private static int VIDEO_HD_WIDTH = 1280;
- private static int VIDEO_HD_HEIGHT = 720;
- private static int VIDEO_FULL_HD_WIDTH = 1920;
- private static int VIDEO_FULL_HD_HEIGHT = 1080;
- private static int VIDEO_ULTRA_HD_WIDTH = 2048;
- private static int VIDEO_ULTRA_HD_HEIGHT = 1536;
+ 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 =
+ "last_watched_channel_id_for_input_";
+ private static final String PREF_KEY_LAST_WATCHED_CHANNEL_URI = "last_watched_channel_uri";
+
+ private static final int VIDEO_SD_WIDTH = 704;
+ private static final int VIDEO_SD_HEIGHT = 480;
+ private static final int VIDEO_HD_WIDTH = 1280;
+ private static final int VIDEO_HD_HEIGHT = 720;
+ private static final int VIDEO_FULL_HD_WIDTH = 1920;
+ private static final int VIDEO_FULL_HD_HEIGHT = 1080;
+ private static final int VIDEO_ULTRA_HD_WIDTH = 2048;
+ private static final int VIDEO_ULTRA_HD_HEIGHT = 1536;
+
+ private static final int AUDIO_CHANNEL_NONE = 0;
+ private static final int AUDIO_CHANNEL_MONO = 1;
+ private static final int AUDIO_CHANNEL_STEREO = 2;
+ private static final int AUDIO_CHANNEL_SURROUND_6 = 6;
+ private static final int AUDIO_CHANNEL_SURROUND_8 = 8;
private enum AspectRatio {
ASPECT_RATIO_4_3(4, 3),
@@ -87,193 +104,205 @@ public class Utils {
}
@Override
+ @SuppressLint("DefaultLocale")
public String toString() {
return String.format("%d:%d", width, height);
}
}
- private Utils() { /* cannot be instantiated */ }
-
- public static Uri getChannelUri(long channelId) {
- return ContentUris.withAppendedId(TvContract.Channels.CONTENT_URI, channelId);
+ private Utils() {
}
- public static String getInputIdForChannel(Context context, long channelId) {
- if (channelId == Channel.INVALID_ID) {
- return null;
+ public static String buildSelectionForIds(String idName, List<Long> ids) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(idName).append(" in (")
+ .append(ids.get(0));
+ for (int i = 1; i < ids.size(); ++i) {
+ sb.append(",").append(ids.get(i));
}
- Uri channelUri = ContentUris.withAppendedId(TvContract.Channels.CONTENT_URI, channelId);
- return getInputIdForChannel(context, channelUri);
+ sb.append(")");
+ return sb.toString();
}
- public static String getInputIdForChannel(Context context, Uri channelUri) {
- if (channelUri == null) {
+ public static String getInputIdForChannel(Context context, long channelId) {
+ if (channelId == Channel.INVALID_ID) {
return null;
}
- String[] projection = { TvContract.Channels.COLUMN_INPUT_ID };
- Cursor cursor = null;
- try {
- cursor = context.getContentResolver().query(channelUri, projection, null, null, null);
+ Uri channelUri = TvContract.buildChannelUri(channelId);
+ String[] projection = {TvContract.Channels.COLUMN_INPUT_ID};
+ try (Cursor cursor = context.getContentResolver()
+ .query(channelUri, projection, null, null, null)) {
if (cursor != null && cursor.moveToNext()) {
- return cursor.getString(0);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
+ return Utils.intern(cursor.getString(0));
}
}
return null;
}
- public static void setLastWatchedChannelId(Context context, String inputId, String physInputId,
- long channelId) {
- if (TextUtils.isEmpty(inputId)) {
- throw new IllegalArgumentException("inputId cannot be empty");
+ public static void setLastWatchedChannel(Context context, Channel channel) {
+ if (channel == null) {
+ Log.e(TAG, "setLastWatchedChannel: channel cannot be null");
+ return;
}
- context.getSharedPreferences(getPreferenceName(inputId), Context.MODE_PRIVATE).edit()
- .putLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID, channelId).apply();
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .putString(PREF_KEY_LAST_SELECTED_TV_INPUT, inputId).apply();
PreferenceManager.getDefaultSharedPreferences(context).edit()
- .putString(PREF_KEY_LAST_SELECTED_PHYS_TV_INPUT, physInputId).apply();
+ .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()
+ .putLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID, channelId).apply();
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .putLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID_FOR_INPUT + channel.getInputId(),
+ channelId).apply();
+ }
}
public static long getLastWatchedChannelId(Context context) {
- String inputId = getLastSelectedInputId(context);
- if (inputId == null) {
- return Channel.INVALID_ID;
- }
- return getLastWatchedChannelId(context, inputId);
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID, Channel.INVALID_ID);
}
- public static long getLastWatchedChannelId(Context context, String inputId) {
- if (TextUtils.isEmpty(inputId)) {
- throw new IllegalArgumentException("inputId cannot be empty");
- }
- return context.getSharedPreferences(getPreferenceName(inputId),
- Context.MODE_PRIVATE).getLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID, Channel.INVALID_ID);
+ public static long getLastWatchedChannelIdForInput(Context context, String inputId) {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID_FOR_INPUT + inputId, Channel.INVALID_ID);
}
- public static String getLastSelectedInputId(Context context) {
+ public static String getLastWatchedChannelUri(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
- .getString(PREF_KEY_LAST_SELECTED_TV_INPUT, null);
+ .getString(PREF_KEY_LAST_WATCHED_CHANNEL_URI, null);
}
- public static String getLastSelectedPhysInputId(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getString(PREF_KEY_LAST_SELECTED_PHYS_TV_INPUT, null);
+ /**
+ * 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))
+ && !TextUtils.isEmpty(uri.getQueryParameter("input"));
}
- public static Program getCurrentProgram(Context context, Uri channelUri) {
- if (channelUri == null) {
- return null;
- }
- long time = System.currentTimeMillis();
- Uri uri = TvContract.buildProgramsUriForChannel(channelUri, time, time);
- String[] projection = {
- TvContract.Programs.COLUMN_TITLE,
- TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
- TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
- TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
- TvContract_Programs_COLUMN_VIDEO_RESOLUTION,
- TvContract.Programs.COLUMN_CONTENT_RATING,
- TvContract.Programs.COLUMN_POSTER_ART_URI,
- TvContract.Programs.COLUMN_THUMBNAIL_URI };
- Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
- String title = null;
- String description = null;
- long startTime = -1;
- long endTime = -1;
- String posterArtUri = null;
- String thumbnailUri = null;
- String videoDefinitionLevel = "";
- String contentRatings = "";
- if (cursor.moveToNext()) {
- title = cursor.getString(0);
- description = cursor.getString(1);
- startTime = cursor.getLong(2);
- endTime = cursor.getLong(3);
- videoDefinitionLevel = cursor.getString(4);
- contentRatings = cursor.getString(5);
- posterArtUri = cursor.getString(6);
- thumbnailUri = cursor.getString(7);
- }
- cursor.close();
-
- // TODO: Consider providing the entire data if needed.
- return new Program.Builder()
- .setTitle(title)
- .setDescription(description)
- .setStartTimeUtcMillis(startTime)
- .setEndTimeUtcMillis(endTime)
- .setVideoDefinitionLevel(videoDefinitionLevel)
- .setContentRatings(stringToContentRatings(contentRatings))
- .setPosterArtUri(posterArtUri)
- .setThumbnailUri(thumbnailUri).build();
- }
-
- public static void updateCurrentVideoResolution(Context context, Long channelId, int format) {
+ /**
+ * Returns {@code true}, if {@code uri} is a channel URI for a specific channel. It is copied
+ * from the hidden method TvContract.isChannelUri.
+ */
+ public static boolean isChannelUriForOneChannel(Uri uri) {
+ return isChannelUriForTunerInput(uri) || TvContract.isChannelUriForPassthroughInput(uri);
+ }
+
+ /**
+ * Returns {@code true}, if {@code uri} is a channel URI for a tuner input. It is copied from
+ * the hidden method TvContract.isChannelUriForTunerInput.
+ */
+ public static boolean isChannelUriForTunerInput(Uri uri) {
+ return isTvUri(uri) && isTwoSegmentUriStartingWith(uri, PATH_CHANNEL);
+ }
+
+ private static boolean isTvUri(Uri uri) {
+ return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
+ && TvContract.AUTHORITY.equals(uri.getAuthority());
+ }
+
+ private static boolean isTwoSegmentUriStartingWith(Uri uri, String pathSegment) {
+ List<String> pathSegments = uri.getPathSegments();
+ return pathSegments.size() == 2 && pathSegment.equals(pathSegments.get(0));
+ }
+
+ /**
+ * 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));
+ }
+
+ /**
+ * Gets the info of the program on particular time.
+ */
+ public static Program getProgramAt(Context context, long channelId, long timeMs) {
if (channelId == Channel.INVALID_ID) {
- return;
+ Log.e(TAG, "getCurrentProgramAt - channelId is invalid");
+ return null;
}
- Uri channelUri = TvContract.buildChannelUri(channelId);
- long time = System.currentTimeMillis();
- Uri uri = TvContract.buildProgramsUriForChannel(channelUri, time, time);
- Cursor cursor = null;
- String[] projection = { TvContract.Programs._ID };
- long programId = Program.INVALID_ID;
- try {
- cursor = context.getContentResolver().query(uri, projection, null, null, null);
- if (cursor == null || !cursor.moveToNext()) {
- return;
+ if (context.getMainLooper().getThread().equals(Thread.currentThread())) {
+ String message = "getCurrentProgramAt called on main thread";
+ if (DEBUG) {
+ // Generating a stack trace can be expensive, only do it in debug mode.
+ Log.w(TAG, message, new IllegalStateException(message));
+ } else {
+ Log.w(TAG, message);
}
- programId = cursor.getLong(0);
- } finally {
- if (cursor != null) {
- cursor.close();
+ }
+ 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);
}
}
+ return null;
+ }
- String videoResolution = getVideoDefinitionLevelString(format);
- if (TextUtils.isEmpty(videoResolution)) {
- return;
- }
- Uri programUri = TvContract.buildProgramUri(programId);
- ContentValues values = new ContentValues();
- values.put(TvContract_Programs_COLUMN_VIDEO_RESOLUTION, videoResolution);
- context.getContentResolver().update(programUri, values, null, null);
+ /**
+ * Gets the info of the current program.
+ */
+ public static Program getCurrentProgram(Context context, long channelId) {
+ return getProgramAt(context, channelId, System.currentTimeMillis());
}
- public static boolean hasChannel(Context context, TvInputInfo name) {
- return hasChannel(context, name, true);
+ /**
+ * 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.
+ */
+ public static String getDurationString(
+ Context context, long startUtcMillis, long endUtcMillis, boolean useShortFormat) {
+ return getDurationString(context, System.currentTimeMillis(),
+ startUtcMillis, endUtcMillis, useShortFormat, 0);
}
- public static boolean hasChannel(Context context, TvInputInfo name, boolean browsableOnly) {
- Uri uri = TvContract.buildChannelsUriForInput(name.getId(), browsableOnly);
- String[] projection = { TvContract.Channels._ID };
- Cursor cursor = null;
- try {
- cursor = context.getContentResolver().query(uri, projection, null, null, null);
- return cursor != null && cursor.getCount() > 0;
- } finally {
- if (cursor != null) {
- cursor.close();
+ @VisibleForTesting
+ static String getDurationString(Context context, long baseMillis,
+ long startUtcMillis, long endUtcMillis, boolean useShortFormat, int flag) {
+ flag |= DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_SHOW_TIME
+ | ((useShortFormat) ? DateUtils.FORMAT_NUMERIC_DATE : 0);
+ if (!isInGivenDay(baseMillis, startUtcMillis)) {
+ flag |= DateUtils.FORMAT_SHOW_DATE;
+ }
+ if (startUtcMillis != endUtcMillis && useShortFormat) {
+ // Do special handling for 12:00 AM when checking if it's in the given day.
+ // If it's start, it's considered as beginning of the day. (e.g. 12:00 AM - 12:30 AM)
+ // If it's end, it's considered as end of the day (e.g. 11:00 PM - 12:00 AM)
+ if (!isInGivenDay(startUtcMillis, endUtcMillis - 1)
+ && endUtcMillis - startUtcMillis < TimeUnit.HOURS.toMillis(11)) {
+ // Do not show date for short format.
+ // Extracting a day is needed because {@link DateUtils@formatDateRange}
+ // adds date if the duration covers multiple days.
+ return DateUtils.formatDateRange(context,
+ startUtcMillis, endUtcMillis - TimeUnit.DAYS.toMillis(1), flag);
}
}
+ return DateUtils.formatDateRange(context, startUtcMillis, endUtcMillis, flag);
}
- public static SharedPreferences getSharedPreferencesOfDisplayNameForInput(Context context) {
- return context.getSharedPreferences(TvSettings.PREFS_FILE, Context.MODE_PRIVATE);
- }
-
- public static String getDisplayNameForInput(Context context, TvInputInfo info) {
- SharedPreferences preferences = getSharedPreferencesOfDisplayNameForInput(context);
- return preferences.getString(TvSettings.PREF_DISPLAY_INPUT_NAME + info.getId(),
- info.loadLabel(context).toString());
- }
-
- public static boolean hasActivity(Context context, TvInputInfo input, String action) {
- return getActivityInfo(context, input, action) != null;
+ @VisibleForTesting
+ 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);
}
public static String getAspectRatioString(int width, int height) {
@@ -302,44 +331,118 @@ public class Utils {
return StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
}
- public static String getVideoDefinitionLevelString(int videoFormat) {
+ public static String getVideoDefinitionLevelString(Context context, int videoFormat) {
switch (videoFormat) {
case StreamInfo.VIDEO_DEFINITION_LEVEL_ULTRA_HD:
- return "Ultra HD";
+ return context.getResources().getString(
+ R.string.video_definition_level_ultra_hd);
case StreamInfo.VIDEO_DEFINITION_LEVEL_FULL_HD:
- return "Full HD";
+ return context.getResources().getString(
+ R.string.video_definition_level_full_hd);
case StreamInfo.VIDEO_DEFINITION_LEVEL_HD:
- return "HD";
+ return context.getResources().getString(R.string.video_definition_level_hd);
case StreamInfo.VIDEO_DEFINITION_LEVEL_SD:
- return "SD";
+ return context.getResources().getString(R.string.video_definition_level_sd);
}
return "";
}
- public static String getAudioChannelString(int channelCount) {
+ public static String getAudioChannelString(Context context, int channelCount) {
switch (channelCount) {
case 1:
- return "MONO";
+ return context.getResources().getString(R.string.audio_channel_mono);
case 2:
- return "STEREO";
+ return context.getResources().getString(R.string.audio_channel_stereo);
case 6:
- return "5.1";
+ return context.getResources().getString(R.string.audio_channel_5_1);
case 8:
- return "7.1";
+ return context.getResources().getString(R.string.audio_channel_7_1);
}
return "";
}
+ public static boolean needToShowSampleRate(Context context, List<TvTrackInfo> tracks) {
+ Set<String> multiAudioStrings = new HashSet<>();
+ for (TvTrackInfo track : tracks) {
+ String multiAudioString = getMultiAudioString(context, track, false);
+ if (multiAudioStrings.contains(multiAudioString)) {
+ return true;
+ }
+ multiAudioStrings.add(multiAudioString);
+ }
+ return false;
+ }
+
+ public static String getMultiAudioString(Context context, TvTrackInfo track,
+ boolean showSampleRate) {
+ if (track.getType() != TvTrackInfo.TYPE_AUDIO) {
+ throw new IllegalArgumentException("Not an audio track: " + track);
+ }
+ String language = context.getString(R.string.default_language);
+ if (track.getLanguage() != null) {
+ language = new Locale(track.getLanguage()).getDisplayName();
+ }
+
+ StringBuilder metadata = new StringBuilder();
+ switch (track.getAudioChannelCount()) {
+ case AUDIO_CHANNEL_NONE:
+ break;
+ case AUDIO_CHANNEL_MONO:
+ metadata.append(context.getString(R.string.multi_audio_channel_mono));
+ break;
+ case AUDIO_CHANNEL_STEREO:
+ metadata.append(context.getString(R.string.multi_audio_channel_stereo));
+ break;
+ case AUDIO_CHANNEL_SURROUND_6:
+ metadata.append(context.getString(R.string.multi_audio_channel_surround_6));
+ break;
+ case AUDIO_CHANNEL_SURROUND_8:
+ metadata.append(context.getString(R.string.multi_audio_channel_surround_8));
+ break;
+ default:
+ metadata.append(context.getString(R.string.multi_audio_channel_suffix,
+ track.getAudioChannelCount()));
+ break;
+ }
+ if (showSampleRate) {
+ int sampleRate = track.getAudioSampleRate();
+ if (sampleRate > 0) {
+ if (metadata.length() > 0) {
+ metadata.append(", ");
+ }
+ int integerPart = sampleRate / 1000;
+ int tenths = (sampleRate % 1000) / 100;
+ metadata.append(integerPart);
+ if (tenths != 0) {
+ metadata.append(".");
+ metadata.append(tenths);
+ }
+ metadata.append("kHz");
+ }
+ }
+
+ if (metadata.length() == 0) {
+ return language;
+ }
+ return context.getString(R.string.multi_audio_display_string_with_channel, language,
+ metadata.toString());
+ }
+
public static TvContentRating[] stringToContentRatings(String commaSeparatedRatings) {
if (TextUtils.isEmpty(commaSeparatedRatings)) {
return null;
}
String[] ratings = commaSeparatedRatings.split("\\s*,\\s*");
- TvContentRating[] contentRatings = new TvContentRating[ratings.length];
- for (int i = 0; i < contentRatings.length; ++i) {
- contentRatings[i] = TvContentRating.unflattenFromString(ratings[i]);
+ List<TvContentRating> contentRatings = new ArrayList<>();
+ for (String rating : ratings) {
+ try {
+ contentRatings.add(TvContentRating.unflattenFromString(rating));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Can't parse the content rating: '" + rating + "'", e);
+ }
}
- return contentRatings;
+ return contentRatings.size() == 0 ?
+ null : contentRatings.toArray(new TvContentRating[contentRatings.size()]);
}
public static String contentRatingsToString(TvContentRating[] contentRatings) {
@@ -355,26 +458,90 @@ public class Utils {
return ratings.toString();
}
- private static ActivityInfo getActivityInfo(Context context, TvInputInfo input, String action) {
- if (input == null) {
- return null;
+ public static boolean isEqualLanguage(String lang1, String lang2) {
+ if (lang1 == null) {
+ return lang2 == null;
+ } else if (lang2 == null) {
+ return false;
+ }
+ try {
+ return TextUtils.equals(
+ new Locale(lang1).getISO3Language(), new Locale(lang2).getISO3Language());
+ } catch (Exception ignored) {
}
+ return false;
+ }
- List<ResolveInfo> infos = context.getPackageManager().queryIntentActivities(
- new Intent(action), PackageManager.GET_ACTIVITIES);
- if (infos == null) {
+ public static boolean isIntentAvailable(Context context, Intent intent) {
+ return context.getPackageManager().queryIntentActivities(
+ intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+ }
+
+ /**
+ * 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;
}
-
- for (ResolveInfo info : infos) {
- if (info.activityInfo.packageName.equals(input.getServiceInfo().packageName)) {
- return info.activityInfo;
- }
+ CharSequence customLabel = input.loadCustomLabel(context);
+ String label = (customLabel == null) ? null : customLabel.toString();
+ if (TextUtils.isEmpty(label)) {
+ label = input.loadLabel(context).toString();
}
- return null;
+ return label;
+ }
+
+ /**
+ * Enable all channels synchronously.
+ */
+ public static void enableAllChannels(Context context) {
+ ContentValues values = new ContentValues();
+ values.put(Channels.COLUMN_BROWSABLE, 1);
+ context.getContentResolver().update(Channels.CONTENT_URI, values, null, null);
+ }
+
+ /**
+ * Converts time in milliseconds to a String.
+ */
+ public static String toTimeString(long timeMillis) {
+ return new Date(timeMillis).toString();
+ }
+
+ /**
+ * Returns a {@link String} object which contains the layout information of the {@code view}.
+ */
+ 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() + "}";
+ }
+
+ /**
+ * Floors 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 5:00:00.
+ */
+ public static long floorTime(long timeMs, long timeUnit) {
+ return timeMs - (timeMs % timeUnit);
+ }
+
+ /**
+ * 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);
}
- private static String getPreferenceName(String inputId) {
- return PREFIX_PREF_NAME + Base64.encodeToString(inputId.getBytes(), Base64.URL_SAFE);
+ /**
+ * 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();
}
}
diff --git a/tests/Android.mk b/tests/Android.mk
index cc2fff01..5053e7d6 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -1,15 +1 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
-# Include all test java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := TVTests
-
-LOCAL_INSTRUMENTATION_FOR := TV
-
-include $(BUILD_PACKAGE)
+include $(call all-subdir-makefiles)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
deleted file mode 100644
index 7c590925..00000000
--- a/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tv.tests" >
-
- <uses-sdk android:minSdkVersion="19" />
-
- <instrumentation
- android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.android.tv" />
-
- <application android:label="TVTest" >
- <uses-library android:name="android.test.runner" />
- </application>
-
-</manifest> \ No newline at end of file
diff --git a/tests/common/Android.mk b/tests/common/Android.mk
new file mode 100644
index 00000000..f8263311
--- /dev/null
+++ b/tests/common/Android.mk
@@ -0,0 +1,23 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# Include all test java files.
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-Iaidl-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-annotations \
+ mockito-target \
+ tv-common \
+ ub-uiautomator
+
+LOCAL_INSTRUMENTATION_FOR := TV
+LOCAL_MODULE := tv-test-common
+LOCAL_MODULE_TAGS := optional
+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/AndroidManifest.xml b/tests/common/AndroidManifest.xml
new file mode 100644
index 00000000..f3ed9a9f
--- /dev/null
+++ b/tests/common/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<!-- Stub AndroidManifest.xml to build resources -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tv.testing"
+ android:versionCode="1">
+ <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21"/>
+ <application />
+</manifest>
diff --git a/tests/common/res/drawable-xhdpi/blue.png b/tests/common/res/drawable-xhdpi/blue.png
new file mode 100644
index 00000000..dd2044ca
--- /dev/null
+++ b/tests/common/res/drawable-xhdpi/blue.png
Binary files differ
diff --git a/tests/common/res/drawable-xhdpi/blue_small.png b/tests/common/res/drawable-xhdpi/blue_small.png
new file mode 100644
index 00000000..22394ebb
--- /dev/null
+++ b/tests/common/res/drawable-xhdpi/blue_small.png
Binary files differ
diff --git a/tests/common/res/drawable-xhdpi/crash_test_android_logo.png b/tests/common/res/drawable-xhdpi/crash_test_android_logo.png
new file mode 100644
index 00000000..2442cf04
--- /dev/null
+++ b/tests/common/res/drawable-xhdpi/crash_test_android_logo.png
Binary files differ
diff --git a/tests/common/res/drawable-xhdpi/green.png b/tests/common/res/drawable-xhdpi/green.png
new file mode 100644
index 00000000..8306b9c3
--- /dev/null
+++ b/tests/common/res/drawable-xhdpi/green.png
Binary files differ
diff --git a/tests/common/res/drawable-xhdpi/green_large.png b/tests/common/res/drawable-xhdpi/green_large.png
new file mode 100644
index 00000000..77bbb231
--- /dev/null
+++ b/tests/common/res/drawable-xhdpi/green_large.png
Binary files differ
diff --git a/tests/common/res/drawable-xhdpi/red.png b/tests/common/res/drawable-xhdpi/red.png
new file mode 100644
index 00000000..89f889b9
--- /dev/null
+++ b/tests/common/res/drawable-xhdpi/red.png
Binary files differ
diff --git a/tests/common/res/drawable-xhdpi/red_large.png b/tests/common/res/drawable-xhdpi/red_large.png
new file mode 100644
index 00000000..c52a1242
--- /dev/null
+++ b/tests/common/res/drawable-xhdpi/red_large.png
Binary files differ
diff --git a/tests/common/src/com/android/tv/testing/ChannelInfo.java b/tests/common/src/com/android/tv/testing/ChannelInfo.java
new file mode 100644
index 00000000..5197630f
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/ChannelInfo.java
@@ -0,0 +1,268 @@
+/*
+ * 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.TvContract;
+import android.support.annotation.Nullable;
+import android.util.SparseArray;
+
+/**
+ * Channel Information.
+ */
+public final class ChannelInfo {
+ private static final SparseArray<String> 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);
+ }
+
+ /**
+ * If this is specify for logo, it will be selected randomly including null.
+ */
+ public static final String GENERATE_LOGO = "GEN";
+ // If the logo is set to {@link ChannelInfo#GENERATE_LOGO}, pick one randomly from this list.
+ private static final int[] LOGOS_RES = {0, R.drawable.crash_test_android_logo};
+
+ 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 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) {
+ builder.setLogoUrl(Utils.getUriStringForResource(
+ context, LOGOS_RES[channelNumber % LOGOS_RES.length]));
+ }
+ return builder.build();
+ }
+
+ 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, 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.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 + "}";
+ }
+
+ /**
+ * 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 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;
+ 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 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, 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
new file mode 100644
index 00000000..aadbf727
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/ChannelUtils.java
@@ -0,0 +1,186 @@
+/*
+ * 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.annotation.TargetApi;
+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.os.Build;
+import android.support.annotation.WorkerThread;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.tv.common.TvCommonConstants;
+
+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";
+
+ /**
+ * Query and return the map of (channel_id, ChannelInfo).
+ * See: {@link ChannelInfo#fromCursor(Cursor)}.
+ */
+ @WorkerThread
+ public static Map<Long, ChannelInfo> queryChannelInfoMapForTvInput(
+ Context context, String inputId) {
+ Uri uri = TvContract.buildChannelsUriForInput(inputId);
+ Map<Long, ChannelInfo> map = new HashMap<>();
+
+ String[] projections = new String[ChannelInfo.PROJECTION.length + 1];
+ projections[0] = Channels._ID;
+ for (int i = 0; i < ChannelInfo.PROJECTION.length; i++) {
+ projections[i + 1] = ChannelInfo.PROJECTION[i];
+ }
+ 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<ChannelInfo> channels) {
+ // Create a map from original network ID to channel row ID for existing channels.
+ SparseArray<Long> 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<Uri, String> 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 (TvCommonConstants.IS_MNC_OR_HIGHER) {
+ 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) {
+ Log.v(TAG, "Inserting "+ channel);
+ uri = resolver.insert(TvContract.Channels.CONTENT_URI, values);
+ } else {
+ Log.v(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<Map<Uri, String>, Void, Void> {
+ private final Context mContext;
+
+ InsertLogosTask(Context context) {
+ mContext = context;
+ }
+
+ @SafeVarargs
+ @Override
+ public final Void doInBackground(Map<Uri, String>... logosList) {
+ for (Map<Uri, String> 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/ComparableTester.java b/tests/common/src/com/android/tv/testing/ComparableTester.java
new file mode 100644
index 00000000..d82e25c8
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/ComparableTester.java
@@ -0,0 +1,115 @@
+/*
+ * 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 junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Tester for {@link java.lang.Comparable}s.
+ *
+ * <p>
+ * 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.
+ *
+ * <pre>{@code
+ * new ComparableTester<String>()
+ * .addEquivelentGroup("Hello", "HELLO")
+ * .addEquivelentGroup("World", "wORLD")
+ * .addEquivelentGroup("ZEBRA")
+ * .test();
+ * }
+ * </pre>
+ *
+ * @param <T> the type of objects to compare.
+ */
+public class ComparableTester<T extends Comparable<T>> {
+
+ private final List<List<T>> listOfGroups = new ArrayList<>();
+
+ @SafeVarargs
+ public final ComparableTester<T> addEquivelentGroup(T... items) {
+ listOfGroups.add(Arrays.asList(items));
+ return this;
+ }
+
+ public void test() {
+ for (int i = 0; i < listOfGroups.size(); i++) {
+ List<T> currentGroup = listOfGroups.get(i);
+ for (int j = 0; j < i; j++) {
+ List<T> lhs = listOfGroups.get(j);
+ assertOrder(i, j, lhs, currentGroup);
+ }
+ assertZero(currentGroup);
+ for (int j = i + 1; j < listOfGroups.size(); j++) {
+ List<T> rhs = listOfGroups.get(j);
+ assertOrder(i, j, currentGroup, rhs);
+ }
+ }
+ }
+
+ private void assertOrder(int less, int more, List<T> lessGroup, List<T> moreGroup) {
+ assertLess(less, more, lessGroup, moreGroup);
+ assertMore(more, less, moreGroup, lessGroup);
+ }
+
+ private void assertLess(int left, int right, Collection<T> leftGroup,
+ Collection<T> rightGroup) {
+ int leftSub = 0;
+ for (T leftItem : leftGroup) {
+ int rightSub = 0;
+ String leftName = "Item[" + left + "," + (leftSub++) + "]";
+ for (T rightItem : rightGroup) {
+ String rightName = "Item[" + right + "," + (rightSub++) + "]";
+ Assert.assertEquals(
+ leftName + " " + leftItem + " compareTo " + rightName + " " + rightItem
+ + " is <0", true, leftItem.compareTo(rightItem) < 0);
+ }
+ }
+ }
+
+ private void assertMore(int left, int right, Collection<T> leftGroup,
+ Collection<T> rightGroup) {
+ int leftSub = 0;
+ for (T leftItem : leftGroup) {
+ int rightSub = 0;
+ String leftName = "Item[" + left + "," + (leftSub++) + "]";
+ for (T rightItem : rightGroup) {
+ String rightName = "Item[" + right + "," + (rightSub++) + "]";
+ Assert.assertEquals(
+ leftName + " " + leftItem + " compareTo " + rightName + " " + rightItem
+ + " is >0", true, leftItem.compareTo(rightItem) > 0);
+ }
+ }
+ }
+
+ private void assertZero(Collection<T> group) {
+ // Test everything against everything in both directions, including against itself.
+ for (T lhs : group) {
+ for (T rhs : group) {
+ Assert.assertEquals(lhs + " compareTo " + rhs, 0, lhs.compareTo(rhs));
+ }
+ }
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/ComparatorTester.java b/tests/common/src/com/android/tv/testing/ComparatorTester.java
new file mode 100644
index 00000000..dc5cf00f
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/ComparatorTester.java
@@ -0,0 +1,128 @@
+/*
+ * 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 static junit.framework.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Tester for {@link Comparator} relationships between groups of T.
+ *
+ * <p>
+ * 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.
+ *
+ * <pre>{@code
+ * ComparatorTester
+ * .withoutEqualsTest(String.CASE_INSENSITIVE_ORDER)
+ * .addComparableGroup("Hello", "HELLO")
+ * .addComparableGroup("World", "wORLD")
+ * .addComparableGroup("ZEBRA")
+ * .test();
+ * }
+ * </pre>
+ *
+ * @param <T> the type of objects to compare.
+ */
+public class ComparatorTester<T> {
+
+ private final List<List<T>> listOfGroups = new ArrayList<>();
+
+ private final Comparator<T> comparator;
+
+
+ public static <T> ComparatorTester<T> withoutEqualsTest(Comparator<T> comparator) {
+ return new ComparatorTester<>(comparator);
+ }
+
+ private ComparatorTester(Comparator<T> comparator) {
+ this.comparator = comparator;
+ }
+
+ @SafeVarargs
+ public final ComparatorTester<T> addComparableGroup(T... items) {
+ listOfGroups.add(Arrays.asList(items));
+ return this;
+ }
+
+ public void test() {
+ for (int i = 0; i < listOfGroups.size(); i++) {
+ List<T> currentGroup = listOfGroups.get(i);
+ for (int j = 0; j < i; j++) {
+ List<T> lhs = listOfGroups.get(j);
+ assertOrder(i, j, lhs, currentGroup);
+ }
+ assertZero(currentGroup);
+ for (int j = i + 1; j < listOfGroups.size(); j++) {
+ List<T> rhs = listOfGroups.get(j);
+ assertOrder(i, j, currentGroup, rhs);
+ }
+ }
+ //TODO: also test equals
+ }
+
+ private void assertOrder(int less, int more, List<T> lessGroup, List<T> moreGroup) {
+ assertLess(less, more, lessGroup, moreGroup);
+ assertMore(more, less, moreGroup, lessGroup);
+ }
+
+ private void assertLess(int left, int right, Collection<T> leftGroup,
+ Collection<T> 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);
+ }
+ }
+ }
+
+ private void assertMore(int left, int right, Collection<T> leftGroup,
+ Collection<T> 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);
+ }
+ }
+ }
+
+ private void assertZero(Collection<T> group) {
+ // Test everything against everything in both directions, including against itself.
+ for (T leftItem : group) {
+ for (T rightItem : group) {
+ assertEquals(leftItem + " compareTo " + rightItem, 0,
+ comparator.compare(leftItem, rightItem));
+ }
+ }
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/Constants.java b/tests/common/src/com/android/tv/testing/Constants.java
new file mode 100644
index 00000000..6575fa9e
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/Constants.java
@@ -0,0 +1,44 @@
+/*
+ * 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 = 1000;
+
+ 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 GENERIC_VIDEO_TRACK = new TvTrackInfo.Builder(
+ TvTrackInfo.TYPE_VIDEO, "Generic Video").build();
+
+ private Constants() {
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/ProgramInfo.java b/tests/common/src/com/android/tv/testing/ProgramInfo.java
new file mode 100644
index 00000000..5b47d104
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/ProgramInfo.java
@@ -0,0 +1,282 @@
+/*
+ * 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.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 (int i = 0; i < DURATIONS_MS.length; i++) {
+ DURATIONS_SUM_MS += DURATIONS_MS[i];
+ }
+ }
+
+ /**
+ * 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)
+ && !GEN_EPISODE.equals(episode)
+ && !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,
+ GEN_EPISODE.equals(episode) ? (index % SEASON_MAX + 1) : seasonNumber,
+ GEN_EPISODE.equals(episode) ? (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 + "}";
+ }
+
+ 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
new file mode 100644
index 00000000..92fe9c82
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/ProgramUtils.java
@@ -0,0 +1,100 @@
+/*
+ * 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.media.tv.TvContract;
+import android.media.tv.TvContract.Programs;
+import android.net.Uri;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+public class ProgramUtils {
+ private static final String TAG = "ProgramUtils";
+
+ // 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,
+ Utils.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<ContentValues> 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);
+ values.put(Programs.COLUMN_SEASON_NUMBER, programAt.seasonNumber);
+ 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) {
+ context.getContentResolver().bulkInsert(Programs.CONTENT_URI,
+ list.toArray(new ContentValues[list.size()]));
+ Log.v(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/TvContentRatingConstants.java b/tests/common/src/com/android/tv/testing/TvContentRatingConstants.java
new file mode 100644
index 00000000..0795a89c
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/TvContentRatingConstants.java
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ *
+ * <p>Domain: com.android.tv
+ * <p>Rating system: US_TV
+ * <p>Rating: US_TV_Y7
+ * <p>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");
+
+ /**
+ * A content rating object.
+ *
+ * <p>Domain: com.android.tv
+ * <p>Rating system: US_TV
+ * <p>Rating: US_TV_MA
+ */
+ public static final TvContentRating CONTENT_RATING_US_TV_MA =
+ TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_MA");
+
+ /**
+ * A content rating object.
+ *
+ * <p>Domain: com.android.tv
+ * <p>Rating system: US_TV
+ * <p>Rating: US_TV_PG
+ * <p>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/sample/src/com/example/sampletvinput/Utils.java b/tests/common/src/com/android/tv/testing/Utils.java
index 4080b262..af4dbd18 100644
--- a/sample/src/com/example/sampletvinput/Utils.java
+++ b/tests/common/src/com/android/tv/testing/Utils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -14,46 +14,36 @@
* limitations under the License.
*/
-package com.example.sampletvinput;
+package com.android.tv.testing;
+import android.annotation.TargetApi;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
import android.media.tv.TvContentRating;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.net.Uri;
+import android.os.Build;
import android.text.TextUtils;
-import android.util.Log;
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-class Utils {
- private static final String TAG = "Utils";
- private static final boolean DEBUG = true;
-
- public static void insertFile(Context context, Uri contentUri, File file) {
- if (DEBUG) {
- Log.d(TAG, "Inserting " + file + " to " + contentUri);
- }
- InputStream is = null;
- OutputStream os = null;
- try {
- is = new FileInputStream(file);
- os = context.getContentResolver().openOutputStream(contentUri);
- copy(is, os);
- } catch (IOException ioe) {
- Log.e(TAG, "Failed to write " + file + " to " + contentUri, ioe);
- } finally {
- IoUtils.closeQuietly(is);
- IoUtils.closeQuietly(os);
+public class Utils {
+ 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 {
diff --git a/tests/common/src/com/android/tv/testing/testinput/ChannelState.java b/tests/common/src/com/android/tv/testing/testinput/ChannelState.java
new file mode 100644
index 00000000..3d234dac
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/testinput/ChannelState.java
@@ -0,0 +1,114 @@
+/*
+ * 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.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.
+ */
+public class ChannelState {
+
+ /**
+ * The video track a channel has by default.
+ */
+ public static TvTrackInfo DEFAULT_VIDEO_TRACK = Constants.FHD1080P50_VIDEO_TRACK;
+ /**
+ * The video track a channel has by default.
+ */
+ public static TvTrackInfo DEFAULT_AUDIO_TRACK = Constants.EN_STEREO_AUDIO_TRACK;
+ /**
+ * The channel is "tuned" and video availale.
+ *
+ * @see #getTuneStatus()
+ */
+ public static int TUNE_STATUS_VIDEO_AVAILABLE = -2;
+
+ private static int CHANNEL_VERSION_DEFAULT = 1;
+ /**
+ * Default ChannelState with version @{value #CHANNEL_VERSION_DEFAULT} and default {@link
+ * 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.
+ */
+ public String getSelectedAudioTrackId() {
+ return mData.mSelectedAudioTrackId;
+ }
+
+ /**
+ * Returns the id of the selected audio track, or null if none is selected.
+ */
+ public String getSelectedVideoTrackId() {
+ return mData.mSelectedVideoTrackId;
+ }
+
+ /**
+ * The current version. Larger version numbers are newer.
+ *
+ * <p>The version is increased by {@link #next(ChannelStateData)}.
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * An unmodifiable list of TvTrackInfo for a channel, suitable for {@link
+ * android.media.tv.TvInputService.Session#notifyTracksChanged(List)}
+ */
+ public List<TvTrackInfo> getTrackInfoList() {
+ return Collections.unmodifiableList(mData.mTvTrackInfos);
+ }
+
+ @Override
+ public String toString() {
+ return "v" + mVersion + ":" + mData;
+ }
+
+ /**
+ * Creates a new ChannelState, with an incremented version and {@code data} provided.
+ *
+ * @param data the data for the new ChannelState
+ */
+ public ChannelState next(ChannelStateData data) {
+ return new ChannelState(mVersion + 1, data);
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.aidl b/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.aidl
new file mode 100644
index 00000000..cdf43adb
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.aidl
@@ -0,0 +1,3 @@
+package com.android.tv.testing.testinput;
+
+parcelable ChannelStateData;
diff --git a/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.java b/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.java
new file mode 100644
index 00000000..9bac9d12
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.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.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.
+ */
+public final class ChannelStateData implements Parcelable {
+ public static final Creator<ChannelStateData> CREATOR = new Creator<ChannelStateData>() {
+ @Override
+ public ChannelStateData createFromParcel(Parcel in) {
+ return new ChannelStateData(in);
+ }
+
+ @Override
+ public ChannelStateData[] newArray(int size) {
+ return new ChannelStateData[size];
+ }
+ };
+
+ public final List<TvTrackInfo> mTvTrackInfos = new ArrayList<>();
+ public int mTuneStatus = ChannelState.TUNE_STATUS_VIDEO_AVAILABLE;
+ public String mSelectedAudioTrackId = ChannelState.DEFAULT_AUDIO_TRACK.getId();
+ public String mSelectedVideoTrackId = ChannelState.DEFAULT_VIDEO_TRACK.getId();
+
+ public ChannelStateData() {
+ mTvTrackInfos.add(ChannelState.DEFAULT_VIDEO_TRACK);
+ mTvTrackInfos.add(ChannelState.DEFAULT_AUDIO_TRACK);
+ }
+
+ private ChannelStateData(Parcel in) {
+ mTuneStatus = in.readInt();
+ in.readTypedList(mTvTrackInfos, TvTrackInfo.CREATOR);
+ mSelectedAudioTrackId = in.readString();
+ mSelectedVideoTrackId = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mTuneStatus);
+ dest.writeTypedList(mTvTrackInfos);
+ dest.writeString(mSelectedAudioTrackId);
+ dest.writeString(mSelectedVideoTrackId);
+ }
+
+ @Override
+ public String toString() {
+ return "{"
+ + "tune=" + mTuneStatus
+ + ", tracks=" + mTvTrackInfos
+ + "}";
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/testinput/ITestInputControl.aidl b/tests/common/src/com/android/tv/testing/testinput/ITestInputControl.aidl
new file mode 100644
index 00000000..a82f378b
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/testinput/ITestInputControl.aidl
@@ -0,0 +1,9 @@
+package com.android.tv.testing.testinput;
+
+
+import com.android.tv.testing.testinput.ChannelStateData;
+
+/** Remote interface for controlling the test TV Input Service */
+interface ITestInputControl {
+ void updateChannelState(int origId, in ChannelStateData data);
+}
diff --git a/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java b/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.java
new file mode 100644
index 00000000..9b3f8835
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/testinput/TestInputControlConnection.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.testing.testinput;
+
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.tv.testing.ChannelInfo;
+
+/**
+ * Connection for controlling the Test TV Input Service.
+ *
+ * <p>Wrapped methods for calling {@link ITestInputControl} that waits for a binding and rethrows
+ * {@link RemoteException} as {@link RuntimeException } are also included.
+ */
+public class TestInputControlConnection implements ServiceConnection {
+ private static final String TAG = "TestInputControlConn";
+ private static final int BOUND_CHECK_INTERVAL_MS = 10;
+
+ private ITestInputControl mControl;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mControl = ITestInputControl.Stub.asInterface(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.w(TAG, "TestInputControl service disconnected unexpectedly.");
+ mControl = null;
+ }
+
+ /**
+ * Is the service currently connected.
+ */
+ public boolean isBound() {
+ return mControl != null;
+ }
+
+ /**
+ * Update the state of the channel.
+ *
+ * @param channel the channel to update.
+ * @param data the new state for the channel.
+ */
+ public void updateChannelState(ChannelInfo channel, ChannelStateData data) {
+ waitUntilBound();
+ try {
+ mControl.updateChannelState(channel.originalNetworkId, data);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 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
new file mode 100644
index 00000000..54aacf20
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/testinput/TestInputControlUtils.java
@@ -0,0 +1,33 @@
+/*
+ * 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.testinput;
+
+import android.content.ComponentName;
+import android.content.Intent;
+
+/**
+ * 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"));
+ }
+
+ 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
new file mode 100644
index 00000000..e7ff4f5d
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/testinput/TvTestInputConstants.java
@@ -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.
+ */
+package com.android.tv.testing.testinput;
+
+import com.android.tv.testing.ChannelInfo;
+
+/**
+ * Constants for interacting with TvTestInput.
+ */
+public final class TvTestInputConstants {
+
+ /**
+ * Channel 1.
+ *
+ * <p> By convention Channel 1 should not be changed. Test often start by tuning to this
+ * channel.
+ */
+ public static final ChannelInfo CH_1 = ChannelInfo.create(null, 1);
+ /**
+ * Channel 2.
+ *
+ * <p> 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
new file mode 100644
index 00000000..3a2f5509
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/uihelper/BaseUiDeviceHelper.java
@@ -0,0 +1,32 @@
+/*
+ * 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.uihelper;
+
+import android.content.res.Resources;
+import android.support.test.uiautomator.UiDevice;
+
+/**
+ * Base class for building UiAutomator Helper classes.
+ */
+public abstract class BaseUiDeviceHelper {
+ protected final UiDevice mUiDevice;
+ protected final Resources mTargetResources;
+
+ public BaseUiDeviceHelper(UiDevice uiDevice, Resources targetResources) {
+ this.mUiDevice = uiDevice;
+ this.mTargetResources = targetResources;
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/ByResource.java b/tests/common/src/com/android/tv/testing/uihelper/ByResource.java
new file mode 100644
index 00000000..a76ee1d3
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/uihelper/ByResource.java
@@ -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.
+ */
+package com.android.tv.testing.uihelper;
+
+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.
+ */
+public final class ByResource {
+
+ /**
+ * Creates a BySelector for the {@code resId} from {@code resources}
+ *
+ * @see By#res(String)
+ */
+ public static BySelector id(Resources resources, int resId) {
+ String id = resources.getResourceName(resId);
+ return By.res(id);
+ }
+
+ /**
+ * Creates a BySelector for the text of {@code stringRes} from {@code resources}.
+ *
+ * @see By#text(String)
+ */
+ public static BySelector text(Resources resources, int stringRes) {
+ String text = resources.getString(stringRes);
+ return By.text(text);
+ }
+
+ 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
new file mode 100644
index 00000000..0297f3db
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/uihelper/Constants.java
@@ -0,0 +1,35 @@
+/*
+ * 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.uihelper;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+
+public final class Constants {
+
+ public static final double EXTRA_TIMEOUT_PRECENT = .05;
+ public static final int MIN_EXTRA_TIMEOUT = 10;
+ public static final long MAX_SHOW_DELAY_MILLIS = 200;
+ public static final String TV_APP_PACKAGE = "com.android.tv";
+ public static final BySelector CHANNEL_BANNER = By.res(TV_APP_PACKAGE, "channel_banner_view");
+ public static final BySelector MENU = By.res(TV_APP_PACKAGE, "menu_list");
+ 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 FOCUSED_VIEW = By.focused(true);
+
+ 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
new file mode 100644
index 00000000..a2476a68
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/uihelper/DialogHelper.java
@@ -0,0 +1,69 @@
+/*
+ * 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.uihelper;
+
+import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondition;
+import static com.android.tv.testing.uihelper.UiDeviceAsserts.waitForCondition;
+
+import android.app.DialogFragment;
+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;
+import com.android.tv.testing.uihelper.BaseUiDeviceHelper;
+import com.android.tv.testing.uihelper.ByResource;
+import com.android.tv.testing.uihelper.Constants;
+
+/**
+ * Helper for testing {@link DialogFragment}s.
+ */
+public class DialogHelper extends BaseUiDeviceHelper {
+ private final BySelector byPinDialog;
+
+ public DialogHelper(UiDevice uiDevice, Resources targetResources) {
+ super(uiDevice, targetResources);
+ byPinDialog = ByResource.id(mTargetResources, R.id.enter_pin);
+ }
+
+ public void assertWaitForPinDialogOpen() {
+ assertWaitForCondition(mUiDevice, Until.hasObject(byPinDialog),
+ Constants.MAX_SHOW_DELAY_MILLIS
+ + mTargetResources.getInteger(R.integer.pin_dialog_anim_duration));
+ }
+
+ public void assertWaitForPinDialogClose() {
+ assertWaitForCondition(mUiDevice, Until.gone(byPinDialog));
+ }
+
+ public void enterPinCodes() {
+ // Enter PIN code '0000' by pressing ENTER key four times.
+ mUiDevice.pressEnter();
+ mUiDevice.pressEnter();
+ mUiDevice.pressEnter();
+ mUiDevice.pressEnter();
+ boolean result = waitForCondition(mUiDevice, Until.gone(byPinDialog));
+ if (!result) {
+ // It's the first time. Confirm the PIN code.
+ mUiDevice.pressEnter();
+ mUiDevice.pressEnter();
+ mUiDevice.pressEnter();
+ mUiDevice.pressEnter();
+ }
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java
new file mode 100644
index 00000000..949e0c9b
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java
@@ -0,0 +1,43 @@
+package com.android.tv.testing.uihelper;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+
+import junit.framework.Assert;
+
+/**
+ * Helper for testing the Live Channels Application.
+ */
+public class LiveChannelsUiDeviceHelper extends BaseUiDeviceHelper {
+ private static final int APPLICATION_START_TIMEOUT_MSEC = 500;
+
+ private final Context mContext;
+
+ public LiveChannelsUiDeviceHelper(UiDevice uiDevice, Resources targetResources,
+ Context context) {
+ super(uiDevice, targetResources);
+ mContext = context;
+ }
+
+ public void assertAppStarted() {
+ Intent intent = mContext.getPackageManager()
+ .getLaunchIntentForPackage(Constants.TV_APP_PACKAGE);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Clear out any previous instances
+ mContext.startActivity(intent);
+ mUiDevice.waitForIdle();
+
+ 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)) {
+ mUiDevice.pressBack();
+ }
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java b/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java
new file mode 100644
index 00000000..fe16ec27
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java
@@ -0,0 +1,190 @@
+/*
+ * 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.uihelper;
+
+import static com.android.tv.testing.uihelper.Constants.MENU;
+
+import android.content.res.Resources;
+import android.support.test.uiautomator.By;
+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 android.support.test.uiautomator.Until;
+
+import com.android.tv.R;
+import com.android.tv.common.TvCommonConstants;
+import com.android.tv.menu.MenuView;
+
+import junit.framework.Assert;
+
+/**
+ * Helper for testing {@link MenuView}.
+ */
+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, getMenuTitleChannelsResId()));
+ }
+
+ public int getMenuTitleChannelsResId() {
+ return TvCommonConstants.IS_MNC_OR_HIGHER ? R.string.menu_title_channels
+ : R.string.menu_title_channels_legacy;
+ }
+
+ public BySelector getByChannels() {
+ return byChannels;
+ }
+
+
+ /**
+ * Navigate to the menu item with the text {@code itemTextResId} in the row with text
+ * {@code rowTitleResId}.
+ * <p>
+ * 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.
+ * @return the item navigated to.
+ */
+ public UiObject2 assertNavigateToMenuItem(int rowTitleResId, int itemTextResId) {
+ UiObject2 row = assertNavigateToRow(rowTitleResId);
+ 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);
+ return assertNavigateToRowItem(listView, itemTextResId);
+ }
+
+ /**
+ * Navigate to the menu row with the text title {@code rowTitleResId}.
+ * <p>
+ * 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.
+ */
+ public UiObject2 assertNavigateToRow(int rowTitleResId) {
+ UiDeviceAsserts.assertHas(mUiDevice, MENU, true);
+ 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);
+ }
+
+ /**
+ * Navigate to the Play controls row.
+ * <p>
+ * Fails if the row can not be navigated to.
+ *
+ * @see #assertNavigateToRow
+ */
+ public void assertNavigateToPlayControlsRow() {
+ UiDeviceAsserts.assertHas(mUiDevice, MENU, true);
+ // The play controls row doesn't have title when selected, so can't use
+ // MenuHelper.assertNavigateToRow().
+ assertNavigateToRow(getMenuTitleChannelsResId());
+ mUiDevice.pressDPadUp();
+ }
+
+ /**
+ * Navigate to the menu item in the given {@code row} with the text {@code itemTextResId} .
+ * <p>
+ * 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,
+ By.hasDescendant(ByResource.text(mTargetResources, itemTextResId)),
+ Direction.RIGHT);
+ }
+
+ public UiObject2 assertPressOptionsChannelSources() {
+ return assertPressMenuItem(R.string.menu_title_options,
+ R.string.options_item_channel_sources);
+ }
+
+
+ public UiObject2 assertPressOptionsClosedCaptions() {
+ return assertPressMenuItem(R.string.menu_title_options,
+ R.string.options_item_closed_caption);
+ }
+
+ public UiObject2 assertPressOptionsDisplayMode() {
+ return assertPressMenuItem(R.string.menu_title_options, R.string.options_item_display_mode);
+ }
+
+ public UiObject2 assertPressOptionsMultiAudio() {
+ return assertPressMenuItem(R.string.menu_title_options, R.string.options_item_multi_audio);
+ }
+
+ public UiObject2 assertPressOptionsParentalControls() {
+ return assertPressMenuItem(R.string.menu_title_options,
+ R.string.options_item_parental_controls);
+ }
+
+ public UiObject2 assertPressProgramGuide() {
+ return assertPressMenuItem(getMenuTitleChannelsResId(),
+ R.string.channels_item_program_guide);
+ }
+
+ /**
+ * Navigate to the menu item with the text {@code itemTextResId} in the row with text
+ * {@code rowTitleResId}.
+ * <p>
+ * 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.
+ * @return the item navigated to.
+ */
+ public UiObject2 assertPressMenuItem(int rowTitleResId, int itemTextResId) {
+ showMenu();
+ UiObject2 item = assertNavigateToMenuItem(rowTitleResId, itemTextResId);
+ mUiDevice.pressDPadCenter();
+ return item;
+ }
+
+ /**
+ * Waits until the menu is visible.
+ */
+ public void assertWaitForMenu() {
+ UiDeviceAsserts.assertWaitForCondition(mUiDevice, Until.hasObject(MENU));
+ }
+
+ /**
+ * Show the menu.
+ * <p>
+ * Fails if the menu does not appear in {@link Constants#MAX_SHOW_DELAY_MILLIS}.
+ */
+ public void showMenu() {
+ if (!mUiDevice.hasObject(MENU)) {
+ 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
new file mode 100644
index 00000000..d342723d
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/uihelper/SidePanelHelper.java
@@ -0,0 +1,63 @@
+/*
+ * 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.uihelper;
+
+import android.content.res.Resources;
+import android.support.test.uiautomator.By;
+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.testing.uihelper.BaseUiDeviceHelper;
+import com.android.tv.testing.uihelper.ByResource;
+import com.android.tv.testing.uihelper.Constants;
+import com.android.tv.testing.uihelper.UiDeviceAsserts;
+import com.android.tv.ui.sidepanel.SideFragment;
+
+import junit.framework.Assert;
+
+/**
+ * Helper for testing {@link SideFragment}s.
+ */
+public class SidePanelHelper extends BaseUiDeviceHelper {
+
+ public SidePanelHelper(UiDevice uiDevice, Resources targetResources) {
+ super(uiDevice, targetResources);
+ }
+
+ public BySelector bySidePanelTitled(int titleResId) {
+ return By.copy(Constants.SIDE_PANEL)
+ .hasDescendant(ByResource.text(mTargetResources, titleResId));
+ }
+
+ public UiObject2 assertNavigateToItem(int resId) {
+ String title = mTargetResources.getString(resId);
+ return assertNavigateToItem(title);
+ }
+
+ public UiObject2 assertNavigateToItem(String title) {
+ 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);
+ }
+}
diff --git a/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java
new file mode 100644
index 00000000..90d5a297
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceAsserts.java
@@ -0,0 +1,146 @@
+/*
+ * 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.uihelper;
+
+import static com.android.tv.testing.uihelper.Constants.FOCUSED_VIEW;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+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.
+ */
+public final class UiDeviceAsserts {
+
+ public static void assertHas(UiDevice uiDevice, BySelector bySelector, boolean expected) {
+ assertEquals("Has " + bySelector, expected, uiDevice.hasObject(bySelector));
+ }
+
+ /**
+ * Assert that {@code searchCondition} becomes true within
+ * {@value Constants#MAX_SHOW_DELAY_MILLIS} milliseconds.
+ *
+ * @param uiDevice the device under test.
+ * @param searchCondition the condition to wait for.
+ */
+ public static void assertWaitForCondition(UiDevice uiDevice,
+ SearchCondition<Boolean> 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 searchCondition the condition to wait for.
+ */
+ public static void assertWaitForCondition(UiDevice uiDevice,
+ SearchCondition<Boolean> searchCondition, long timeout) {
+ boolean result = waitForCondition(uiDevice, searchCondition, timeout);
+ assertTrue(searchCondition + " not true after " + timeout / 1000.0 + " seconds.", result);
+ }
+
+ /**
+ * Wait until {@code searchCondition} becomes true.
+ *
+ * @param uiDevice The device under test.
+ * @param searchCondition The condition to wait for.
+ * @return {@true} if the condition is met, otherwise {@code false}.
+ */
+ public static boolean waitForCondition(UiDevice uiDevice,
+ SearchCondition<Boolean> searchCondition) {
+ return waitForCondition(uiDevice, searchCondition, Constants.MAX_SHOW_DELAY_MILLIS);
+ }
+
+ private static boolean waitForCondition(UiDevice uiDevice,
+ SearchCondition<Boolean> searchCondition, long timeout) {
+ long adjustedTimeout = timeout + Math.max(Constants.MIN_EXTRA_TIMEOUT,
+ (long) (timeout * Constants.EXTRA_TIMEOUT_PRECENT));
+ 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}.
+ * <p>
+ * 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}.
+ * <p>
+ * 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 direction the direction to start navigating.
+ * @return the object navigated to.
+ */
+ 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<Boolean> untilHasFocusedDescendant = Until
+ .hasObject(hasFocusedDescendant);
+
+ 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.
+ UiDeviceUtils.pressDpad(uiDevice, direction);
+ UiObject2Asserts.assertWaitForCondition(container, untilHasFocusedDescendant);
+ }
+
+ for (UiObject2 c : container.getChildren()) {
+ if (c.isFocused() || c.hasObject(hasFocusedDescendant)) {
+ focusedChild = c;
+ break;
+ }
+ }
+ if (focusedChild == null) {
+ Assert.fail("No focused item found in container " + container);
+ }
+ if (focusedChild.hasObject(selector)) {
+ return focusedChild;
+ }
+ if (!UiObject2Utils.hasSiblingInDirection(focusedChild, direction)) {
+ direction = Direction.reverse(direction);
+ count++;
+ }
+ UiDeviceUtils.pressDpad(uiDevice, direction);
+ }
+ Assert.fail("Could not find item with " + selector);
+ return null;
+ }
+
+ 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
new file mode 100644
index 00000000..420e0824
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/uihelper/UiDeviceUtils.java
@@ -0,0 +1,70 @@
+/*
+ * 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.uihelper;
+
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.view.KeyEvent;
+
+/**
+ * Static utility methods for {@link UiDevice}.
+ */
+public final class UiDeviceUtils {
+
+ public static boolean pressDpad(UiDevice uiDevice, Direction direction) {
+ switch (direction) {
+ case UP:
+ return uiDevice.pressDPadUp();
+ case DOWN:
+ return uiDevice.pressDPadDown();
+ case LEFT:
+ return uiDevice.pressDPadLeft();
+ case RIGHT:
+ return uiDevice.pressDPadRight();
+ default:
+ throw new IllegalArgumentException(direction.toString());
+ }
+ }
+
+
+ public static void pressKeys(UiDevice uiDevice, int... keyCodes) {
+ for (int k : keyCodes) {
+ uiDevice.pressKeyCode(k);
+ }
+ }
+
+ /**
+ * Parses the string and sends the corresponding individual key preses.
+ * <p>
+ * <b>Note:</b> only handles 0-9, '.', and '-'.
+ */
+ public static void pressKeys(UiDevice uiDevice, String keys) {
+ for (char c : keys.toCharArray()) {
+ if (c >= '0' && c <= '9') {
+ uiDevice.pressKeyCode(KeyEvent.KEYCODE_0 + c - '0');
+ } else if (c == '-') {
+ uiDevice.pressKeyCode(KeyEvent.KEYCODE_MINUS);
+ } else if (c == '.') {
+ uiDevice.pressKeyCode(KeyEvent.KEYCODE_PERIOD);
+ } else {
+ throw new IllegalArgumentException(c + " is not supported");
+ }
+ }
+ }
+
+ 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
new file mode 100644
index 00000000..9db70930
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/uihelper/UiObject2Asserts.java
@@ -0,0 +1,60 @@
+/*
+ * 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.uihelper;
+
+import static junit.framework.Assert.assertTrue;
+
+import android.support.test.uiautomator.SearchCondition;
+import android.support.test.uiautomator.UiObject2;
+
+/**
+ * Asserts for {@link UiObject2}s.
+ */
+public final class UiObject2Asserts {
+
+ /**
+ * Assert that {@code searchCondition} becomes true within
+ * {@value Constants#MAX_SHOW_DELAY_MILLIS} milliseconds.
+ *
+ * @param uiObject the device under test.
+ * @param searchCondition the condition to wait for.
+ */
+ public static void assertWaitForCondition(UiObject2 uiObject,
+ SearchCondition<Boolean> 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 searchCondition the condition to wait for.
+ */
+ public static void assertWaitForCondition(UiObject2 uiObject,
+ SearchCondition<Boolean> 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_PRECENT));
+ }
+
+ 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
new file mode 100644
index 00000000..2a997a67
--- /dev/null
+++ b/tests/common/src/com/android/tv/testing/uihelper/UiObject2Utils.java
@@ -0,0 +1,61 @@
+/*
+ * 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.uihelper;
+
+import android.graphics.Point;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
+
+/**
+ * Static utility methods for {@link UiObject2}s.
+ */
+public class UiObject2Utils {
+
+ public static boolean hasSiblingInDirection(UiObject2 theUiObject, Direction direction) {
+ Point myCenter = theUiObject.getVisibleCenter();
+ for (UiObject2 sibling : theUiObject.getParent().getChildren()) {
+ Point siblingCenter = sibling.getVisibleCenter();
+ switch (direction) {
+ case UP:
+ if (myCenter.y > siblingCenter.y) {
+ return true;
+ }
+ break;
+ case DOWN:
+ if (myCenter.y < siblingCenter.y) {
+ return true;
+ }
+ break;
+ case LEFT:
+ if (myCenter.x > siblingCenter.x) {
+ return true;
+ }
+ break;
+ case RIGHT:
+ if (myCenter.x < siblingCenter.x) {
+ return true;
+ }
+ break;
+ default:
+ throw new IllegalArgumentException(direction.toString());
+ }
+ }
+ return false;
+ }
+
+ private UiObject2Utils() {
+ }
+}
diff --git a/tests/func/Android.mk b/tests/func/Android.mk
new file mode 100644
index 00000000..53ed5e72
--- /dev/null
+++ b/tests/func/Android.mk
@@ -0,0 +1,21 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := TVFuncTests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ tv-test-common \
+ ub-uiautomator \
+
+LOCAL_INSTRUMENTATION_FOR := TV
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/tests/func/AndroidManifest.xml b/tests/func/AndroidManifest.xml
new file mode 100644
index 00000000..29b018e1
--- /dev/null
+++ b/tests/func/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tv.tests.ui" >
+
+ <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21" />
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="Live Channel Functional Tests"
+ android:targetPackage="com.android.tv" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+</manifest>
diff --git a/tests/func/src/com/android/tv/tests/ui/ChannelBannerViewTest.java b/tests/func/src/com/android/tv/tests/ui/ChannelBannerViewTest.java
new file mode 100644
index 00000000..61939fdb
--- /dev/null
+++ b/tests/func/src/com/android/tv/tests/ui/ChannelBannerViewTest.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.tests.ui;
+
+import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondition;
+
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.tv.R;
+import com.android.tv.testing.uihelper.Constants;
+
+@SmallTest
+public class ChannelBannerViewTest extends LiveChannelsTestCase {
+ // Channel banner show duration with the grace period.
+ private long mShowDurationMillis;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mLiveChannelsHelper.assertAppStarted();
+ mShowDurationMillis = mTargetResources.getInteger(R.integer.channel_banner_show_duration)
+ + Constants.MAX_SHOW_DELAY_MILLIS;
+ }
+
+ public void testChannelBannerAppearDisappear() {
+ mDevice.pressDPadCenter();
+ assertWaitForCondition(mDevice, Until.hasObject(Constants.CHANNEL_BANNER));
+ assertWaitForCondition(mDevice, Until.gone(Constants.CHANNEL_BANNER), mShowDurationMillis);
+ }
+
+ public void testChannelBannerShownWhenTune() {
+ mDevice.pressDPadDown();
+ assertWaitForCondition(mDevice, Until.hasObject(Constants.CHANNEL_BANNER));
+ mDevice.pressDPadUp();
+ assertWaitForCondition(mDevice, Until.hasObject(Constants.CHANNEL_BANNER));
+ }
+}
diff --git a/tests/func/src/com/android/tv/tests/ui/ChannelSourcesTest.java b/tests/func/src/com/android/tv/tests/ui/ChannelSourcesTest.java
new file mode 100644
index 00000000..9959185f
--- /dev/null
+++ b/tests/func/src/com/android/tv/tests/ui/ChannelSourcesTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.tv.R;
+import com.android.tv.testing.uihelper.ByResource;
+import com.android.tv.testing.uihelper.SidePanelHelper;
+
+/**
+ * Tests for channel sources.
+ */
+@LargeTest
+public class ChannelSourcesTest extends LiveChannelsTestCase {
+ private SidePanelHelper mSidePanelHelper;
+ private BySelector mByChannelSourceSidePanel;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mSidePanelHelper = new SidePanelHelper(mDevice, mTargetResources);
+ mByChannelSourceSidePanel = mSidePanelHelper
+ .bySidePanelTitled(R.string.side_panel_title_channel_sources);
+ }
+
+ //TODO: create a cancelable test channel setup.
+
+ public void testSetup_cancel() {
+ mLiveChannelsHelper.assertAppStarted();
+ mMenuHelper.assertPressOptionsChannelSources();
+ assertWaitForCondition(mDevice, Until.hasObject(mByChannelSourceSidePanel));
+
+ mSidePanelHelper.assertNavigateToItem(R.string.channel_source_item_setup);
+ mDevice.pressDPadCenter();
+
+ assertWaitForCondition(mDevice,
+ Until.hasObject(ByResource.text(mTargetResources, R.string.setup_title)));
+ 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
new file mode 100644
index 00000000..8190d79d
--- /dev/null
+++ b/tests/func/src/com/android/tv/tests/ui/LiveChannelsAppTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.assertHas;
+import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondition;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.LargeTest;
+
+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;
+import com.android.tv.testing.uihelper.SidePanelHelper;
+
+/**
+ * Basic tests for the LiveChannels app.
+ */
+@LargeTest
+public class LiveChannelsAppTest extends LiveChannelsTestCase {
+ private SidePanelHelper mSidePanelHelper;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mSidePanelHelper = new SidePanelHelper(mDevice, mTargetResources);
+ mLiveChannelsHelper.assertAppStarted();
+ pressKeysForChannel(TvTestInputConstants.CH_1);
+ getInstrumentation().waitForIdleSync();
+ }
+
+ public void testChannelSourcesCancel() {
+ mMenuHelper.assertPressOptionsChannelSources();
+ BySelector byChannelSourcesSidePanel = mSidePanelHelper
+ .bySidePanelTitled(R.string.channel_source_item_customize_channels);
+ assertWaitForCondition(mDevice, Until.hasObject(byChannelSourcesSidePanel));
+ mDevice.pressBack();
+ assertWaitForCondition(mDevice, Until.gone(byChannelSourcesSidePanel));
+ assertHas(mDevice, Constants.MENU, false);
+ }
+
+ public void testClosedCaptionsCancel() {
+ mMenuHelper.assertPressOptionsClosedCaptions();
+ BySelector byClosedCaptionSidePanel = mSidePanelHelper
+ .bySidePanelTitled(R.string.side_panel_title_closed_caption);
+ assertWaitForCondition(mDevice, Until.hasObject(byClosedCaptionSidePanel));
+ mDevice.pressBack();
+ assertWaitForCondition(mDevice, Until.gone(byClosedCaptionSidePanel));
+ assertHas(mDevice, Constants.MENU, false);
+ }
+
+ 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();
+ updateThenTune(data, TvTestInputConstants.CH_2);
+
+ mMenuHelper.assertPressOptionsDisplayMode();
+ BySelector byDisplayModeSidelPanel = mSidePanelHelper
+ .bySidePanelTitled(R.string.side_panel_title_display_mode);
+ assertWaitForCondition(mDevice, Until.hasObject(byDisplayModeSidelPanel));
+ mDevice.pressBack();
+ assertWaitForCondition(mDevice, Until.gone(byDisplayModeSidelPanel));
+ assertHas(mDevice, Constants.MENU, false);
+ }
+
+ public void testMenu() {
+ mDevice.pressMenu();
+
+ assertWaitForCondition(mDevice, Until.hasObject(Constants.MENU));
+ assertHas(mDevice, mMenuHelper.getByChannels(), true);
+ }
+
+ public void testMultiAudioCancel() {
+ ChannelStateData data = new ChannelStateData();
+ data.mTvTrackInfos.add(com.android.tv.testing.Constants.GENERIC_AUDIO_TRACK);
+ updateThenTune(data, TvTestInputConstants.CH_2);
+
+ mMenuHelper.assertPressOptionsMultiAudio();
+ BySelector byMultiAudioSidelPanel = mSidePanelHelper
+ .bySidePanelTitled(R.string.side_panel_title_multi_audio);
+ assertWaitForCondition(mDevice, Until.hasObject(byMultiAudioSidelPanel));
+ mDevice.pressBack();
+ assertWaitForCondition(mDevice, Until.gone(byMultiAudioSidelPanel));
+ assertHas(mDevice, Constants.MENU, false);
+ }
+
+ public void testPinCancel() {
+ mMenuHelper.showMenu();
+ mMenuHelper.assertPressOptionsParentalControls();
+ DialogHelper dialogHelper = new DialogHelper(mDevice, mTargetResources);
+ dialogHelper.assertWaitForPinDialogOpen();
+ mDevice.pressBack();
+ dialogHelper.assertWaitForPinDialogClose();
+ assertHas(mDevice, Constants.MENU, false);
+ }
+}
diff --git a/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java b/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java
new file mode 100644
index 00000000..af61abc4
--- /dev/null
+++ b/tests/func/src/com/android/tv/tests/ui/LiveChannelsTestCase.java
@@ -0,0 +1,96 @@
+/*
+ * 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 android.content.Context;
+import android.content.res.Resources;
+import android.support.test.uiautomator.UiDevice;
+import android.test.InstrumentationTestCase;
+
+import com.android.tv.testing.ChannelInfo;
+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.uihelper.Constants;
+import com.android.tv.testing.uihelper.LiveChannelsUiDeviceHelper;
+import com.android.tv.testing.uihelper.MenuHelper;
+import com.android.tv.testing.uihelper.UiDeviceUtils;
+
+/**
+ * Base test case for LiveChannel UI tests.
+ */
+public abstract class LiveChannelsTestCase extends InstrumentationTestCase {
+ protected final TestInputControlConnection mConnection = new TestInputControlConnection();
+
+ protected UiDevice mDevice;
+ protected Resources mTargetResources;
+ protected MenuHelper mMenuHelper;
+ protected LiveChannelsUiDeviceHelper mLiveChannelsHelper;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Context context = getInstrumentation().getContext();
+ context.bindService(TestInputControlUtils.createIntent(), mConnection,
+ Context.BIND_AUTO_CREATE);
+ mDevice = UiDevice.getInstance(getInstrumentation());
+ mTargetResources = getInstrumentation().getTargetContext().getResources();
+ mMenuHelper = new MenuHelper(mDevice, mTargetResources);
+ mLiveChannelsHelper = new LiveChannelsUiDeviceHelper(mDevice, mTargetResources, context);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mConnection.isBound()) {
+ getInstrumentation().getContext().unbindService(mConnection);
+ }
+
+ // TODO: robustly handle left over pops from failed tests.
+ // 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)) {
+ mDevice.pressBack();
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Send the keys for the channel number of {@code channel} and press the DPAD
+ * center.
+ *
+ * <p>Usually this will tune to the given channel.
+ */
+ protected void pressKeysForChannel(ChannelInfo channel) {
+ UiDeviceUtils.pressKeys(mDevice, channel.number);
+ // TODO: Sometimes, next statement doesn't close the SelectInputView. It makes the tests
+ // which use this method flaky. b/22509489
+ mDevice.pressDPadCenter();
+ }
+
+ /**
+ * Update the channel state to {@code data} then tune to that channel.
+ *
+ * @param data the state to update the channel with.
+ * @param channel the channel to tune to
+ */
+ protected void updateThenTune(ChannelStateData data, ChannelInfo channel) {
+ mConnection.updateChannelState(channel, data);
+ pressKeysForChannel(channel);
+ }
+}
diff --git a/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java b/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java
new file mode 100644
index 00000000..b96ecf18
--- /dev/null
+++ b/tests/func/src/com/android/tv/tests/ui/PlayControlsRowViewTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.Constants.FOCUSED_VIEW;
+import static com.android.tv.testing.uihelper.Constants.MENU;
+import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondition;
+
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.KeyEvent;
+
+import com.android.tv.R;
+import com.android.tv.testing.testinput.TvTestInputConstants;
+import com.android.tv.testing.uihelper.DialogHelper;
+import com.android.tv.testing.uihelper.SidePanelHelper;
+
+@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.MNC)
+public class PlayControlsRowViewTest extends LiveChannelsTestCase {
+ private static final int BUTTON_INDEX_PLAY_PAUSE = 2;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mLiveChannelsHelper.assertAppStarted();
+ pressKeysForChannel(TvTestInputConstants.CH_1);
+ // Tune to a new channel to ensure that the channel is changed.
+ mDevice.pressDPadUp();
+ getInstrumentation().waitForIdleSync();
+ }
+
+ /**
+ * Test the normal case. The play/pause button should have focus initially.
+ */
+ public void testFocusedViewInNormalCase() {
+ mMenuHelper.showMenu();
+ mMenuHelper.assertNavigateToPlayControlsRow();
+ assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE);
+ mDevice.pressBack();
+ }
+
+ /**
+ * 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
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
+ mMenuHelper.assertWaitForMenu();
+ assertButtonHasFocus(BUTTON_INDEX_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);
+ mDevice.pressBack();
+ }
+
+ public void testFocusedViewInMenu() {
+ mMenuHelper.showMenu();
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_PLAY);
+ assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE);
+ mMenuHelper.assertNavigateToRow(R.string.menu_title_channels);
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_NEXT);
+ assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE);
+ }
+
+ public void testKeepPausedWhileParentalControlChange() {
+ // Pause the playback.
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE);
+ mMenuHelper.assertWaitForMenu();
+ assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE);
+ // Show parental controls fragment.
+ mMenuHelper.assertPressOptionsParentalControls();
+ DialogHelper dialogHelper = new DialogHelper(mDevice, mTargetResources);
+ dialogHelper.assertWaitForPinDialogOpen();
+ dialogHelper.enterPinCodes();
+ dialogHelper.assertWaitForPinDialogClose();
+ SidePanelHelper sidePanelHelper = new SidePanelHelper(mDevice, mTargetResources);
+ BySelector bySidePanel = sidePanelHelper.bySidePanelTitled(R.string.menu_parental_controls);
+ assertWaitForCondition(mDevice, Until.hasObject(bySidePanel));
+ mDevice.pressEnter();
+ mDevice.pressEnter();
+ mDevice.pressBack();
+ // Return to the main menu.
+ mMenuHelper.assertWaitForMenu();
+ assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE);
+ }
+
+ public void testKeepPausedAfterVisitingHome() {
+ // Pause the playback.
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE);
+ mMenuHelper.assertWaitForMenu();
+ assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE);
+ // Press HOME twice to visit the home screen and return to Live Channels.
+ mDevice.pressHome();
+ // Wait until home screen is shown.
+ mDevice.waitForIdle();
+ mDevice.pressHome();
+ // Wait until TV is resumed.
+ mDevice.waitForIdle();
+ // Return to the main menu.
+ mMenuHelper.assertWaitForMenu();
+ assertButtonHasFocus(BUTTON_INDEX_PLAY_PAUSE);
+ }
+
+ private void assertButtonHasFocus(int expectedButtonIndex) {
+ 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);
+ }
+}
diff --git a/tests/func/src/com/android/tv/tests/ui/ProgramGuidePerformanceTest.java b/tests/func/src/com/android/tv/tests/ui/ProgramGuidePerformanceTest.java
new file mode 100644
index 00000000..833ed954
--- /dev/null
+++ b/tests/func/src/com/android/tv/tests/ui/ProgramGuidePerformanceTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.uiautomator.Until;
+import android.test.suitebuilder.annotation.LargeTest;
+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(mMenuHelper.getMenuTitleChannelsResId(),
+ 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/ProgramGuideTest.java b/tests/func/src/com/android/tv/tests/ui/ProgramGuideTest.java
new file mode 100644
index 00000000..2778e54a
--- /dev/null
+++ b/tests/func/src/com/android/tv/tests/ui/ProgramGuideTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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 android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.tv.guide.ProgramGuide;
+import com.android.tv.testing.uihelper.Constants;
+
+import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertHas;
+import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondition;
+
+/**
+ * Tests for {@link ProgramGuide}.
+ */
+@LargeTest
+public class ProgramGuideTest extends LiveChannelsTestCase {
+
+ public void testCancel() {
+ mLiveChannelsHelper.assertAppStarted();
+ mMenuHelper.assertPressProgramGuide();
+ 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
new file mode 100644
index 00000000..dd727eaa
--- /dev/null
+++ b/tests/func/src/com/android/tv/tests/ui/TimeoutTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.assertHas;
+import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondition;
+
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.Suppress;
+
+import com.android.tv.R;
+import com.android.tv.testing.uihelper.Constants;
+
+/**
+ * Test timeout events like the menu despairing after no input.
+ * <p>
+ * <b>WARNING</b> some of these timeouts are 60 seconds. These tests will take a long time
+ * complete.
+ */
+@Suppress // TODO: b/20111741
+@LargeTest
+public class TimeoutTest extends LiveChannelsTestCase {
+
+ public void testMenu() {
+ mLiveChannelsHelper.assertAppStarted();
+ mDevice.pressMenu();
+
+ assertWaitForCondition(mDevice, Until.hasObject(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),
+ mTargetResources.getInteger(R.integer.program_guide_show_duration));
+ assertHas(mDevice, Constants.MENU, false);
+ }
+}
diff --git a/tests/input/Android.mk b/tests/input/Android.mk
new file mode 100644
index 00000000..ea1d0f71
--- /dev/null
+++ b/tests/input/Android.mk
@@ -0,0 +1,25 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := TVTestInput
+LOCAL_MODULE_TAGS := optional
+LOCAL_PROGUARD_ENABLED := disabled
+# Overlay view related functionality requires system APIs.
+LOCAL_SDK_VERSION := system_current
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ tv-test-common \
+ tv-common
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../common/res $(LOCAL_PATH)/res
+LOCAL_AAPT_FLAGS := --auto-add-overlay \
+ --extra-packages com.android.tv.testing
+
+include $(BUILD_PACKAGE)
+
+ifneq ($(filter TV,$(TARGET_BUILD_APPS)),)
+ $(call dist-for-goals,apps_only,$(LOCAL_BUILT_MODULE):$(LOCAL_PACKAGE_NAME).apk)
+endif
+
diff --git a/tests/input/AndroidManifest.xml b/tests/input/AndroidManifest.xml
new file mode 100644
index 00000000..e02924ea
--- /dev/null
+++ b/tests/input/AndroidManifest.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tv.testinput">
+
+ <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21"/>
+
+ <!-- Required to update or read existing channel and program information in TvProvider. -->
+ <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
+ <!-- Required to update channel and program information in TvProvider. -->
+ <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+
+ <application android:label="@string/sample_tv_input"
+ android:icon="@drawable/android_48dp"
+ android:theme="@android:style/Theme.Holo.Light.NoActionBar" >
+ <!-- Launched by the TV app before it uses TestTvInputService to set up channels for this
+ input. -->
+ <activity android:name=".TestTvInputSetupActivity" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ <service android:name=".TestTvInputService"
+ android:permission="android.permission.BIND_TV_INPUT"
+ android:label="@string/simple_input_label">
+ <!-- Required filter used by the system to launch our account service. -->
+ <intent-filter>
+ <action android:name="android.media.tv.TvInputService" />
+ </intent-filter>
+ <!-- An XML file which describes this input. This provides a pointer to the
+ TestTvInputSetupActivity to the system/TV app. -->
+ <meta-data android:name="android.media.tv.input"
+ android:resource="@xml/testtvinputservice" />
+ </service>
+ <service android:name=".TestInputControlService" android:exported="true"/>
+
+ </application>
+
+ <instrumentation
+ android:name=".instrument.TestSetupInstrumentation"
+ android:label="Test Setup Instrument"
+ android:targetPackage="com.android.tv.testinput" />
+
+ <uses-feature
+ android:name="android.hardware.touchscreen"
+ android:required="false" />
+ <uses-feature
+ android:name="android.software.leanback"
+ android:required="true" />
+ <!-- Required to expose this app in the store only when the device has TV input framework
+ with the TV app. -->
+ <uses-feature
+ android:name="android.software.live_tv"
+ android:required="true" />
+</manifest>
diff --git a/tests/input/res/drawable-xhdpi/android_48dp.png b/tests/input/res/drawable-xhdpi/android_48dp.png
new file mode 100644
index 00000000..9ea1cd14
--- /dev/null
+++ b/tests/input/res/drawable-xhdpi/android_48dp.png
Binary files differ
diff --git a/sample/res/drawable-xhdpi/icon.png b/tests/input/res/drawable-xhdpi/icon.png
index 8497c28c..8497c28c 100644
--- a/sample/res/drawable-xhdpi/icon.png
+++ b/tests/input/res/drawable-xhdpi/icon.png
Binary files differ
diff --git a/tests/input/res/values/strings.xml b/tests/input/res/values/strings.xml
new file mode 100644
index 00000000..3f2ab3f7
--- /dev/null
+++ b/tests/input/res/values/strings.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+<resources>
+ <string name="app_name" translatable="false">Test TV Inputs</string>
+ <string name="about_app" translatable="false">About TV Test Inputs</string>
+ <string name="version" translatable="false">Version: %1$s</string>
+ <string name="sample_tv_input" translatable="false">Test TV Input</string>
+ <string name="simple_input_label" translatable="false">Test Input</string>
+ <string name="simple_setup_title" translatable="false">Setup Test Input</string>
+ <string name="simple_setup_message" translatable="false">Do you want to register detected channels?</string>
+</resources>
diff --git a/tests/input/res/xml/testtvinputservice.xml b/tests/input/res/xml/testtvinputservice.xml
new file mode 100644
index 00000000..74be3892
--- /dev/null
+++ b/tests/input/res/xml/testtvinputservice.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
+ android:setupActivity="com.android.tv.testinput.TestTvInputSetupActivity" />
diff --git a/tests/input/src/com/android/tv/testinput/TestInputControl.java b/tests/input/src/com/android/tv/testinput/TestInputControl.java
new file mode 100644
index 00000000..cd85c86e
--- /dev/null
+++ b/tests/input/src/com/android/tv/testinput/TestInputControl.java
@@ -0,0 +1,98 @@
+/*
+ * 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.testinput;
+
+import android.content.ContentUris;
+import android.content.Context;
+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}.
+ *
+ * <p>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.
+ *
+ * <p> 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 final LongSparseArray<ChannelInfo> mId2ChannelInfoMap = new LongSparseArray<>();
+ private final LongSparseArray<ChannelState> mOrigId2StateMap = new LongSparseArray<>();
+
+ private java.lang.String mInputId;
+ private boolean initialized;
+
+ private TestInputControl() {
+ }
+
+ public static TestInputControl getInstance() {
+ return INSTANCE;
+ }
+
+ public synchronized void init(Context context, String inputId) {
+ if (!initialized) {
+ // TODO run initialization in a separate thread.
+ mInputId = inputId;
+ updateChannelMap(context);
+ initialized = true;
+ }
+ }
+
+ private void updateChannelMap(Context context) {
+ mId2ChannelInfoMap.clear();
+ Map<Long, ChannelInfo> channelIdToInfoMap =
+ ChannelUtils.queryChannelInfoMapForTvInput(context, mInputId);
+ for (Long channelId : channelIdToInfoMap.keySet()) {
+ mId2ChannelInfoMap.put(channelId, channelIdToInfoMap.get(channelId));
+ }
+ Log.i(TAG, "Initialized channel map for " + mInputId + " with " + mId2ChannelInfoMap.size()
+ + " channels");
+ }
+
+ public ChannelInfo getChannelInfo(Uri channelUri) {
+ return mId2ChannelInfoMap.get(ContentUris.parseId(channelUri));
+ }
+
+ public ChannelState getChannelState(int originalNetworkId) {
+ return mOrigId2StateMap.get(originalNetworkId, ChannelState.DEFAULT);
+ }
+
+ @Override
+ public synchronized void updateChannelState(int origId, ChannelStateData data)
+ throws RemoteException {
+ ChannelState state;
+ ChannelState orig = getChannelState(origId);
+ state = orig.next(data);
+ mOrigId2StateMap.put(origId, state);
+
+ Log.i(TAG, "Setting channel " + origId + " state to " + state);
+ }
+}
diff --git a/tests/input/src/com/android/tv/testinput/TestInputControlService.java b/tests/input/src/com/android/tv/testinput/TestInputControlService.java
new file mode 100644
index 00000000..4a5668cc
--- /dev/null
+++ b/tests/input/src/com/android/tv/testinput/TestInputControlService.java
@@ -0,0 +1,32 @@
+/*
+ * 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.testinput;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Testcases communicate to the {@link TestInputControl} via
+ * {@link com.android.tv.testing.testinput.ITestInputControl}.
+ */
+public class TestInputControlService extends Service {
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return TestInputControl.getInstance();
+ }
+}
diff --git a/tests/input/src/com/android/tv/testinput/TestTvInputService.java b/tests/input/src/com/android/tv/testinput/TestTvInputService.java
new file mode 100644
index 00000000..98ac9438
--- /dev/null
+++ b/tests/input/src/com/android/tv/testinput/TestTvInputService.java
@@ -0,0 +1,345 @@
+/*
+ * 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.testinput;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.media.PlaybackParams;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvInputService;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Surface;
+
+import com.android.tv.common.TvCommonConstants;
+import com.android.tv.testing.ChannelInfo;
+import com.android.tv.testing.testinput.ChannelState;
+
+import java.util.Date;
+
+/**
+ * Simple TV input service which provides test channels.
+ */
+public class TestTvInputService extends TvInputService {
+ private static final String TAG = "TestTvInputServices";
+ private static final int REFRESH_DELAY_MS = 1000 / 5;
+ private static final boolean DEBUG = false;
+ private TestInputControl mBackend = TestInputControl.getInstance();
+
+ public static String buildInputId(Context context) {
+ return TvContract.buildInputId(new ComponentName(context, TestTvInputService.class));
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mBackend.init(this, buildInputId(this));
+ }
+
+ @Override
+ public Session onCreateSession(String inputId) {
+ Log.v(TAG, "Creating session for " + inputId);
+ return new SimpleSessionImpl(this);
+ }
+
+ /**
+ * 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;
+
+ private final Paint mTextPaint = new Paint();
+ private final DrawRunnable mDrawRunnable = new DrawRunnable();
+ private Surface mSurface = null;
+ private ChannelInfo mChannel = null;
+ private ChannelState mCurrentState = null;
+ private String mCurrentVideoTrackId = null;
+ private String mCurrentAudioTrackId = null;
+
+ private long mRecordStartTimeMs;
+ private long mPausedTimeMs;
+ // The time in milliseconds when the current position is lastly updated.
+ private long mLastCurrentPositionUpdateTimeMs;
+ // The current playback position.
+ private long mCurrentPositionMs;
+ // 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;
+ }
+ sendEmptyMessageDelayed(MSG_SEEK, SEEK_DELAY_MS);
+ }
+ super.handleMessage(msg);
+ }
+ };
+
+ SimpleSessionImpl(Context context) {
+ super(context);
+ mTextPaint.setColor(Color.BLACK);
+ mTextPaint.setTextSize(150);
+ mHandler.post(mDrawRunnable);
+ if (DEBUG) {
+ Log.v(TAG, "Created session " + this);
+ }
+ }
+
+ private void setAudioTrack(String selectedAudioTrackId) {
+ Log.i(TAG, "Set audio track to " + selectedAudioTrackId);
+ mCurrentAudioTrackId = selectedAudioTrackId;
+ notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, mCurrentAudioTrackId);
+ }
+
+ private void setVideoTrack(String selectedVideoTrackId) {
+ Log.i(TAG, "Set video track to " + selectedVideoTrackId);
+ mCurrentVideoTrackId = selectedVideoTrackId;
+ notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, mCurrentVideoTrackId);
+ }
+
+ @Override
+ public void onRelease() {
+ if (DEBUG) {
+ Log.v(TAG, "Releasing session " + this);
+ }
+ mDrawRunnable.cancel();
+ mHandler.removeCallbacks(mDrawRunnable);
+ mSurface = null;
+ mChannel = null;
+ mCurrentState = null;
+ }
+
+ @Override
+ public boolean onSetSurface(Surface surface) {
+ synchronized (mDrawRunnable) {
+ mSurface = surface;
+ }
+ if (surface != null) {
+ if (DEBUG) {
+ Log.v(TAG, "Surface set");
+ }
+ } else {
+ if (DEBUG) {
+ Log.v(TAG, "Surface unset");
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onSurfaceChanged(int format, int width, int height) {
+ super.onSurfaceChanged(format, width, height);
+ Log.d(TAG, "format=" + format + " width=" + width + " height=" + height);
+ }
+
+ @Override
+ public void onSetStreamVolume(float volume) {
+ // No-op
+ }
+
+ @Override
+ public boolean onTune(Uri channelUri) {
+ Log.i(TAG, "Tune to " + channelUri);
+ ChannelInfo info = mBackend.getChannelInfo(channelUri);
+ synchronized (mDrawRunnable) {
+ if (info == null || mChannel == null
+ || mChannel.originalNetworkId != info.originalNetworkId) {
+ mCurrentState = null;
+ }
+ mChannel = info;
+ mCurrentVideoTrackId = null;
+ mCurrentAudioTrackId = null;
+ }
+ if (mChannel == null) {
+ Log.i(TAG, "Channel not found for " + channelUri);
+ notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
+ } else {
+ Log.i(TAG, "Tuning to " + mChannel);
+ }
+ if (TvCommonConstants.HAS_TIME_SHIFT_API) {
+ notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_AVAILABLE);
+ mRecordStartTimeMs = mCurrentPositionMs = mLastCurrentPositionUpdateTimeMs
+ = System.currentTimeMillis();
+ mPausedTimeMs = 0;
+ mHandler.sendEmptyMessageDelayed(MSG_SEEK, SEEK_DELAY_MS);
+ mSpeed = 1;
+ }
+ return true;
+ }
+
+ @Override
+ public void onSetCaptionEnabled(boolean enabled) {
+ // No-op
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ Log.d(TAG, "onKeyDown (keyCode=" + keyCode + ", event=" + event + ")");
+ return true;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ Log.d(TAG, "onKeyUp (keyCode=" + keyCode + ", event=" + event + ")");
+ return true;
+ }
+
+ @Override
+ public long onTimeShiftGetCurrentPosition() {
+ Log.d(TAG, "currentPositionMs=" + mCurrentPositionMs);
+ return mCurrentPositionMs;
+ }
+
+ @Override
+ public long onTimeShiftGetStartPosition() {
+ return mRecordStartTimeMs;
+ }
+
+ @Override
+ public void onTimeShiftPause() {
+ mCurrentPositionMs = mPausedTimeMs = mLastCurrentPositionUpdateTimeMs
+ = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onTimeShiftResume() {
+ mSpeed = 1;
+ mPausedTimeMs = 0;
+ mLastCurrentPositionUpdateTimeMs = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onTimeShiftSeekTo(long timeMs) {
+ mLastCurrentPositionUpdateTimeMs = System.currentTimeMillis();
+ mCurrentPositionMs = Math.max(mRecordStartTimeMs,
+ Math.min(timeMs, mLastCurrentPositionUpdateTimeMs));
+ }
+
+ @Override
+ public void onTimeShiftSetPlaybackParams(PlaybackParams params) {
+ mSpeed = params.getSpeed();
+ }
+
+ private final class DrawRunnable implements Runnable {
+ private volatile boolean mIsCanceled = false;
+
+ @Override
+ public void run() {
+ if (mIsCanceled) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Draw task running");
+ }
+ boolean updatedState = false;
+ ChannelState oldState;
+ ChannelState newState = null;
+ Surface currentSurface;
+ ChannelInfo currentChannel;
+
+ synchronized (this) {
+ oldState = mCurrentState;
+ currentSurface = mSurface;
+ currentChannel = mChannel;
+ if (currentChannel != null) {
+ newState = mBackend.getChannelState(currentChannel.originalNetworkId);
+ if (oldState == null || newState.getVersion() > oldState.getVersion()) {
+ mCurrentState = newState;
+ updatedState = true;
+ }
+ } else {
+ mCurrentState = null;
+ }
+ }
+
+ draw(currentSurface, currentChannel);
+ if (updatedState) {
+ update(oldState, newState, currentChannel);
+ }
+
+ if (!mIsCanceled) {
+ mHandler.postDelayed(this, REFRESH_DELAY_MS);
+ }
+ }
+
+ 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.
+ notifyContentAllowed();
+ setAudioTrack(newState.getSelectedAudioTrackId());
+ setVideoTrack(newState.getSelectedVideoTrackId());
+ } else {
+ notifyVideoUnavailable(newState.getTuneStatus());
+ }
+ }
+ }
+
+ private void draw(Surface surface, ChannelInfo currentChannel) {
+ if (surface != null) {
+ String now = TvCommonConstants.HAS_TIME_SHIFT_API
+ ? new Date(mCurrentPositionMs).toString() : new Date().toString();
+ String name = currentChannel == null ? "Null" : currentChannel.name;
+ Canvas c = surface.lockCanvas(null);
+ c.drawColor(0xFF888888);
+ c.drawText(name, 100f, 200f, mTextPaint);
+ c.drawText(now, 100f, 400f, mTextPaint);
+ surface.unlockCanvasAndPost(c);
+ if (DEBUG) {
+ Log.v(TAG, "Post to canvas");
+ }
+ } else {
+ if (DEBUG) {
+ Log.v(TAG, "No surface");
+ }
+ }
+ }
+
+ public void cancel() {
+ mIsCanceled = true;
+ }
+ }
+ }
+}
diff --git a/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java b/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java
new file mode 100644
index 00000000..732972cc
--- /dev/null
+++ b/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java
@@ -0,0 +1,119 @@
+/*
+ * 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.testinput;
+
+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;
+
+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}.
+ */
+public class TestTvInputSetupActivity extends Activity {
+ private static final String TAG = "TestTvInputSetup";
+ private String mInputId;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mInputId = getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
+
+ DialogFragment newFragment = new MyAlertDialogFragment();
+ newFragment.show(getFragmentManager(), "dialog");
+ }
+
+ private void registerChannels(int channelCount) {
+ TestTvInputSetupActivity context = this;
+ registerChannels(context, mInputId, false, channelCount);
+ }
+
+ public static void registerChannels(Context context, String inputId, boolean updateBrowsable,
+ int channelCount) {
+ Log.i(TAG, "Registering " + channelCount + " channels");
+ List<ChannelInfo> 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<Long, ChannelInfo> 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) {
+ 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,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ getActivity().finish();
+ }
+ }).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
new file mode 100644
index 00000000..379bce86
--- /dev/null
+++ b/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java
@@ -0,0 +1,134 @@
+/*
+ * 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.testinput.instrument;
+
+import android.app.Activity;
+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.
+ *
+ * <p><pre>{@code
+ * adb shell am instrument \
+ * -e testSetupMode {func,jank,unit} \
+ * -w com.android.tv.testinput/.instrument.TestSetupInstrumentation
+ * }</pre>
+ *
+ * <p>Optional arguments are:
+ * <pre>
+ * -e channelCount number
+ * </pre>
+ */
+public class TestSetupInstrumentation extends Instrumentation {
+ private static final String TAG = "TestSetupInstrument";
+ private static final String TEST_SETUP_MODE_ARG = "testSetupMode";
+ private static final String CHANNEL_COUNT_ARG = "channelCount";
+ private Bundle mArguments;
+ private String mInputId;
+
+ /**
+ * Fails an instrumentation request.
+ *
+ * @param errMsg an error message
+ */
+ protected void fail(String errMsg) {
+ Log.e(TAG, errMsg);
+ Bundle result = new Bundle();
+ result.putString("error", errMsg);
+ finish(Activity.RESULT_CANCELED, result);
+ }
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ super.onCreate(arguments);
+ mArguments = arguments;
+ start();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ try {
+ mInputId = TestTvInputService.buildInputId(getContext());
+ setup();
+ finish(Activity.RESULT_OK, new Bundle());
+ } catch (TestSetupException e) {
+ fail(e.getMessage());
+ }
+ }
+
+ 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");
+ } 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);
+ break;
+ case "jank":
+ channelCount = getArgumentAsInt(CHANNEL_COUNT_ARG,
+ Constants.JANK_TEST_CHANNEL_COUNT);
+ break;
+ case "unit":
+ channelCount = getArgumentAsInt(CHANNEL_COUNT_ARG,
+ Constants.UNIT_TEST_CHANNEL_COUNT);
+ break;
+ default:
+ throw new TestSetupException(
+ "Unknown " + TEST_SETUP_MODE_ARG + " of " + testSetupMode);
+ }
+ TestTvInputSetupActivity.registerChannels(getContext(), mInputId, true, channelCount);
+ }
+ }
+
+ private int getArgumentAsInt(String arg, int defaultValue) {
+ String stringValue = mArguments.getString(arg);
+ if (stringValue != null) {
+ try {
+ return Integer.parseInt(stringValue);
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Unable to parse arg " + arg + " with value " + stringValue
+ + " to a integer.", e);
+ }
+ }
+ return defaultValue;
+ }
+
+ static class TestSetupException extends Exception {
+ public TestSetupException(String msg) {
+ super(msg);
+ }
+
+ public static TestSetupException fromMissingArg(String arg) {
+ return new TestSetupException(
+ String.format("Error: missing mandatory argument '%s'", arg));
+ }
+ }
+}
diff --git a/tests/jank/Android.mk b/tests/jank/Android.mk
new file mode 100644
index 00000000..28f8ddf0
--- /dev/null
+++ b/tests/jank/Android.mk
@@ -0,0 +1,22 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := TVJankTests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ tv-test-common \
+ ub-janktesthelper \
+ ub-uiautomator \
+
+LOCAL_INSTRUMENTATION_FOR := TV
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/tests/jank/AndroidManifest.xml b/tests/jank/AndroidManifest.xml
new file mode 100644
index 00000000..fa09917d
--- /dev/null
+++ b/tests/jank/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tv.tests.jank" >
+
+ <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21" />
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="Live Channel Jank Tests"
+ android:targetPackage="com.android.tv" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+</manifest>
diff --git a/tests/jank/src/com/android/tv/tests/jank/ProgramGuideJankTest.java b/tests/jank/src/com/android/tv/tests/jank/ProgramGuideJankTest.java
new file mode 100644
index 00000000..096fb727
--- /dev/null
+++ b/tests/jank/src/com/android/tv/tests/jank/ProgramGuideJankTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.jank;
+
+import static com.android.tv.testing.uihelper.UiDeviceAsserts.assertWaitForCondition;
+
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.support.test.jank.JankTest;
+import android.support.test.jank.JankTestBase;
+import android.support.test.jank.WindowContentFrameStatsMonitor;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+import com.android.tv.R;
+import com.android.tv.common.TvCommonConstants;
+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";
+
+ private static final String STARTING_CHANNEL = "13";
+ private static final int EXPECTED_FRAMES = 5;
+
+ 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();
+ pressKeysForChannelNumber(STARTING_CHANNEL);
+ }
+
+ @JankTest(expectedFrames = 7,
+ beforeTest = "warmProgramGuide",
+ beforeLoop = "selectProgramGuideMenuItem",
+ afterLoop = "clearProgramGuide")
+ @WindowContentFrameStatsMonitor
+ public void testShowProgramGuide() {
+ mDevice.pressDPadCenter();
+
+ // Full show has two animations.
+ long delay = mTargetResources.getInteger(R.integer.program_guide_anim_duration) * 2;
+ waitForIdleAtLeast(delay);
+ }
+
+ @JankTest(expectedFrames = EXPECTED_FRAMES,
+ beforeLoop = "showProgramGuide")
+ @WindowContentFrameStatsMonitor
+ public void testClearProgramGuide() {
+ mDevice.pressBack();
+ // Full show has two animations.
+ waitForIdleAtLeast(mTargetResources.getInteger(R.integer.program_guide_anim_duration) * 2);
+ }
+
+ @JankTest(expectedFrames = EXPECTED_FRAMES,
+ beforeLoop = "showProgramGuide",
+ afterLoop = "clearProgramGuide")
+ @WindowContentFrameStatsMonitor
+ public void testScrollDown() {
+ mDevice.pressDPadDown();
+ waitForIdleAtLeast(mTargetResources
+ .getInteger(R.integer.program_guide_table_detail_toggle_anim_duration));
+ }
+
+ @JankTest(expectedFrames = EXPECTED_FRAMES,
+ beforeLoop = "showProgramGuide",
+ afterLoop = "clearProgramGuide")
+ @WindowContentFrameStatsMonitor
+ public void testScrollRight() {
+ mDevice.pressDPadRight();
+ waitForIdleAtLeast(mTargetResources
+ .getInteger(R.integer.program_guide_table_detail_toggle_anim_duration));
+ }
+
+ /**
+ * {@link UiDevice#waitForIdle() Wait for idle} , then sleep if needed, then wait for idle
+ * again.
+ *
+ * @param delayInMillis The minimum amount of time to delay. This is usually the expected
+ * duration of the animation.
+ */
+ private void waitForIdleAtLeast(long delayInMillis) {
+
+ // This seems to give the most reliable numbers.
+ // The first wait until idle usually returned in 1ms.
+ // Sometimes it would take the whole duration. If we sleep after that we get bad fps
+ // because nothing is happening after the idle ends.
+ //
+ // So sleeping only for the remaining about ensure there is at least enough time for the
+ // animation to complete. If we sleep then wait for idle again. This will usually allow
+ // the animation to complete.
+
+ long startTime = SystemClock.uptimeMillis();
+ mDevice.waitForIdle();
+
+ long idle = SystemClock.uptimeMillis() - startTime;
+ if (DEBUG) {
+ Log.d(TAG, "Waited for idle " + (idle) / 1000.0 + " sec");
+ }
+ if (idle < delayInMillis) {
+ long more = delayInMillis - idle;
+ SystemClock.sleep(more);
+ Log.d(TAG, "Slept " + (more) / 1000.0 + " sec");
+ mDevice.waitForIdle();
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Total wait " + (SystemClock.uptimeMillis() - startTime) / 1000.0 + " sec");
+ }
+ }
+
+ //TODO: move to a mixin/helper
+ protected void pressKeysForChannelNumber(String channel) {
+ UiDeviceUtils.pressKeys(mDevice, channel);
+ mDevice.pressDPadCenter();
+ }
+
+ public void selectProgramGuideMenuItem() {
+ mMenuHelper.showMenu();
+ int rowTitleResId = TvCommonConstants.IS_MNC_OR_HIGHER ? R.string.menu_title_channels
+ : R.string.menu_title_channels_legacy;
+ mMenuHelper.assertNavigateToMenuItem(rowTitleResId, R.string.channels_item_program_guide);
+ mDevice.waitForIdle();
+ }
+
+ public void warmProgramGuide() {
+ // TODO: b/21078199 First time Program Guide is opened there is a noticeable delay
+ selectProgramGuideMenuItem();
+ mDevice.pressDPadCenter();
+ assertWaitForCondition(mDevice, Until.hasObject(Constants.PROGRAM_GUIDE));
+ mDevice.pressBack();
+
+ }
+
+ public void clearProgramGuide() {
+ mDevice.pressBack();
+ assertWaitForCondition(mDevice, Until.gone(Constants.PROGRAM_GUIDE));
+ }
+
+ public void showProgramGuide() {
+ 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))) {
+ mDevice.pressDPadRight();
+ }
+ }
+}
diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk
new file mode 100644
index 00000000..1cd4f153
--- /dev/null
+++ b/tests/unit/Android.mk
@@ -0,0 +1,21 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ mockito-target \
+ tv-test-common \
+
+LOCAL_PACKAGE_NAME := TVUnitTests
+
+LOCAL_INSTRUMENTATION_FOR := TV
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml
new file mode 100644
index 00000000..aebb3772
--- /dev/null
+++ b/tests/unit/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tv.tests" >
+
+ <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21" />
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="Live Channel Unit Tests"
+ android:targetPackage="com.android.tv" />
+
+ <application android:label="TVTest" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+</manifest>
diff --git a/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java b/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java
new file mode 100644
index 00000000..99d5ea3e
--- /dev/null
+++ b/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tv;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.test.ActivityInstrumentationTestCase2;
+
+import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.testing.ChannelInfo;
+import com.android.tv.testing.testinput.ChannelStateData;
+import com.android.tv.testing.testinput.TestInputControlConnection;
+import com.android.tv.testing.testinput.TestInputControlUtils;
+
+import java.util.List;
+
+/**
+ * Base TestCase for tests that need a {@link MainActivity}.
+ */
+public abstract class BaseMainActivityTestCase
+ extends ActivityInstrumentationTestCase2<MainActivity> {
+ private static final String TAG = "BaseMainActivityTest";
+ private static final int CHANNEL_LOADING_CHECK_INTERVAL_MS = 10;
+
+ protected final TestInputControlConnection mConnection = new TestInputControlConnection();
+
+ protected MainActivity mActivity;
+
+ public BaseMainActivityTestCase(Class<MainActivity> activityClass) {
+ super(activityClass);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // TODO: ensure the SampleInputs are setup.
+ setActivityInitialTouchMode(false);
+ mActivity = getActivity();
+ getInstrumentation().getContext()
+ .bindService(TestInputControlUtils.createIntent(), mConnection,
+ Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mConnection.isBound()) {
+ getInstrumentation().getContext().unbindService(mConnection);
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Tune to {@code channel}.
+ *
+ * @param channel the channel to tune to.
+ */
+ 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);
+ }
+ }
+
+ /**
+ * Sleep until @{@link ChannelDataManager#isDbLoadFinished()} is true.
+ */
+ protected void waitUntilChannelLoadingFinish() {
+ ChannelDataManager channelDataManager = mActivity.getChannelDataManager();
+ while (!channelDataManager.isDbLoadFinished()) {
+ getInstrumentation().waitForIdleSync();
+ SystemClock.sleep(CHANNEL_LOADING_CHECK_INTERVAL_MS);
+ }
+ }
+
+ /**
+ * Tune to the channel with {@code name}.
+ *
+ * @param name the name of the channel to find.
+ */
+ protected void tuneToChannel(String name) {
+ Channel c = findChannelWithName(name);
+ tuneToChannel(c);
+ }
+
+ /**
+ * Tune to channel.
+ */
+ protected void tuneToChannel(ChannelInfo channel) {
+ tuneToChannel(channel.name);
+ }
+
+ /**
+ * Update the channel state to {@code data} then tune to that channel.
+ *
+ * @param data the state to update the channel with.
+ * @param channel the channel to tune to
+ */
+ protected void updateThenTune(ChannelStateData data, ChannelInfo channel) {
+ mConnection.updateChannelState(channel, data);
+ tuneToChannel(channel);
+ }
+
+ private Channel findChannelWithName(String displayName) {
+ waitUntilChannelLoadingFinish();
+ List<Channel> channelList = mActivity.getChannelDataManager().getChannelList();
+ for (Channel c : channelList) {
+ if (c.getDisplayName().equals(displayName)) {
+ return c;
+ }
+ }
+ throw new AssertionError("'" + displayName + "' channel not found");
+ }
+
+}
diff --git a/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java b/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.java
new file mode 100644
index 00000000..6e01f423
--- /dev/null
+++ b/tests/unit/src/com/android/tv/CurrentPositionMediatorTest.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;
+
+import static com.android.tv.TimeShiftManager.INVALID_TIME;
+import static com.android.tv.TimeShiftManager.REQUEST_TIMEOUT_MS;
+
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.MediumTest;
+
+@MediumTest
+public class CurrentPositionMediatorTest extends BaseMainActivityTestCase {
+ private TimeShiftManager.CurrentPositionMediator mMediator;
+
+ public CurrentPositionMediatorTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mMediator = mActivity.getTimeShiftManager().mCurrentPositionMediator;
+ }
+
+ @UiThreadTest
+ public void testInitialize() throws Throwable {
+ long currentTimeMs = System.currentTimeMillis();
+ mMediator.initialize(currentTimeMs);
+ assertCurrentPositionMediator(INVALID_TIME, currentTimeMs);
+ }
+
+ @UiThreadTest
+ public void testOnSeekRequested() throws Throwable {
+ long seekToTimeMs = System.currentTimeMillis() - REQUEST_TIMEOUT_MS * 3;
+ mMediator.onSeekRequested(seekToTimeMs);
+ assertNotSame("Seek request time", INVALID_TIME, mMediator.mSeekRequestTimeMs);
+ assertEquals("Current position", seekToTimeMs, mMediator.mCurrentPositionMs);
+ }
+
+ @UiThreadTest
+ public void testOnCurrentPositionChangedInvalidInput() throws Throwable {
+ long seekToTimeMs = System.currentTimeMillis() - REQUEST_TIMEOUT_MS * 3;
+ long newCurrentTimeMs = seekToTimeMs + REQUEST_TIMEOUT_MS;
+ mMediator.onSeekRequested(seekToTimeMs);
+ mMediator.onCurrentPositionChanged(newCurrentTimeMs);
+ assertNotSame("Seek request time", INVALID_TIME, mMediator.mSeekRequestTimeMs);
+ assertNotSame("Current position", seekToTimeMs, mMediator.mCurrentPositionMs);
+ assertNotSame("Current position", newCurrentTimeMs, mMediator.mCurrentPositionMs);
+ }
+
+ @UiThreadTest
+ public void testOnCurrentPositionChangedValidInput() throws Throwable {
+ long seekToTimeMs = System.currentTimeMillis() - REQUEST_TIMEOUT_MS * 3;
+ long newCurrentTimeMs = seekToTimeMs + REQUEST_TIMEOUT_MS - 1;
+ mMediator.onSeekRequested(seekToTimeMs);
+ mMediator.onCurrentPositionChanged(newCurrentTimeMs);
+ assertCurrentPositionMediator(INVALID_TIME, newCurrentTimeMs);
+ }
+
+ 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/MainActivityTest.java b/tests/unit/src/com/android/tv/MainActivityTest.java
new file mode 100644
index 00000000..ae789895
--- /dev/null
+++ b/tests/unit/src/com/android/tv/MainActivityTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.test.suitebuilder.annotation.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 java.util.List;
+
+/**
+ * Tests for {@link MainActivity}.
+ */
+@MediumTest
+public class MainActivityTest extends BaseMainActivityTestCase {
+
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ public void testInitialConditions() {
+ waitUntilChannelLoadingFinish();
+ List<Channel> channelList = mActivity.getChannelDataManager().getChannelList();
+ assertTrue("Expected at least one channel", channelList.size() > 0);
+ assertFalse("PIP disabled", mActivity.isPipEnabled());
+ }
+
+ public void testTuneToChannel() throws Throwable {
+ tuneToChannel(TvTestInputConstants.CH_2);
+ assertChannelBannerShown(true);
+ assertChannelName(TvTestInputConstants.CH_2.name);
+ }
+
+ public void testShowProgramGuide() throws Throwable {
+ tuneToChannel(TvTestInputConstants.CH_2);
+ showProgramGuide();
+ assertChannelBannerShown(false);
+ assertProgramGuide(true);
+ }
+
+ private void showProgramGuide() throws Throwable {
+ // Run on UI thread so views can be modified
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getOverlayManager().showProgramGuide();
+ }
+ });
+ }
+
+ private void assertChannelName(String displayName) {
+ TextView channelNameView = (TextView) mActivity.findViewById(R.id.channel_name);
+ assertEquals("Channel Name", displayName, channelNameView.getText());
+ }
+
+ private View assertProgramGuide(boolean isShown) {
+ return assertViewIsShown("Program Guide", R.id.program_guide, isShown);
+ }
+
+ private ChannelBannerView assertChannelBannerShown(boolean isShown) {
+ View v = assertExpectedBannerSceneClassShown(ChannelBannerView.class, isShown);
+ return (ChannelBannerView) v;
+ }
+
+ private View assertExpectedBannerSceneClassShown(Class<ChannelBannerView> expectedClass,
+ boolean expectedShown) throws AssertionError {
+ View v = assertViewIsShown(expectedClass.getSimpleName(), R.id.scene_transition_common,
+ expectedShown);
+ if (v != null) {
+ assertEquals(expectedClass, v.getClass());
+ }
+ return v;
+ }
+
+ private View assertViewIsShown(String viewName, int viewId, boolean expected)
+ throws AssertionError {
+ View view = mActivity.findViewById(viewId);
+ if (view == null) {
+ if (expected) {
+ throw new AssertionError("View " + viewName + " not found");
+ } else {
+ return null;
+ }
+ }
+ 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
new file mode 100644
index 00000000..878d4293
--- /dev/null
+++ b/tests/unit/src/com/android/tv/TimeShiftManagerTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD;
+import static com.android.tv.TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT;
+import static com.android.tv.TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS;
+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 android.test.suitebuilder.annotation.MediumTest;
+
+@MediumTest
+public class TimeShiftManagerTest extends BaseMainActivityTestCase {
+ private TimeShiftManager mTimeShiftManager;
+
+ public TimeShiftManagerTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTimeShiftManager = mActivity.getTimeShiftManager();
+ }
+
+ public void testDisableActions() {
+ enableAllActions(true);
+ assertActionState(true, true, true, true, true, true);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_PLAY, false);
+ assertActionState(false, true, true, true, true, true);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_PAUSE, false);
+ assertActionState(false, false, true, true, true, true);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_REWIND, false);
+ assertActionState(false, false, false, true, true, true);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_FAST_FORWARD, false);
+ assertActionState(false, false, false, false, true, true);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, false);
+ assertActionState(false, false, false, false, false, true);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT, false);
+ assertActionState(false, false, false, false, false, false);
+ }
+
+ public void testEnableActions() {
+ enableAllActions(false);
+ assertActionState(false, false, false, false, false, false);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_PLAY, true);
+ assertActionState(true, false, false, false, false, false);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_PAUSE, true);
+ assertActionState(true, true, false, false, false, false);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_REWIND, true);
+ assertActionState(true, true, true, false, false, false);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_FAST_FORWARD, true);
+ assertActionState(true, true, true, true, false, false);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, true);
+ assertActionState(true, true, true, true, true, false);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT, true);
+ assertActionState(true, true, true, true, true, true);
+ }
+
+ private void enableAllActions(boolean enabled) {
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_PLAY, enabled);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_PAUSE, enabled);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_REWIND, enabled);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_FAST_FORWARD, enabled);
+ mTimeShiftManager.enableAction(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS, enabled);
+ 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,
+ mTimeShiftManager.isActionEnabled(TIME_SHIFT_ACTION_ID_PLAY));
+ assertEquals("Pause Action", pauseEnabled,
+ mTimeShiftManager.isActionEnabled(TIME_SHIFT_ACTION_ID_PAUSE));
+ assertEquals("Rewind Action", rewindEnabled,
+ mTimeShiftManager.isActionEnabled(TIME_SHIFT_ACTION_ID_REWIND));
+ assertEquals("Fast Forward Action", fastForwardEnabled,
+ mTimeShiftManager.isActionEnabled(TIME_SHIFT_ACTION_ID_FAST_FORWARD));
+ assertEquals("Jump To Previous Action", jumpToPreviousEnabled,
+ mTimeShiftManager.isActionEnabled(TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS));
+ 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
new file mode 100644
index 00000000..38ccdfb6
--- /dev/null
+++ b/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java
@@ -0,0 +1,646 @@
+/*
+ * 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.data;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.media.tv.TvContract.Channels;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockCursor;
+import android.test.suitebuilder.annotation.SmallTest;
+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.mockito.Matchers;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test for {@link com.android.tv.data.ChannelDataManager}
+ *
+ * A test method may include tests for multiple methods to minimize the DB access.
+ */
+@SmallTest
+public class ChannelDataManagerTest extends AndroidTestCase {
+ 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";
+
+ private ChannelDataManager mChannelDataManager;
+ private HandlerThread mHandlerThread;
+ private TestChannelDataManagerListener mListener;
+ private FakeContentResolver mContentResolver;
+ private FakeContentProvider mContentProvider;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ assertTrue("More than 2 channels to test", Constants.UNIT_TEST_CHANNEL_COUNT > 2);
+ TvInputManagerHelper mockHelper = Mockito.mock(TvInputManagerHelper.class);
+ Mockito.when(mockHelper.hasTvInputInfo(Matchers.anyString())).thenReturn(true);
+
+ mContentProvider = new FakeContentProvider(getContext());
+ mContentResolver = new FakeContentResolver();
+ mContentResolver.addProvider(TvContract.AUTHORITY, mContentProvider);
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mChannelDataManager = new ChannelDataManager(
+ getContext(), mockHelper, mContentResolver, mHandlerThread.getLooper());
+ mListener = new TestChannelDataManagerListener();
+ mChannelDataManager.addListener(mListener);
+
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mHandlerThread.quitSafely();
+ mChannelDataManager.stop();
+ }
+
+ private void startAndWaitForComplete() throws Exception {
+ mChannelDataManager.start();
+ try {
+ assertTrue(mListener.loadFinishedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ throw e;
+ }
+ }
+
+ private void restart() throws Exception {
+ mChannelDataManager.stop();
+ mListener.reset();
+ startAndWaitForComplete();
+ }
+
+ public void testIsDbLoadFinished() throws Exception {
+ startAndWaitForComplete();
+ assertTrue(mChannelDataManager.isDbLoadFinished());
+ }
+
+ /**
+ * Test for following methods
+ * - {@link ChannelDataManager#getChannelCount}
+ * - {@link ChannelDataManager#getChannelList}
+ * - {@link ChannelDataManager#getChannel}
+ */
+ public void testGetChannels() throws Exception {
+ startAndWaitForComplete();
+
+ // Test {@link ChannelDataManager#getChannelCount}
+ assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT, mChannelDataManager.getChannelCount());
+
+ // Test {@link ChannelDataManager#getChannelList}
+ List<ChannelInfo> channelInfoList = new ArrayList<>();
+ for (int i = 1; i <= Constants.UNIT_TEST_CHANNEL_COUNT; i++) {
+ channelInfoList.add(ChannelInfo.create(getContext(), i));
+ }
+ List<Channel> channelList = mChannelDataManager.getChannelList();
+ for (Channel channel : channelList) {
+ boolean found = false;
+ for (ChannelInfo channelInfo : channelInfoList) {
+ if (TextUtils.equals(channelInfo.name, channel.getDisplayName())
+ && TextUtils.equals(channelInfo.name, channel.getDisplayName())) {
+ found = true;
+ channelInfoList.remove(channelInfo);
+ break;
+ }
+ }
+ assertTrue("Cannot find (" + channel + ")", found);
+ }
+
+ // Test {@link ChannelDataManager#getChannelIndex()}
+ for (Channel channel : channelList) {
+ assertEquals(channel, mChannelDataManager.getChannel(channel.getId()));
+ }
+ }
+
+ /**
+ * Test for {@link ChannelDataManager#getChannelCount} when no channel is available.
+ */
+ public void testGetChannels_noChannels() throws Exception {
+ mContentProvider.clear();
+ startAndWaitForComplete();
+ assertEquals(0, mChannelDataManager.getChannelCount());
+ }
+
+ /**
+ * Test for following methods and channel listener with notifying change.
+ * - {@link ChannelDataManager#updateBrowsable}
+ * - {@link ChannelDataManager#applyUpdatedValuesToDb}
+ */
+ public void testBrowsable() throws Exception {
+ startAndWaitForComplete();
+
+ // Test if all channels are browable
+ List<Channel> channelList = new ArrayList<>(mChannelDataManager.getChannelList());
+ List<Channel> browsableChannelList = mChannelDataManager.getBrowsableChannelList();
+ for (Channel browsableChannel : browsableChannelList) {
+ boolean found = channelList.remove(browsableChannel);
+ assertTrue("Cannot find (" + browsableChannel + ")", found);
+ }
+ assertEquals(0, channelList.size());
+
+ // Prepare for next tests.
+ TestChannelDataManagerChannelListener channelListener =
+ new TestChannelDataManagerChannelListener();
+ Channel channel1 = mChannelDataManager.getChannelList().get(0);
+ mChannelDataManager.addChannelListener(channel1.getId(), channelListener);
+
+ // Test {@link ChannelDataManager#updateBrowsable} & notification.
+ mChannelDataManager.updateBrowsable(channel1.getId(), false, false);
+ assertTrue(mListener.channelBrowsableChangedCalled);
+ assertFalse(mChannelDataManager.getBrowsableChannelList().contains(channel1));
+ MoreAsserts.assertContentsInAnyOrder(channelListener.updatedChannels, channel1);
+ channelListener.reset();
+
+ // Test {@link ChannelDataManager#applyUpdatedValuesToDb}
+ mChannelDataManager.applyUpdatedValuesToDb();
+ restart();
+ browsableChannelList = mChannelDataManager.getBrowsableChannelList();
+ assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT - 1, browsableChannelList.size());
+ assertFalse(browsableChannelList.contains(channel1));
+ }
+
+ /**
+ * Test for following methods and channel listener without notifying change.
+ * - {@link ChannelDataManager#updateBrowsable}
+ * - {@link ChannelDataManager#applyUpdatedValuesToDb}
+ */
+ public void testBrowsable_skipNotification() throws Exception {
+ startAndWaitForComplete();
+
+ // Prepare for next tests.
+ TestChannelDataManagerChannelListener channelListener =
+ new TestChannelDataManagerChannelListener();
+ Channel channel1 = mChannelDataManager.getChannelList().get(0);
+ Channel channel2 = mChannelDataManager.getChannelList().get(1);
+ mChannelDataManager.addChannelListener(channel1.getId(), channelListener);
+ mChannelDataManager.addChannelListener(channel2.getId(), channelListener);
+
+ // Test {@link ChannelDataManager#updateBrowsable} & skip notification.
+ mChannelDataManager.updateBrowsable(channel1.getId(), false, true);
+ mChannelDataManager.updateBrowsable(channel2.getId(), false, true);
+ mChannelDataManager.updateBrowsable(channel1.getId(), true, true);
+ assertFalse(mListener.channelBrowsableChangedCalled);
+ List<Channel> browsableChannelList = mChannelDataManager.getBrowsableChannelList();
+ assertTrue(browsableChannelList.contains(channel1));
+ assertFalse(browsableChannelList.contains(channel2));
+
+ // Test {@link ChannelDataManager#applyUpdatedValuesToDb}
+ mChannelDataManager.applyUpdatedValuesToDb();
+ restart();
+ browsableChannelList = mChannelDataManager.getBrowsableChannelList();
+ assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT - 1, browsableChannelList.size());
+ assertFalse(browsableChannelList.contains(channel2));
+ }
+
+ /**
+ * Test for following methods and channel listener.
+ * - {@link ChannelDataManager#updateLocked}
+ * - {@link ChannelDataManager#applyUpdatedValuesToDb}
+ */
+ public void testLocked() throws Exception {
+ startAndWaitForComplete();
+
+ // Test if all channels aren't locked at the first time.
+ List<Channel> channelList = mChannelDataManager.getChannelList();
+ for (Channel channel : channelList) {
+ assertFalse(channel + " is locked", channel.isLocked());
+ }
+
+ // Prepare for next tests.
+ Channel channel = mChannelDataManager.getChannelList().get(0);
+
+ // Test {@link ChannelDataManager#updateLocked}
+ mChannelDataManager.updateLocked(channel.getId(), true);
+ assertTrue(mChannelDataManager.getChannel(channel.getId()).isLocked());
+
+ // Test {@link ChannelDataManager#applyUpdatedValuesToDb}.
+ mChannelDataManager.applyUpdatedValuesToDb();
+ restart();
+ assertTrue(mChannelDataManager.getChannel(channel.getId()).isLocked());
+
+ // Cleanup
+ mChannelDataManager.updateLocked(channel.getId(), false);
+ }
+
+ /**
+ * Test ChannelDataManager when channels in TvContract are updated, removed, or added.
+ */
+ public void testChannelListChanged() throws Exception {
+ startAndWaitForComplete();
+
+ // Test channel add.
+ mListener.reset();
+ long testChannelId = Constants.UNIT_TEST_CHANNEL_COUNT + 1;
+ ChannelInfo testChannelInfo = ChannelInfo.create(getContext(), (int) testChannelId);
+ testChannelId = Constants.UNIT_TEST_CHANNEL_COUNT + 1;
+ mContentProvider.simulateInsert(testChannelInfo);
+ assertTrue(mListener.channeListUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT + 1, mChannelDataManager.getChannelCount());
+
+ // Test channel update
+ mListener.reset();
+ TestChannelDataManagerChannelListener channelListener =
+ new TestChannelDataManagerChannelListener();
+ mChannelDataManager.addChannelListener(testChannelId, channelListener);
+ String newName = testChannelInfo.name + "_test";
+ mContentProvider.simulateUpdate(testChannelId, newName);
+ assertTrue(mListener.channeListUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(channelListener.channelChangedLatch.await(
+ WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(0, channelListener.removedChannels.size());
+ assertEquals(1, channelListener.updatedChannels.size());
+ Channel updatedChannel = channelListener.updatedChannels.get(0);
+ assertEquals(testChannelId, updatedChannel.getId());
+ assertEquals(testChannelInfo.number, updatedChannel.getDisplayNumber());
+ assertEquals(newName, updatedChannel.getDisplayName());
+ assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT + 1,
+ mChannelDataManager.getChannelCount());
+
+ // Test channel remove.
+ mListener.reset();
+ channelListener.reset();
+ mContentProvider.simulateDelete(testChannelId);
+ assertTrue(mListener.channeListUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(channelListener.channelChangedLatch.await(
+ WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(1, channelListener.removedChannels.size());
+ assertEquals(0, channelListener.updatedChannels.size());
+ Channel removedChannel = channelListener.removedChannels.get(0);
+ assertEquals(newName, removedChannel.getDisplayName());
+ assertEquals(testChannelInfo.number, removedChannel.getDisplayNumber());
+ assertEquals(Constants.UNIT_TEST_CHANNEL_COUNT, mChannelDataManager.getChannelCount());
+ }
+
+ private class ChannelInfoWrapper {
+ public ChannelInfo channelInfo;
+ public boolean browsable;
+ public boolean locked;
+ public ChannelInfoWrapper(ChannelInfo channelInfo) {
+ this.channelInfo = channelInfo;
+ browsable = true;
+ locked = false;
+ }
+ }
+
+ private class FakeContentResolver extends MockContentResolver {
+ @Override
+ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+ super.notifyChange(uri, observer, syncToNetwork);
+ if (DEBUG) {
+ Log.d(TAG, "onChanged(uri=" + uri + ", observer=" + observer + ")");
+ }
+ // Do not call {@link ContentObserver#onChange} directly
+ // to run it on the {@link #mHandlerThread}.
+ if (observer != null) {
+ observer.dispatchChange(false, uri);
+ } else {
+ mChannelDataManager.getContentObserver().dispatchChange(false, uri);
+ }
+ }
+ }
+
+ // This implements the minimal methods in content resolver
+ // and detailed assumptions are written in each method.
+ private class FakeContentProvider extends MockContentProvider {
+ private SparseArray<ChannelInfoWrapper> mChannelInfoList = new SparseArray<>();
+
+ public FakeContentProvider(Context context) {
+ super(context);
+ for (int i = 1; i <= Constants.UNIT_TEST_CHANNEL_COUNT; i++) {
+ mChannelInfoList.put(i,
+ new ChannelInfoWrapper(ChannelInfo.create(getContext(), 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)
+ */
+ @Override
+ 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);
+ if (projection == null || projection.length == 0) {
+ Log.d(TAG, " projection=" + projection);
+ } else {
+ for (int i = 0; i < projection.length; i++) {
+ Log.d(TAG, " projection=" + projection[i]);
+ }
+ }
+ Log.d(TAG," selection=" + selection);
+ }
+ assertChannelUri(uri);
+ return new FakeCursor(projection);
+ }
+
+ /**
+ * 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) {
+ if (DEBUG) Log.d(TAG, "update(uri=" + uri + ", selection=" + selection);
+ assertChannelUri(uri);
+ List<Long> channelIds = new ArrayList<>();
+ try {
+ long channelId = ContentUris.parseId(uri);
+ channelIds.add(channelId);
+ } catch (NumberFormatException e) {
+ // Update for multiple channels.
+ if (TextUtils.isEmpty(selection)) {
+ for (int i = 0; i < mChannelInfoList.size(); i++) {
+ channelIds.add((long) mChannelInfoList.keyAt(i));
+ }
+ } else {
+ // See {@link Utils#buildSelectionForIds} for the syntax.
+ String selectionForId = selection.substring(
+ selection.indexOf("(") + 1, selection.lastIndexOf(")"));
+ String[] ids = selectionForId.split(", ");
+ if (ids != null) {
+ for (String id : ids) {
+ channelIds.add(Long.parseLong(id));
+ }
+ }
+ }
+ }
+ int updateCount = 0;
+ for (long channelId : channelIds) {
+ boolean updated = false;
+ ChannelInfoWrapper channel = mChannelInfoList.get((int) channelId);
+ if (channel == null) {
+ return 0;
+ }
+ if (values.containsKey(COLUMN_BROWSABLE)) {
+ updated = true;
+ channel.browsable = (values.getAsInteger(COLUMN_BROWSABLE) == 1);
+ }
+ if (values.containsKey(COLUMN_LOCKED)) {
+ updated = true;
+ channel.locked = (values.getAsInteger(COLUMN_LOCKED) == 1);
+ }
+ updateCount += updated ? 1 : 0;
+ }
+ if (updateCount > 0) {
+ if (channelIds.size() == 1) {
+ mContentResolver.notifyChange(uri, null);
+ } else {
+ mContentResolver.notifyChange(Channels.CONTENT_URI, null);
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Update to channel(uri=" + uri + ") is ignored for " + values);
+ }
+ }
+ return updateCount;
+ }
+
+ /**
+ * 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(getContext(), (int) channelId)));
+ mContentResolver.notifyChange(TvContract.buildChannelUri(channelId), null);
+ }
+
+ /**
+ * Simulates channel data delete.
+ */
+ public void simulateDelete(long channelId) {
+ mChannelInfoList.remove((int) channelId);
+ mContentResolver.notifyChange(TvContract.buildChannelUri(channelId), null);
+ }
+
+ /**
+ * 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);
+ builder.setName(newName);
+ channel.channelInfo = builder.build();
+ mContentResolver.notifyChange(TvContract.buildChannelUri(channelId), null);
+ }
+
+ private void assertChannelUri(Uri uri) {
+ assertTrue("Uri(" + uri + ") isn't channel uri",
+ uri.toString().startsWith(Channels.CONTENT_URI.toString()));
+ }
+
+ public void clear() {
+ mChannelInfoList.clear();
+ }
+
+ public ChannelInfoWrapper get(int position) {
+ return mChannelInfoList.get(mChannelInfoList.keyAt(position));
+ }
+
+ public int getCount() {
+ return mChannelInfoList.size();
+ }
+
+ public long keyAt(int position) {
+ return mChannelInfoList.keyAt(position);
+ }
+ }
+
+ private class FakeCursor extends MockCursor {
+ private 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 String[] mColumns;
+ private int mPosition;
+
+ public FakeCursor(String[] columns) {
+ mColumns = (columns == null) ? ALL_COLUMNS : columns;
+ mPosition = -1;
+ }
+
+ @Override
+ public String getColumnName(int columnIndex) {
+ return mColumns[columnIndex];
+ }
+
+ @Override
+ public int getColumnIndex(String columnName) {
+ for (int i = 0; i < mColumns.length; i++) {
+ if (mColumns[i].equalsIgnoreCase(columnName)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public long getLong(int columnIndex) {
+ String columnName = getColumnName(columnIndex);
+ switch (columnName) {
+ case Channels._ID:
+ return mContentProvider.keyAt(mPosition);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Column (" + columnName + ") is ignored in getLong()");
+ }
+ return 0;
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ String columnName = getColumnName(columnIndex);
+ ChannelInfoWrapper channel = mContentProvider.get(mPosition);
+ switch (columnName) {
+ case Channels.COLUMN_DISPLAY_NAME:
+ return channel.channelInfo.name;
+ case Channels.COLUMN_DISPLAY_NUMBER:
+ return channel.channelInfo.number;
+ case Channels.COLUMN_INPUT_ID:
+ return DUMMY_INPUT_ID;
+ case Channels.COLUMN_VIDEO_FORMAT:
+ return channel.channelInfo.getVideoFormat();
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Column (" + columnName + ") is ignored in getString()");
+ }
+ return null;
+ }
+
+ @Override
+ public int getInt(int columnIndex) {
+ String columnName = getColumnName(columnIndex);
+ ChannelInfoWrapper channel = mContentProvider.get(mPosition);
+ switch (columnName) {
+ case Channels.COLUMN_ORIGINAL_NETWORK_ID:
+ return channel.channelInfo.originalNetworkId;
+ case COLUMN_BROWSABLE:
+ return channel.browsable ? 1 : 0;
+ case COLUMN_LOCKED:
+ return channel.locked ? 1 : 0;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Column (" + columnName + ") is ignored in getInt()");
+ }
+ return 0;
+ }
+
+ @Override
+ public int getCount() {
+ return mContentProvider.getCount();
+ }
+
+ @Override
+ public boolean moveToNext() {
+ return ++mPosition < mContentProvider.getCount();
+ }
+
+ @Override
+ public void close() {
+ // No-op.
+ }
+ }
+
+ private class TestChannelDataManagerListener implements ChannelDataManager.Listener {
+ public CountDownLatch loadFinishedLatch = new CountDownLatch(1);
+ public CountDownLatch channeListUpdatedLatch = new CountDownLatch(1);
+ public boolean channelBrowsableChangedCalled;
+
+ @Override
+ public void onLoadFinished() {
+ loadFinishedLatch.countDown();
+ }
+
+ @Override
+ public void onChannelListUpdated() {
+ channeListUpdatedLatch.countDown();
+ }
+
+ @Override
+ public void onChannelBrowsableChanged() {
+ channelBrowsableChangedCalled = true;
+ }
+
+ public void reset() {
+ loadFinishedLatch = new CountDownLatch(1);
+ channeListUpdatedLatch = new CountDownLatch(1);
+ channelBrowsableChangedCalled = false;
+ }
+ }
+
+ private class TestChannelDataManagerChannelListener
+ implements ChannelDataManager.ChannelListener {
+ public CountDownLatch channelChangedLatch = new CountDownLatch(1);
+ public List<Channel> removedChannels = new ArrayList<>();
+ public List<Channel> updatedChannels = new ArrayList<>();
+
+ @Override
+ public void onChannelRemoved(Channel channel) {
+ removedChannels.add(channel);
+ channelChangedLatch.countDown();
+ }
+
+ @Override
+ public void onChannelUpdated(Channel channel) {
+ updatedChannels.add(channel);
+ channelChangedLatch.countDown();
+ }
+
+ public void reset() {
+ channelChangedLatch = new CountDownLatch(1);
+ removedChannels.clear();
+ updatedChannels.clear();
+ }
+ }
+}
diff --git a/tests/unit/src/com/android/tv/data/ChannelNumberTest.java b/tests/unit/src/com/android/tv/data/ChannelNumberTest.java
new file mode 100644
index 00000000..9914f75e
--- /dev/null
+++ b/tests/unit/src/com/android/tv/data/ChannelNumberTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.data;
+
+import static com.android.tv.data.ChannelNumber.parseChannelNumber;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.tv.testing.ComparableTester;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link ChannelNumber}.
+ */
+@SmallTest
+public class ChannelNumberTest extends TestCase {
+
+ /**
+ * Test method for {@link ChannelNumber#ChannelNumber()}.
+ */
+ public void testChannelNumber() {
+ assertChannelEquals(new ChannelNumber(), "", false, "");
+ }
+
+ /**
+ * Test method for
+ * {@link com.android.tv.data.ChannelNumber#parseChannelNumber(java.lang.String)}.
+ */
+ public void testParseChannelNumber() {
+ assertNull(parseChannelNumber(""));
+ assertNull(parseChannelNumber(" "));
+ assertChannelEquals(parseChannelNumber("1"), "1", false, "");
+ assertChannelEquals(parseChannelNumber("1234 4321"), "1234", true, "4321");
+ assertChannelEquals(parseChannelNumber("3-4"), "3", true, "4");
+ assertChannelEquals(parseChannelNumber("5.6"), "5", true, "6");
+ }
+
+ /**
+ * Test method for {@link ChannelNumber#compareTo(com.android.tv.data.ChannelNumber)}.
+ */
+ public void testCompareTo() {
+ new ComparableTester<ChannelNumber>()
+ .addEquivelentGroup(parseChannelNumber("1"), parseChannelNumber("1"))
+ .addEquivelentGroup(parseChannelNumber("2"))
+ .addEquivelentGroup(parseChannelNumber("2 1"), parseChannelNumber("2.1"),
+ parseChannelNumber("2-1"))
+ .addEquivelentGroup(parseChannelNumber("2-2"))
+ .addEquivelentGroup(parseChannelNumber("2-10"))
+ .addEquivelentGroup(parseChannelNumber("3"))
+ .addEquivelentGroup(parseChannelNumber("10"))
+ .addEquivelentGroup(parseChannelNumber("100"))
+ .test();
+ }
+
+ /**
+ * Test method for {@link ChannelNumber#compare(java.lang.String, java.lang.String)}.
+ */
+ public void testCompare() {
+ // Only need to test nulls, the reset is tested by testComparteTo
+ assertEquals("compareTo(null,null)", 0, ChannelNumber.compare(null, null));
+ assertEquals("compareTo(1,1)", 0, ChannelNumber.compare("1", "1"));
+ assertEquals("compareTo(null,1)<0", true, ChannelNumber.compare(null, "1") < 0);
+ assertEquals("compareTo(1,null)>0", true, ChannelNumber.compare("1", null) > 0);
+ }
+
+ 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
new file mode 100644
index 00000000..dc41fda0
--- /dev/null
+++ b/tests/unit/src/com/android/tv/data/ChannelTest.java
@@ -0,0 +1,222 @@
+/*
+ * 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.data;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.test.AndroidTestCase;
+
+import com.android.tv.testing.ComparatorTester;
+import com.android.tv.util.TvInputManagerHelper;
+
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.Comparator;
+
+/**
+ * Tests for {@link Channel}.
+ */
+public class ChannelTest extends AndroidTestCase {
+ // 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";
+ // Used for testing TV inputs defined inside of Live Channels.
+ 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 TEST_APP_LINK_TEXT = "test_app_link_text";
+ private static final ActivityInfo TEST_ACTIVITY_INFO = new ActivityInfo();
+
+ private Context mMockContext;
+ private Intent mInvalidIntent;
+ private Intent mValidIntent;
+ private Intent mLiveChannelsIntent;
+ private Intent mLeanbackTvInputIntent;
+
+ public void setUp() throws Exception {
+ super.setUp();
+ mInvalidIntent = new Intent(Intent.ACTION_VIEW);
+ mInvalidIntent.setComponent(new ComponentName(INVALID_TV_INPUT_PACKAGE_NAME, ".test"));
+ mValidIntent = new Intent(Intent.ACTION_VIEW);
+ mValidIntent.setComponent(new ComponentName(LEANBACK_TV_INPUT_PACKAGE_NAME, ".test"));
+ mLiveChannelsIntent = new Intent(Intent.ACTION_VIEW);
+ mLiveChannelsIntent.setComponent(
+ new ComponentName(LIVE_CHANNELS_PACKAGE_NAME, ".MainActivity"));
+ mLeanbackTvInputIntent = new Intent(Intent.ACTION_VIEW);
+ mLeanbackTvInputIntent.setComponent(
+ 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(mLiveChannelsIntent);
+ Mockito.when(mockPackageManager.getLeanbackLaunchIntentForPackage(
+ NONE_LEANBACK_TV_INPUT_PACKAGE_NAME)).thenReturn(null);
+ Mockito.when(mockPackageManager.getLeanbackLaunchIntentForPackage(
+ LEANBACK_TV_INPUT_PACKAGE_NAME)).thenReturn(mLeanbackTvInputIntent);
+
+ // Channel.getAppLinkIntent() calls initAppLinkTypeAndIntent() which calls
+ // Intent.resolveActivityInfo() which calls PackageManager.getActivityInfo().
+ Mockito.doAnswer(new Answer<ActivityInfo>() {
+ 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.<ComponentName>any(), Mockito.anyInt());
+
+ mMockContext = Mockito.mock(Context.class);
+ Mockito.when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+ Mockito.when(mMockContext.getPackageName()).thenReturn(LIVE_CHANNELS_PACKAGE_NAME);
+ Mockito.when(mMockContext.getPackageManager()).thenReturn(mockPackageManager);
+ }
+
+ 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_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,
+ 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,
+ 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);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ private void assertAppLinkType(int expectedType, String inputPackageName, String appLinkText,
+ Intent appLinkIntent) {
+ Channel testChannel = new Channel.Builder()
+ .setPackageName(inputPackageName)
+ .setAppLinkText(appLinkText)
+ .setAppLinkIntentUri(appLinkIntent == null ? null : appLinkIntent.toUri(0))
+ .build();
+ assertEquals("Unexpected app-link type for for " + testChannel,
+ expectedType, testChannel.getAppLinkType(mMockContext));
+ }
+
+ public void testComparator() {
+ final String PARTNER_INPUT_ID = "partner";
+
+ TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class);
+ Mockito.when(manager.isPartnerInput(Matchers.anyString())).thenAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ String inputId = (String) invocation.getArguments()[0];
+ return PARTNER_INPUT_ID.equals(inputId);
+ }
+ });
+ Comparator<Channel> comparator = new TestChannelComparator(manager);
+ ComparatorTester<Channel> comparatorTester =
+ ComparatorTester.withoutEqualsTest(comparator);
+ comparatorTester.addComparableGroup(
+ new Channel.Builder().setInputId(PARTNER_INPUT_ID).setDisplayNumber("100").build());
+ comparatorTester.addComparableGroup(
+ new Channel.Builder().setInputId("1").setDisplayNumber("2").build());
+ comparatorTester.addComparableGroup(
+ new Channel.Builder().setInputId("2").setDisplayNumber("1.0").build());
+ 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());
+ comparatorTester.addComparableGroup(
+ new Channel.Builder().setInputId("2").setDisplayNumber("2.0").build());
+ comparatorTester.addComparableGroup(
+ new Channel.Builder().setInputId("2").setDisplayNumber("12.2").build());
+ comparatorTester.test();
+ }
+
+ private class TestChannelComparator extends Channel.DefaultComparator {
+ public TestChannelComparator(TvInputManagerHelper manager) {
+ super(null, manager);
+ }
+
+ @Override
+ public String getInputLabelForChannel(Channel channel) {
+ return channel.getInputId();
+ }
+ };
+}
diff --git a/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java b/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java
new file mode 100644
index 00000000..31ad54f0
--- /dev/null
+++ b/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java
@@ -0,0 +1,533 @@
+/*
+ * 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.data;
+
+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.test.AndroidTestCase;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockCursor;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.tv.testing.Constants;
+import com.android.tv.testing.ProgramInfo;
+import com.android.tv.util.FakeClock;
+import com.android.tv.util.Utils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test for {@link com.android.tv.data.ProgramDataManager}
+ */
+@SmallTest
+public class ProgramDataManagerTest extends AndroidTestCase {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "ProgramDataManagerTest";
+
+ // Wait time for expected success.
+ private static final long WAIT_TIME_OUT_MS = 1000L;
+ // Wait time for expected failure.
+ private static final long FAILURE_TIME_OUT_MS = 300L;
+
+ // TODO: Use TvContract constants, once they become public.
+ private static final String PARAM_CHANNEL = "channel";
+ private static final String PARAM_START_TIME = "start_time";
+ private static final String PARAM_END_TIME = "end_time";
+
+ private ProgramDataManager mProgramDataManager;
+ private FakeClock mClock;
+ private HandlerThread mHandlerThread;
+ private TestProgramDataManagerListener mListener;
+ private FakeContentResolver mContentResolver;
+ private FakeContentProvider mContentProvider;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mClock = new FakeClock();
+ mListener = new TestProgramDataManagerListener();
+ mContentProvider = new FakeContentProvider(getContext());
+ mContentResolver = new FakeContentResolver();
+ mContentResolver.addProvider(TvContract.AUTHORITY, mContentProvider);
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mProgramDataManager = new ProgramDataManager(
+ mContentResolver, mClock, mHandlerThread.getLooper());
+ mProgramDataManager.addListener(mListener);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mHandlerThread.quitSafely();
+ mProgramDataManager.stop();
+ }
+
+ private void startAndWaitForComplete() throws Exception {
+ mProgramDataManager.start();
+ assertTrue(mListener.programUpdatedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ private static boolean equals(ProgramInfo lhs, long lhsStarTimeMs, Program rhs) {
+ return TextUtils.equals(lhs.title, rhs.getTitle())
+ && TextUtils.equals(lhs.episode, rhs.getEpisodeTitle())
+ && TextUtils.equals(lhs.description, rhs.getDescription())
+ && lhsStarTimeMs == rhs.getStartTimeUtcMillis()
+ && lhsStarTimeMs + lhs.durationMs == rhs.getEndTimeUtcMillis();
+ }
+
+ /**
+ * Test for {@link ProgramInfo#getIndex} and {@link ProgramInfo#getStartTimeMs}.
+ */
+ 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);
+ assertTrue(startTimeMs <= mClock.currentTimeMillis());
+ assertTrue(mClock.currentTimeMillis() < startTimeMs + programAt.durationMs);
+ }
+ }
+
+ /**
+ * Test for following methods.
+ *
+ * <p>
+ * {@link ProgramDataManager#getCurrentProgram(long)},
+ * {@link ProgramDataManager#getPrograms(long, long)},
+ * {@link ProgramDataManager#setPrefetchTimeRange(long)}.
+ * </p>
+ */
+ public void testGetPrograms() throws Exception {
+ // Initial setup to test {@link ProgramDataManager#setPrefetchTimeRange(long)}.
+ long preventSnapDelayMs = ProgramDataManager.PROGRAM_GUIDE_SNAP_TIME_MS * 2;
+ long prefetchTimeRangeStartMs = System.currentTimeMillis() + preventSnapDelayMs;
+ mClock.setCurrentTimeMillis(prefetchTimeRangeStartMs + preventSnapDelayMs);
+ mProgramDataManager.setPrefetchTimeRange(prefetchTimeRangeStartMs);
+
+ startAndWaitForComplete();
+
+ 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());
+
+ // Test {@link ProgramDataManager#getPrograms(long)}.
+ // Case #1: Normal case
+ List<Program> programs =
+ mProgramDataManager.getPrograms(channelId, mClock.currentTimeMillis());
+ ProgramInfo stub = ProgramInfo.create();
+ int index = stub.getIndex(mClock.currentTimeMillis(), channelId);
+ for (Program program : programs) {
+ ProgramInfo programInfoAt = stub.build(getContext(), index);
+ long startTimeMs = stub.getStartTimeMs(index, channelId);
+ assertTrue(program.toString() + " differ from " + programInfoAt,
+ equals(programInfoAt, startTimeMs, program));
+ index++;
+ }
+ // 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());
+
+ // Test {@link ProgramDataManager#setPrefetchTimeRange(long)}.
+ programs = mProgramDataManager.getPrograms(channelId,
+ prefetchTimeRangeStartMs - TimeUnit.HOURS.toMillis(1));
+ for (Program program : programs) {
+ assertTrue(program.getEndTimeUtcMillis() >= prefetchTimeRangeStartMs);
+ }
+ }
+ }
+
+ /**
+ * Test for following methods.
+ *
+ * <p>
+ * {@link ProgramDataManager#addOnCurrentProgramUpdatedListener},
+ * {@link ProgramDataManager#removeOnCurrentProgramUpdatedListener}.
+ * </p>
+ */
+ public void testCurrentProgramListener() throws Exception {
+ 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);
+ mClock.setCurrentTimeMillis(nextProgramStartTimeMs - (WAIT_TIME_OUT_MS / 2));
+
+ startAndWaitForComplete();
+ // Note that changing current time doesn't affect the current program
+ // because current program is updated after waiting for the program's duration.
+ // See {@link ProgramDataManager#updateCurrentProgram}.
+ mClock.setCurrentTimeMillis(mClock.currentTimeMillis() + WAIT_TIME_OUT_MS);
+ TestProgramDataManagerOnCurrentProgramUpdatedListener listener =
+ new TestProgramDataManagerOnCurrentProgramUpdatedListener();
+ mProgramDataManager.addOnCurrentProgramUpdatedListener(testChannelId, listener);
+ assertTrue(listener.currentProgramUpdatedLatch.await(WAIT_TIME_OUT_MS,
+ TimeUnit.MILLISECONDS));
+ assertEquals(testChannelId, listener.updatedChannelId);
+ assertTrue(ProgramDataManagerTest.equals(
+ nextProgramInfo, nextProgramStartTimeMs,
+ mProgramDataManager.getCurrentProgram(testChannelId)));
+ assertEquals(listener.updatedProgram, mProgramDataManager.getCurrentProgram(testChannelId));
+ }
+
+ /**
+ * Test if program data is refreshed after the program insertion.
+ */
+ public void testContentProviderUpdate() throws Exception {
+ final long testChannelId = 1;
+ startAndWaitForComplete();
+ // Force program data manager to update program data whenever it's changes.
+ mProgramDataManager.setProgramPrefetchUpdateWait(0);
+ mListener.reset();
+ List<Program> programList =
+ mProgramDataManager.getPrograms(testChannelId, mClock.currentTimeMillis());
+ assertNotNull(programList);
+ 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));
+ programList = mProgramDataManager.getPrograms(testChannelId, mClock.currentTimeMillis());
+ assertTrue(lastProgramEndTime
+ < programList.get(programList.size() - 1).getEndTimeUtcMillis());
+ }
+
+ /**
+ * Test for {@link ProgramDataManager#setPauseProgramUpdate(boolean)}.
+ */
+ public void testSetPauseProgramUpdate() throws Exception {
+ final long testChannelId = 1;
+ startAndWaitForComplete();
+ // Force program data manager to update program data whenever it's changes.
+ mProgramDataManager.setProgramPrefetchUpdateWait(0);
+ mListener.reset();
+ mProgramDataManager.setPauseProgramUpdate(true);
+ mContentProvider.simulateAppend(testChannelId);
+ assertFalse(mListener.programUpdatedLatch.await(FAILURE_TIME_OUT_MS,
+ TimeUnit.MILLISECONDS));
+ }
+
+ private class FakeContentResolver extends MockContentResolver {
+ @Override
+ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+ super.notifyChange(uri, observer, syncToNetwork);
+ if (DEBUG) {
+ Log.d(TAG, "onChanged(uri=" + uri + ")");
+ }
+ if (observer != null) {
+ observer.dispatchChange(false, uri);
+ } else {
+ mProgramDataManager.getContentObserver().dispatchChange(false, uri);
+ }
+ }
+ }
+
+ private static class ProgramInfoWrapper {
+ private final int index;
+ private final long startTimeMs;
+ private final ProgramInfo programInfo;
+ public ProgramInfoWrapper(int index, long startTimeMs, ProgramInfo programInfo) {
+ this.index = index;
+ this.startTimeMs = startTimeMs;
+ this.programInfo = programInfo;
+ }
+ }
+
+ // This implements the minimal methods in content resolver
+ // and detailed assumptions are written in each method.
+ private class FakeContentProvider extends MockContentProvider {
+ private SparseArray<List<ProgramInfoWrapper>> mProgramInfoList = new SparseArray<>();
+
+ /**
+ * Constructor for FakeContentProvider
+ * <p>
+ * This initializes program info assuming that
+ * channel IDs are 1, 2, 3, ... {@link Constants#UNIT_TEST_CHANNEL_COUNT}.
+ * </p>
+ */
+ 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 endTimeMs = startTimeMs + (ProgramDataManager.PROGRAM_GUIDE_MAX_TIME_RANGE / 2);
+ for (int i = 1; i <= Constants.UNIT_TEST_CHANNEL_COUNT; i++) {
+ List<ProgramInfoWrapper> programInfoList = new ArrayList<>();
+ ProgramInfo stub = ProgramInfo.create();
+ int index = stub.getIndex(startTimeMs, i);
+ long programStartTimeMs = stub.getStartTimeMs(index, i);
+ while (programStartTimeMs < endTimeMs) {
+ ProgramInfo programAt = stub.build(getContext(), index);
+ programInfoList.add(
+ new ProgramInfoWrapper(index, programStartTimeMs, programAt));
+ index++;
+ programStartTimeMs += programAt.durationMs;
+ }
+ mProgramInfoList.put(i, programInfoList);
+ }
+ }
+
+ @Override
+ 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);
+ if (projection == null || projection.length == 0) {
+ Log.d(TAG, " projection=" + projection);
+ } else {
+ for (int i = 0; i < projection.length; i++) {
+ Log.d(TAG, " projection=" + projection[i]);
+ }
+ }
+ Log.d(TAG," selection=" + selection);
+ }
+ long startTimeMs = Long.parseLong(uri.getQueryParameter(PARAM_START_TIME));
+ long endTimeMs = Long.parseLong(uri.getQueryParameter(PARAM_END_TIME));
+ if (startTimeMs == 0 || endTimeMs == 0) {
+ throw new UnsupportedOperationException();
+ }
+ assertProgramUri(uri);
+ long channelId;
+ try {
+ channelId = Long.parseLong(uri.getQueryParameter(PARAM_CHANNEL));
+ } catch (NumberFormatException e) {
+ channelId = -1;
+ }
+ return new FakeCursor(projection, channelId, startTimeMs, endTimeMs);
+ }
+
+ /**
+ * 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 =
+ mClock.currentTimeMillis() + ProgramDataManager.PROGRAM_GUIDE_MAX_TIME_RANGE;
+ List<ProgramInfoWrapper> programList = mProgramInfoList.get((int) channelId);
+ if (mProgramInfoList == null) {
+ return;
+ }
+ ProgramInfo stub = ProgramInfo.create();
+ ProgramInfoWrapper last = programList.get(programList.size() - 1);
+ while (last.startTimeMs < endTimeMs) {
+ ProgramInfo nextProgramInfo = stub.build(getContext(), last.index + 1);
+ ProgramInfoWrapper next = new ProgramInfoWrapper(last.index + 1,
+ last.startTimeMs + last.programInfo.durationMs, nextProgramInfo);
+ programList.add(next);
+ last = next;
+ }
+ mContentResolver.notifyChange(TvContract.Programs.CONTENT_URI, null);
+ }
+
+ private void assertProgramUri(Uri uri) {
+ assertTrue("Uri(" + uri + ") isn't channel uri",
+ uri.toString().startsWith(TvContract.Programs.CONTENT_URI.toString()));
+ }
+
+ public ProgramInfoWrapper get(long channelId, int position) {
+ List<ProgramInfoWrapper> programList = mProgramInfoList.get((int) channelId);
+ if (programList == null || position >= programList.size()) {
+ return null;
+ }
+ return programList.get(position);
+ }
+ }
+
+ private class FakeCursor extends MockCursor {
+ private 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;
+ private final long mEndTimeMs;
+ private final int mCount;
+ private long mChannelId;
+ private int mProgramPosition;
+ private ProgramInfoWrapper mCurrentProgram;
+
+ /**
+ * 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 startTimeMs start of the time range to query programs.
+ * @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;
+ mIsQueryForSingleChannel = (channelId > 0);
+ mChannelId = channelId;
+ mProgramPosition = -1;
+ mStartTimeMs = startTimeMs;
+ mEndTimeMs = endTimeMs;
+ int count = 0;
+ while (moveToNext()) {
+ count++;
+ }
+ mCount = count;
+ // Rewind channel Id and program index.
+ mChannelId = channelId;
+ mProgramPosition = -1;
+ if (DEBUG) {
+ Log.d(TAG, "FakeCursor(columns=" + columns + ", channelId=" + channelId
+ + ", startTimeMs=" + startTimeMs + ", endTimeMs=" + endTimeMs
+ + ") has mCount=" + mCount);
+ }
+ }
+
+ @Override
+ public String getColumnName(int columnIndex) {
+ return mColumns[columnIndex];
+ }
+
+ @Override
+ public int getColumnIndex(String columnName) {
+ for (int i = 0; i < mColumns.length; i++) {
+ if (mColumns[i].equalsIgnoreCase(columnName)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public int getInt(int columnIndex) {
+ if (DEBUG) {
+ Log.d(TAG, "Column (" + getColumnName(columnIndex) + ") is ignored in getInt()");
+ }
+ return 0;
+ }
+
+ @Override
+ public long getLong(int columnIndex) {
+ String columnName = getColumnName(columnIndex);
+ switch (columnName) {
+ case TvContract.Programs.COLUMN_CHANNEL_ID:
+ return mChannelId;
+ case TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS:
+ return mCurrentProgram.startTimeMs;
+ case TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS:
+ return mCurrentProgram.startTimeMs + mCurrentProgram.programInfo.durationMs;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Column (" + columnName + ") is ignored in getLong()");
+ }
+ return 0;
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ String columnName = getColumnName(columnIndex);
+ switch (columnName) {
+ case TvContract.Programs.COLUMN_TITLE:
+ return mCurrentProgram.programInfo.title;
+ case TvContract.Programs.COLUMN_SHORT_DESCRIPTION:
+ return mCurrentProgram.programInfo.description;
+ case TvContract.Programs.COLUMN_EPISODE_TITLE:
+ return mCurrentProgram.programInfo.episode;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Column (" + columnName + ") is ignored in getString()");
+ }
+ return null;
+ }
+
+ @Override
+ public int getCount() {
+ return mCount;
+ }
+
+ @Override
+ public boolean moveToNext() {
+ while (true) {
+ ProgramInfoWrapper program = mContentProvider.get(mChannelId, ++mProgramPosition);
+ if (program == null || program.startTimeMs >= mEndTimeMs) {
+ if (mIsQueryForSingleChannel) {
+ return false;
+ } else {
+ if (++mChannelId > Constants.UNIT_TEST_CHANNEL_COUNT) {
+ return false;
+ }
+ mProgramPosition = -1;
+ }
+ } else if (program.startTimeMs + program.programInfo.durationMs >= mStartTimeMs) {
+ mCurrentProgram = program;
+ break;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void close() {
+ // No-op.
+ }
+ }
+
+ private class TestProgramDataManagerListener implements ProgramDataManager.Listener {
+ public CountDownLatch programUpdatedLatch = new CountDownLatch(1);
+
+ @Override
+ public void onProgramUpdated() {
+ programUpdatedLatch.countDown();
+ }
+
+ public void reset() {
+ programUpdatedLatch = new CountDownLatch(1);
+ }
+ }
+
+ private class TestProgramDataManagerOnCurrentProgramUpdatedListener implements
+ OnCurrentProgramUpdatedListener {
+ public CountDownLatch currentProgramUpdatedLatch = new CountDownLatch(1);
+ public long updatedChannelId = -1;
+ public Program updatedProgram = null;
+
+ @Override
+ public void onCurrentProgramUpdated(long channelId, Program program) {
+ updatedChannelId = channelId;
+ updatedProgram = program;
+ currentProgramUpdatedLatch.countDown();
+ }
+ }
+}
diff --git a/tests/unit/src/com/android/tv/data/ProgramTest.java b/tests/unit/src/com/android/tv/data/ProgramTest.java
new file mode 100644
index 00000000..b4d78733
--- /dev/null
+++ b/tests/unit/src/com/android/tv/data/ProgramTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.data;
+
+import static android.media.tv.TvContract.Programs.Genres.COMEDY;
+import static android.media.tv.TvContract.Programs.Genres.FAMILY_KIDS;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link Program}.
+ */
+public class ProgramTest extends TestCase {
+
+ 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);
+
+ public void testBuild() {
+ Program program = new Program.Builder().build();
+ assertEquals("isValid", false, program.isValid());
+ }
+
+ public void testNoGenres() {
+ Program program = new Program.Builder()
+ .setCanonicalGenres("")
+ .build();
+ assertNullCanonicalGenres(program);
+ assertHasGenre(program, NOT_FOUND_GENRE, false);
+ assertHasGenre(program, FAMILY_GENRE_ID, false);
+ assertHasGenre(program, COMEDY_GENRE_ID, false);
+ assertHasGenre(program, GenreItems.ID_ALL_CHANNELS, true);
+ }
+
+ public void testFamilyGenre() {
+ 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);
+ assertHasGenre(program, COMEDY_GENRE_ID, false);
+ assertHasGenre(program, GenreItems.ID_ALL_CHANNELS, true);
+ }
+
+ public void testFamilyComedyGenre() {
+ 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);
+ assertHasGenre(program, COMEDY_GENRE_ID, true);
+ assertHasGenre(program, GenreItems.ID_ALL_CHANNELS, true);
+ }
+
+ public void testOtherGenre() {
+ Program program = new Program.Builder()
+ .setCanonicalGenres("other")
+ .build();
+ assertCanonicalGenres(program);
+ assertHasGenre(program, NOT_FOUND_GENRE, false);
+ assertHasGenre(program, FAMILY_GENRE_ID, false);
+ assertHasGenre(program, COMEDY_GENRE_ID, false);
+ assertHasGenre(program, GenreItems.ID_ALL_CHANNELS, true);
+ }
+
+ private static void assertNullCanonicalGenres(Program program) {
+ String[] actual = program.getCanonicalGenres();
+ assertNull("Expected null canonical genres but was " + Arrays.toString(actual), actual);
+ }
+
+ private static void assertCanonicalGenres(Program program, String... expected) {
+ assertEquals("canonical genres", Arrays.asList(expected),
+ Arrays.asList(program.getCanonicalGenres()));
+ }
+
+ private static void assertHasGenre(Program program, int genreId, boolean expected) {
+ assertEquals("hasGenre(" + genreId + ")", expected, program.hasGenre(genreId));
+ }
+}
diff --git a/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java b/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java
new file mode 100644
index 00000000..4ffd9fa9
--- /dev/null
+++ b/tests/unit/src/com/android/tv/menu/TvOptionsRowAdapterTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.media.tv.TvTrackInfo;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.SmallTest;
+
+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.testinput.ChannelStateData;
+import com.android.tv.testing.testinput.TvTestInputConstants;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests for {@link TvOptionsRowAdapter}.
+ */
+@SmallTest
+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;
+
+
+ // TODO: Refactor TvOptionsRowAdapter so it does not rely on MainActivity
+ private TvOptionsRowAdapter mTvOptionsRowAdapter;
+
+ public TvOptionsRowAdapterTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTvOptionsRowAdapter = new TvOptionsRowAdapter(mActivity,
+ Collections.<CustomAction>emptyList());
+ tuneToChannel(TvTestInputConstants.CH_1);
+ }
+
+ public void testUpdateAudioAction_2tracks() {
+ mTvOptionsRowAdapter.update();
+ ChannelStateData data = new ChannelStateData();
+ data.mTvTrackInfos.add(Constants.GENERIC_AUDIO_TRACK);
+ updateThenTune(data, TvTestInputConstants.CH_2);
+ waitUntilTracksHaveSize(2);
+
+ boolean result = mTvOptionsRowAdapter.updateActions();
+ assertEquals("update Action had change", true, result);
+ assertEquals("Multi Audio enabled", true,
+ MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled());
+ }
+
+ public void testUpdateAudioAction_1track() {
+ mTvOptionsRowAdapter.update();
+ ChannelStateData data = new ChannelStateData();
+ data.mTvTrackInfos.clear();
+ data.mTvTrackInfos.add(Constants.GENERIC_AUDIO_TRACK);
+ updateThenTune(data, TvTestInputConstants.CH_2);
+ waitUntilTracksHaveSize(1);
+
+ boolean result = mTvOptionsRowAdapter.updateActions();
+ assertEquals("update Action had change", true, result);
+ assertEquals("Multi Audio enabled", false,
+ MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled());
+ }
+
+ public void testUpdateAudioAction_noTracks() {
+ mTvOptionsRowAdapter.update();
+ ChannelStateData data = new ChannelStateData();
+ data.mTvTrackInfos.clear();
+ updateThenTune(data, TvTestInputConstants.CH_2);
+ waitUntilTracksHaveSize(0);
+
+ boolean result = mTvOptionsRowAdapter.updateActions();
+ assertEquals("update Action had change", false, result);
+ assertEquals("Multi Audio enabled", false,
+ MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled());
+ }
+
+ private void waitUntilTracksHaveSize(int expected) {
+ long start = SystemClock.elapsedRealtime();
+ while (SystemClock.elapsedRealtime() < start + WAIT_TRACK_SIZE_TIMEOUT_MS) {
+ getInstrumentation().waitForIdleSync();
+ List<TvTrackInfo> tracks = mActivity.getTracks(TvTrackInfo.TYPE_AUDIO);
+ if (tracks != null && tracks.size() == expected) {
+ return;
+ }
+ SystemClock.sleep(TRACK_SIZE_CHECK_INTERVAL_MS);
+ }
+ fail("Waited for " + WAIT_TRACK_SIZE_TIMEOUT_MS + " milliseconds for track size to be "
+ + expected);
+ }
+}
diff --git a/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java b/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java
new file mode 100644
index 00000000..9b0e2805
--- /dev/null
+++ b/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.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.recommendation;
+
+import android.test.AndroidTestCase;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit tests for {@link ChannelRecord}.
+ */
+public class ChannelRecordTest extends AndroidTestCase {
+ private static final int CHANNEL_RECORD_MAX_HISTORY_SIZE = ChannelRecord.MAX_HISTORY_SIZE;
+
+ private Random mRandom;
+ private ChannelRecord mChannelRecord;
+ private long mLatestWatchEndTimeMs;
+
+ public void setUp() throws Exception {
+ super.setUp();
+ mLatestWatchEndTimeMs = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
+ mChannelRecord = new ChannelRecord(getContext(), null, false);
+ mRandom = RecommendationUtils.createTestRandom();
+ }
+
+ public void testGetLastWatchEndTime_noHistory() {
+ assertEquals(0, mChannelRecord.getLastWatchEndTimeMs());
+ }
+
+ public void testGetLastWatchEndTime_oneHistory() {
+ addWatchLog();
+
+ assertEquals(mLatestWatchEndTimeMs, mChannelRecord.getLastWatchEndTimeMs());
+ }
+
+ public void testGetLastWatchEndTime_maxHistories() {
+ for (int i = 0; i < CHANNEL_RECORD_MAX_HISTORY_SIZE; ++i) {
+ addWatchLog();
+ }
+
+ assertEquals(mLatestWatchEndTimeMs, mChannelRecord.getLastWatchEndTimeMs());
+ }
+
+ public void testGetLastWatchEndTime_moreThanMaxHistories() {
+ for (int i = 0; i < CHANNEL_RECORD_MAX_HISTORY_SIZE + 1; ++i) {
+ addWatchLog();
+ }
+
+ assertEquals(mLatestWatchEndTimeMs, mChannelRecord.getLastWatchEndTimeMs());
+ }
+
+ public void testGetTotalWatchDuration_noHistory() {
+ assertEquals(0, mChannelRecord.getTotalWatchDurationMs());
+ }
+
+ public void testGetTotalWatchDuration_oneHistory() {
+ long durationMs = addWatchLog();
+
+ assertEquals(durationMs, mChannelRecord.getTotalWatchDurationMs());
+ }
+
+ public void testGetTotalWatchDuration_maxHistories() {
+ long totalWatchTimeMs = 0;
+ for (int i = 0; i < CHANNEL_RECORD_MAX_HISTORY_SIZE; ++i) {
+ long durationMs = addWatchLog();
+ totalWatchTimeMs += durationMs;
+ }
+
+ assertEquals(totalWatchTimeMs, mChannelRecord.getTotalWatchDurationMs());
+ }
+
+ public void testGetTotalWatchDuration_moreThanMaxHistories() {
+ long totalWatchTimeMs = 0;
+ long firstDurationMs = 0;
+ for (int i = 0; i < CHANNEL_RECORD_MAX_HISTORY_SIZE + 1; ++i) {
+ long durationMs = addWatchLog();
+ totalWatchTimeMs += durationMs;
+ if (i == 0) {
+ firstDurationMs = durationMs;
+ }
+ }
+
+ // Only latest CHANNEL_RECORD_MAX_HISTORY_SIZE logs are remained.
+ assertEquals(totalWatchTimeMs - firstDurationMs, mChannelRecord.getTotalWatchDurationMs());
+ }
+
+ /**
+ * Add new log history to channelRecord which its duration is lower than 1 minute.
+ *
+ * @return New watch log's duration time in milliseconds.
+ */
+ private long addWatchLog() {
+ // Time hopping with random seconds.
+ 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));
+ 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
new file mode 100644
index 00000000..ee9fa95f
--- /dev/null
+++ b/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java
@@ -0,0 +1,128 @@
+/*
+ * 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.recommendation;
+
+import android.test.AndroidTestCase;
+
+import com.android.tv.data.Channel;
+import com.android.tv.recommendation.RecommendationUtils.ChannelRecordSortedMapHelper;
+import com.android.tv.recommendation.Recommender.Evaluator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base test case for Recommendation Evaluator Unit tests.
+ */
+public abstract class EvaluatorTestCase<T extends Evaluator> extends AndroidTestCase {
+ private static final long INVALID_CHANNEL_ID = -1;
+
+ private ChannelRecordSortedMapHelper mChannelRecordSortedMap;
+ private RecommendationDataManager mDataManager;
+
+ public T mEvaluator;
+
+ public void setUp() throws Exception {
+ super.setUp();
+ mChannelRecordSortedMap = new ChannelRecordSortedMapHelper(getContext());
+ mDataManager = RecommendationUtils
+ .createMockRecommendationDataManager(mChannelRecordSortedMap);
+ Recommender mRecommender = new FakeRecommender();
+ mEvaluator = createEvaluator();
+ mEvaluator.setRecommender(mRecommender);
+ mChannelRecordSortedMap.setRecommender(mRecommender);
+ mChannelRecordSortedMap.resetRandom(RecommendationUtils.createTestRandom());
+ }
+
+ /**
+ * Each evaluator test has to create Evaluator in {@code mEvaluator}.
+ */
+ public abstract T createEvaluator();
+
+ public void addChannels(int numberOfChannels) {
+ mChannelRecordSortedMap.addChannels(numberOfChannels);
+ }
+
+ public Channel addChannel() {
+ return mChannelRecordSortedMap.addChannel();
+ }
+
+ 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));
+ }
+
+ public List<Long> getChannelIdListSorted() {
+ return new ArrayList<>(mChannelRecordSortedMap.keySet());
+ }
+
+ public long getLatestWatchEndTimeMs() {
+ long latestWatchEndTimeMs = 0;
+ for (ChannelRecord channelRecord : mChannelRecordSortedMap.values()) {
+ latestWatchEndTimeMs = Math.max(latestWatchEndTimeMs,
+ channelRecord.getLastWatchEndTimeMs());
+ }
+ return latestWatchEndTimeMs;
+ }
+
+ /**
+ * Check whether scores of each channels are valid.
+ */
+ protected void assertChannelScoresValid() {
+ assertEquals(Evaluator.NOT_RECOMMENDED, mEvaluator.evaluateChannel(INVALID_CHANNEL_ID));
+ assertEquals(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",
+ score == Evaluator.NOT_RECOMMENDED || (0.0 <= score && score <= 1.0));
+ }
+ }
+
+ /**
+ * Notify that loading channels and watch logs are finished.
+ */
+ protected void notifyChannelAndWatchLogLoaded() {
+ mEvaluator.onChannelRecordListChanged(new ArrayList<>(mChannelRecordSortedMap.values()));
+ }
+
+ private class FakeRecommender extends Recommender {
+ public FakeRecommender() {
+ super(new Recommender.Listener() {
+ @Override
+ public void onRecommenderReady() {
+ }
+
+ @Override
+ public void onRecommendationChanged() {
+ }
+ }, true, mDataManager);
+ }
+
+ @Override
+ public ChannelRecord getChannelRecord(long channelId) {
+ return mChannelRecordSortedMap.get(channelId);
+ }
+ }
+}
diff --git a/tests/unit/src/com/android/tv/recommendation/FavoriteChannelEvaluatorTest.java b/tests/unit/src/com/android/tv/recommendation/FavoriteChannelEvaluatorTest.java
new file mode 100644
index 00000000..c33271bc
--- /dev/null
+++ b/tests/unit/src/com/android/tv/recommendation/FavoriteChannelEvaluatorTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.recommendation;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit tests for {@link FavoriteChannelEvaluator}.
+ */
+public class FavoriteChannelEvaluatorTest extends EvaluatorTestCase<FavoriteChannelEvaluator> {
+ private static final int DEFAULT_NUMBER_OF_CHANNELS = 4;
+ private static final long DEFAULT_WATCH_START_TIME_MS =
+ System.currentTimeMillis() - TimeUnit.DAYS.toMillis(2);
+ private static final long DEFAULT_WATCH_END_TIME_MS =
+ System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
+ private static final long DEFAULT_MAX_WATCH_DURATION_MS = TimeUnit.HOURS.toMillis(1);
+
+ public FavoriteChannelEvaluator createEvaluator() {
+ return new FavoriteChannelEvaluator();
+ }
+
+ public void testOneChannelWithNoWatchLog() {
+ long channelId = addChannel().getId();
+ notifyChannelAndWatchLogLoaded();
+
+ assertEquals(Recommender.Evaluator.NOT_RECOMMENDED,
+ mEvaluator.evaluateChannel(channelId));
+ }
+
+ public void testOneChannelWithRandomWatchLogs() {
+ addChannel();
+ addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, DEFAULT_WATCH_END_TIME_MS,
+ DEFAULT_MAX_WATCH_DURATION_MS);
+ notifyChannelAndWatchLogLoaded();
+
+ assertChannelScoresValid();
+ }
+
+ public void testMultiChannelsWithNoWatchLog() {
+ addChannels(DEFAULT_NUMBER_OF_CHANNELS);
+ notifyChannelAndWatchLogLoaded();
+
+ List<Long> channelIdList = getChannelIdListSorted();
+ for (long channelId : channelIdList) {
+ assertEquals(Recommender.Evaluator.NOT_RECOMMENDED,
+ mEvaluator.evaluateChannel(channelId));
+ }
+ }
+
+ public void testMultiChannelsWithRandomWatchLogs() {
+ addChannels(DEFAULT_NUMBER_OF_CHANNELS);
+ addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, DEFAULT_WATCH_END_TIME_MS,
+ DEFAULT_MAX_WATCH_DURATION_MS);
+ notifyChannelAndWatchLogLoaded();
+
+ assertChannelScoresValid();
+ }
+
+ public void testMultiChannelsWithSimpleWatchLogs() {
+ addChannels(DEFAULT_NUMBER_OF_CHANNELS);
+ // For two channels which has ID x and y (x < y), the channel y is more watched
+ // than the channel x. (Duration is longer than channel x)
+ long latestWatchEndTimeMs = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(2);
+ long durationMs = 0;
+ List<Long> channelIdList = getChannelIdListSorted();
+ for (long channelId : channelIdList) {
+ durationMs += TimeUnit.MINUTES.toMillis(30);
+ addWatchLog(channelId, latestWatchEndTimeMs, durationMs);
+ latestWatchEndTimeMs += durationMs;
+ }
+ notifyChannelAndWatchLogLoaded();
+
+ assertChannelScoresValid();
+ // Channel score must be increased as channel ID increased.
+ double previousScore = Recommender.Evaluator.NOT_RECOMMENDED;
+ for (long channelId : channelIdList) {
+ double score = mEvaluator.evaluateChannel(channelId);
+ assertTrue(previousScore <= score);
+ previousScore = score;
+ }
+ }
+
+ public void testTwoChannelsWithSameWatchDuration() {
+ long channelOne = addChannel().getId();
+ long channelTwo = addChannel().getId();
+ addWatchLog(channelOne, System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1),
+ TimeUnit.MINUTES.toMillis(30));
+ addWatchLog(channelTwo, System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(30),
+ TimeUnit.MINUTES.toMillis(30));
+ notifyChannelAndWatchLogLoaded();
+
+ assertTrue(mEvaluator.evaluateChannel(channelOne) ==
+ mEvaluator.evaluateChannel(channelTwo));
+ }
+
+ public void testTwoChannelsWithDifferentWatchDuration() {
+ long channelOne = addChannel().getId();
+ long channelTwo = addChannel().getId();
+ addWatchLog(channelOne, System.currentTimeMillis() - TimeUnit.HOURS.toMillis(3),
+ TimeUnit.MINUTES.toMillis(30));
+ addWatchLog(channelTwo, System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2),
+ TimeUnit.HOURS.toMillis(1));
+ notifyChannelAndWatchLogLoaded();
+
+ // Channel two was watched longer than channel one, so it's score is bigger.
+ assertTrue(mEvaluator.evaluateChannel(channelOne) < mEvaluator.evaluateChannel(channelTwo));
+
+ addWatchLog(channelOne, System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1),
+ TimeUnit.HOURS.toMillis(1));
+
+ // Now, channel one was watched longer than channel two, so it's score is bigger.
+ assertTrue(mEvaluator.evaluateChannel(channelOne) > mEvaluator.evaluateChannel(channelTwo));
+ }
+
+ public void testScoreIncreasesWithNewWatchLog() {
+ long channelId = addChannel().getId();
+ addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, DEFAULT_WATCH_END_TIME_MS,
+ DEFAULT_MAX_WATCH_DURATION_MS);
+ notifyChannelAndWatchLogLoaded();
+
+ long latestWatchEndTimeMs = getLatestWatchEndTimeMs();
+ double previousScore = mEvaluator.evaluateChannel(channelId);
+
+ addWatchLog(channelId, latestWatchEndTimeMs, TimeUnit.MINUTES.toMillis(10));
+
+ // Score must be increased because total watch duration of the channel increases.
+ assertTrue(previousScore <= mEvaluator.evaluateChannel(channelId));
+ }
+}
diff --git a/tests/unit/src/com/android/tv/recommendation/RecentChannelEvaluatorTest.java b/tests/unit/src/com/android/tv/recommendation/RecentChannelEvaluatorTest.java
new file mode 100644
index 00000000..a888ceea
--- /dev/null
+++ b/tests/unit/src/com/android/tv/recommendation/RecentChannelEvaluatorTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.recommendation;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit tests for {@link RecentChannelEvaluator}.
+ */
+public class RecentChannelEvaluatorTest extends EvaluatorTestCase<RecentChannelEvaluator> {
+ private static final int DEFAULT_NUMBER_OF_CHANNELS = 4;
+ private static final long DEFAULT_WATCH_START_TIME_MS =
+ System.currentTimeMillis() - TimeUnit.DAYS.toMillis(2);
+ private static final long DEFAULT_WATCH_END_TIME_MS =
+ System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
+ private static final long DEFAULT_MAX_WATCH_DURATION_MS = TimeUnit.HOURS.toMillis(1);
+
+ public RecentChannelEvaluator createEvaluator() {
+ return new RecentChannelEvaluator();
+ }
+
+ public void testOneChannelWithNoWatchLog() {
+ long channelId = addChannel().getId();
+ notifyChannelAndWatchLogLoaded();
+
+ assertEquals(Recommender.Evaluator.NOT_RECOMMENDED,
+ mEvaluator.evaluateChannel(channelId));
+ }
+
+ public void testOneChannelWithRandomWatchLogs() {
+ addChannel();
+ addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, DEFAULT_WATCH_END_TIME_MS,
+ DEFAULT_MAX_WATCH_DURATION_MS);
+ notifyChannelAndWatchLogLoaded();
+
+ assertChannelScoresValid();
+ }
+
+ public void testMultiChannelsWithNoWatchLog() {
+ addChannels(DEFAULT_NUMBER_OF_CHANNELS);
+ notifyChannelAndWatchLogLoaded();
+
+ List<Long> channelIdList = getChannelIdListSorted();
+ for (long channelId : channelIdList) {
+ assertEquals(Recommender.Evaluator.NOT_RECOMMENDED,
+ mEvaluator.evaluateChannel(channelId));
+ }
+ }
+
+ public void testMultiChannelsWithRandomWatchLogs() {
+ addChannels(DEFAULT_NUMBER_OF_CHANNELS);
+ addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, DEFAULT_WATCH_END_TIME_MS,
+ DEFAULT_MAX_WATCH_DURATION_MS);
+ notifyChannelAndWatchLogLoaded();
+
+ assertChannelScoresValid();
+ }
+
+ public void testMultiChannelsWithSimpleWatchLogs() {
+ addChannels(DEFAULT_NUMBER_OF_CHANNELS);
+ // Every channel has one watch log with 1 hour. Also, for two channels
+ // which has ID x and y (x < y), the channel y is watched later than the channel x.
+ long latestWatchEndTimeMs = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(2);
+ List<Long> channelIdList = getChannelIdListSorted();
+ for (long channelId : channelIdList) {
+ addWatchLog(channelId, latestWatchEndTimeMs, TimeUnit.HOURS.toMillis(1));
+ latestWatchEndTimeMs += TimeUnit.HOURS.toMillis(1);
+ }
+ notifyChannelAndWatchLogLoaded();
+
+ assertChannelScoresValid();
+ // Channel score must be increased as channel ID increased.
+ double previousScore = Recommender.Evaluator.NOT_RECOMMENDED;
+ for (long channelId : channelIdList) {
+ double score = mEvaluator.evaluateChannel(channelId);
+ assertTrue(previousScore <= score);
+ previousScore = score;
+ }
+ }
+
+ public void testScoreIncreasesWithNewWatchLog() {
+ addChannels(DEFAULT_NUMBER_OF_CHANNELS);
+ addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, DEFAULT_WATCH_END_TIME_MS,
+ DEFAULT_MAX_WATCH_DURATION_MS);
+ notifyChannelAndWatchLogLoaded();
+
+ List<Long> channelIdList = getChannelIdListSorted();
+ long latestWatchEndTimeMs = getLatestWatchEndTimeMs();
+ for (long channelId : channelIdList) {
+ double previousScore = mEvaluator.evaluateChannel(channelId);
+
+ long durationMs = TimeUnit.MINUTES.toMillis(10);
+ addWatchLog(channelId, latestWatchEndTimeMs, durationMs);
+ latestWatchEndTimeMs += durationMs;
+
+ // Score must be increased because recentness of the log increases.
+ assertTrue(previousScore <= mEvaluator.evaluateChannel(channelId));
+ }
+ }
+
+ public void testScoreDecreasesWithIncrementOfWatchedLogUpdatedTime() {
+ addChannels(DEFAULT_NUMBER_OF_CHANNELS);
+ addRandomWatchLogs(DEFAULT_WATCH_START_TIME_MS, DEFAULT_WATCH_END_TIME_MS,
+ DEFAULT_MAX_WATCH_DURATION_MS);
+ notifyChannelAndWatchLogLoaded();
+
+ Map<Long, Double> scores = new HashMap<>();
+ List<Long> channelIdList = getChannelIdListSorted();
+ long latestWatchedEndTimeMs = getLatestWatchEndTimeMs();
+
+ for (long channelId : channelIdList) {
+ scores.put(channelId, mEvaluator.evaluateChannel(channelId));
+ }
+
+ long newChannelId = addChannel().getId();
+ addWatchLog(newChannelId, latestWatchedEndTimeMs, TimeUnit.MINUTES.toMillis(10));
+
+ for (long channelId : channelIdList) {
+ // Score must be decreased because LastWatchLogUpdateTime increases by new log.
+ assertTrue(mEvaluator.evaluateChannel(channelId) <= scores.get(channelId));
+ }
+ }
+}
diff --git a/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java b/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java
new file mode 100644
index 00000000..d275bfbb
--- /dev/null
+++ b/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java
@@ -0,0 +1,180 @@
+/*
+ * 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.recommendation;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.tv.data.Channel;
+
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Random;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+
+public class RecommendationUtils {
+ private static final String TAG = "RecommendationUtils";
+ private static final long INVALID_CHANNEL_ID = -1;
+ private static final long DEFAULT_RANDOM_SEED = getSeed();
+
+ 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Create a mock RecommendationDataManager backed by a {@link ChannelRecordSortedMapHelper}.
+ */
+ public static RecommendationDataManager createMockRecommendationDataManager(
+ final ChannelRecordSortedMapHelper channelRecordSortedMap) {
+ RecommendationDataManager dataManager = Mockito.mock(RecommendationDataManager.class);
+ Mockito.doAnswer(new Answer<Integer>() {
+ @Override
+ public Integer answer(InvocationOnMock invocation) throws Throwable {
+ return channelRecordSortedMap.size();
+ }
+ }).when(dataManager).getChannelRecordCount();
+ Mockito.doAnswer(new Answer<Collection<ChannelRecord>>() {
+ @Override
+ public Collection<ChannelRecord> answer(InvocationOnMock invocation) throws Throwable {
+ return channelRecordSortedMap.values();
+ }
+ }).when(dataManager).getChannelRecords();
+ Mockito.doAnswer(new Answer<ChannelRecord>() {
+ @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;
+ }
+
+ public static class ChannelRecordSortedMapHelper extends TreeMap<Long, ChannelRecord> {
+ private Context mContext;
+ private Recommender mRecommender;
+ private Random mRandom = createTestRandom();
+
+ public ChannelRecordSortedMapHelper(Context context) {
+ mContext = context;
+ }
+
+ public void setRecommender(Recommender recommender) {
+ mRecommender = recommender;
+ }
+
+ public void resetRandom(Random random) {
+ mRandom = random;
+ }
+
+ /**
+ * 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) {
+ addChannel();
+ }
+ }
+
+ /**
+ * Add new one channel by adding channel record to {@code channelRecordMap} with no history.
+ * This action corresponds to loading one channel in the RecommendationDataManger.
+ *
+ * @return The new channel was made by this method.
+ */
+ public Channel addChannel() {
+ long channelId = size();
+ Channel channel = new Channel.Builder().setId(channelId).build();
+ ChannelRecord channelRecord = new ChannelRecord(mContext, channel, false);
+ put(channelId, channelRecord);
+ return channel;
+ }
+
+ /**
+ * 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) {
+ long latestWatchEndTimeMs = watchStartTimeMs;
+ long previousChannelId = INVALID_CHANNEL_ID;
+ List<Long> channelIdList = new ArrayList<>(keySet());
+ while (latestWatchEndTimeMs < watchEndTimeMs) {
+ long channelId = channelIdList.get(mRandom.nextInt(channelIdList.size()));
+ if (previousChannelId == channelId) {
+ // Time hopping with random minutes.
+ latestWatchEndTimeMs += TimeUnit.MINUTES.toMillis(mRandom.nextInt(30) + 1);
+ }
+ long watchedDurationMs = mRandom.nextInt((int) maxWatchDurationMs) + 1;
+ if (!addWatchLog(channelId, latestWatchEndTimeMs, watchedDurationMs)) {
+ return false;
+ }
+ latestWatchEndTimeMs += watchedDurationMs;
+ previousChannelId = channelId;
+ }
+ return true;
+ }
+
+ /**
+ * Add new watch log to channel that id is {@code ChannelId}. Add watch log starts from
+ * {@code watchStartTimeMs} with duration {@code durationTimeMs}. If adding is finished,
+ * notify the recommender that there's a new watch log.
+ *
+ * @return true if adding watch log success, otherwise false.
+ */
+ public boolean addWatchLog(long channelId, long watchStartTimeMs, long durationTimeMs) {
+ ChannelRecord channelRecord = get(channelId);
+ if (channelRecord == null ||
+ watchStartTimeMs + durationTimeMs > System.currentTimeMillis()) {
+ return false;
+ }
+
+ channelRecord.logWatchHistory(new WatchedProgram(null, watchStartTimeMs,
+ watchStartTimeMs + durationTimeMs));
+ if (mRecommender != null) {
+ mRecommender.onNewWatchLog(channelRecord);
+ }
+ return true;
+ }
+ }
+}
diff --git a/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java b/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java
new file mode 100644
index 00000000..4f16d168
--- /dev/null
+++ b/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java
@@ -0,0 +1,324 @@
+/*
+ * 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.recommendation;
+
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+import com.android.tv.data.Channel;
+import com.android.tv.recommendation.RecommendationUtils.ChannelRecordSortedMapHelper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+public class RecommenderTest extends AndroidTestCase {
+ private static final int DEFAULT_NUMBER_OF_CHANNELS = 5;
+ private static final long DEFAULT_WATCH_START_TIME_MS =
+ System.currentTimeMillis() - TimeUnit.DAYS.toMillis(2);
+ private static final long DEFAULT_WATCH_END_TIME_MS =
+ System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
+ private static final long DEFAULT_MAX_WATCH_DURATION_MS = TimeUnit.HOURS.toMillis(1);
+
+ private final Comparator<Channel> CHANNEL_SORT_KEY_COMPARATOR = new Comparator<Channel>() {
+ @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;
+ private FakeEvaluator mEvaluator;
+ private ChannelRecordSortedMapHelper mChannelRecordSortedMap;
+ private boolean mOnRecommenderReady;
+ private boolean mOnRecommendationChanged;
+ private Channel mChannel_1;
+ private Channel mChannel_2;
+ private Channel mChannel_3;
+ private Channel mChannel_4;
+
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mChannelRecordSortedMap = new ChannelRecordSortedMapHelper(getContext());
+ mDataManager = RecommendationUtils
+ .createMockRecommendationDataManager(mChannelRecordSortedMap);
+ mChannelRecordSortedMap.resetRandom(RecommendationUtils.createTestRandom());
+ }
+
+ public void testRecommendChannels_includeRecommendedOnly_allChannelsHaveNoScore() {
+ createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS);
+
+ // Recommender doesn't recommend any channels because all channels are not recommended.
+ assertEquals(0, mRecommender.recommendChannels().size());
+ assertEquals(0, mRecommender.recommendChannels(-5).size());
+ assertEquals(0, mRecommender.recommendChannels(0).size());
+ assertEquals(0, mRecommender.recommendChannels(3).size());
+ assertEquals(0, mRecommender.recommendChannels(4).size());
+ assertEquals(0, mRecommender.recommendChannels(5).size());
+ }
+
+ public void testRecommendChannels_notIncludeRecommendedOnly_allChannelsHaveNoScore() {
+ createRecommender(false, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS);
+
+ // Recommender recommends every channel because it recommends not-recommended channels too.
+ assertEquals(4, mRecommender.recommendChannels().size());
+ assertEquals(0, mRecommender.recommendChannels(-5).size());
+ assertEquals(0, mRecommender.recommendChannels(0).size());
+ assertEquals(3, mRecommender.recommendChannels(3).size());
+ assertEquals(4, mRecommender.recommendChannels(4).size());
+ assertEquals(4, mRecommender.recommendChannels(5).size());
+ }
+
+ public void testRecommendChannels_includeRecommendedOnly_allChannelsHaveScore() {
+ createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS);
+
+ setChannelScores_scoreIncreasesAsChannelIdIncreases();
+
+ // 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);
+ 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);
+ }
+
+ public void testRecommendChannels_notIncludeRecommendedOnly_allChannelsHaveScore() {
+ createRecommender(false, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS);
+
+ setChannelScores_scoreIncreasesAsChannelIdIncreases();
+
+ // 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);
+ 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);
+ }
+
+ public void testRecommendChannels_includeRecommendedOnly_fewChannelsHaveScore() {
+ createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS);
+
+ mEvaluator.setChannelScore(mChannel_1.getId(), 1.0);
+ 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);
+ 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);
+ }
+
+ public void testRecommendChannels_notIncludeRecommendedOnly_fewChannelsHaveScore() {
+ createRecommender(false, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS);
+
+ mEvaluator.setChannelScore(mChannel_1.getId(), 1.0);
+ mEvaluator.setChannelScore(mChannel_2.getId(), 1.0);
+
+ assertEquals(4, mRecommender.recommendChannels().size());
+ 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);
+
+ assertEquals(4, mRecommender.recommendChannels(4).size());
+ 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);
+ }
+
+ public void testGetChannelSortKey_recommendAllChannels() {
+ createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS);
+
+ setChannelScores_scoreIncreasesAsChannelIdIncreases();
+
+ List<Channel> expectedChannelList = mRecommender.recommendChannels();
+ List<Channel> channelList = Arrays.asList(mChannel_1, mChannel_2, mChannel_3, mChannel_4);
+ Collections.sort(channelList, CHANNEL_SORT_KEY_COMPARATOR);
+
+ // Recommended channel list and channel list sorted by sort key must be the same.
+ MoreAsserts.assertContentsInOrder(channelList, expectedChannelList.toArray());
+ assertSortKeyNotInvalid(channelList);
+ }
+
+ public void testGetChannelSortKey_recommendFewChannels() {
+ // Test with recommending 3 channels.
+ createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS);
+
+ setChannelScores_scoreIncreasesAsChannelIdIncreases();
+
+ List<Channel> 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,
+ mRecommender.getChannelSortKey(mChannel_1.getId()));
+
+ List<Channel> channelList = Arrays.asList(mChannel_2, mChannel_3, mChannel_4);
+ Collections.sort(channelList, CHANNEL_SORT_KEY_COMPARATOR);
+
+ MoreAsserts.assertContentsInOrder(channelList, expectedChannelList.toArray());
+ assertSortKeyNotInvalid(channelList);
+ }
+
+ public void testListener_onRecommendationChanged() {
+ createRecommender(true, START_DATAMANAGER_RUNNABLE_ADD_FOUR_CHANNELS);
+ // 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);
+
+ // Set lastRecommendationUpdatedTimeUtcMs to check recommendation changed because,
+ // recommender has a minimum recommendation update period.
+ mRecommender.setLastRecommendationUpdatedTimeUtcMs(
+ System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(10));
+ long latestWatchEndTimeMs = DEFAULT_WATCH_START_TIME_MS;
+ 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)));
+ latestWatchEndTimeMs += TimeUnit.MINUTES.toMillis(10);
+ }
+
+ // onRecommendationChanged must be called, because recommend channels are not empty,
+ // by setting score to each channel.
+ assertTrue(mOnRecommendationChanged);
+ }
+
+ 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);
+ }
+ });
+
+ // After loading channels and watch logs are finished, recommender must be available to use.
+ assertTrue(mOnRecommenderReady);
+ }
+
+ private void assertSortKeyNotInvalid(List<Channel> channelList) {
+ for (Channel channel : channelList) {
+ 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);
+
+ mEvaluator = new FakeEvaluator();
+ mRecommender.registerEvaluator(mEvaluator);
+ mChannelRecordSortedMap.setRecommender(mRecommender);
+
+ // When mRecommender is instantiated, its dataManager will be started, and load channels
+ // and watch history data if it is not started.
+ if (startDataManagerRunnable != null) {
+ startDataManagerRunnable.run();
+ mRecommender.onChannelRecordChanged();
+ }
+ // After loading channels and watch history data are finished,
+ // RecommendationDataManager calls listener.onChannelRecordLoaded()
+ // which will be mRecommender.onChannelRecordLoaded().
+ mRecommender.onChannelRecordLoaded();
+ }
+
+ private List<Long> getChannelIdListSorted() {
+ return new ArrayList<>(mChannelRecordSortedMap.keySet());
+ }
+
+ private void setChannelScores_scoreIncreasesAsChannelIdIncreases() {
+ List<Long> channelIdList = getChannelIdListSorted();
+ double score = Math.pow(0.5, channelIdList.size());
+ for (long channelId : channelIdList) {
+ // Channel with smaller id has smaller score than channel with higher id.
+ mEvaluator.setChannelScore(channelId, score);
+ score *= 2.0;
+ }
+ }
+
+ private class FakeEvaluator extends Recommender.Evaluator {
+ private Map<Long, Double> mChannelScore = new HashMap<>();
+
+ @Override
+ public double evaluateChannel(long channelId) {
+ if (getRecommender().getChannelRecord(channelId) == null) {
+ return NOT_RECOMMENDED;
+ }
+ Double score = mChannelScore.get(channelId);
+ return score == null ? NOT_RECOMMENDED : score;
+ }
+
+ public void setChannelScore(long channelId, double score) {
+ mChannelScore.put(channelId, score);
+ }
+ }
+}
diff --git a/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java b/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java
new file mode 100644
index 00000000..e3c92859
--- /dev/null
+++ b/tests/unit/src/com/android/tv/recommendation/RoutineWatchEvaluatorTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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.recommendation;
+
+import android.test.MoreAsserts;
+
+import com.android.tv.data.Program;
+import com.android.tv.recommendation.RoutineWatchEvaluator.ProgramTime;
+
+import java.util.Calendar;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class RoutineWatchEvaluatorTest extends EvaluatorTestCase<RoutineWatchEvaluator> {
+
+ public RoutineWatchEvaluator createEvaluator() {
+ return new RoutineWatchEvaluator();
+ }
+
+ public void testSplitTextToWords() {
+ assertSplitTextToWords("");
+ assertSplitTextToWords("Google", "Google");
+ assertSplitTextToWords("The Big Bang Theory", "The", "Big", "Bang", "Theory");
+ assertSplitTextToWords("Hello, world!", "Hello", "world");
+ assertSplitTextToWords("Adam's Rib", "Adam's", "Rib");
+ assertSplitTextToWords("G.I. Joe", "G.I", "Joe");
+ assertSplitTextToWords("A.I.", "A.I");
+ }
+
+ public void testCalculateMaximumMatchedWordSequenceLength() {
+ assertMaximumMatchedWordSequenceLength(0, "", "Google");
+ assertMaximumMatchedWordSequenceLength(2, "The Big Bang Theory", "Big Bang");
+ assertMaximumMatchedWordSequenceLength(2, "The Big Bang Theory", "Theory Of Big Bang");
+ assertMaximumMatchedWordSequenceLength(4, "The Big Bang Theory", "The Big Bang Theory");
+ assertMaximumMatchedWordSequenceLength(1, "Modern Family", "Family Guy");
+ assertMaximumMatchedWordSequenceLength(1, "The Simpsons", "The Walking Dead");
+ assertMaximumMatchedWordSequenceLength(3, "Game Of Thrones 1", "Game Of Thrones 6");
+ assertMaximumMatchedWordSequenceLength(0, "Dexter", "Friends");
+ }
+
+ public void testProgramTime_createFromProgram() {
+ Calendar time = Calendar.getInstance();
+ int todayDayOfWeek = time.get(Calendar.DAY_OF_WEEK);
+ // Value of DayOfWeek is between 1 and 7 (inclusive).
+ int tomorrowDayOfWeek = (todayDayOfWeek % 7) + 1;
+
+ // Today 00:00 - 01:00.
+ ProgramTime programTimeToday0000_0100 = ProgramTime.createFromProgram(
+ createDummyProgram(todayAtHourMin(0, 0), TimeUnit.HOURS.toMillis(1)));
+ assertProgramTime(todayDayOfWeek, hourMinuteToSec(0, 0), hourMinuteToSec(1, 0),
+ programTimeToday0000_0100);
+
+ // Today 23:30 - 24:30.
+ ProgramTime programTimeToday2330_2430 = ProgramTime.createFromProgram(
+ createDummyProgram(todayAtHourMin(23, 30), TimeUnit.HOURS.toMillis(1)));
+ assertProgramTime(todayDayOfWeek, hourMinuteToSec(23, 30), hourMinuteToSec(24, 30),
+ programTimeToday2330_2430);
+
+ // Tomorrow 00:00 - 01:00.
+ ProgramTime programTimeTomorrow0000_0100 = ProgramTime.createFromProgram(
+ createDummyProgram(tomorrowAtHourMin(0, 0), TimeUnit.HOURS.toMillis(1)));
+ assertProgramTime(tomorrowDayOfWeek, hourMinuteToSec(0, 0), hourMinuteToSec(1, 0),
+ programTimeTomorrow0000_0100);
+
+ // Tomorrow 23:30 - 24:30.
+ ProgramTime programTimeTomorrow2330_2430 = ProgramTime.createFromProgram(
+ createDummyProgram(tomorrowAtHourMin(23, 30), TimeUnit.HOURS.toMillis(1)));
+ assertProgramTime(tomorrowDayOfWeek, hourMinuteToSec(23, 30), hourMinuteToSec(24, 30),
+ programTimeTomorrow2330_2430);
+
+ // Today 18:00 - Tomorrow 12:00.
+ ProgramTime programTimeToday1800_3600 = ProgramTime.createFromProgram(
+ createDummyProgram(todayAtHourMin(18, 0), TimeUnit.HOURS.toMillis(18)));
+ // Maximum duration of ProgramTime is 12 hours.
+ // So, this program looks like it ends at Tomorrow 06:00 (30:00).
+ assertProgramTime(todayDayOfWeek, hourMinuteToSec(18, 0), hourMinuteToSec(30, 0),
+ programTimeToday1800_3600);
+ }
+
+ public void testCalculateOverlappedIntervalScore() {
+ // Today 21:00 - 24:00.
+ ProgramTime programTimeToday2100_2400 = ProgramTime.createFromProgram(
+ createDummyProgram(todayAtHourMin(21, 0), TimeUnit.HOURS.toMillis(3)));
+ // Today 22:00 - 01:00.
+ ProgramTime programTimeToday2200_0100 = ProgramTime.createFromProgram(
+ createDummyProgram(todayAtHourMin(22, 0), TimeUnit.HOURS.toMillis(3)));
+ // Tomorrow 00:00 - 03:00.
+ ProgramTime programTimeTomorrow0000_0300 = ProgramTime.createFromProgram(
+ createDummyProgram(tomorrowAtHourMin(0, 0), TimeUnit.HOURS.toMillis(3)));
+ // Tomorrow 20:00 - Tomorrow 23:00.
+ ProgramTime programTimeTomorrow2000_2300 = ProgramTime.createFromProgram(
+ createDummyProgram(tomorrowAtHourMin(20, 0), TimeUnit.HOURS.toMillis(3)));
+
+ // Check intersection time and commutative law in all cases.
+ int oneHourInSec = hourMinuteToSec(1, 0);
+ assertOverlappedIntervalScore(2 * oneHourInSec, true,
+ programTimeToday2100_2400, programTimeToday2200_0100);
+ assertOverlappedIntervalScore(0, false,
+ programTimeToday2100_2400, programTimeTomorrow0000_0300);
+ assertOverlappedIntervalScore(2 * oneHourInSec, false,
+ programTimeToday2100_2400, programTimeTomorrow2000_2300);
+ assertOverlappedIntervalScore(oneHourInSec, true,
+ programTimeToday2200_0100, programTimeTomorrow0000_0300);
+ assertOverlappedIntervalScore(oneHourInSec, false,
+ programTimeToday2200_0100, programTimeTomorrow2000_2300);
+ assertOverlappedIntervalScore(0, false,
+ programTimeTomorrow0000_0300, programTimeTomorrow2000_2300);
+ }
+
+ public void testGetTimeOfDayInSec() {
+ // Time was set as 00:00:00. So, getTimeOfDay must returns 0 (= 0 * 60 * 60 + 0 * 60 + 0).
+ assertEquals("TimeOfDayInSec", hourMinuteToSec(0, 0),
+ RoutineWatchEvaluator.getTimeOfDayInSec(todayAtHourMin(0, 0)));
+
+ // Time was set as 23:59:59. So, getTimeOfDay must returns 23 * 60 + 60 + 59 * 60 + 59.
+ assertEquals("TimeOfDayInSec", hourMinuteSecondToSec(23, 59, 59),
+ RoutineWatchEvaluator.getTimeOfDayInSec(todayAtHourMinSec(23, 59, 59)));
+ }
+
+ private void assertSplitTextToWords(String text, String... words) {
+ List<String> wordList = RoutineWatchEvaluator.splitTextToWords(text);
+ MoreAsserts.assertContentsInOrder(wordList, words);
+ }
+
+ private void assertMaximumMatchedWordSequenceLength(int expectedLength,
+ String text1, String text2) {
+ List<String> wordList1 = RoutineWatchEvaluator.splitTextToWords(text1);
+ List<String> wordList2 = RoutineWatchEvaluator.splitTextToWords(text2);
+ assertEquals("MaximumMatchedWordSequenceLength", expectedLength,
+ mEvaluator.calculateMaximumMatchedWordSequenceLength(wordList1, wordList2));
+ assertEquals("MaximumMatchedWordSequenceLength", expectedLength,
+ mEvaluator.calculateMaximumMatchedWordSequenceLength(wordList2, wordList1));
+ }
+
+ private void assertProgramTime(int expectedWeekDay, int expectedStartTimeOfDayInSec,
+ int expectedEndTimeOfDayInSec, ProgramTime actualProgramTime) {
+ assertEquals("Weekday", expectedWeekDay, actualProgramTime.weekDay);
+ assertEquals("StartTimeOfDayInSec",
+ expectedStartTimeOfDayInSec, actualProgramTime.startTimeOfDayInSec);
+ assertEquals("EndTimeOfDayInSec",
+ expectedEndTimeOfDayInSec, actualProgramTime.endTimeOfDayInSec);
+ }
+
+ private void assertOverlappedIntervalScore(int expectedSeconds, boolean overlappedOnSameDay,
+ ProgramTime t1, ProgramTime t2) {
+ double score = (double) expectedSeconds;
+ if (!overlappedOnSameDay) {
+ score *= RoutineWatchEvaluator.MULTIPLIER_FOR_UNMATCHED_DAY_OF_WEEK;
+ }
+ // Two tests for testing commutative law.
+ assertEquals("OverlappedIntervalScore",
+ score, mEvaluator.calculateOverlappedIntervalScore(t1, t2));
+ assertEquals("OverlappedIntervalScore",
+ score, mEvaluator.calculateOverlappedIntervalScore(t2, t1));
+ }
+
+ private int hourMinuteToSec(int hour, int minute) {
+ return hourMinuteSecondToSec(hour, minute, 0);
+ }
+
+ private int hourMinuteSecondToSec(int hour, int minute, int second) {
+ return hour * 60 * 60 + minute * 60 + second;
+ }
+
+ private Calendar todayAtHourMin(int hour, int minute) {
+ return todayAtHourMinSec(hour, minute, 0);
+ }
+
+ private Calendar todayAtHourMinSec(int hour, int minute, int second) {
+ Calendar time = Calendar.getInstance();
+ time.set(Calendar.HOUR_OF_DAY, hour);
+ time.set(Calendar.MINUTE, minute);
+ time.set(Calendar.SECOND, second);
+ return time;
+ }
+
+ private Calendar tomorrowAtHourMin(int hour, int minute) {
+ Calendar time = todayAtHourMin(hour, minute);
+ time.add(Calendar.DATE, 1);
+ return time;
+ }
+
+ private Program createDummyProgram(Calendar startTime, long programDurationMs) {
+ long startTimeMs = startTime.getTimeInMillis();
+
+ return new Program.Builder()
+ .setStartTimeUtcMillis(startTimeMs)
+ .setEndTimeUtcMillis(startTimeMs + programDurationMs)
+ .build();
+ }
+}
diff --git a/tests/src/com/android/tv/tests/TvActivityTest.java b/tests/unit/src/com/android/tv/tests/TvActivityTest.java
index 4b918826..92998ab3 100644
--- a/tests/src/com/android/tv/tests/TvActivityTest.java
+++ b/tests/unit/src/com/android/tv/tests/TvActivityTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -17,9 +17,11 @@
package com.android.tv.tests;
import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
import com.android.tv.TvActivity;
+@MediumTest
public class TvActivityTest extends ActivityInstrumentationTestCase2<TvActivity> {
public TvActivityTest() {
diff --git a/tests/unit/src/com/android/tv/ui/SetupViewTest.java b/tests/unit/src/com/android/tv/ui/SetupViewTest.java
new file mode 100644
index 00000000..ce3b79fb
--- /dev/null
+++ b/tests/unit/src/com/android/tv/ui/SetupViewTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+import android.content.pm.ResolveInfo;
+import android.media.tv.TvInputInfo;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+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.mockito.Matchers;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Test for {@link SetupView}
+ */
+@SmallTest
+public class SetupViewTest extends AndroidTestCase {
+ public void testComparator() throws Exception {
+ final LinkedHashMap<String, Boolean> INPUT_ID_TO_NEW_INPUT = new LinkedHashMap<>();
+ INPUT_ID_TO_NEW_INPUT.put("2_new_input", true);
+ INPUT_ID_TO_NEW_INPUT.put("4_new_input", true);
+ INPUT_ID_TO_NEW_INPUT.put("0_old_input", false);
+ INPUT_ID_TO_NEW_INPUT.put("1_old_input", false);
+ INPUT_ID_TO_NEW_INPUT.put("3_old_input", false);
+
+ SetupUtils setupUtils = Mockito.mock(SetupUtils.class);
+ Mockito.when(setupUtils.isNewInput(Matchers.anyString())).thenAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ String inputId = (String) invocation.getArguments()[0];
+ return INPUT_ID_TO_NEW_INPUT.get(inputId);
+ }
+ }
+ );
+ TvInputManagerHelper inputManager = Mockito.mock(TvInputManagerHelper.class);
+ Mockito.when(inputManager.getDefaultTvInputInfoComparator()).thenReturn(
+ new Comparator<TvInputInfo>() {
+ @Override
+ public int compare(TvInputInfo lhs, TvInputInfo rhs) {
+ return lhs.getId().compareTo(rhs.getId());
+ }
+ }
+ );
+ SetupView.TvInputInfoComparator comparator =
+ new SetupView.TvInputInfoComparator(setupUtils, inputManager);
+ ComparatorTester<TvInputInfo> comparatorTester =
+ ComparatorTester.withoutEqualsTest(comparator);
+ ResolveInfo resolveInfo = TestUtils.createResolveInfo("test", "test");
+ for (String id : INPUT_ID_TO_NEW_INPUT.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);
+ comparatorTester.addComparableGroup(info1, info2, info3);
+ }
+ comparatorTester.test();
+ }
+}
diff --git a/tests/unit/src/com/android/tv/util/FakeClock.java b/tests/unit/src/com/android/tv/util/FakeClock.java
new file mode 100644
index 00000000..a4ab2e4f
--- /dev/null
+++ b/tests/unit/src/com/android/tv/util/FakeClock.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.util;
+
+public class FakeClock implements Clock {
+ private long mCurrentTimeMillis;
+
+ public FakeClock() {
+ mCurrentTimeMillis = System.currentTimeMillis();
+ }
+
+ public void setCurrentTimeMillis(long time) {
+ mCurrentTimeMillis = time;
+ }
+
+ @Override
+ public long currentTimeMillis() {
+ return mCurrentTimeMillis;
+ }
+}
diff --git a/tests/unit/src/com/android/tv/util/ImageCacheTest.java b/tests/unit/src/com/android/tv/util/ImageCacheTest.java
new file mode 100644
index 00000000..a73b79fe
--- /dev/null
+++ b/tests/unit/src/com/android/tv/util/ImageCacheTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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 static com.android.tv.util.BitmapUtils.createScaledBitmapInfo;
+
+import android.graphics.Bitmap;
+
+import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link ImageCache}.
+ */
+public class ImageCacheTest extends TestCase {
+
+ private static final Bitmap ORIG = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);
+
+ private static final String KEY = "same";
+ private static final ScaledBitmapInfo INFO_200 = createScaledBitmapInfo(KEY, ORIG, 200, 200);
+ private static final ScaledBitmapInfo INFO_100 = createScaledBitmapInfo(KEY, ORIG, 100, 100);
+ private static final ScaledBitmapInfo INFO_50 = createScaledBitmapInfo(KEY, ORIG, 50, 50);
+ private static final ScaledBitmapInfo INFO_25 = createScaledBitmapInfo(KEY, ORIG, 25, 25);
+
+ private ImageCache mImageCache;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mImageCache = ImageCache.newInstance(0.1f);
+ }
+
+ //TODO: Empty the cache in the setup. Try using @VisibleForTesting
+
+ public void testPutIfLarger_smaller() throws Exception {
+
+ mImageCache.putIfNeeded( INFO_50);
+ assertSame("before", INFO_50, mImageCache.get(KEY));
+
+ mImageCache.putIfNeeded( INFO_25);
+ assertSame("after", INFO_50, mImageCache.get(KEY));
+ }
+
+ public void testPutIfLarger_larger() throws Exception {
+ mImageCache.putIfNeeded( INFO_50);
+ assertSame("before", INFO_50, mImageCache.get(KEY));
+
+ mImageCache.putIfNeeded(INFO_100);
+ assertSame("after", INFO_100, mImageCache.get(KEY));
+ }
+
+ public void testPutIfLarger_alreadyMax() throws Exception {
+
+ mImageCache.putIfNeeded( INFO_100);
+ assertSame("before", INFO_100, mImageCache.get(KEY));
+
+ mImageCache.putIfNeeded( INFO_200);
+ assertSame("after", INFO_100, mImageCache.get(KEY));
+ }
+}
diff --git a/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java b/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java
new file mode 100644
index 00000000..ef707470
--- /dev/null
+++ b/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java
@@ -0,0 +1,52 @@
+package com.android.tv.util;
+
+import android.graphics.Bitmap;
+import android.test.AndroidTestCase;
+
+import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
+
+/**
+ * Tests for {@link ScaledBitmapInfo}.
+ */
+public class ScaledBitmapInfoTest extends AndroidTestCase {
+
+ 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);
+
+ public void testSize_B100x100to50x50() {
+ ScaledBitmapInfo actual = BitmapUtils.createScaledBitmapInfo("B80x100", B80x100, 50, 50);
+ assertScaledBitmapSize(2, 40, 50, actual);
+ }
+
+ public void testNeedsToReload_B100x100to50x50() {
+ ScaledBitmapInfo actual = BitmapUtils.createScaledBitmapInfo("B80x100", B80x100, 50, 50);
+ assertNeedsToReload(false, actual, 25, 25);
+ assertNeedsToReload(false, actual, 50, 50);
+ assertNeedsToReload(false, actual, 99, 99);
+ assertNeedsToReload(true, actual, 100, 100);
+ assertNeedsToReload(true, actual, 101, 101);
+ }
+
+ /**
+ * Reproduces <a href="http://b/20488453">b/20488453</a>.
+ */
+ public void testBug20488453() {
+ 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 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
new file mode 100644
index 00000000..872e8c51
--- /dev/null
+++ b/tests/unit/src/com/android/tv/util/TestUtils.java
@@ -0,0 +1,65 @@
+/*
+ * 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.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.media.tv.TvInputInfo;
+
+import com.android.tv.common.TvCommonConstants;
+
+import java.lang.reflect.Constructor;
+
+/**
+ * A class that includes convenience methods for testing.
+ */
+public class TestUtils {
+ public static TvInputInfo createTvInputInfo(ResolveInfo service, String id, String parentId,
+ int type, boolean isHardwareInput) throws Exception {
+ // Create a mock TvInputInfo by using private constructor
+ // TODO: Find better way to mock TvInputInfo.
+ // Note that mockito doesn't support mock/spy on final object.
+ if (!TvCommonConstants.IS_MNC_PREVIEW && !TvCommonConstants.IS_MNC_OR_HIGHER) {
+ return createTvInputInfoForLmp(service, id, parentId, type);
+ }
+ return createTvInputInfoForMnc(service, id, parentId, type, isHardwareInput);
+ }
+
+ private static TvInputInfo createTvInputInfoForLmp(ResolveInfo service, String id,
+ String parentId, int type) throws Exception {
+ Constructor<TvInputInfo> constructor = TvInputInfo.class.getDeclaredConstructor(new Class[]{
+ ResolveInfo.class, String.class, String.class, int.class});
+ constructor.setAccessible(true);
+ return constructor.newInstance(service, id, parentId, type);
+ }
+
+ private static TvInputInfo createTvInputInfoForMnc(ResolveInfo service, String id,
+ String parentId, int type, boolean isHardwareInput) throws Exception {
+ Constructor<TvInputInfo> constructor = TvInputInfo.class.getDeclaredConstructor(new Class[]{
+ ResolveInfo.class, String.class, String.class, int.class, boolean.class});
+ constructor.setAccessible(true);
+ return constructor.newInstance(service, id, parentId, type, isHardwareInput);
+ }
+
+ public static ResolveInfo createResolveInfo(String packageName, String name) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = new ServiceInfo();
+ resolveInfo.serviceInfo.packageName = packageName;
+ resolveInfo.serviceInfo.name = name;
+ return resolveInfo;
+ }
+}
diff --git a/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java b/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java
new file mode 100644
index 00000000..6251d1a3
--- /dev/null
+++ b/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.pm.ResolveInfo;
+import android.media.tv.TvInputInfo;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.tv.testing.ComparatorTester;
+
+import java.util.LinkedHashMap;
+
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Test for {@link TvInputManagerHelper}
+ */
+@SmallTest
+public class TvInputManagerHelperTest extends AndroidTestCase {
+ public void testComparator() throws Exception {
+ final LinkedHashMap<String, Boolean> 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);
+
+ TvInputManagerHelper manager = Mockito.mock(TvInputManagerHelper.class);
+ Mockito.doAnswer(new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ TvInputInfo info = (TvInputInfo) invocation.getArguments()[0];
+ return INPUT_ID_TO_PARTNER_INPUT.get(info.getId());
+ }
+ }).when(manager).isPartnerInput(Mockito.<TvInputInfo>any());
+ Mockito.doAnswer(new Answer<String>() {
+ @Override
+ public String answer(InvocationOnMock invocation) throws Throwable {
+ TvInputInfo info = (TvInputInfo) invocation.getArguments()[0];
+ return info.getId();
+ }
+ }).when(manager).loadLabel(Mockito.<TvInputInfo>any());
+
+ ComparatorTester<TvInputInfo> 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);
+ }
+ comparatorTester.test();
+ }
+}
diff --git a/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java b/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java
new file mode 100644
index 00000000..b657f49c
--- /dev/null
+++ b/tests/unit/src/com/android/tv/util/TvTrackInfoUtilsTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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 static com.android.tv.util.TvTrackInfoUtils.getBestTrackInfo;
+
+import android.media.tv.TvTrackInfo;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.tv.testing.ComparatorTester;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Tests for {@link com.android.tv.util.TvTrackInfoUtils}.
+ */
+@SmallTest
+public class TvTrackInfoUtilsTest extends TestCase {
+
+ private static final String UN_MATCHED_ID = "no matching ID";
+
+ private static final TvTrackInfo INFO_1_EN_1 = create("1", "en", 1);
+
+ private static final TvTrackInfo INFO_2_EN_5 = create("2", "en", 5);
+
+ private static final TvTrackInfo INFO_3_FR_5 = create("3", "fr", 5);
+
+ private static TvTrackInfo create(String id, String fr, int audioChannelCount) {
+ return new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, id)
+ .setLanguage(fr)
+ .setAudioChannelCount(audioChannelCount)
+ .build();
+ }
+
+ private static final List<TvTrackInfo> ALL = Arrays.asList(INFO_1_EN_1, INFO_2_EN_5, INFO_3_FR_5);
+
+ public void testGetBestTrackInfo_empty() {
+ TvTrackInfo result = getBestTrackInfo(Collections.<TvTrackInfo>emptyList(),
+ UN_MATCHED_ID, "en", 1);
+ assertEquals("best track ", null, result);
+ }
+
+ public void testGetBestTrackInfo_exactMatch() {
+ TvTrackInfo result = getBestTrackInfo(ALL, "1", "en", 1);
+ assertEquals("best track ", INFO_1_EN_1, result);
+ }
+
+ public void testGetBestTrackInfo_langAndChannelCountMatch() {
+ TvTrackInfo result = getBestTrackInfo(ALL, UN_MATCHED_ID, "en", 5);
+ assertEquals("best track ", INFO_2_EN_5, result);
+ }
+
+ public void testGetBestTrackInfo_languageOnlyMatch() {
+ TvTrackInfo result = getBestTrackInfo(ALL, UN_MATCHED_ID, "fr", 1);
+ assertEquals("best track ", INFO_3_FR_5, result);
+ }
+
+ public void testGetBestTrackInfo_noMatches() {
+ TvTrackInfo result = getBestTrackInfo(ALL, UN_MATCHED_ID, "kr", 1);
+ assertEquals("best track ", INFO_1_EN_1, result);
+ }
+
+
+ public void testComparator() {
+ Comparator<TvTrackInfo> comparator = TvTrackInfoUtils.createComparator("1", "en", 1);
+ ComparatorTester.withoutEqualsTest(comparator)
+ // lang not match
+ .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
+ .addComparableGroup(create("2", "en", 1), create("3", "en", 1))
+ // 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
new file mode 100644
index 00000000..1cdda744
--- /dev/null
+++ b/tests/unit/src/com/android/tv/util/UtilsTest_GetDurationString.java
@@ -0,0 +1,250 @@
+/*
+ * 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.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import android.text.format.DateUtils;
+
+import java.lang.Exception;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+/**
+ * Tests for {@link com.android.tv.util.Utils#getDurationString}.
+ * <p/>
+ * 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 extends AndroidTestCase {
+ // TODO: Mock Context so we can specify current time and locale for test.
+ private Locale mLocale;
+ private static final long DATE_2015_2_1_MS = getFeb2015InMillis(1, 0, 0);
+
+ // All possible list for a paramter to test parameter independent result.
+ private static final boolean[] PARAM_USE_SHORT_FORMAT = {false, true};
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // Set locale to US
+ mLocale = Locale.getDefault();
+ Locale.setDefault(Locale.US);
+ }
+
+ /**
+ * Return time in millis assuming that whose year is 2015 and month is Jan.
+ */
+ private static long getJan2015InMillis(int date, int hour, int minutes) {
+ return new GregorianCalendar(
+ 2015, Calendar.JANUARY, date, hour, minutes).getTimeInMillis();
+ }
+
+ private static long getJan2015InMillis(int date, int hour) {
+ return getJan2015InMillis(date, hour, 0);
+ }
+
+ /**
+ * Return time in millis assuming that whose year is 2015 and month is Feb.
+ */
+ private static long getFeb2015InMillis(int date, int hour, int minutes) {
+ return new GregorianCalendar(
+ 2015, Calendar.FEBRUARY, date, hour, minutes).getTimeInMillis();
+ }
+
+ private static long getFeb2015InMillis(int date, int hour) {
+ return getFeb2015InMillis(date, hour, 0);
+ }
+
+ public void testSameDateAndTime() {
+ assertEquals("3:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(1, 3), getFeb2015InMillis(1, 3), false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("03:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(1, 3), getFeb2015InMillis(1, 3), false,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ public void testDurationWithinToday() {
+ assertEquals("12:00 – 3:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ DATE_2015_2_1_MS, getFeb2015InMillis(1, 3), false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("00:00 – 03:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ DATE_2015_2_1_MS, getFeb2015InMillis(1, 3), false,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ public void testDurationFromYesterdayToToday() {
+ assertEquals("Jan 31, 3:00 AM – Feb 1, 4:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getJan2015InMillis(31, 3), getFeb2015InMillis(1, 4), false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("Jan 31, 03:00 – Feb 1, 04:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getJan2015InMillis(31, 3), getFeb2015InMillis(1, 4), false,
+ DateUtils.FORMAT_24HOUR));
+ assertEquals("1/31, 11:30 PM – 12:30 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getJan2015InMillis(31, 23, 30), getFeb2015InMillis(1, 0, 30), true,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("1/31, 23:30 – 00:30",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getJan2015InMillis(31, 23, 30), getFeb2015InMillis(1, 0, 30), true,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ public void testDurationFromTodayToTomorrow() {
+ assertEquals("Feb 1, 3:00 AM – Feb 2, 4:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(1, 3), getFeb2015InMillis(2, 4), false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("Feb 1, 03:00 – Feb 2, 04:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(1, 3), getFeb2015InMillis(2, 4), false,
+ DateUtils.FORMAT_24HOUR));
+ assertEquals("2/1, 3:00 AM – 2/2, 4:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(1, 3), getFeb2015InMillis(2, 4), true,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("2/1, 03:00 – 2/2, 04:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(1, 3), getFeb2015InMillis(2, 4), true,
+ DateUtils.FORMAT_24HOUR));
+
+ assertEquals("Feb 1, 11:30 PM – Feb 2, 12:30 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(1, 23, 30), getFeb2015InMillis(2, 0, 30), false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("Feb 1, 23:30 – Feb 2, 00:30",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(1, 23, 30), getFeb2015InMillis(2, 0, 30), false,
+ DateUtils.FORMAT_24HOUR));
+ assertEquals("11:30 PM – 12:30 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(1, 23, 30), getFeb2015InMillis(2, 0, 30), true,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("23:30 – 00:30",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(1, 23, 30), getFeb2015InMillis(2, 0, 30), true,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ public void testDurationWithinTomorrow() {
+ assertEquals("Feb 2, 2:00 – 4:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(2, 2), getFeb2015InMillis(2, 4), false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("Feb 2, 02:00 – 04:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(2, 2), getFeb2015InMillis(2, 4), false,
+ DateUtils.FORMAT_24HOUR));
+ assertEquals("2/2, 2:00 – 4:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(2, 2), getFeb2015InMillis(2, 4), true,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("2/2, 02:00 – 04:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(2, 2), getFeb2015InMillis(2, 4), true,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ public void testStartOfDay() {
+ assertEquals("12:00 – 1:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ DATE_2015_2_1_MS, getFeb2015InMillis(1, 1), false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("00:00 – 01:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ DATE_2015_2_1_MS, getFeb2015InMillis(1, 1), false,
+ DateUtils.FORMAT_24HOUR));
+
+ assertEquals("Feb 2, 12:00 – 1:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(2, 0), getFeb2015InMillis(2, 1), false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("Feb 2, 00:00 – 01:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(2, 0), getFeb2015InMillis(2, 1), false,
+ DateUtils.FORMAT_24HOUR));
+ assertEquals("2/2, 12:00 – 1:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(2, 0), getFeb2015InMillis(2, 1), true,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("2/2, 00:00 – 01:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(2, 0), getFeb2015InMillis(2, 1), true,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ public void testEndOfDay() {
+ for (boolean useShortFormat : PARAM_USE_SHORT_FORMAT) {
+ assertEquals("11:00 PM – 12:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(1, 23), getFeb2015InMillis(2, 0), useShortFormat,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("23:00 – 00:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(1, 23), getFeb2015InMillis(2, 0), useShortFormat,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ assertEquals("Feb 2, 11:00 PM – 12:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(2, 23), getFeb2015InMillis(3, 0), false,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("Feb 2, 23:00 – 00:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(2, 23), getFeb2015InMillis(3, 0), false,
+ DateUtils.FORMAT_24HOUR));
+ assertEquals("2/2, 11:00 PM – 12:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(2, 23), getFeb2015InMillis(3, 0), true,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("2/2, 23:00 – 00:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ getFeb2015InMillis(2, 23), getFeb2015InMillis(3, 0), true,
+ DateUtils.FORMAT_24HOUR));
+ }
+
+ public void testMidnight() {
+ for (boolean useShortFormat : PARAM_USE_SHORT_FORMAT) {
+ assertEquals("12:00 AM",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ DATE_2015_2_1_MS, DATE_2015_2_1_MS, useShortFormat,
+ DateUtils.FORMAT_12HOUR));
+ assertEquals("00:00",
+ Utils.getDurationString(getContext(), DATE_2015_2_1_MS,
+ DATE_2015_2_1_MS, DATE_2015_2_1_MS, useShortFormat,
+ 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_IsInGivenDay.java b/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java
new file mode 100644
index 00000000..160a2231
--- /dev/null
+++ b/tests/unit/src/com/android/tv/util/UtilsTest_IsInGivenDay.java
@@ -0,0 +1,61 @@
+/*
+ * 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.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+/**
+ * Tests for {@link com.android.tv.util.Utils#isInGivenDay}.
+ */
+@SmallTest
+public class UtilsTest_IsInGivenDay extends AndroidTestCase {
+ public void testIsInGivenDay() {
+ assertTrue(Utils.isInGivenDay(
+ new GregorianCalendar(2015, Calendar.JANUARY, 1).getTimeInMillis(),
+ new GregorianCalendar(2015, Calendar.JANUARY, 1, 0, 30).getTimeInMillis()));
+ }
+
+ public void testIsNotInGivenDay() {
+ assertFalse(Utils.isInGivenDay(
+ new GregorianCalendar(2015, Calendar.JANUARY, 1).getTimeInMillis(),
+ new GregorianCalendar(2015, Calendar.JANUARY, 2).getTimeInMillis()));
+ }
+
+ public void testIfTimeZoneApplied() {
+ TimeZone timeZone = TimeZone.getDefault();
+
+ 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();
+
+ // 2015.01.01 10:00 in KST = 2015.01.01 01:00 in UTC
+ long date2015Start10AMMs =
+ new GregorianCalendar(2015, Calendar.JANUARY, 1, 10, 0).getTimeInMillis();
+
+ // Those two times aren't in the same day in UTC, but they are in KST.
+ assertTrue(Utils.isInGivenDay(date2015StartMs, date2015Start10AMMs));
+
+ TimeZone.setDefault(timeZone);
+ }
+}
diff --git a/version.mk b/version.mk
new file mode 100644
index 00000000..7e3b1117
--- /dev/null
+++ b/version.mk
@@ -0,0 +1,120 @@
+#
+# 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.
+#
+
+# The version code scheme for the package apk is:
+# Cmmbbbtad
+# where
+# M - major version (one or more digits)
+# C - code major version (for legacy resons this is M+3)
+# m - minor version (exactly 2)
+# bbb - automatically specified build number (exactly 3 digits)
+# t - build type (exactly 1 digit). Current valid values are:
+# 0 : eng build
+# 1 : build server build
+# a - device architecture (exactly 1 digit). Current valid values are:
+# 0 : non-native
+# 1 : armv5te
+# 3 : armv7-a
+# 5 : mips
+# 7 : x86
+# d - asset density (exactly 1 digit). Current valid values are:
+# 0 : all densities
+# Mmmbbb is specified manually. tad is automatically set during the build.
+#
+# For the client jar, the version code is agnostic to the target architecture and density: Mmbbbt00
+#
+# NOTE: arch needs to be more significant than density because x86 devices support running ARM
+# code in emulation mode, so all x86 versions must be higher than all ARM versions to ensure
+# we deliver true x86 code to those devices.
+#
+
+# Specify the following manually. Note that base_version_minor must be exactly 2 digit and
+# base_version_build must be exactly 3 digits.
+# Always submit version number changes as DO NOT MERGE
+
+
+base_version_major := 1
+# Change this for each branch
+base_version_minor := 05
+# The date of the first commit checked in to the current branch
+base_version_since := 2015-05-21
+
+# code_version_major will overflow at 22
+code_version_major := $(shell echo $$(($(base_version_major)+3)))
+
+git_commit_count := $(shell git --git-dir $(LOCAL_PATH)/.git rev-list --since=$(base_version_since) --no-merges --count HEAD)
+git_commit_count_usb := $(shell git --git-dir $(LOCAL_PATH)/../UsbTunerTvInput/.git rev-list --since=$(base_version_since) --no-merges --count HEAD)
+code_version_build := $(shell printf "%03d" $$(($(git_commit_count)+$(git_commit_count_usb))))
+
+
+#####################################################
+#####################################################
+# Collect automatic version code parameters
+ifneq "" "$(filter eng.%,$(BUILD_NUMBER))"
+ # This is an eng build
+ base_version_buildtype := 0
+else
+ # This is a build server build
+ base_version_buildtype := 1
+endif
+
+ifeq "$(TARGET_ARCH)" "x86"
+ base_version_arch := 7
+else ifeq "$(TARGET_ARCH)" "mips"
+ base_version_arch := 5
+else ifeq "$(TARGET_ARCH)" "arm"
+ ifeq ($(TARGET_ARCH_VARIANT),armv5te)
+ base_version_arch := 1
+ else
+ base_version_arch := 3
+ endif
+else
+ base_version_arch := 0
+endif
+
+# Currently supported densities.
+base_version_density := 0
+
+# Build the version code
+version_code_package := $(code_version_major)$(base_version_minor)$(code_version_build)$(base_version_buildtype)$(base_version_arch)$(base_version_density)
+
+# The version name scheme for the package apk is:
+# - For eng build (t=0): M.mm.bbb eng.$(USER)-hh-date-ad
+# - For build server (t=1): M.mm.bbb (nnnnnn-ad)
+# where nnnnnn is the build number from the build server (no zero-padding)
+# and hh is the git hash
+# On eng builds, the BUILD_NUMBER has the user and timestamp inline
+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)
+ 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))
+endif
+
+# Cleanup the locals
+code_version_major :=
+code_version_build :=
+base_version_major :=
+base_version_minor :=
+base_version_since :=
+base_version_buildtype :=
+base_version_arch :=
+base_version_density :=
+git_commit_count :=
+git_commit_count_usb :=
+git_hash :=
+date_string :=