aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Chalko <nchalko@google.com>2015-11-04 15:15:29 -0800
committerNick Chalko <nchalko@google.com>2015-11-10 13:59:03 -0800
commit7d67089aa1e9aa2123c3cd2f386d7019a1544db1 (patch)
tree6f90c2065a853628dd7704788dd41b787acb6fae
parent07b043dc3db83d6d20f0e8513b946830ab00e37b (diff)
downloadTV-7d67089aa1e9aa2123c3cd2f386d7019a1544db1.tar.gz
Sync to ub-tv-glee at 1.07.007
hash dce17da9f45fc4304787b1898d9915511b1df954 Change-Id: I08ac6fc0123a6653644281155e35c11b71bc5fa0
-rw-r--r--Android.mk6
-rw-r--r--AndroidManifest.xml24
-rw-r--r--assets/licenses.html92
-rw-r--r--common/Android.mk12
-rw-r--r--common/AndroidManifest.xml22
-rw-r--r--common/res/drawable/setup_action_button_done.xml30
-rw-r--r--common/res/layout/activity_stepped_setup.xml22
-rw-r--r--common/res/layout/fragment_setup_multi_pane.xml37
-rw-r--r--common/res/values/integers.xml21
-rw-r--r--common/res/values/strings.xml21
-rw-r--r--common/res/values/styles.xml22
-rw-r--r--common/res/values/themes.xml22
-rw-r--r--common/src/com/android/tv/common/BooleanSystemProperty.java (renamed from src/com/android/tv/util/BooleanSystemProperty.java)12
-rw-r--r--common/src/com/android/tv/common/TvCommonConstants.java25
-rw-r--r--common/src/com/android/tv/common/feature/Feature.java38
-rw-r--r--common/src/com/android/tv/common/feature/FeatureUtils.java86
-rw-r--r--common/src/com/android/tv/common/feature/GServiceFeature.java41
-rw-r--r--common/src/com/android/tv/common/feature/PropertyFeature.java52
-rw-r--r--common/src/com/android/tv/common/ui/setup/OnActionClickListener.java29
-rw-r--r--common/src/com/android/tv/common/ui/setup/SetupActionHelper.java33
-rw-r--r--common/src/com/android/tv/common/ui/setup/SetupFragment.java106
-rw-r--r--common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java50
-rw-r--r--common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java64
-rw-r--r--common/src/com/android/tv/common/ui/setup/SetupStep.java77
-rw-r--r--common/src/com/android/tv/common/ui/setup/SteppedSetupActivity.java113
-rw-r--r--proguard.flags13
-rw-r--r--res/anim/select_input_activity_enter.xml30
-rw-r--r--res/anim/select_input_activity_exit.xml30
-rw-r--r--res/animator/channel_banner_enter.xml1
-rw-r--r--res/animator/channel_banner_exit.xml1
-rw-r--r--res/animator/program_guide_side_panel_text_state_animator.xml2
-rw-r--r--res/drawable-xhdpi/ic_message_lock_no_permission.pngbin0 -> 3553 bytes
-rw-r--r--res/drawable/channel_card_progress_bar.xml2
-rw-r--r--res/drawable/program_guide_table_header_column_last_item_background.xml2
-rw-r--r--res/drawable/program_guide_table_item_background.xml2
-rw-r--r--res/drawable/program_guide_time_indicator.xml2
-rw-r--r--res/layout/activity_select_input.xml24
-rw-r--r--res/layout/fragment_welcome.xml65
-rw-r--r--res/layout/fragment_welcome_page.xml45
-rw-r--r--res/layout/option_item_switch.xml2
-rw-r--r--res/layout/program_guide_table_header_row_item.xml2
-rw-r--r--res/layout/program_guide_table_item.xml2
-rw-r--r--res/layout/tunable_tv_view.xml12
-rw-r--r--res/values-af/strings.xml10
-rw-r--r--res/values-am/strings.xml8
-rw-r--r--res/values-ar/strings.xml8
-rw-r--r--res/values-bg/strings.xml8
-rw-r--r--res/values-bn-rBD/strings.xml8
-rw-r--r--res/values-ca/strings.xml8
-rw-r--r--res/values-cs/strings.xml8
-rw-r--r--res/values-da/strings.xml14
-rw-r--r--res/values-de/strings.xml8
-rw-r--r--res/values-el/strings.xml8
-rw-r--r--res/values-en-rAU/strings.xml8
-rw-r--r--res/values-en-rGB/strings.xml8
-rw-r--r--res/values-en-rIN/strings.xml8
-rw-r--r--res/values-es-rUS/strings.xml8
-rw-r--r--res/values-es/strings.xml8
-rw-r--r--res/values-et-rEE/strings.xml8
-rw-r--r--res/values-eu-rES/strings.xml8
-rw-r--r--res/values-fa/strings.xml10
-rw-r--r--res/values-fi/strings.xml8
-rw-r--r--res/values-fr-rCA/strings.xml8
-rw-r--r--res/values-fr/strings.xml8
-rw-r--r--res/values-gl-rES/strings.xml8
-rw-r--r--res/values-hi/strings.xml8
-rw-r--r--res/values-hr/strings.xml8
-rw-r--r--res/values-hu/strings.xml24
-rw-r--r--res/values-hy-rAM/strings.xml8
-rw-r--r--res/values-in/strings.xml8
-rw-r--r--res/values-is-rIS/strings.xml8
-rw-r--r--res/values-it/strings.xml12
-rw-r--r--res/values-iw/strings.xml8
-rw-r--r--res/values-ja/strings.xml8
-rw-r--r--res/values-ka-rGE/strings.xml8
-rw-r--r--res/values-kk-rKZ/strings.xml8
-rw-r--r--res/values-km-rKH/strings.xml8
-rw-r--r--res/values-kn-rIN/strings.xml8
-rw-r--r--res/values-ko/strings.xml8
-rw-r--r--res/values-ky-rKG/strings.xml8
-rw-r--r--res/values-ldrtl/integers.xml2
-rw-r--r--res/values-lo-rLA/strings.xml8
-rw-r--r--res/values-lt/strings.xml12
-rw-r--r--res/values-lv/strings.xml8
-rw-r--r--res/values-mk-rMK/strings.xml8
-rw-r--r--res/values-ml-rIN/strings.xml8
-rw-r--r--res/values-mn-rMN/strings.xml8
-rw-r--r--res/values-mr-rIN/strings.xml8
-rw-r--r--res/values-ms-rMY/strings.xml8
-rw-r--r--res/values-my-rMM/strings.xml8
-rw-r--r--res/values-nb/strings.xml8
-rw-r--r--res/values-ne-rNP/strings.xml8
-rw-r--r--res/values-nl/strings.xml24
-rw-r--r--res/values-pl/strings.xml10
-rw-r--r--res/values-pt-rPT/strings.xml10
-rw-r--r--res/values-pt/strings.xml8
-rw-r--r--res/values-ro/strings.xml10
-rw-r--r--res/values-ru/strings.xml8
-rw-r--r--res/values-si-rLK/strings.xml8
-rw-r--r--res/values-sk/strings.xml8
-rw-r--r--res/values-sl/strings.xml8
-rw-r--r--res/values-sr/strings.xml8
-rw-r--r--res/values-sv/strings.xml8
-rw-r--r--res/values-sw/strings.xml8
-rw-r--r--res/values-ta-rIN/strings.xml8
-rw-r--r--res/values-te-rIN/strings.xml8
-rw-r--r--res/values-th/strings.xml8
-rw-r--r--res/values-tl/strings.xml8
-rw-r--r--res/values-tr/strings.xml8
-rw-r--r--res/values-uk/strings.xml8
-rw-r--r--res/values-ur-rPK/strings.xml8
-rw-r--r--res/values-uz-rUZ/strings.xml8
-rw-r--r--res/values-vi/strings.xml8
-rw-r--r--res/values-zh-rCN/strings.xml8
-rw-r--r--res/values-zh-rHK/strings.xml8
-rw-r--r--res/values-zh-rTW/strings.xml8
-rw-r--r--res/values-zu/strings.xml8
-rw-r--r--res/values/arrays.xml18
-rw-r--r--res/values/attr.xml2
-rw-r--r--res/values/colors.xml4
-rw-r--r--res/values/dimens.xml23
-rw-r--r--res/values/strings.xml55
-rw-r--r--res/values/styles.xml13
-rw-r--r--res/values/themes.xml8
-rw-r--r--src/com/android/tv/ChannelTuner.java50
-rw-r--r--src/com/android/tv/Features.java66
-rw-r--r--src/com/android/tv/MainActivity.java286
-rw-r--r--src/com/android/tv/SelectInputActivity.java99
-rw-r--r--src/com/android/tv/SetupPassthroughActivity.java93
-rw-r--r--src/com/android/tv/TvApplication.java154
-rw-r--r--src/com/android/tv/analytics/Analytics.java18
-rw-r--r--src/com/android/tv/analytics/ConfigurationInfo.java30
-rw-r--r--src/com/android/tv/analytics/OptOutPreferenceHelper.java112
-rw-r--r--src/com/android/tv/analytics/SendConfigInfoRunnable.java53
-rw-r--r--src/com/android/tv/analytics/StubAnalytics.java19
-rw-r--r--src/com/android/tv/analytics/StubTracker.java12
-rw-r--r--src/com/android/tv/analytics/Tracker.java26
-rw-r--r--src/com/android/tv/data/Channel.java2
-rw-r--r--src/com/android/tv/data/ChannelDataManager.java75
-rw-r--r--src/com/android/tv/data/ChannelLogoFetcher.java28
-rw-r--r--src/com/android/tv/data/ChannelNumber.java35
-rw-r--r--src/com/android/tv/data/ProgramDataManager.java135
-rw-r--r--src/com/android/tv/data/WatchedHistoryManager.java323
-rw-r--r--src/com/android/tv/guide/ProgramGrid.java7
-rw-r--r--src/com/android/tv/guide/ProgramGuide.java6
-rw-r--r--src/com/android/tv/guide/ProgramItemView.java8
-rw-r--r--src/com/android/tv/guide/ProgramManager.java64
-rw-r--r--src/com/android/tv/guide/ProgramTableAdapter.java12
-rw-r--r--src/com/android/tv/menu/AppLinkCardView.java32
-rw-r--r--src/com/android/tv/menu/ChannelCardView.java10
-rw-r--r--src/com/android/tv/menu/ChannelsPosterPrefetcher.java4
-rw-r--r--src/com/android/tv/menu/ChannelsRowAdapter.java4
-rw-r--r--src/com/android/tv/menu/Menu.java16
-rw-r--r--src/com/android/tv/menu/MenuLayoutManager.java28
-rw-r--r--src/com/android/tv/menu/MenuRowView.java4
-rw-r--r--src/com/android/tv/menu/TvOptionsRowAdapter.java9
-rw-r--r--src/com/android/tv/onboarding/AppOverviewFragment.java106
-rw-r--r--src/com/android/tv/onboarding/OnboardingActivity.java234
-rw-r--r--src/com/android/tv/onboarding/PagingIndicator.java235
-rw-r--r--src/com/android/tv/onboarding/SetupSourcesFragment.java64
-rw-r--r--src/com/android/tv/onboarding/WelcomeFragment.java101
-rw-r--r--src/com/android/tv/onboarding/WelcomePageFragment.java49
-rw-r--r--src/com/android/tv/receiver/AudioCapabilitiesReceiver.java62
-rw-r--r--src/com/android/tv/receiver/BootCompletedReceiver.java62
-rw-r--r--src/com/android/tv/receiver/PackageIntentsReceiver.java35
-rw-r--r--src/com/android/tv/recommendation/NotificationService.java25
-rw-r--r--src/com/android/tv/recommendation/RecentChannelEvaluator.java2
-rw-r--r--src/com/android/tv/recommendation/RecommendationDataManager.java58
-rw-r--r--src/com/android/tv/search/DataManagerSearch.java254
-rw-r--r--src/com/android/tv/search/LocalSearchProvider.java16
-rw-r--r--src/com/android/tv/search/ProgramGuideSearchFragment.java11
-rw-r--r--src/com/android/tv/search/SearchInterface.java41
-rw-r--r--src/com/android/tv/search/TvProviderSearch.java18
-rw-r--r--src/com/android/tv/ui/ChannelBannerView.java5
-rw-r--r--src/com/android/tv/ui/KeypadChannelSwitchView.java11
-rw-r--r--src/com/android/tv/ui/SelectInputView.java170
-rw-r--r--src/com/android/tv/ui/SetupView.java2
-rw-r--r--src/com/android/tv/ui/TunableTvView.java87
-rw-r--r--src/com/android/tv/ui/TvTransitionManager.java1
-rw-r--r--src/com/android/tv/ui/TvViewUiManager.java6
-rw-r--r--src/com/android/tv/ui/sidepanel/AboutFragment.java84
-rw-r--r--src/com/android/tv/ui/sidepanel/ActionItem.java2
-rw-r--r--src/com/android/tv/ui/sidepanel/ChannelSourcesFragment.java2
-rw-r--r--src/com/android/tv/ui/sidepanel/CheckBoxItem.java2
-rw-r--r--src/com/android/tv/ui/sidepanel/Item.java2
-rw-r--r--src/com/android/tv/ui/sidepanel/RadioButtonItem.java2
-rw-r--r--src/com/android/tv/ui/sidepanel/SwitchItem.java10
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java2
-rw-r--r--src/com/android/tv/util/AsyncDbTask.java3
-rw-r--r--src/com/android/tv/util/Clock.java2
-rw-r--r--src/com/android/tv/util/CollectionUtils.java41
-rw-r--r--src/com/android/tv/util/EngOnlyFeature.java (renamed from src/com/android/tv/receiver/NotificationReceiver.java)20
-rw-r--r--src/com/android/tv/util/ImageCache.java3
-rw-r--r--src/com/android/tv/util/MainThreadExecutor.java86
-rw-r--r--src/com/android/tv/util/MemoryManageable.java29
-rw-r--r--src/com/android/tv/util/MultiLongSparseArray.java117
-rw-r--r--src/com/android/tv/util/OnboardingUtils.java99
-rw-r--r--src/com/android/tv/util/PermissionUtils.java70
-rw-r--r--src/com/android/tv/util/PipInputManager.java3
-rw-r--r--src/com/android/tv/util/RecurringRunner.java2
-rw-r--r--src/com/android/tv/util/SetupUtils.java98
-rw-r--r--src/com/android/tv/util/SoftPreconditions.java146
-rw-r--r--src/com/android/tv/util/SystemProperties.java8
-rw-r--r--src/com/android/tv/util/TvInputManagerHelper.java26
-rw-r--r--src/com/android/tv/util/Utils.java122
-rw-r--r--tests/common/Android.mk2
-rw-r--r--tests/common/src/com/android/tv/testing/ChannelInfo.java2
-rw-r--r--tests/common/src/com/android/tv/testing/ComparatorTester.java2
-rw-r--r--tests/common/src/com/android/tv/testing/Utils.java55
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/ChannelStateData.aidl2
-rw-r--r--tests/common/src/com/android/tv/testing/testinput/ITestInputControl.aidl2
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java2
-rw-r--r--tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java4
-rw-r--r--tests/func/Android.mk2
-rw-r--r--tests/func/src/com/android/tv/tests/ui/AboutFragmentTest.java57
-rw-r--r--tests/input/src/com/android/tv/testinput/TestTvInputService.java2
-rw-r--r--tests/jank/Android.mk2
-rw-r--r--tests/unit/Android.mk2
-rw-r--r--tests/unit/src/com/android/tv/BaseMainActivityTestCase.java3
-rw-r--r--tests/unit/src/com/android/tv/FeaturesTest.java34
-rw-r--r--tests/unit/src/com/android/tv/MainActivityTest.java2
-rw-r--r--tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java44
-rw-r--r--tests/unit/src/com/android/tv/data/ChannelNumberTest.java5
-rw-r--r--tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java38
-rw-r--r--tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java146
-rw-r--r--tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java4
-rw-r--r--tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java4
-rw-r--r--tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java24
-rw-r--r--tests/unit/src/com/android/tv/recommendation/RecommenderTest.java3
-rw-r--r--tests/unit/src/com/android/tv/util/ImageCacheTest.java2
-rw-r--r--tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java100
-rw-r--r--tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java85
-rw-r--r--version.mk8
233 files changed, 6369 insertions, 784 deletions
diff --git a/Android.mk b/Android.mk
index fa917317..9558c0e0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -31,7 +31,7 @@ include $(LOCAL_PATH)/buildconfig.mk
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(LOCAL_BUILDCONFIG_CLASS)
-LOCAL_PACKAGE_NAME := TV
+LOCAL_PACKAGE_NAME := LiveTv
# It is required for com.android.providers.tv.permission.ALL_EPG_DATA
LOCAL_PRIVILEGED_MODULE := true
@@ -40,6 +40,7 @@ LOCAL_SDK_VERSION := system_current
LOCAL_RESOURCE_DIR := \
$(TOP)/prebuilts/sdk/current/support/v7/recyclerview/res \
$(TOP)/prebuilts/sdk/current/support/v17/leanback/res \
+ $(LOCAL_PATH)/common/res \
$(LOCAL_PATH)/res
LOCAL_STATIC_JAVA_LIBRARIES := \
@@ -50,20 +51,19 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-v17-leanback \
tv-common
-
LOCAL_JAVACFLAGS := -Xlint:deprecation -Xlint:unchecked
LOCAL_AAPT_FLAGS := --auto-add-overlay \
--extra-packages android.support.v7.recyclerview \
--extra-packages android.support.v17.leanback \
+ --extra-packages com.android.tv.common \
--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 2fcba3de..1f2db39f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.GLOBAL_SEARCH" tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_TV_LISTINGS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.MODIFY_PARENTAL_CONTROLS" />
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
@@ -29,9 +30,6 @@
<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" />
<!-- Limit only for Android TV -->
<uses-feature android:name="android.software.leanback" android:required="true" />
@@ -83,6 +81,21 @@
<activity android:name=".LauncherActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+ <activity android:name=".SetupPassthroughActivity"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar">
+ <intent-filter>
+ <action android:name="com.android.tv.intent.action.INPUT_SETUP" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".SelectInputActivity"
+ android:launchMode="singleTask"
+ android:theme="@style/Theme.SelectInputActivity" />
+
+ <activity android:name=".onboarding.OnboardingActivity"
+ android:launchMode="singleTop" />
+
<provider android:name="com.android.tv.search.LocalSearchProvider"
android:authorities="com.android.tv.search"
android:exported="true"
@@ -93,7 +106,7 @@
<service android:name="com.android.tv.recommendation.NotificationService"
android:exported="false" />
- <receiver android:name="com.android.tv.receiver.NotificationReceiver">
+ <receiver android:name="com.android.tv.receiver.BootCompletedReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
@@ -101,7 +114,7 @@
<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-->
+ <!-- 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"/>
@@ -124,6 +137,5 @@
android:resource="@xml/tv_content_rating_systems" />
</receiver>
-
</application>
</manifest>
diff --git a/assets/licenses.html b/assets/licenses.html
new file mode 100644
index 00000000..83b740a0
--- /dev/null
+++ b/assets/licenses.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>
+<!-- START Live Channels licenses -->
+<h3>Notices for files:</h3>
+<ul>
+ <li>protobuf</li>
+</ul>
+<pre>Copyright 2008, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Code generated by the Protocol Buffer compiler is owned by the owner
+of the input file used when generating it. This code is not
+standalone and requires a support library to be linked with it. This
+support library is itself covered by the above license.
+</pre>
+
+<!-- END Live Channels licenses -->
+<!-- START USB Tuner licenses -->
+<h3>Notices for files:</h3>
+<ul>
+ <li>icu4j</li>
+</ul>
+<pre>Copyright (c) 1995-2013 International Business Machines Corporation and others
+
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, and/or sell copies of the
+Software, and to permit persons to whom the Software is furnished to do so,
+provided that the above copyright notice(s) and this permission notice appear
+in all copies of the Software and that both the above copyright notice(s) and
+this permission notice appear in supporting documentation.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
+NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE
+LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY
+DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder shall not be
+used in advertising or otherwise to promote the sale, use or other dealings in
+this Software without prior written authorization of the copyright holder.
+</pre>
+<!-- END USB Tuner licenses -->
+
+</body></html> \ No newline at end of file
diff --git a/common/Android.mk b/common/Android.mk
index 7ee4e043..d0c5988b 100644
--- a/common/Android.mk
+++ b/common/Android.mk
@@ -8,7 +8,19 @@ LOCAL_MODULE := tv-common
LOCAL_MODULE_TAGS := optional
LOCAL_SDK_VERSION := current
+LOCAL_RESOURCE_DIR := \
+ $(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-recyclerview \
+ android-support-v17-leanback \
+
+LOCAL_AAPT_FLAGS := --auto-add-overlay \
+ --extra-packages android.support.v7.recyclerview \
+ --extra-packages android.support.v17.leanback \
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/common/AndroidManifest.xml b/common/AndroidManifest.xml
new file mode 100644
index 00000000..19fb2a00
--- /dev/null
+++ b/common/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.common"
+ android:versionCode="1">
+ <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21"/>
+ <application />
+</manifest>
diff --git a/common/res/drawable/setup_action_button_done.xml b/common/res/drawable/setup_action_button_done.xml
new file mode 100644
index 00000000..66687282
--- /dev/null
+++ b/common/res/drawable/setup_action_button_done.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.
+ -->
+
+<!-- STOPSHIP: Remove this resource once setup page is implemented correctly. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:state_focused="true">
+ <shape>
+ <solid android:color="#01278B"/>
+ </shape>
+ </item>
+ <item>
+ <shape>
+ <solid android:color="#01274B"/>
+ </shape>
+ </item>
+</selector>
diff --git a/common/res/layout/activity_stepped_setup.xml b/common/res/layout/activity_stepped_setup.xml
new file mode 100644
index 00000000..f742c539
--- /dev/null
+++ b/common/res/layout/activity_stepped_setup.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:keepScreenOn="true" />
diff --git a/common/res/layout/fragment_setup_multi_pane.xml b/common/res/layout/fragment_setup_multi_pane.xml
new file mode 100644
index 00000000..4918a2fb
--- /dev/null
+++ b/common/res/layout/fragment_setup_multi_pane.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.
+ -->
+
+<!-- The whole XML will be changed once it's implemented with GuidedStepFragment -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+ <!-- The layout_width should not be 0dp for GuidedStepFragment. see b/24738452 -->
+ <FrameLayout
+ android:id="@+id/guided_step_fragment_container"
+ android:layout_width="200dp"
+ android:layout_height="match_parent"
+ android:layout_weight="6"
+ android:background="#01579B" />
+ <Button
+ android:id="@+id/button_done"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/setup_action_button_done"
+ android:text="@string/action_text_done" />
+</LinearLayout>
diff --git a/common/res/values/integers.xml b/common/res/values/integers.xml
new file mode 100644
index 00000000..0ec544dc
--- /dev/null
+++ b/common/res/values/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>
+ <!-- Setup screen -->
+ <integer name="setup_slide_anim_duration">250</integer>
+</resources>
diff --git a/common/res/values/strings.xml b/common/res/values/strings.xml
new file mode 100644
index 00000000..25782443
--- /dev/null
+++ b/common/res/values/strings.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>
+ <!-- TODO: Change translatable to true once implemented. -->
+ <string name="action_text_done" translatable="false">Done</string>
+</resources>
diff --git a/common/res/values/styles.xml b/common/res/values/styles.xml
new file mode 100644
index 00000000..fa551906
--- /dev/null
+++ b/common/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="Widget.Setup.GuidedActionsContainerStyle" parent="Widget.Leanback.GuidedActionsContainerStyle">
+ <item name="android:background">@android:color/transparent</item>
+ </style>
+</resources>
diff --git a/common/res/values/themes.xml b/common/res/values/themes.xml
new file mode 100644
index 00000000..ae76cf90
--- /dev/null
+++ b/common/res/values/themes.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.Setup.GuidedStep" parent="Theme.Leanback.GuidedStep">
+ <item name="guidedActionsContainerStyle">@style/Widget.Setup.GuidedActionsContainerStyle</item>
+ </style>
+</resources>
diff --git a/src/com/android/tv/util/BooleanSystemProperty.java b/common/src/com/android/tv/common/BooleanSystemProperty.java
index 6786868e..77a6023c 100644
--- a/src/com/android/tv/util/BooleanSystemProperty.java
+++ b/common/src/com/android/tv/common/BooleanSystemProperty.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tv.util;
+package com.android.tv.common;
import android.util.Log;
@@ -32,7 +32,7 @@ import java.util.List;
* "https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/SystemProperties.java"
* >android.os.SystemProperties.getBoolean</a>.
*/
-public final class BooleanSystemProperty {
+public class BooleanSystemProperty {
private final static String TAG = "BooleanSystemProperty";
private final static boolean DEBUG = false;
private static final List<BooleanSystemProperty> ALL_PROPERTIES = new ArrayList<>();
@@ -40,6 +40,12 @@ public final class BooleanSystemProperty {
private final String mKey;
private Boolean mValue = null;
+ /**
+ * Create a boolean system property.
+ *
+ * @param key the system property key.
+ * @param defaultValue the value to return if the property is undefined or empty.
+ */
public BooleanSystemProperty(String key, boolean defaultValue) {
mDefaultValue = defaultValue;
mKey = key;
@@ -55,7 +61,7 @@ public final class BooleanSystemProperty {
/**
* Gets system properties set by <code>adb shell setprop <em>key</em> <em>value</em></code>
*
- * @param key the property key.
+ * @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.
*/
diff --git a/common/src/com/android/tv/common/TvCommonConstants.java b/common/src/com/android/tv/common/TvCommonConstants.java
index 6b2cf14a..e01eb52f 100644
--- a/common/src/com/android/tv/common/TvCommonConstants.java
+++ b/common/src/com/android/tv/common/TvCommonConstants.java
@@ -32,6 +32,31 @@ public final class TvCommonConstants {
*/
public static final boolean HAS_TIME_SHIFT_API = Build.VERSION.SDK_INT >= 23;
+ /**
+ * An intent action to launch setup activity of a TV input. The intent should include
+ * TV input ID in the value of {@link EXTRA_INPUT_ID}. Optionally, given the value of
+ * {@link EXTRA_ACTIVITY_AFTER_COMPLETION}, the activity will be launched after the setup
+ * activity successfully finishes.
+ */
+ public static final String INTENT_ACTION_INPUT_SETUP =
+ "com.android.tv.intent.action.INPUT_SETUP";
+ /**
+ * A constant for the key to indicate a TV input ID for the intent action
+ * {@link INTENT_ACTION_INPUT_SETUP}.
+ *
+ * <p>Value type: String
+ */
+ public static final String EXTRA_INPUT_ID =
+ "com.android.tv.intent.extra.INPUT_ID";
+ /**
+ * A constant for the key to indicate an Activity launch intent for the intent action
+ * {@link INTENT_ACTION_INPUT_SETUP}.
+ *
+ * <p>Value type: Intent (Parcelable)
+ */
+ public static final String EXTRA_ACTIVITY_AFTER_COMPLETION =
+ "com.android.tv.intent.extra.ACTIVITY_AFTER_COMPLETION";
+
private TvCommonConstants() {
}
}
diff --git a/common/src/com/android/tv/common/feature/Feature.java b/common/src/com/android/tv/common/feature/Feature.java
new file mode 100644
index 00000000..b3a8336c
--- /dev/null
+++ b/common/src/com/android/tv/common/feature/Feature.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.common.feature;
+
+import android.content.Context;
+
+/**
+ * A feature is elements of code that are turned off for most users until a feature is fully
+ * launched.
+ *
+ * <p>Expected usage is:
+ * <pre>{@code
+ * if (MY_FEATURE.isEnabled(context) {
+ * showNewCoolUi();
+ * } else{
+ * showOldBoringUi();
+ * }
+ * }</pre>
+ */
+public interface Feature {
+ boolean isEnabled(Context context);
+
+
+}
diff --git a/common/src/com/android/tv/common/feature/FeatureUtils.java b/common/src/com/android/tv/common/feature/FeatureUtils.java
new file mode 100644
index 00000000..2a676948
--- /dev/null
+++ b/common/src/com/android/tv/common/feature/FeatureUtils.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.common.feature;
+
+import android.content.Context;
+
+/**
+ * Static utilities for features.
+ */
+public class FeatureUtils {
+
+ /**
+ * Returns a feature that is enabled if any of {@code features} is enabled.
+ *
+ * @param features the features to or
+ */
+ public static Feature OR(final Feature... features) {
+ return new Feature() {
+ @Override
+ public boolean isEnabled(Context context) {
+ for (Feature f : features) {
+ if (f.isEnabled(context)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Returns a feature that is enabled if all of {@code features} is enabled.
+ *
+ * @param features the features to and
+ */
+ public static Feature AND(final Feature... features) {
+ return new Feature() {
+ @Override
+ public boolean isEnabled(Context context) {
+ for (Feature f : features) {
+ if (!f.isEnabled(context)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ };
+ }
+
+ /**
+ * A feature that is always enabled.
+ */
+ public static final Feature ON = new Feature() {
+ @Override
+ public boolean isEnabled(Context context) {
+ return true;
+ }
+ };
+
+ /**
+ * A feature that is always disabled.
+ */
+ public static final Feature OFF = new Feature() {
+ @Override
+ public boolean isEnabled(Context context) {
+ return false;
+ }
+ };
+
+ private FeatureUtils() {
+ }
+}
diff --git a/common/src/com/android/tv/common/feature/GServiceFeature.java b/common/src/com/android/tv/common/feature/GServiceFeature.java
new file mode 100644
index 00000000..10b369f1
--- /dev/null
+++ b/common/src/com/android/tv/common/feature/GServiceFeature.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.common.feature;
+
+import android.content.Context;
+
+
+/**
+ * A feature controlled by a GServices flag.
+ */
+public class GServiceFeature implements Feature {
+ private static final String LIVECHANNELS_PREFIX = "livechannels:";
+ private final String mKey;
+ private final boolean mDefaultValue;
+
+ public GServiceFeature(String key, boolean defaultValue) {
+ mKey = LIVECHANNELS_PREFIX + key;
+ mDefaultValue = defaultValue;
+ }
+
+ @Override
+ public boolean isEnabled(Context context) {
+
+ // GServices is not available outside of Google.
+ return mDefaultValue;
+ }
+}
diff --git a/common/src/com/android/tv/common/feature/PropertyFeature.java b/common/src/com/android/tv/common/feature/PropertyFeature.java
new file mode 100644
index 00000000..27bccda1
--- /dev/null
+++ b/common/src/com/android/tv/common/feature/PropertyFeature.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.common.feature;
+
+import android.content.Context;
+
+import com.android.tv.common.BooleanSystemProperty;
+
+/**
+ * Feature controlled by system property.
+ *
+ * <p>See {@link BooleanSystemProperty} for instructions on how to set using adb.
+ */
+public final class PropertyFeature implements Feature {
+ private final BooleanSystemProperty mProperty;
+
+ /**
+ * Create System Property backed feature.
+ *
+ * @param key the system property key. Length must be <= 31 characters.
+ * @param defaultValue the value to return if the property is undefined or empty.
+ */
+ public PropertyFeature(String key, boolean defaultValue) {
+ if (key.length() > 31) {
+ // Since Features are initialized at startup and the keys are static go ahead and kill
+ // the application.
+ throw new IllegalArgumentException(
+ "Property keys have a max length of 31 characters but '" + key + "' is " + key
+ .length() + " characters.");
+ }
+ mProperty = new BooleanSystemProperty(key, defaultValue);
+ }
+
+ @Override
+ public boolean isEnabled(Context context) {
+ return mProperty.getValue();
+ }
+}
diff --git a/common/src/com/android/tv/common/ui/setup/OnActionClickListener.java b/common/src/com/android/tv/common/ui/setup/OnActionClickListener.java
new file mode 100644
index 00000000..39af2d83
--- /dev/null
+++ b/common/src/com/android/tv/common/ui/setup/OnActionClickListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.common.ui.setup;
+
+/**
+ * A listener for the action click.
+ */
+public interface OnActionClickListener {
+ /**
+ * Called when the action is clicked.
+ *
+ * @param id action id.
+ */
+ void onActionClick(int id);
+}
diff --git a/common/src/com/android/tv/common/ui/setup/SetupActionHelper.java b/common/src/com/android/tv/common/ui/setup/SetupActionHelper.java
new file mode 100644
index 00000000..ed999990
--- /dev/null
+++ b/common/src/com/android/tv/common/ui/setup/SetupActionHelper.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.common.ui.setup;
+
+import android.app.Fragment;
+
+/**
+ * Helper class for the execution in the fragment.
+ */
+public class SetupActionHelper {
+ /**
+ * Executes the action of the given {@code actionId}.
+ */
+ public static void onActionClick(Fragment fragment, int actionId) {
+ if (fragment.getActivity() instanceof OnActionClickListener) {
+ ((OnActionClickListener) fragment.getActivity()).onActionClick(actionId);
+ }
+ }
+}
diff --git a/common/src/com/android/tv/common/ui/setup/SetupFragment.java b/common/src/com/android/tv/common/ui/setup/SetupFragment.java
new file mode 100644
index 00000000..46501652
--- /dev/null
+++ b/common/src/com/android/tv/common/ui/setup/SetupFragment.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.common.ui.setup;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.app.Fragment;
+import android.content.Context;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.support.v4.view.animation.LinearOutSlowInInterpolator;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+
+import com.android.tv.common.R;
+
+/**
+ * A fragment which slides when it is entering/exiting.
+ */
+public abstract class SetupFragment extends Fragment {
+ public static final int ANIM_ENTER = 1;
+ public static final int ANIM_EXIT = 2;
+ public static final int ANIM_POP_ENTER = 3;
+ public static final int ANIM_POP_EXIT = 4;
+
+ private static int sScreenWidth;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(getLayoutResourceId(), container, false);
+ // After the transition animation, we need to request the focus. If not, this fragment
+ // doesn't have the focus.
+ view.requestFocus();
+ return view;
+ }
+
+ /**
+ * Returns the layout resource ID for this fragment.
+ */
+ protected abstract int getLayoutResourceId();
+
+ @Override
+ public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
+ if (sScreenWidth == 0) {
+ DisplayManager displayManager =
+ (DisplayManager) getActivity().getSystemService(Context.DISPLAY_SERVICE);
+ Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ Point size = new Point();
+ display.getSize(size);
+ sScreenWidth = size.x;
+ }
+
+ switch (nextAnim) {
+ case ANIM_ENTER:
+ return createTranslateAnimator(sScreenWidth, 0);
+ case ANIM_EXIT:
+ return createTranslateAnimator(0, -sScreenWidth);
+ case ANIM_POP_ENTER:
+ return createTranslateAnimator(-sScreenWidth, 0);
+ case ANIM_POP_EXIT:
+ return createTranslateAnimator(0, sScreenWidth);
+ }
+ return super.onCreateAnimator(transit, enter, nextAnim);
+ }
+
+ private Animator createTranslateAnimator(int start, int end) {
+ ObjectAnimator animator = new ObjectAnimator();
+ animator.setProperty(View.TRANSLATION_X);
+ animator.setFloatValues(start, end);
+ animator.setDuration(getResources().getInteger(R.integer.setup_slide_anim_duration));
+ animator.setInterpolator(new LinearOutSlowInInterpolator());
+ return animator;
+ }
+
+ protected void setOnClickAction(View view, final int actionId) {
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onActionClick(actionId);
+ }
+ });
+ }
+
+ protected void onActionClick(int actionId) {
+ SetupActionHelper.onActionClick(this, actionId);
+ }
+}
diff --git a/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java
new file mode 100644
index 00000000..c744a4ab
--- /dev/null
+++ b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.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.common.ui.setup;
+
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A fragment for channel source info/setup.
+ */
+public abstract class SetupGuidedStepFragment extends GuidedStepFragment {
+ @Override
+ public GuidanceStylist onCreateGuidanceStylist() {
+ return new GuidanceStylist() {
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Guidance guidance) {
+ View view = super.onCreateView(inflater, container, guidance);
+ if (guidance.getIconDrawable() == null) {
+ // Icon view should not take up space when we don't use image.
+ getIconView().setVisibility(View.GONE);
+ }
+ return view;
+ }
+ };
+ }
+
+ @Override
+ public void onGuidedActionClicked(GuidedAction action) {
+ SetupActionHelper.onActionClick(this, (int) action.getId());
+ }
+}
diff --git a/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java
new file mode 100644
index 00000000..59416eff
--- /dev/null
+++ b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.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.common.ui.setup;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+
+import com.android.tv.common.R;
+
+/**
+ * A fragment for channel source info/setup.
+ */
+public abstract class SetupMultiPaneFragment extends SetupFragment {
+ public static final int ACTION_DONE = 1;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = super.onCreateView(inflater, container, savedInstanceState);
+ getFragmentManager().beginTransaction()
+ .replace(R.id.guided_step_fragment_container, getContentFragment()).commit();
+ View doneButton = view.findViewById(R.id.button_done);
+ if (needsDoneButton()) {
+ doneButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View paramView) {
+ SetupActionHelper.onActionClick(SetupMultiPaneFragment.this, ACTION_DONE);
+ }
+ });
+ } else {
+ doneButton.setVisibility(View.GONE);
+ }
+ return view;
+ }
+
+ @Override
+ protected int getLayoutResourceId() {
+ return R.layout.fragment_setup_multi_pane;
+ }
+
+ abstract protected Fragment getContentFragment();
+
+ protected boolean needsDoneButton() {
+ return true;
+ }
+}
diff --git a/common/src/com/android/tv/common/ui/setup/SetupStep.java b/common/src/com/android/tv/common/ui/setup/SetupStep.java
new file mode 100644
index 00000000..b91ed6e2
--- /dev/null
+++ b/common/src/com/android/tv/common/ui/setup/SetupStep.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.common.ui.setup;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.support.annotation.Nullable;
+
+/**
+ * An interface for the setup step.
+ */
+public abstract class SetupStep {
+ private final SetupStep mPreviousStep;
+ private final int mPreviousBackStackRecordCount;
+
+ public SetupStep(FragmentManager fragmentManager, @Nullable SetupStep previousStep) {
+ mPreviousStep = previousStep;
+ mPreviousBackStackRecordCount = fragmentManager.getBackStackEntryCount();
+ }
+
+ /**
+ * Returns fragment to represent this step.
+ */
+ protected abstract Fragment onCreateFragment();
+
+ /**
+ * Returns whether this step needs to be added to the back stack or not.
+ *
+ * <p>The default behavior is to add the fragment to the back stack.
+ */
+ protected boolean needsToBeAddedToBackStack() {
+ return true;
+ }
+
+ /**
+ * Returns whether this step needs fragment transition animations or not.
+ *
+ * <p>The default value is {@code} true.
+ */
+ protected boolean needsFragmentTransitionAnimation() {
+ return true;
+ }
+
+ /**
+ * Executes the given action.
+ */
+ public abstract void executeAction(int actionId);
+
+ /**
+ * Returns the back stack record count at the moment when this step starts.
+ */
+ public int getPreviousBackStackRecordCount() {
+ return mPreviousBackStackRecordCount;
+ }
+
+ /**
+ * Returns the previous step.
+ */
+ @Nullable
+ public SetupStep getPreviousStep() {
+ return mPreviousStep;
+ }
+}
diff --git a/common/src/com/android/tv/common/ui/setup/SteppedSetupActivity.java b/common/src/com/android/tv/common/ui/setup/SteppedSetupActivity.java
new file mode 100644
index 00000000..e454dc49
--- /dev/null
+++ b/common/src/com/android/tv/common/ui/setup/SteppedSetupActivity.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.common.ui.setup;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager.OnBackStackChangedListener;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+
+import com.android.tv.common.R;
+
+/**
+ * Stepped setup activity for onboarding screens or setup activity for TIS.
+ */
+public abstract class SteppedSetupActivity extends Activity implements OnActionClickListener {
+ private boolean mStartedInitialStep = false;
+ private SetupStep mStep;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(R.style.Theme_Setup_GuidedStep);
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_stepped_setup);
+ startInitialStep();
+ getFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
+ @Override
+ public void onBackStackChanged() {
+ if (mStep != null) {
+ // Need to change step to the previous one if the current step is popped from
+ // the back stack.
+ if (mStep.needsToBeAddedToBackStack()
+ && getFragmentManager().getBackStackEntryCount()
+ <= mStep.getPreviousBackStackRecordCount()) {
+ mStep = mStep.getPreviousStep();
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Returns the current step.
+ */
+ public SetupStep getCurrentStep() {
+ return mStep;
+ }
+
+ /**
+ * The inherited class should provide the initial step.
+ *
+ * <p>If this method returns {@code null} during {@link #onCreate}, then call
+ * {@link #startInitialStep} explicitly later with non null initial setup step.
+ *
+ * @see SetupStep
+ */
+ protected abstract SetupStep onCreateInitialStep();
+
+ /**
+ * Starts the initial step.
+ *
+ * <p>The inherited class can call this method later explicitly if it doesn't want the initial
+ * step to be started in onCreate().
+ *
+ * @see SetupStep
+ */
+ protected void startInitialStep() {
+ if (mStartedInitialStep) {
+ return;
+ }
+ SetupStep step = onCreateInitialStep();
+ if (step != null) {
+ startStep(step);
+ mStartedInitialStep = true;
+ }
+ }
+
+ /**
+ * Starts next step.
+ */
+ protected void startStep(SetupStep step) {
+ mStep = step;
+ Fragment fragment = step.onCreateFragment();
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ if (step.needsFragmentTransitionAnimation()) {
+ ft.setCustomAnimations(SetupFragment.ANIM_ENTER, SetupFragment.ANIM_EXIT,
+ SetupFragment.ANIM_POP_ENTER, SetupFragment.ANIM_POP_EXIT);
+ }
+ if (step.needsToBeAddedToBackStack()) {
+ ft.addToBackStack(null);
+ }
+ ft.replace(R.id.fragment_container, fragment).commit();
+ }
+
+ @Override
+ public void onActionClick(int actionId) {
+ mStep.executeAction(actionId);
+ }
+}
diff --git a/proguard.flags b/proguard.flags
index 0900d498..0fe6fc09 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -20,29 +20,24 @@
-dontwarn android.support.**
-dontwarn com.ibm.icu.**
-dontwarn com.google.android.exoplayer.**
--dontwarn com.google.android.usbtuner.**
--dontwarn com.google.android.tv.dvr.**
+-dontwarn com.android.usbtuner.**
+-dontwarn com.android.tv.dvr.**
# This is due to legacy API katniss is referencing. Seems safe.
-dontwarn com.google.android.volley.**
-dontwarn com.google.android.common.**
# Keep the methods called from native code.
--keepclasseswithmembers class com.google.android.usbtuner.UsbTunerInterface {
+-keepclasseswithmembers class com.android.usbtuner.UsbTunerInterface {
int openDvbFrontEndFd();
int openDvbDemuxFd();
int openDvbDvrFd();
}
--keepclasseswithmembers class com.google.android.usbtuner.*DataSource {
+-keepclasseswithmembers class com.android.usbtuner.*DataSource {
int readAt(long, byte[], int, int);
long getSize();
void close();
}
-# For software AC3 decoding
--keepclasseswithmembers class com.google.android.ssplayer.sink.FFmpegAc3Decoder {
- long mNativeContext;
- void decodeDone(java.nio.ByteBuffer, long);
-}
# For tests
-keep @android.support.annotation.VisibleForTesting class *
diff --git a/res/anim/select_input_activity_enter.xml b/res/anim/select_input_activity_enter.xml
new file mode 100644
index 00000000..ee0bc650
--- /dev/null
+++ b/res/anim/select_input_activity_enter.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.
+ -->
+
+<!-- This should be the same animation as @animator/channel_banner_enter. -->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <translate
+ android:fromYDelta="3%"
+ android:toYDelta="0"
+ android:duration="@integer/channel_banner_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+ <alpha
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"
+ android:duration="@integer/channel_banner_anim_duration"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/res/anim/select_input_activity_exit.xml b/res/anim/select_input_activity_exit.xml
new file mode 100644
index 00000000..933159a0
--- /dev/null
+++ b/res/anim/select_input_activity_exit.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.
+ -->
+
+<!-- This should be the same animation as @animator/channel_banner_exit. -->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <translate
+ android:fromYDelta="0"
+ android:toYDelta="3%"
+ android:duration="@integer/channel_banner_anim_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in" />
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="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_enter.xml b/res/animator/channel_banner_enter.xml
index 1594b2d5..84ffed90 100644
--- a/res/animator/channel_banner_enter.xml
+++ b/res/animator/channel_banner_enter.xml
@@ -15,6 +15,7 @@
~ limitations under the License.
-->
+<!-- This should be the same animation as @anim/select_input_activity_enter. -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:propertyName="translationY"
diff --git a/res/animator/channel_banner_exit.xml b/res/animator/channel_banner_exit.xml
index 9ee407fa..411a567b 100644
--- a/res/animator/channel_banner_exit.xml
+++ b/res/animator/channel_banner_exit.xml
@@ -15,6 +15,7 @@
~ limitations under the License.
-->
+<!-- This should be the same animation as @anim/select_input_activity_exit. -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:propertyName="translationY"
diff --git a/res/animator/program_guide_side_panel_text_state_animator.xml b/res/animator/program_guide_side_panel_text_state_animator.xml
index 5425fe5b..782e3a2d 100644
--- a/res/animator/program_guide_side_panel_text_state_animator.xml
+++ b/res/animator/program_guide_side_panel_text_state_animator.xml
@@ -59,4 +59,4 @@
android:interpolator="@android:interpolator/accelerate_decelerate" />
</set>
</item>
-</selector>
+</selector> \ No newline at end of file
diff --git a/res/drawable-xhdpi/ic_message_lock_no_permission.png b/res/drawable-xhdpi/ic_message_lock_no_permission.png
new file mode 100644
index 00000000..4d53726b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_message_lock_no_permission.png
Binary files differ
diff --git a/res/drawable/channel_card_progress_bar.xml b/res/drawable/channel_card_progress_bar.xml
index 2f020fb7..670bff96 100644
--- a/res/drawable/channel_card_progress_bar.xml
+++ b/res/drawable/channel_card_progress_bar.xml
@@ -28,4 +28,4 @@
</shape>
</clip>
</item>
-</layer-list>
+</layer-list> \ No newline at end of file
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
index 2ef699f6..2bd75ae3 100644
--- a/res/drawable/program_guide_table_header_column_last_item_background.xml
+++ b/res/drawable/program_guide_table_header_column_last_item_background.xml
@@ -22,4 +22,4 @@
android:bottomRightRadius="@dimen/program_guide_table_background_round_radius_right"
android:topLeftRadius="0dp"
android:topRightRadius="0dp" />
-</shape>
+</shape> \ No newline at end of file
diff --git a/res/drawable/program_guide_table_item_background.xml b/res/drawable/program_guide_table_item_background.xml
index 299c20dc..ffe47751 100644
--- a/res/drawable/program_guide_table_item_background.xml
+++ b/res/drawable/program_guide_table_item_background.xml
@@ -53,4 +53,4 @@
<item>
<nine-patch android:src="@drawable/program_guide_table_item_background_normal" />
</item>
-</selector>
+</selector> \ No newline at end of file
diff --git a/res/drawable/program_guide_time_indicator.xml b/res/drawable/program_guide_time_indicator.xml
index dcd74f16..81037773 100644
--- a/res/drawable/program_guide_time_indicator.xml
+++ b/res/drawable/program_guide_time_indicator.xml
@@ -28,4 +28,4 @@
<solid android:color="@color/program_guide_time_indicator_color" />
</shape>
</item>
-</layer-list>
+</layer-list> \ No newline at end of file
diff --git a/res/layout/activity_select_input.xml b/res/layout/activity_select_input.xml
new file mode 100644
index 00000000..75cac528
--- /dev/null
+++ b/res/layout/activity_select_input.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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/select_input" />
+
+</FrameLayout>
diff --git a/res/layout/fragment_welcome.xml b/res/layout/fragment_welcome.xml
new file mode 100644
index 00000000..7ef0aeec
--- /dev/null
+++ b/res/layout/fragment_welcome.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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:layout_gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="0.55"
+ android:background="#0287CB"
+ android:fontFamily="@string/condensed_font"
+ android:gravity="center"
+ android:text="@string/illustration_area_text"
+ android:textColor="@android:color/white"
+ android:textSize="64sp" />
+
+ <FrameLayout
+ android:id="@+id/page_container"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="0.3"
+ android:background="#0277BB" />
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="0.15"
+ android:background="#0277BB">
+ <com.android.tv.onboarding.PagingIndicator
+ android:id="@+id/page_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:layout_gravity="top|center_horizontal" />
+ <Button
+ android:id="@+id/button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end"
+ android:layout_marginEnd="32dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:background="#0287CB"
+ android:text="@string/welcome_next_button_text" />
+ </FrameLayout>
+
+</LinearLayout>
diff --git a/res/layout/fragment_welcome_page.xml b/res/layout/fragment_welcome_page.xml
new file mode 100644
index 00000000..7af0ead7
--- /dev/null
+++ b/res/layout/fragment_welcome_page.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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="0.4"
+ android:fontFamily="@string/condensed_font"
+ android:gravity="center"
+ android:text="@string/illustration_area_text"
+ android:textColor="@android:color/white"
+ android:textSize="32sp" />
+
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0.6"
+ android:fontFamily="@string/condensed_font"
+ android:gravity="top|center_horizontal"
+ android:text="@string/illustration_area_text"
+ android:textColor="@android:color/white"
+ android:textSize="16sp" />
+
+</LinearLayout>
diff --git a/res/layout/option_item_switch.xml b/res/layout/option_item_switch.xml
index 96f890c4..4750ebc4 100644
--- a/res/layout/option_item_switch.xml
+++ b/res/layout/option_item_switch.xml
@@ -37,4 +37,4 @@
android:background="@null"
android:clickable="false" />
-</LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/program_guide_table_header_row_item.xml b/res/layout/program_guide_table_header_row_item.xml
index 6e3de2f5..fdd055b4 100644
--- a/res/layout/program_guide_table_header_row_item.xml
+++ b/res/layout/program_guide_table_header_row_item.xml
@@ -22,4 +22,4 @@
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" />
+ android:textColor="@color/program_guide_table_header_row_time_text_color" /> \ No newline at end of file
diff --git a/res/layout/program_guide_table_item.xml b/res/layout/program_guide_table_item.xml
index 11b6c52e..dab18ac8 100644
--- a/res/layout/program_guide_table_item.xml
+++ b/res/layout/program_guide_table_item.xml
@@ -30,4 +30,4 @@
android:textColor="@color/program_guide_table_item_program_title_text_color"
android:lineSpacingExtra="1sp"
android:maxLines="2"
- android:ellipsize="end" />
+ android:ellipsize="end" /> \ No newline at end of file
diff --git a/res/layout/tunable_tv_view.xml b/res/layout/tunable_tv_view.xml
index 15fa68cd..4ca9426a 100644
--- a/res/layout/tunable_tv_view.xml
+++ b/res/layout/tunable_tv_view.xml
@@ -16,13 +16,17 @@
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <View
+ <TextView
android:id="@+id/hide_screen"
- android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@android:color/black" />
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:fontFamily="@string/font"
+ android:textSize="@dimen/tvview_block_text_size"
+ android:lineSpacingExtra="4sp"
+ android:background="@android:color/black"
+ android:textColor="@color/tvview_block_text_color" />
<ProgressBar
android:id="@+id/buffering_spinner"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 54162822..1e4578ce 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -101,7 +101,7 @@
</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="menu_parental_controls" msgid="2474294054521345840">"Ouerkontroles"</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>
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Help om Regstreekse Kanale te verbeter"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Deel anonieme gebruik- en diagnostiese data met Google sodat ons Regstreekse Kanale beter kan maak en kwessies soos omval en vries kan voorkom."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Gebruik die verstekprogram vir Regstreekse TV om hierdie kanaal te kyk."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Gebruik die verstekprogram vir Regstreekse TV om hierdie program te kyk."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Hierdie program is <xliff:g id="RATING">%1$s</xliff:g> gegradeer.\nGebruik die verstekprogram vir Regstreekse TV om hierdie kanaal 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="tvview_msg_audio_only" msgid="1356866203687173329">"Net oudio"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Regstreekse kanale word nie op hierdie toestel met Android Lollipop gesteun nie."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Regstreekse kanale het toestemming nodig om die TV-lysinskrywings te lees."</string>
</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index f11ac50d..11b86b58 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Live TVን ለማሻሻል አግዝ"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"የቀጥታ ስርጭት ሰርጦችን የተሻሉ ማድረግ እንድንችል እና እንደ መሰናከል እና ቀጥ ማለት የመሳሰሉ ችግሮችን መከላከል እንድንችል ከGoogle ጋ ማንነትን የማያሳውቅ የአጠቃቀም እና የምርመራ ውሂብን ይጋሩ።"</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"ይህን ሰርጥ ለመመልከት ነባሪውን የቀጥተኛ ቴሌቪዥን መተግበሪያ ይጠቀሙ።"</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"ይህን ፕሮግራም ለመመልከት ነባሪውን የቀጥተኛ ቴሌቪዥን መተግበሪያ ይጠቀሙ።"</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"ይህ ፕሮግራም የ<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="tvview_msg_audio_only" msgid="1356866203687173329">"ኦዲዮ ብቻ"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"ቀጥተኛ ሰርጦች እዚህ መሣሪያ ላይ በAndroid Lollipop አይደገፉም።"</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"ቀጥተኛ ሰርጦች የቴሌቪዥን ዝርዝሮችን ለማንበብ ፍቃድ ያስፈልጋቸዋል።"</string>
</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 0af0f788..ee0268da 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"المساعدة في تحسين القنوات المباشرة"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"‏يمكنك مشاركة بيانات مجهولة المصدر حول الاستخدام والتشخيص باستخدام Google، حتى نتمكن من تحسين تطبيق البث التلفزيوني المباشر ومنع حدوث مشكلات مثل الأعطال والتجميد."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"‏لمشاهدة هذه القناة، يمكنك استخدام تطبيق Live TV الافتراضي."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"‏لمشاهدة هذا البرنامج، يمكنك استخدام تطبيق Live TV الافتراضي."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"‏هذا البرنامج يحمل التقييم <xliff:g id="RATING">%1$s</xliff:g>.\nلمشاهدة هذا البرنامج، يمكنك استخدام تطبيق Live TV الافتراضي."</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="tvview_msg_audio_only" msgid="1356866203687173329">"الصوت فقط"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"‏القنوات المباشرة غير متوافقة على هذا الجهاز مع Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"‏تحتاج القنوات المباشرة إلى إذن لقراءة قوائم TV."</string>
</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 82234947..917879aa 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Съдействие за подобряването на Live TV"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Споделяйте с Google анонимни данни за употребата и диагностиката, за да подобрим Телевизия онлайн и да предотвратим проблеми като сривове и замръзвания."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"За да гледате този канал, използвайте стандартното приложение за телевизия на живо."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"За да гледате тази програма, използвайте стандартното приложение за телевизия на живо."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Тази програма е класифицирана като „<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="tvview_msg_audio_only" msgid="1356866203687173329">"Само аудио"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Приложението Live TV не се поддържа на това устройство с Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Приложението Live TV се нуждае от разрешение, за да чете телевизионните програми."</string>
</resources>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index a5974e1e..d8cde82d 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"লাইভ চ্যানেলগুলি উন্নত করতে সহায়তা করুন"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Google এর সাথে নামবিহীন ব্যবহার এবং ডায়াগনস্টিক ডেটা ভাগ করুন যাতে করে আমরা লাইভ চ্যানেলগুলিকে আরো উন্নত করতে এবং ক্র্যাশ ও ফ্রিজ হয়ে যাওয়ার মতো সমস্যাগুলিকে আটকাতে পারি৷"</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"এই চ্যানেলটি দেখতে, ডিফল্ট লাইভ টিভি অ্যাপ্লিকেশানটি ব্যবহার করুন৷"</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"এই প্রোগ্রামটি দেখতে, ডিফল্ট লাইভ টিভি অ্যাপ্লিকেশানটি ব্যবহার করুন৷"</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"এই প্রোগ্রামটি <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="tvview_msg_audio_only" msgid="1356866203687173329">"কেবলমাত্র অডিও"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"এই ডিভাইসটিতে Android Lollipop এর সাথে লাইভ চ্যানেলগুলি সমর্থিত নয়।"</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"টিভির তালিকাগুলি পড়ার জন্য লাইভ চ্যানেলগুলিকে অনুমতি নিতে হবে।"</string>
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 571e8e1f..e4187d7b 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Vull contribuir a millorar Canals en directe"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Comparteix dades d\'ús i de diagnòstic anònimes amb Google per millorar TV en directe i evitar problemes, com ara bloqueigs i errors."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Per mirar aquest canal, fes servir l\'aplicació Televisió en directe predeterminada."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Per mirar aquest programa, fes servir l\'aplicació Televisió en directe predeterminada."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Aquest programa té la classificació següent: <xliff:g id="RATING">%1$s</xliff:g>.\nPer mirar-lo, fes servir l\'aplicació Televisió en directe predeterminada."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Només àudio"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Canals en directe no s\'admet en aquest dispositiu amb Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Canals en directe necessita permís per consultar la llista de canals del televisor."</string>
</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 63acd728..f41f067a 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Pomáhat s vylepšováním aktivních kanálů"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Sdílet s Googlem anonymní statistiky využití a diagnostické údaje, abychom Televizi online mohli zlepšit a zabránit problémům, jako jsou selhání a zamrzání."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Chcete-li tento kanál sledovat, použijte výchozí aplikaci Live TV."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Chcete-li tento program sledovat, použijte výchozí aplikaci Live TV."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Tento program má hodnocení <xliff:g id="RATING">%1$s</xliff:g>.\nChcete-li tento program sledovat, použijte výchozí aplikaci Live TV."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Pouze zvuk"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"V tomto zařízení se systémem Android Lollipop nejsou aktivní kanály podporovány."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Aktivní kanály potřebují oprávnění ke čtení televizních programů."</string>
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 4a4ccf6e..082984a5 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -40,7 +40,7 @@
<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_parental_controls" msgid="7880060136509458287">"Børnesikring"</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>
@@ -101,7 +101,7 @@
</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="menu_parental_controls" msgid="2474294054521345840">"Børnesikring"</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>
@@ -132,7 +132,7 @@
<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_create_pin" msgid="3385754356793309946">"Angiv en pinkode for at aktivere børnesikring"</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>
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Hjælp med at forbedre Tv-kanaler"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Del anonyme forbrugs- og diagnostikdata med Google, så vi kan forbedre Tv-kanaler og forhindre problemer, som f.eks. nedbrud og fastfrysning."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Se denne kanal ved hjælp af standardappen til direkte tv."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Se dette program ved hjælp af standardappen til direkte tv."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Dette program er klassificeret som <xliff:g id="RATING">%1$s</xliff:g>.\nSe dette program ved hjælp af standardappen til direkte tv."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Kun lyd"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Tv-kanaler understøttes ikke på denne enhed med Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Tv-kanaler skal have tilladelse for at kunne læse tv-guiden."</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 4a5fad19..d7e061f4 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Bei der Verbesserung von Live TV helfen"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Teilen Sie anonyme Nutzungs- und Diagnosedaten mit Google, damit wir Live-TV verbessern können und die App nicht mehr hängen bleibt oder abstürzt."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Um sich diesen Kanal anzusehen, verwenden Sie die Standard-App für die Liveverfolgung von TV-Sendungen."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Um sich dieses Programm anzusehen, verwenden Sie die Standard-App für die Liveverfolgung von TV-Sendungen."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Diese Sendung wurde als \"<xliff:g id="RATING">%1$s</xliff:g>\" eingestuft.\nUm sich diese Sendung anzusehen, verwenden Sie die Standard-App für die Liveverfolgung von TV-Sendungen."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Nur Audio"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Live TV wird auf diesem Gerät mit Android Lollipop nicht unterstützt."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Live TV benötigt einen Lesezugriff für die Kanalliste."</string>
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index b670308d..9af2ec7f 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -144,11 +144,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Συμβολή στη βελτίωση των Zωντανών καναλιών"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Μοιραστείτε ανώνυμα δεδομένα χρήσης και διαγνωστικών στοιχείων με τη Google για να βελτιώσουμε τα Ζωντανά κανάλια και να αποτρέψουμε προβλήματα όπως διακοπές λειτουργίας και πάγωμα της εφαρμογής."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Για να παρακολουθήσετε αυτό το κανάλι, χρησιμοποιήστε την προεπιλεγμένη εφαρμογή ζωντανής τηλεοπτικής μετάδοσης."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Για να παρακολουθήσετε αυτό το πρόγραμμα, χρησιμοποιήστε την προεπιλεγμένη εφαρμογή ζωντανής τηλεοπτικής μετάδοσης."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Αυτό το πρόγραμμα χαρακτηρίστηκε <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="tvview_msg_audio_only" msgid="1356866203687173329">"Μόνο ήχος"</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>
@@ -175,4 +181,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Τα ζωντανά κανάλια δεν υποστηρίζονται σε αυτήν τη συσκευή με Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Τα ζωντανά κανάλια χρειάζονται άδεια για να διαβάσουν τις τηλεοπτικές καταχωρίσεις."</string>
</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 59c896cf..e8657f31 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Help improve Live TV"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Share anonymous usage and diagnostics data with Google so we can make Live TV better and prevent issues such as crashing and freezing."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"To watch this channel, use the default Live TV app."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"To watch this programme, use the default Live TV app."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"This programme is rated <xliff:g id="RATING">%1$s</xliff:g>.\nTo watch this programme, use the default Live TV app."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Audio only"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Live TV is not supported on this device with Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Live TV needs permission to read the TV listings."</string>
</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 59c896cf..e8657f31 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Help improve Live TV"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Share anonymous usage and diagnostics data with Google so we can make Live TV better and prevent issues such as crashing and freezing."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"To watch this channel, use the default Live TV app."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"To watch this programme, use the default Live TV app."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"This programme is rated <xliff:g id="RATING">%1$s</xliff:g>.\nTo watch this programme, use the default Live TV app."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Audio only"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Live TV is not supported on this device with Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Live TV needs permission to read the TV listings."</string>
</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 59c896cf..e8657f31 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Help improve Live TV"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Share anonymous usage and diagnostics data with Google so we can make Live TV better and prevent issues such as crashing and freezing."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"To watch this channel, use the default Live TV app."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"To watch this programme, use the default Live TV app."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"This programme is rated <xliff:g id="RATING">%1$s</xliff:g>.\nTo watch this programme, use the default Live TV app."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Audio only"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Live TV is not supported on this device with Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Live TV needs permission to read the TV listings."</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 65271e71..cbaffe0f 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Ayudar a mejorar Canales en vivo"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Comparte datos anónimos acerca del uso y el diagnóstico con Google para que podamos mejorar Canales en vivo y evitar problemas como el bloqueo o el congelamiento de la imagen."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Para mirar este canal, usa la app de TV en vivo predeterminada."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Para mirar este programa, usa la app de TV en vivo predeterminada."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Este programa está calificado como <xliff:g id="RATING">%1$s</xliff:g>.\nPara mirarlo, usa la app de TV en vivo predeterminada."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Solo audio"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"\"Canales en vivo\" no es compatible con este dispositivo con Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"\"Canales en vivo\" necesita permiso para leer las listas de canales de televisión."</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index cab1c6ac..56805d4a 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Ayudar a mejorar Canales en directo"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Comparte datos de uso y de diagnóstico anónimos con Google para mejorar TV en directo y evitar bloqueos y fallos."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Para ver este canal, utiliza la aplicación de TV en directo predeterminada."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Para ver este programa, utiliza la aplicación de TV en directo predeterminada."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Este programa está clasificado como <xliff:g id="RATING">%1$s</xliff:g>.\nPara verlo, utiliza la aplicación de TV en directo predeterminada."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Solo audio"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Canales en directo no se admite en este dispositivo con Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Canales en directo necesita permiso para consultar la lista de canales del televisor."</string>
</resources>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
index 76179624..d125e071 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Aita täiustada reaalajas kanaleid"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Jagage Google\'iga anonüümseid kasutus- ja diagnostikaandmeid, et saaksime rakenduse Reaalajakanalid paremaks muuta ning vältida näiteks kokkujooksmist ja hangumist."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Selle kanali vaatamiseks kasutage otsesaadete vaikerakendust."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Selle saate vaatamiseks kasutage otsesaadete vaikerakendust."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Selle saate reiting on <xliff:g id="RATING">%1$s</xliff:g>.\nSelle saate vaatamiseks kasutage otsesaadete vaikerakendust."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Ainult heli"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Reaalajas kanaleid selles seadmes operatsioonisüsteemiga Android Lollipop ei toetata."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Reaalajas kanalid vajavad telekavade lugemiseks luba."</string>
</resources>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index d8fbf738..ebf9865c 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Lagundu Zuzeneko kanalak hobetzen"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Partekatu erabilerari buruzko datu eta diagnostiko anonimoak Google-rekin, Telebista zuzenean zerbitzua hobe dezagun, besteak beste, hutsegiteak gertatzea edo irudia izoztuta geratzea ekiditeko."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Kanala ikusteko, erabili telebista zuzenean ikusteko aplikazio lehenetsia."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Telesaioa ikusi ahal izateko, erabili telebista zuzenean ikusteko aplikazio lehenetsia."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Telesaioa <xliff:g id="RATING">%1$s</xliff:g> gisa sailkatu da.\nIkusi ahal izateko, erabili telebista zuzenean ikusteko aplikazio lehenetsia."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Audioa soilik"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Android Lollipop duen gailu honetan ezin da erabili Zuzeneko kanalak aplikazioa."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Zuzeneko kanalak aplikazioak baimena behar du telebistako programazioa irakurtzeko."</string>
</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index e1450109..74c0d43a 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -146,17 +146,23 @@
<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="about_menu_improve" msgid="3712578027009311401">"کمک به بهبود «کانال‌های مستقیم»"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"‏داده‌های عیب‌یابی و مصرف بی‌نام را با Google به اشتراک بگذارید تا ما کانال‌های زنده را بهتر کنیم و از مشکلاتی مانند خرابی و ثابت ماندن تصویر جلوگیری کنیم."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"برای تماشای این کانال، از برنامه «پخش مستقیم» پیش‌فرض استفاده کنید."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"برای تماشای این برنامه، از برنامه «پخش مستقیم» پیش‌فرض استفاده کنید."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"به این برنامه رتبه <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="tvview_msg_audio_only" msgid="1356866203687173329">"فقط صدا"</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>
+ <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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"‏کانال‌های مستقیم در این دستگاه دارای Android Lollipop پشتیبانی نمی‌شوند."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"کانال‌های مستقیم به مجوز خواندن فهرست‌های تلویزیون نیاز دارند."</string>
</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 3c9e5675..ee0d51bf 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Auta parantamaan live-kanavia"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Jakamalla Googlelle anonyymejä käyttö- ja diagnostiikkatietoja autat meitä parantamaan livekanavia ja estämään kaatumisen ja pysähtymisen kaltaisia ongelmia."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Katsele tätä kanavaa TV-lähetyksien oletussovelluksessa."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Katsele tätä ohjelmaa TV-lähetyksien oletussovelluksessa."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Tämän ohjelman ikärajoitus on <xliff:g id="RATING">%1$s</xliff:g>.\nKatsele tätä ohjelmaa TV-lähetyksien oletussovelluksessa."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Vain ääni"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Live-kanavia ei tueta tällä Android Lollipop -laitteella."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Live-kanavat tarvitsee luvan TV-ohjelmatietojen lukemiseen."</string>
</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 9225be18..5243cf73 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Aider à améliorer les chaînes en direct"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Envoyez-nous des données anonymes d\'utilisation et de diagnostic pour nous aider à améliorer Chaîne en direct et à éviter les problèmes de plantage ou de blocage."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Pour regarder cette chaîne, utilisez l\'application de télévision en direct par défaut."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Pour regarder ce programme, utilisez l\'application de télévision en direct par défaut."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Ce programme est classé <xliff:g id="RATING">%1$s</xliff:g>.\nPour le regarder, utilisez l\'application de télévision en direct par défaut."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Audio uniquement"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Les chaînes en direct ne sont pas prises en charge sur cet appareil avec Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Les chaînes en direct ont besoin de l\'autorisation nécessaire pour lire les programmes télé."</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index b9146382..9b82fe4e 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Contribuer à l\'amélioration de l\'application Chaînes en direct"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Envoyez-nous des données anonymes d\'utilisation et de diagnostic pour nous aider à améliorer TV en direct et à éviter les problèmes de plantage ou de blocage."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Pour regarder cette chaîne, utilisez l\'application de télévision en direct par défaut."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Pour regarder ce programme, utilisez l\'application de télévision en direct par défaut."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Classification de ce programme : <xliff:g id="RATING">%1$s</xliff:g>.\nPour regarder ce programme, utilisez l\'application de télévision en direct par défaut."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Audio uniquement"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"L\'application Chaînes en direct n\'est pas compatible avec les appareils équipés d\'Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"L\'application Chaînes en direct a besoin d\'une autorisation pour accéder au programme TV."</string>
</resources>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
index 40d0b713..4661c7cd 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Axudar a mellorar Canles en directo"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Comparte datos de uso e de diagnóstico anónimos con Google para que poidamos mellorar a TV en directo e evitar bloqueos e fallos."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Para ver esta canle, utiliza a aplicación predeterminada TV en directo."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Para ver este programa, utiliza a aplicación predeterminada TV en directo."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Este programa está clasificado como <xliff:g id="RATING">%1$s</xliff:g>.\nPara velo, utiliza a aplicación predeterminada TV en directo."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Só audio"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"As canles en directo non son compatibles neste dispositivo con Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"As canles en directo necesitan permiso para ler a lista de canles da televisión."</string>
</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 9603627a..0c676b54 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"लाइव चैनल बेहतर बनाने में सहायता करें"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Google के साथ अनाम उपयोग और निदान डेटा साझा करें ताकि हम लाइव चैनल को बेहतर बना सकें और क्रैश होने तथा फ़्रीज होने जैसी समस्‍याओं को रोक सकें."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"इस चैनल को देखने के लिए, डिफ़ॉल्ट लाइव टीवी ऐप का उपयोग करें."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"इस कार्यक्रम को देखने के लिए, डिफ़ॉल्ट लाइव टीवी ऐप का उपयोग करें."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"इस कार्यक्रम को <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="tvview_msg_audio_only" msgid="1356866203687173329">"केवल ऑडियो"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Android Lollipop वाले इस डिवाइस पर लाइव चैनल समर्थित नहीं है."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"लाइव चैनल को टीवी सूची पढ़ने के लिए अनुमति की आवश्यकता होती है."</string>
</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 2cc8fa17..6f7ca541 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Pomogni poboljšati Kanale uživo"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Dijelite anonimne podatke o upotrebi i dijagnostici s Googleom kako bismo poboljšali TV kanale uživo i spriječili poteškoće kao što su rušenja i zamrzavanja."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Da biste gledali ovaj kanal, upotrijebite zadanu aplikaciju za TV uživo."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Da biste gledali ovaj program, upotrijebite zadanu aplikaciju za TV uživo."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Ovaj program ima ocjenu <xliff:g id="RATING">%1$s</xliff:g>.\nDa biste ga gledali, upotrijebite zadanu aplikaciju za TV uživo."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Samo zvuk"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Kanali uživo nisu podržani na ovom uređaju s Androidom Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Kanali uživo trebaju dopuštenje za čitanje TV unosa."</string>
</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index a93f9bd1..aa0d6bce 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -109,7 +109,7 @@
<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_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>
@@ -129,28 +129,34 @@
<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_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_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="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="about_menu_improve" msgid="3712578027009311401">"Segítek az élő csatornák javításában"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Névtelen használati és diagnosztikai adatok megosztása a Google-lal, hogy továbbfejleszthessük az Élő csatornák szolgáltatást, és megelőzhessük a rendszer összeomlását és lefagyását."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"A csatorna megtekintéséhez használja az alapértelmezett „Live TV” alkalmazást."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"A program megtekintéséhez használja az alapértelmezett „Live TV” alkalmazást."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"A program besorolása: <xliff:g id="RATING">%1$s</xliff:g>.\nA megtekintéséhez használja az alapértelmezett „Live TV” alkalmazást."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Csak hang"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Ezen az Android Lollipopot futtató eszközön nem támogatott az Élő csatornák alkalmazás."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Az Élő csatornák alkalmazásnak engedélyre van szüksége a tévéműsorok listázásának olvasásához."</string>
</resources>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index 9da7cae9..3997fcbe 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Օգնել բարելավել «Ուղիղ եթերը»"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Google-ին ուղարկեք օգտագործման և ախտորոշման անանուն տվյալներ, որոնք մենք կօգնեն կատարելագործել Live TV-ը և կանխել դրա հետ կապված խնդիրները, օրինակ՝ աշխատանքի խափանումը և սառեցումը:"</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Այս ալիքը դիտելու համար օգտագործեք Live TV կանխադրված հավելվածը:"</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Այս հաղորդումը դիտելու համար օգտագործեք Live TV կանխադրված հավելվածը:"</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Այս հաղորդումը գնահատվել է որպես <xliff:g id="RATING">%1$s</xliff:g>:\nԱյս հաղորդումը դիտելու համար օգտագործեք Live TV կենդանի կանխադրված հավելվածը:"</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="tvview_msg_audio_only" msgid="1356866203687173329">"Միայն ձայն"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Ուղիղ եթերը չի աջակցվում Android Lollipop համակարգով սարքի վրա:"</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Հեռուստատեսային ծրագրերը կարդալու համար ուղիղ եթերին թույլտվություն է անհրաժեշտ:"</string>
</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 3e0fc156..3be20980 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Bantu meningkatkan Saluran Siaran Langsung"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Bagikan data penggunaan dan diagnostik anonim kepada Google agar kami dapat membuat Saluran TV Langsung jadi lebih baik, serta mencegah masalah seperti aplikasi mogok dan berhenti tiba-tiba."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Untuk menonton saluran ini, gunakan aplikasi TV Siaran Langsung default."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Untuk menonton program ini, gunakan aplikasi TV Siaran Langsung default."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Program ini memiliki rating <xliff:g id="RATING">%1$s</xliff:g>.\nUntuk menonton program ini, gunakan aplikasi TV Siaran Langsung default."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Audio saja"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Saluran Langsung tidak didukung pada perangkat dengan Android Lollipop ini."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Saluran Langsung membutuhkan izin untuk membaca cantuman TV."</string>
</resources>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
index 51ea7ada..b8eb5d9c 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Hjálpa til við að bæta rásir í beinni"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Deila nafnlausum gögnum um notkun og greiningu með Google til að við getum gert beinar útsendingar betri og komið í veg fyrir vandamál eins og hrun og að mynd frjósi."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Til að horfa á þessa rás skaltu nota sjálfgefna forritið fyrir sjónvarp í beinni útsendingu."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Til að horfa á þennan þátt skaltu nota sjálfgefna forritið fyrir sjónvarp í beinni útsendingu."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Þessi þáttur er flokkaður sem <xliff:g id="RATING">%1$s</xliff:g>.\nTil að horfa á þennan þátt skaltu nota sjálfgefna forritið fyrir sjónvarp í beinni útsendingu."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Aðeins hljóð"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Rásir í beinni eru ekki studdar í þessu tæki með Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Rásir í beinni þurfa heimild til að lesa sjónvarpsdagskrána."</string>
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index ff669579..ef266889 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -40,7 +40,7 @@
<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_parental_controls" msgid="7880060136509458287">"Controllo genitori"</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>
@@ -101,7 +101,7 @@
</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="menu_parental_controls" msgid="2474294054521345840">"Controllo genitori"</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>
@@ -148,11 +148,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Contribuisci a migliorare Live TV"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Condividi con Google dati anonimi sull\'utilizzo e sulla diagnostica per consentirci di migliorare l\'app Dirette TV ed di evitare problemi, quali arresti anomali e blocchi."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Per guardare questo canale, utilizza l\'app predefinita per la trasmissione di contenuti TV in tempo reale."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Per guardare questo programma utilizza l\'app predefinita per la trasmissione di contenuti TV in tempo reale."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"La valutazione di questo programma è <xliff:g id="RATING">%1$s</xliff:g>.\nPer guardare questo programma utilizza l\'app predefinita per la trasmissione di contenuti TV in tempo reale."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Solo audio"</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>
@@ -179,4 +185,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Live TV non è supportato su questo dispositivo con Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Live TV richiede l\'autorizzazione per leggere gli elenchi TV."</string>
</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 73dd113a..29595185 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"עוזרים לשפר את הערוצים בשידור חי"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"‏שתף נתוני שימוש ואבחון אנונימיים עם Google כדי שנוכל לשפר את ערוצי השידורים החיים ולמנוע בעיות כמו קריסות וקפיאת תמונה."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"כדי לצפות בערוץ הזה, השתמש באפליקציית ברירת המחדל לטלוויזיה בשידור חי."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"כדי לצפות בתכנית זו, השתמש באפליקציית ברירת המחדל לטלוויזיה בשידור חי."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"סיווג התכנית הזו הוא <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="tvview_msg_audio_only" msgid="1356866203687173329">"אודיו בלבד"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"‏Live TV אינו נתמך במכשיר זה עם Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"‏Live TV זקוק להרשאה כדי לקרוא את לוחות שידורי הטלוויזיה."</string>
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 1c896000..288c4e3c 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"ライブチャンネルの改善に協力する"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"クラッシュやフリーズの問題を防止し、ライブチャンネルの向上に役立てるため、匿名の診断情報や使用状況データをGoogleと共有してください。"</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"このチャンネルを視聴するには、デフォルトのライブテレビアプリを使用してください。"</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"この番組を視聴するには、デフォルトのライブテレビアプリを使用してください。"</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"この番組は「<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="tvview_msg_audio_only" msgid="1356866203687173329">"音声のみ"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"ライブチャンネルは、Android Lollipopを搭載するこの端末では利用できません。"</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"ライブチャンネルには、TV番組表を読み取る権限が必要です。"</string>
</resources>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index 57c932e7..fdfb94f0 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"მსურს პირდაპირი არხების გაუმჯობესების ხელშეწყობა"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"პირდაპირი არხების გაუმჯობესების მიზნით და ავარიულად გათიშვებისა თუ გაჭედვების თავიდან ასაცილებლად, გამოყენების და დიაგნოსტიკური მონაცემების Google-თან გაზიარება."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"ამ არხის საყურებლად, გამოიყენეთ პირდაპირი ტელევიზიის ნაგულისხმევი აპი."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"ამ პროგრამის საყურებლად, გამოიყენეთ პირდაპირი ტელევიზიის ნაგულისხმევი აპი."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"ამ პროგრამის რეიტინგია: <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="tvview_msg_audio_only" msgid="1356866203687173329">"მხოლოდ აუდიო"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"პირდაპირი არხები არ არის მხარდაჭერილი ამ მოწყობილობაზე Android Lollipop-ით."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"პირდაპირი არხები საჭიროებს ნებართვას ტელეპროგრამის წასაკითხად."</string>
</resources>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index e11a51ff..28be4c0f 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Live TV қолданбасын жақсартуға көмектесу"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Анонимді пайдалану және диагностика деректерін Google жүйесімен бөлісіңіз, сонда Live TV қолданбасын жақсартып, жаңылыс не қатып қалу сияқты ақаулардың алдын аласыз."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Бұл арнаны көру үшін әдепкі Live TV қолданбасын пайдаланыңыз."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Бұл бағдарламаны көру үшін әдепкі Live TV қолданбасын пайдаланыңыз."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Бұл бағдарлама <xliff:g id="RATING">%1$s</xliff:g> болып бағаланды.\nБұл бағдарламаны көру үшін әдепкі Live TV қолданбасын пайдаланыңыз."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Тек аудио"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Android Lollipop жүйесі орнатылған құрылғыларда Live TV қолданбасына қолдау көрсетілмейді."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Live TV үшін ТД тізімдерін оқу рұқсаты қажет."</string>
</resources>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index cdc5495d..ae26c164 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"ជួយធ្វើឲ្យប៉ុស្តិ៍ផ្សាយផ្ទាល់ប្រសើងជាងមុន"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"ចែករំលែកទិន្នន័យវិនិច្ឆ័យ និងទិន្នន័យប្រើប្រាស់អនាមិកជាមួយ Google នោះយើងនឹងអាចធ្វើឲ្យប៉ុស្តិ៍ផ្សាយបន្តផ្ទាល់ប្រសើរឡើង និងទប់ស្កាត់បញ្ហាដូចជា៖ ការគាំង និងការមិនដំណើរការជាដើម។"</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"ដើម្បីមើលប៉ុស្តិ៍នេះ សូមប្រើកម្មវិធីទូរទស្សន៍ផ្សាយផ្ទាល់លំនាំដើម"</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"ដើម្បីមើលកម្មវិធីនេះ សូមប្រើកម្មវិធីទូរទស្សន៍ផ្សាយផ្ទាល់លំនាំដើម"</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"កម្មវិធីនេះត្រូវបានវាយតម្លៃ <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="tvview_msg_audio_only" msgid="1356866203687173329">"តែ​អូឌីយ៉ូ​ប៉ុណ្ណោះ"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"ប៉ុស្តិ៍ផ្សាយផ្ទាល់មិនត្រូវបានគាំទ្រលើឧបករណ៍ដែលដំណើរការដោយ Android Lollipop នេះទេ។"</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"ប៉ុស្តិ៍ផ្សាយផ្ទាល់ត្រូវការសិទ្ធិអនុញ្ញាតដើម្បីអានបញ្ជីទូរទស្សន៍។"</string>
</resources>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index 7bc94e51..d35f019f 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"ಲೈವ್ ಚಾನಲ್‌ಗಳನ್ನು ಸುಧಾರಿಸಲು ಸಹಾಯ ಮಾಡಿ"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"ಅನಾಮಧೇಯ ಬಳಕೆ ಮತ್ತು ಡಯಾಗ್ನೋಸ್ಟಿಕ್ಸ್ ಡೇಟಾವನ್ನು Google ಜೊತೆಗೆ ಹಂಚಿಕೊಳ್ಳಿ, ಈ ಮೂಲಕ ನಮಗೆ ಲೈವ್ ಚಾನಲ್‌ಗಳನ್ನು ಉತ್ತಮಗೊಳಿಸಲು ಹಾಗೂ ಕ್ರ್ಯಾಶಿಂಗ್ ಮತ್ತು ಫ್ರೀಜಿಂಗ್‌ನಂತಹ ಸಮಸ್ಯೆಗಳನ್ನು ತಡೆಯಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"ಈ ಚಾನಲ್ ವೀಕ್ಷಿಸಲು, ಡೀಫಾಲ್ಟ್ ಲೈವ್ ಟಿವಿ ಅಪ್ಲಿಕೇಶನ್ ಬಳಸಿ."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು ವೀಕ್ಷಿಸಲು, ಡೀಫಾಲ್ಟ್ ಲೈವ್ ಟಿವಿ ಅಪ್ಲಿಕೇಶನ್ ಬಳಸಿ."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"ಈ ಕಾರ್ಯಕ್ರಮವನ್ನು <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="tvview_msg_audio_only" msgid="1356866203687173329">"ಆಡಿಯೊ ಮಾತ್ರ"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Android Lollipop ಹೊಂದಿರುವ ಈ ಸಾಧನದಲ್ಲಿ ಲೈವ್ ಚಾನಲ್‌ಗಳು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"ಟಿವಿ ಪಟ್ಟಿಗಳನ್ನು ರೀಡ್ ಮಾಡಲು ಲೈವ್ ಚಾನಲ್‌ಗಳಿಗೆ ಅನುಮತಿ ಅಗತ್ಯವಿದೆ."</string>
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index e07f2138..50e375c6 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -148,11 +148,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"실시간 채널 개선에 도움 주기"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Google에서 실시간 채널을 향상하고 다운과 정지 등의 문제를 방지할 수 있도록 익명의 사용 및 진단 데이터를 공유합니다."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"이 채널을 감상하려면 기본 실시간 TV 앱을 사용합니다."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"이 프로그램을 감상하려면 기본 실시간 TV 앱을 사용합니다."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"이 프로그램의 등급은 <xliff:g id="RATING">%1$s</xliff:g>입니다.\n이 프로그램을 감상하려면 기본 실시간 TV 앱을 사용합니다."</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="tvview_msg_audio_only" msgid="1356866203687173329">"오디오 전용"</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>
@@ -179,4 +185,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"실시간 채널은 이 Android Lollipop 기기에서 지원되지 않습니다."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"TV 목록을 읽으려면 실시간 채널에 권한이 필요합니다."</string>
</resources>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index 13866dcd..f37c3f28 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Түз ободогу каналдарды жакшыртууга жардам бериңиз"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Түз обо каналдарын жакшыртып, бузулуу жана тоңдуруу сыяктуу маселелердин алдын алышыбыз үчүн, колдонуу жана дарт аныктоо дайындарын Google менен жашыруун бөлүшүңүз."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Бул каналды көрүү үчүн демейки Түз ободогу сыналгы колдонмосун пайдаланыңыз."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Бул программаны көрүү үчүн демейки Түз ободогу сыналгы колдонмосун пайдаланыңыз."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Бул программанын рейтинги <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="tvview_msg_audio_only" msgid="1356866203687173329">"Аудио гана"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Бул Android Lollipop орнотулган түзмөктө Түз ободогу каналдар колдоого алынбайт."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Сыналгы тизмелерин оруу үчүн Түз ободогу каналдарга уруксат керек."</string>
</resources>
diff --git a/res/values-ldrtl/integers.xml b/res/values-ldrtl/integers.xml
index 30bb2edd..edac42a6 100644
--- a/res/values-ldrtl/integers.xml
+++ b/res/values-ldrtl/integers.xml
@@ -18,4 +18,4 @@
<resources>
<integer name="fullscreen_dialog_enter_translation_x">-200</integer>
<integer name="fullscreen_dialog_exit_translation_x">200</integer>
-</resources>
+</resources> \ No newline at end of file
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
index d6c6e4ce..067f9956 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"ຊ່ວຍ​ປັບ​ປຸງ​ຊ່ອງ​ສົດ"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"ສະ​ແດງ​ການ​ໃຊ້​ນິ​ລະ​ນາມ ແລະ ຂໍ້​ມູນ​ວິ​ນິ​ໄສ​ກັບ Google ເພື່ອ​ທີ່​ພວກ​ເຮົາ​ຈະ​ສາ​ມາດ​ເຮັດ​ໃຫ້ຊ່ອງ​ສົດດີ​ຂຶ້ນ ແລະ ປ້ອງ​ກັນ​ບັນ​ຫາ​ຕ່າງໆ​ເຊັ່ນ: ການ​ຂັດ​ຂ້ອງ ແລະ ການ​ຈຶ້ງ​ໄວ້."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"ເພື່ອເບິ່ງຊ່ອງນີ້, ໃຊ້ແອັບ Live TV ມາດຕະຖານ."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"ເພື່ອເບິ່ງໂປຣແກຣມນີ້, ໃຊ້ແອັບ Live TV ມາດຕະຖານ."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"ໂປຣແກຣມນີ້ຖືກຈັດປະເພດ <xliff:g id="RATING">%1$s</xliff:g>.\nເພື່ອເບິ່ງໂປຣແກຣມນີ້, ໃຊ້ແອັບ Live TV ມາດຕະຖານ."</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="tvview_msg_audio_only" msgid="1356866203687173329">"ສຽງເທົ່ານັ້ນ"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"ຊ່ອງ​ສົດ​ບໍ່​ຖືກ​ຮອງ​ຮັບ​ຢູ່​ໃນ​ອຸ​ປະ​ກອນ​ນີ້​ດ້ວຍ Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"ຊ່ອງ​ສົດ​ຕ້ອງ​ມີ​ການ​ອະ​ນຸ​ຍາດ​ເພື່ອ​ອ່ານ​ການ​ຈັດ​ລາຍ​ການ TV."</string>
</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index f3641f8a..06d5e4c8 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -40,7 +40,7 @@
<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_parental_controls" msgid="7880060136509458287">"Tėvų kontrolė"</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>
@@ -101,7 +101,7 @@
</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="menu_parental_controls" msgid="2474294054521345840">"Tėvų kontrolė"</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>
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Padėti tobulinti „Live TV“"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Bendrinkite anoniminius naudojimo ir diagnostikos duomenis su „Google“, kad galėtume patobulinti Tiesioginius kanalus ir apsaugoti nuo problemų, pvz., užstrigimo."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Jei norite žiūrėti šį kanalą, naudokite numatytąją tiesioginės TV programą."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Jei norite žiūrėti šią programą, naudokite numatytąją tiesioginės TV programą."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Ši programa įvertinta <xliff:g id="RATING">%1$s</xliff:g>.\nJei norite žiūrėti šią programą, naudokite numatytąją tiesioginės TV programą."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Tik garso įrašas"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Programa „Live TV“ nepalaikoma šiame įrenginyje, nes jame veikia „Lollipop“ versijos „Android“."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Programai „Live TV“ reikalingas leidimas skaityti TV įrašus."</string>
</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 22524cc5..3f63bd17 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Palīdzēt uzlabot tiešraides kanālus"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Kopīgojiet anonīmus lietojuma un diagnostikas datus ar uzņēmumu Google, lai mēs varētu uzlabot lietotni Dzīvie kanāli un novērst tādas problēmas kā avarēšana vai lēna reaģēšana."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Lai skatītos šo kanālu, izmantojiet noklusējuma lietotni televīzijas skatīšanai tiešraidē."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Lai skatītos šo programmu, izmantojiet noklusējuma lietotni televīzijas skatīšanai tiešraidē."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Šīs programma ir novērtēta kā “<xliff:g id="RATING">%1$s</xliff:g>”.\nLai skatītos šo programmu, izmantojiet noklusējuma programmu televīzijas skatīšanai tiešraidē."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Tikai audio"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Šajā ierīcē, kurā instalēta operētājsistēma Android Lollipop, netiek atbalstīta lietotne Tiešraides kanāli."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Lietotnei Tiešraides kanāli ir nepieciešama atļauja lasīt TV sarakstus."</string>
</resources>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
index 2c345ac1..e76a8216 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Помогнете да се подобрат каналите во живо"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Споделувајте анонимни употреба и дијагностика со Google за да можеме да ги подобриме ТВ каналите во живо и да спречиме проблеми како падови и замрзнување."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"За да го гледате овој канал, користете ја стандардната апликација за телевизија во живо."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"За да ја гледате програмава, користете ја стандардната апликација за телевизија во живо."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Оваа програма е оценета <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="tvview_msg_audio_only" msgid="1356866203687173329">"Само звук"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Каналите во живо не се поддржани на уредов со Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"На каналите во живо им е потребна дозвола за да ги читаат ТВ-листите."</string>
</resources>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index 1e38cdd7..5ea46a96 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"തത്സമയ ചാനലുകൾ മെച്ചപ്പെടുത്താൻ സഹായിക്കുക"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"പേരുവിവരങ്ങളില്ലാത്ത ഉപയോഗ വിവരങ്ങളും പ്രശ്നനിർണ്ണയ വിവരങ്ങളും Google-മായി പങ്കിടുക, അതുവഴി ഞങ്ങൾക്ക് തത്സമയ ചാനലുകൾ കൂടുതൽ മെച്ചപ്പെടുത്താനും ക്രാഷിംഗും ഫ്രീസിംഗും പോലുള്ള പ്രശ്നങ്ങൾ തടയാനും കഴിയും."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"ഈ ചാനൽ കാണുന്നതിന്, സ്ഥിര \'തത്സമയ ടിവി ആപ്പ്\' ഉപയോഗിക്കുക."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"ഈ പ്രോഗ്രാം കാണുന്നതിന്, സ്ഥിര \'തത്സമയ ടിവി ആപ്പ്\' ഉപയോഗിക്കുക."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"ഈ പ്രോഗ്രാമിനെ <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="tvview_msg_audio_only" msgid="1356866203687173329">"ഓഡിയോ മാത്രം"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Android Lollipop ഉള്ള ഈ ഉപകരണത്തിൽ തത്സമയ ചാനലുകൾക്ക് പിന്തുണയില്ല."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"TV ലിസ്റ്റിംഗുകൾ വായിക്കുന്നതിന് തത്സമയ ചാനലുകൾക്ക് അനുമതി ആവശ്യമാണ്."</string>
</resources>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index e534f439..7e0d4c2d 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Шууд сувгийг сайжруулахад туслах"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Та хэрэглээ, оношлогооны мэдээг Google-д автоматаар хуваалцсанаар бид шууд сувгийг гэмтэх, зогсохоос сэргийлж, сайжруулах болно."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Энэ сувгийг үзэхийн тулд үндсэн шууд ТВ-н апп-г ашиглаарай,"</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Энэ хөтөлбөрийг үзэхийн тулд үндсэн шууд ТВ-н апп ашиглаарай."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Энэ хөтөлбөр нь <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="tvview_msg_audio_only" msgid="1356866203687173329">"Зөвхөн аудио"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Android Lollipop-той төхөөрөмжид шууд сувгийг дэмжихгүй."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Шууд сувагт TВ-н жагсаалтыг унших зөвшөөрөл шаардлагатай."</string>
</resources>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index 9f01345d..827e2a11 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"थेट चॅनेल सुधारण्यात मदत करा"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Google सह अनामित वापर आणि निदान डेटा सामायिक करा जेणेकरून आम्‍ही थेट चॅनेल आणखी चांगले बनवू शकू आणि क्रॅश होणे आणि गोठविणे यासारख्‍या समस्या प्रतिबंधित करू शकू."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"हे चॅनेल पाहण्यासाठी, डीफॉल्ट थेट टीव्ही अॅप वापरा."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"हा प्रोग्राम पाहण्यासाठी, डीफॉल्ट थेट टीव्ही अॅप वापरा."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"हा प्रोग्राम <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="tvview_msg_audio_only" msgid="1356866203687173329">"फक्त ऑडिओ"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Android Lollipop सह थेट चॅनेल या डिव्हाइसवर समर्थित नाहीत."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"थेट चॅनेलना टीव्ही सूचींचे वाचन करण्यासाठी परवानगीची आवश्यकता आहे."</string>
</resources>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
index 03609345..02293c34 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Bantu meningkatkan Saluran Langsung"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Kongsi data penggunaan dan diagnostik awanama dengan Google supaya kami dapat menjadikan Saluran Langsung lebih baik dan menghalang isu seperti ranap dan pegun."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Untuk menonton saluran ini, gunakan apl lalai TV Langsung."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Untuk menonton program ini, gunakan apl lalai TV Langsung."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Atur cara ini telah dinilai <xliff:g id="RATING">%1$s</xliff:g>.\nUntuk menonton program ini, gunakan apl lalai TV Langsung."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Audio sahaja"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Saluran Langsung tidak disokong pada peranti ini dengan Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Saluran Langsung memerlukan kebenaran untuk membaca penyenaraian TV."</string>
</resources>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index 6c962f4d..e0715554 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"တိုက်ရိုက်လွှင့်ချန်နယ်များ တိုးတက်အောင်ကူညီပါ"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"အသုံးပြုမှုနှင့် အမှားရှာဖွေမှုဒေတာများကို Google နှင့်မျှဝေခြင်းဖြင့် ပျက်စီးခြင်းနှင့် ရပ်တန့်သွားခြင်းကဲ့သို့သော ပြဿနာများကို ကာကွယ်ပြီး တိုက်ရိုက်လွှင့်နေသောချယ်နယ်များကို ပိုမိုကောင်းမွန်စေပါ။"</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"ဤချန်နယ်ကိုကြည့်ရန်၊ မူလတိုက်ရိုက်လွှင့်သည့်တီဗွီအပ်ဖ်ကို အသုံးပြုပါ။"</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"အစီအစဉ်ကိုကြည့်ရန်၊ မူလတိုက်ရိုက်လွှင့်သည့် တီဗွီအပ်ဖ်ကိုအသုံးပြုပါ။"</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"ဤအစီအစဉ်ကို <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="tvview_msg_audio_only" msgid="1356866203687173329">"အသံ သီးသန့်"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Android Lollipop ဗားရှင်းဖြင့်လည်ပတ်သော ဤကိရိယာတွင် တိုက်ရိုက်ထုတ်လွှင့်သောချန်နယ်လိုင်းများအား အသုံးမပြုနိင်ပါ။"</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"တီဗီစာရင်းများကို ဖတ်ရှုရန် တိုက်ရိုက်ထုတ်လွှင့်သောချန်နယ်လိုင်းများတွင် ခွင့်ပြူချက်လိုအပ်သည်။"</string>
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index d15dab71..4a2eb95a 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Bidra til å forbedre Live TV"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Del anonyme bruksdata og diagnostikkdata med Google sånn at vi kan gjøre Direkte-TV bedre og forhindre problemer (for eksempel når appen kræsjer eller fryser)."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"For å se på denne kanalen, bruk Live TV-appen som er angitt som standard."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"For å se dette programmet, bruk Live TV-appen som er angitt som standard."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Dette programmet er vurdert som <xliff:g id="RATING">%1$s</xliff:g>.\nFor å se dette programmet, bruk Live TV-appen som er angitt som standard."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Bare lyd"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Live TV støttes ikke på denne enheten med Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Live TV trenger tillatelse til å lese TV-programoversikten."</string>
</resources>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index a12c2b89..ed1592e5 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"लाइभ च्यानलहरु सुधार गर्न मद्दत गर्नुहोस्"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Google सँग अज्ञात प्रयोग डेटा र लक्षण पहिचान गर्न डेटा साझेदारी गर्नुहोस् ताकि हामी लाइभ च्यानलहरू राम्रो बनाउन र क्र्यास हुने तथा स्थिर हुने समस्याहरूलाई रोक्न सक्छौं।"</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"यो च्यानल हेर्न, पूर्वनिर्धारित लाइभ TV अनुप्रयोग प्रयोग गर्नुहोस्।"</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"यो कार्यक्रम हेर्न, पूर्वनिर्धारित लाइभ TV अनुप्रयोगको प्रयोग गर्नुहोस्।"</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"यो कार्यक्रम मूल्याङ्कन गरिएको छ <xliff:g id="RATING">%1$s</xliff:g>।\n यो कार्यक्रम हेर्न, पूर्वनिर्धारित लाइभ TV अनुप्रयोग प्रयोग गर्नुहोस्।"</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="tvview_msg_audio_only" msgid="1356866203687173329">"अडियो मात्र"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"लाइभ च्यानलहरु Android Lollipop भएको यस यन्त्रमा समर्थित छैन।"</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"लाइभ च्यानलहरुलाई TV सूचीहरूलाई पढ्न अनुमति आवश्यक पदर्छ।"</string>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 84a23493..c09de0db 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -90,7 +90,7 @@
<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_source_item_customize_channels_description" msgid="3124895053900086630">"Kanalen kiezen voor je 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>
@@ -129,13 +129,13 @@
<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_unlock_channel" msgid="4797922378296393173">"Je pincode opgeven om dit kanaal te bekijken"</string>
+ <string name="pin_enter_unlock_program" msgid="7311628843209871203">"Je pincode opgeven om dit programma te bekijken"</string>
+ <string name="pin_enter_pin" msgid="249314665028035038">"Je 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>
+ <string name="pin_enter_again" msgid="2618999754723090427">"Je pincode bevestigen"</string>
+ <string name="pin_enter_old_pin" msgid="4588282612931041919">"Je huidige pincode opgeven"</string>
<plurals name="pin_enter_countdown">
<item quantity="one" msgid="5555357148779935468">"Je hebt 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">"Je hebt vijf keer een verkeerde pincode ingevoerd.\nProbeer het over <xliff:g id="REMAINING_SECONDS">%1$d</xliff:g> seconden opnieuw."</item>
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Helpen bij het verbeteren van Live kanalen"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Deel anonieme diagnostische en gebruiksgegevens met Google zodat we Live kanalen kunnen verbeteren en problemen, zoals crashen en vastlopen, kunnen voorkomen."</string>
+ <string name="tvview_channel_locked" msgid="6486375335718400728">"Als je dit kanaal wilt bekijken, druk je rechts en geef je je pincode op"</string>
<string name="tvview_content_locked" msgid="391823084917017730">"Als je dit programma wilt bekijken, druk je rechts en geef je je 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="tvview_content_locked_format" msgid="3741874636031338247">"Dit programma is beoordeeld als <xliff:g id="RATING">%1$s</xliff:g>.\nAls je dit programma wilt bekijken, druk je rechts en geef je je pincode op."</string>
+ <string name="tvview_channel_locked_no_permission" msgid="677653135227590620">"Gebruik de standaardapp voor live tv om dit kanaal te bekijken."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Gebruik de standaardapp voor live tv om dit programma te bekijken."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Dit programma heeft de classificatie <xliff:g id="RATING">%1$s</xliff:g>.\nGebruik de standaardapp voor live tv om dit programma te bekijken."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Alleen audio"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"De app \'Live kanalen\' wordt niet ondersteund op dit apparaat met Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"De app \'Live kanalen\' heeft toestemming nodig om tv-vermeldingen te lezen."</string>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index d1225bd8..7d472369 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Pomóż w ulepszaniu Telewizji online"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Udostępniaj Google anonimowe informacje o użytkowaniu i dane diagnostyczne, by pomóc w ulepszaniu Telewizji online i usuwaniu błędów takich jak np. zatrzymywanie obrazu."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Aby oglądać ten kanał, użyj standardowej aplikacji Telewizja na żywo."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Aby obejrzeć ten program, użyj standardowej aplikacji Telewizja na żywo."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Ten program ma ocenę <xliff:g id="RATING">%1$s</xliff:g>.\nAby go obejrzeć, użyj standardowej aplikacji Telewizja na żywo."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Tylko dźwięk"</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>
@@ -170,11 +176,13 @@
<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_not_passthrough_input" msgid="4502101097091087411">"Nieodpowiedni typ tunera. Uruchom aplikację Telewizja online 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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Telewizja online nie jest obsługiwana na tym urządzeniu z Androidem Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Aby odczytywać programy telewizyjne, Telewizja online potrzebuje uprawnień."</string>
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 5d1f5165..4c0bdf0c 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -89,7 +89,7 @@
<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" msgid="2836316051162501082">"Person. lista de 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>
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Ajudar a melhorar a aplicação Canais em direto"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Partilhar dados de diagnóstico e de utilização anónimos com a Google para melhorar os Canais em Direto e evitar problemas, como falhas e bloqueios."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Para ver este canal, utilize a aplicação TV em direto predefinida."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Para ver este programa, utilize a aplicação TV em direto predefinida."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Este programa tem a classificação <xliff:g id="RATING">%1$s</xliff:g>.\nPara o ver, utilize a aplicação TV em direto predefinida."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Apenas áudio"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"A aplicação Canais em direto não é compatível com este dispositivo com o Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"A aplicação Canais em direto necessita de autorização para ler as listagens de programas de TV."</string>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 345271e5..eb49ee68 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Ajudar a melhorar os Canais ao vivo"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Compartilhar dados anônimos de uso e diagnóstico com o Google para que possamos melhorar os Canais ao vivo e evitar problemas, como falhas e congelamento."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Para assistir este canal, use o app padrão de TV ao vivo."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Para assistir este programa, use o app padrão de TV ao vivo."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Este programa foi classificado como <xliff:g id="RATING">%1$s</xliff:g>.\nPara assistir este programa, use o app padrão de TV ao vivo."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Somente áudio"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"O app \"Canais ao vivo\" não é compatível com este dispositivo com Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"O app \"Canais ao vivo\" precisa de permissão para ler a programação de TV."</string>
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index f46e6bd2..89a34be5 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -40,7 +40,7 @@
<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_parental_controls" msgid="7880060136509458287">"Control parental"</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>
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Contribuiți la îmbunătățirea aplicației „Canale live”"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Trimiteți la Google date anonime de utilizare și de diagnosticare pentru a îmbunătăți aplicația Canale live și pentru a preveni probleme cum ar blocarea."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Pentru a viziona acest canal, folosiți aplicația prestabilită pentru programe TV live."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Pentru a viziona acest program, folosiți aplicația prestabilită pentru programe TV live."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Acest program este evaluat cu <xliff:g id="RATING">%1$s</xliff:g>.\nPentru a viziona acest program, folosiți aplicația prestabilită pentru programe TV live."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Numai conținut audio"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Aplicația „Canale live” nu este acceptată pe acest dispozitiv cu Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Aplicația „Canale live” are nevoie de permisiunea de a citi lista de programe TV."</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index ee8ff7ec..97b32c27 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Не помогать улучшить \"Прямой эфир\""</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Разрешите отправку анонимных отчетов об использовании и данных диагностики в Google, чтобы мы могли улучшить работу Телеканалов и устранить сбои и зависания."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Чтобы смотреть этот канал, используйте приложение для просмотра телепрограмм по умолчанию."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Чтобы посмотреть эту программу, используйте приложение для просмотра телепрограмм по умолчанию."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Возрастное ограничение для этой программы: <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="tvview_msg_audio_only" msgid="1356866203687173329">"Только аудио"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Прямой эфир не поддерживается на этом устройстве с Android 5.0."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Требуется разрешение для чтения телепрограмм."</string>
</resources>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
index 57652c6e..5fb0919b 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"සජීවි නාලිකා වැඩි දියුණු කිරීමට උදව් කරන්න"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Google සමග නිර්නාමික භාවිත සහ දෝෂ නිර්ණ දත්ත බෙදා ගන්න, එවිට අපට සජීවී නාලිකා වඩාත් යහපත් කිරීමට සහ බිඳ වැටීම් සහ සිරවීම් වැනි ගැටලු වැළැක්වීමට හැකිය."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"මෙම නාලිකාව නැරඹීම සඳහා, පෙරනිමි සජීවි TV යෙදුම භාවිතා කරන්න."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"මෙම වැඩසටහන නැරඹීමට, පෙරනිමි සජීවි TV යෙදුම භාවිතා කරන්න."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"මෙම වැඩසටහන <xliff:g id="RATING">%1$s</xliff:g> ලෙස වර්ගීකරණය කර ඇත.\nමෙම වැඩසටහන නැරඹීමට, පෙරනිමි සජීවි TV යෙදුම භාවිතා කරන්න."</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="tvview_msg_audio_only" msgid="1356866203687173329">"ශ්‍රව්‍ය පමණයි"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Android Lollipop සමගින් සජීවී නාලිකා මෙම උපාංගය මත සහාය නොදක්වයි."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"සජීවී නාලිකාවලට TV ලැයිස්තුගත කිරීම් කියවීමට අවසරය අවශ්‍යයි."</string>
</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 71f76b1f..2d33a44e 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Pomáhať s vylepšovaním aktívnych kanálov"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Zdieľať s Googlom anonymné štatistiky o používaní a diagnostické údaje, aby sme mohli Televíziu online zlepšiť a zabrániť problémom, ako sú zlyhania a zamŕzania."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Ak chcete sledovať tento kanál, použite predvolenú aplikáciu Live TV."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Ak chcete sledovať tento program, použite predvolenú aplikáciu Live TV."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Tento program má hodnotenie <xliff:g id="RATING">%1$s</xliff:g>.\nAk ho chcete sledovať, použite predvolenú aplikáciu Live TV."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Iba zvuk"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"V tomto zariadení so systémom Android Lollipop nie sú aktívne kanály podporované."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Aktívne kanály potrebujú povolenie na čítanie televíznych programov."</string>
</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index b5499ec0..e4588f2f 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Pomoč pri izboljšanju kanalov v živo"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Deljenje anonimnih podatkov o uporabi in diagnostičnih podatkov z Googlom zaradi izboljšanja Televizije v živo in preprečevanja težav, kot so zrušitve in zmrzovanje."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Če želite gledati ta kanal, uporabite privzeto aplikacijo za TV v živo."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Če želite gledati to oddajo, uporabite privzeto aplikacijo za TV v živo."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Ta oddaja ima kategorijo vsebine <xliff:g id="RATING">%1$s</xliff:g>.\nČe jo želite gledati, uporabite privzeto aplikacijo za TV v živo."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Samo zvok"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Kanali v živo niso podprti v tej napravi s sistemom Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Kanali v živo potrebujejo dovoljenje za branje televizijskih sporedov."</string>
</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index e86fb28f..cfec71c4 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Шаљи податке ради побољшања Live TV-а"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Делите анонимне податке о коришћењу и дијагностици са Google-ом да бисмо побољшали Канале уживо и спречили проблемe, попут отказивања и неодазивања."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Да бисте гледали овај канал, користите подразумевану апликацију за ТВ уживо."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Да бисте гледали овај програм, користите подразумевану апликацију за ТВ уживо."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Овај програм има оцену <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="tvview_msg_audio_only" msgid="1356866203687173329">"Само аудио"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Канали уживо нису подржани на овом уређају који користи Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Каналима уживо треба дозвола за читање листе TV канала."</string>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 6faf0b0a..06742efb 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Hjälp till att förbättra Livekanaler"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Dela anonym information om användning och diagnostik med Google så att vi kan förbättra Livekanaler och förhindra problem som att appen fastnar eller kraschar."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Använd standardappen för live-TV om du vill titta på den här kanalen."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Använd standardappen för live-TV om du vill titta på programmet."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Programmet har klassificeringen <xliff:g id="RATING">%1$s</xliff:g>.\nAnvänd standardappen för live-TV om du vill titta på 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="tvview_msg_audio_only" msgid="1356866203687173329">"Endast ljud"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Livekanaler stöds inte på enheter med Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Livekanaler behöver behörighet att läsa TV-tablåer."</string>
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 1f44ca98..de96aae4 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Saidia kuboresha Vituo vya Moja kwa moja"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Shiriki data isiyokutambulisha ya matumizi na uchanganuzi wa matatizo kwa Google ili tuweze kuboresha programu ya Televisheni Mtandaoni na kuzuia matatizo ya kuacha kufanya kazi na kukwama."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Ili kutazama kituo hiki, tumia chaguo-msingi ya programu ya TV ya moja kwa moja."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Ili kutazama kituo hiki, tumia chaguo-msingi ya programu ya TV ya moja kwa moja."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Programu hii imekadiriwa <xliff:g id="RATING">%1$s</xliff:g>.\nIli kutazama kituo hiki, tumia chaguo-msingi ya programu ya TV ya moja kwa moja."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Sauti pekee"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Kipengele cha Vituo vya Moja kwa Moja hakiwezi kutumiwa kwenye kifaa hiki kwa kutumia Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Kipengele cha Vituo vya Moja kwa Moja kinahitaji ruhusa ili kuonyesha orodha za TV."</string>
</resources>
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
index 38179f14..73720cda 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"நேரலைச் சேனல்களை மேம்படுத்த உதவு"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"உபயோகம் மற்றும் பிழை ஆய்வுகள் தரவை அநாமதேயமாக Google உடன் பகிரும், இதன் மூலம் எங்களால் நேரலை சேனல்களை மேலும் சிறப்பாக்க முடியும் மற்றும் சிதைவு, செயல்படாமல் இருத்தல் போன்ற சிக்கல்களைத் தடுக்க முடியும்."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"இந்தச் சேனலைப் பார்க்க, இயல்புநிலை லைவ் டிவி பயன்பாட்டைப் பயன்படுத்தவும்."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"இந்த நிகழ்ச்சியைப் பார்க்க, இயல்புநிலை லைவ் டிவி பயன்பாட்டைப் பயன்படுத்தவும்."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"இந்த நிகழ்ச்சி <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="tvview_msg_audio_only" msgid="1356866203687173329">"ஆடியோ மட்டும்"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Android Lollipop இல் இயங்கும் இந்தச் சாதனத்தில் நேரலைச் சேனல்களுக்கு ஆதரவில்லை."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"டிவி பட்டியல்களைப் படிக்க, நேரலைச் சேனல்களுக்கு அனுமதி தேவை."</string>
</resources>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
index 2c0b861d..ef699362 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"ప్రత్యక్ష ప్రసార ఛానెల్‌లను మెరుగుపరచడంలో సహాయపడండి"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"అనామక వినియోగ మరియు విశ్లేషణల డేటాను Googleతో భాగస్వామ్యం చేయండి, తద్వారా మేము లైవ్ ఛానెల్‌లను మెరుగ్గా చేయగలము అలాగే క్రాష్ కావడం మరియు స్తంభించడం వంటి సమస్యలను నిరోధించగలము."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"ఈ ఛానెల్‌ను చూడటానికి, డిఫాల్ట్ లైవ్ టీవీ అనువర్తనాన్ని ఉపయోగించండి."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"ఈ కార్యక్రమాన్ని చూడటానికి, డిఫాల్ట్ లైవ్ టీవీ అనువర్తనాన్ని ఉపయోగించండి."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"ఈ కార్యక్రమానికి <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="tvview_msg_audio_only" msgid="1356866203687173329">"ఆడియో మాత్రమే"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Android Lollipop అమలులో ఉన్న ఈ పరికరంలో ప్రత్యక్ష ప్రసార ఛానెల్‌లకు మద్దతు లేదు."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"టీవీ జాబితాలను చదవడానికి ప్రత్యక్ష ప్రసార ఛానెల్‌లకు అనుమతి అవసరం."</string>
</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 951c1b39..5207dcf1 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"ช่วยปรับปรุง Live TV"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"แชร์ข้อมูลการใช้งานและการวินิจฉัยที่ไม่ระบุชื่อกับ Google เพื่อให้เราสามารถทำให้รายการถ่ายทอดสดดีขึ้น และป้องกันปัญหาต่างๆ เช่น การขัดข้องและการค้าง"</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"หากต้องการดูช่องนี้ ให้ใช้แอปทีวีออนไลน์ที่เป็นค่าเริ่มต้น"</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"หากต้องการดูโปรแกรมนี้ ให้ใช้แอปทีวีออนไลน์ที่เป็นค่าเริ่มต้น"</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"โปรแกรมนี้ได้รับคะแนน <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="tvview_msg_audio_only" msgid="1356866203687173329">"เฉพาะเสียง"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Live TV ไม่ได้รับการสนับสนุนบนอุปกรณ์นี้ซึ่งมี Android Lollipop"</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Live TV ต้องใช้สิทธิ์ในการอ่านรายการทีวี"</string>
</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 672fb3f9..98512bbf 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Tulungang pahusayin ang Mga Live na Channel"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Magbahagi ng anonymous na data ng paggamit at mga diagnostic sa Google upang mas mapaganda namin ang Mga Live Channel at mapigilan ang mga isyu tulad ng pagka-crash at pagfi-freeze."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Upang mapanood ang channel na ito, gamitin ang default na Live TV app."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Upang mapanood ang programang ito, gamitin ang default na Live TV app."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Ang programang ito ay binigyan ng rating na <xliff:g id="RATING">%1$s</xliff:g>.\nUpang mapanood ang programang ito, gamitin ang default na Live TV app."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Audio lang"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Hindi sinusuportahan ang Live TV sa device na ito na may Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Kailangan ng pahintulot ng Live TV upang mabasa ang mga listahan sa TV."</string>
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 6a551a2f..c33de768 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Live TV\'ı iyileştirmeye yardımcı ol"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Canlı Kanallar\'ı daha iyi hale getirebilmemiz ve kilitlenme, donma gibi sorunları önleyebilmemeiz için anonim kullanım ve teşhis verilerini Google ile paylaşın."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Bu kanalı izlemek için varsayılan Canlı TV uygulamasını kullanın."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Bu programı izlemek için varsayılan Canlı TV uygulamasını kullanın."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Bu program <xliff:g id="RATING">%1$s</xliff:g> olarak derecelendirildi.\nBu programı izlemek için varsayılan Canlı TV uygulamasını kullanın."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Yalnızca ses"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Bu cihazda Android Lollipop ile Canlı Kanallar desteklenmemektedir."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Canlı Kanallar\'ın TV kanal listesini okuması için izin gerekiyor."</string>
</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 0a7fc140..e711982a 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Допомагати покращити додаток Live TV"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Надсилати в Google анонімні дані про використання та діагностику. Це допоможе нам покращити додаток Телеканали та вирішити такі проблеми, як аварійне завершення роботи й зависання."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Щоб дивитися цей канал, відкрийте додаток для прямих трансляцій за умовчанням."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Щоб дивитися цю телепередачу, відкрийте додаток для прямих трансляцій за умовчанням."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Категорія цієї телепередачі – <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="tvview_msg_audio_only" msgid="1356866203687173329">"Лише аудіо"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Додаток Live TV не підтримується на цьому пристрої з ОС Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Додатку Live TV потрібен дозвіл переглядати програму телепередач."</string>
</resources>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
index 0fe0f19f..8ba08f6e 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"لائیو چینلز بہتر بنانے میں مدد کریں"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"‏Google کے ساتھ گمنام استعمال اور تشخیصاتی ڈیٹا کا اشتراک کریں تاکہ ہم لائیو چینلز بہتر بنا اور کریشنگ اور فریزنگ جیسے مسائل کو روک سکیں۔"</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"‏اس چینل کو دیکھنے کیلئے ڈیفالٹ لائیو TV ایپ استعمال کریں۔"</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"‏اس پروگرام کو دیکھنے کیلئے ڈیفالٹ لائیو TV ایپ استعمال کریں۔"</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"‏اس پروگرام کی درجہ بندی <xliff:g id="RATING">%1$s</xliff:g> کی گئی ہے۔\nاس پروگرام کو دیکھنے کیلئے ڈیفالٹ لائیو TV ایپ استعمال کریں۔"</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="tvview_msg_audio_only" msgid="1356866203687173329">"صرف آڈیو"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"‏Android Lollipop والے اس آلے پر لائیو چینلز کی معاونت نہیں ہے۔"</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"‏لائیو چینلز کو TV فہرستیں پڑھنے کیلئے اجازت کی ضرورت ہے۔"</string>
</resources>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index 0f3d5d6b..31eeaa67 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Jonli efir ilovasini yaxshilashga ko‘maklashish"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Jonli kanallar xizmatini yaxshilash va ishdan chiqish hamda qotib qolish muammolarining oldini olish uchun Google’ga anonim tarzda foydalanish to‘g‘risidagi va tashxis ma’lumotlarini yuboring."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Bu kanallarni ko‘rish uchun birlamchi “Jonli efir” ilovasidan foydalaning."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Bu dasturni ko‘rish uchun birlamchi “Jonli efir” ilovasidan foydalaning."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Bu dastur uchun yosh cheklovi: <xliff:g id="RATING">%1$s</xliff:g>.\nUshbu dasturni ko‘rish uchun birlamchi “Jonli efir” ilovasidan foydalaning."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Faqat audio"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Jonli efir Android Lollipop tizimi o‘rnatilgan mazkur qurilmada ishlamaydi."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Jonli efir televizor ro‘yxatlarini o‘qish uchun ruxsat zarur."</string>
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 9bcd206f..3b77f658 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Giúp cải tiến ứng dụng Kênh trực tiếp"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Chia sẻ ẩn danh dữ liệu sử dụng và chẩn đoán với Google để chúng tôi có thể làm cho Kênh trực tiếp tốt hơn và ngăn chặn các sự cố như bị lỗi và treo."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Để xem kênh này, hãy sử dụng ứng dụng Truyền hình trực tiếp mặc định."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Để xem chương trình này, hãy sử dụng ứng dụng Truyền hình trực tiếp mặc định."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"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 sử dụng ứng dụng Truyền hình trực tiếp mặc định."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Chỉ âm thanh"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Kênh trực tiếp không được hỗ trợ trên thiết bị chạy Android Lollipop này."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Kênh trực tiếp cần quyền đọc danh sách TV."</string>
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index e95eacb6..42382221 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"帮助改进直播频道"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"将匿名使用情形和诊断数据提供给 Google,以协助我们改进直播频道并避免发生崩溃和死机等问题。"</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"要观看此频道,请使用默认的“电视直播”应用。"</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"要观看此节目,请使用默认的“电视直播”应用。"</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"该节目的分级是“<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="tvview_msg_audio_only" msgid="1356866203687173329">"仅提供音频"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"此 Android Lollipop 设备不支持直播频道。"</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"直播频道需要获取相应权限才能读取电视节目列表。"</string>
</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 54428d83..86f003f9 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"協助改善「直播頻道」"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"與 Google 分享匿名使用和診斷資料,以協助改善「直播頻道」,並防止當機問題發生。"</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"如要觀看此頻道,請使用預設電視直播應用程式。"</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"如要觀看此節目,請使用預設電視直播應用程式。"</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"此節目評級為「<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="tvview_msg_audio_only" msgid="1356866203687173329">"只限音效"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"此 Android Lollipop 裝置不支援「直播頻道」。"</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"「直播頻道」需要權限方可讀取電視節目表。"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 3e93c9a3..5a1c5ed4 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"協助改善直播頻道"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"將匿名的使用情形資料與診斷資料提供給 Google,協助我們改善直播頻道以及避免發生當機和畫面凍結等問題。"</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"如要觀看這個頻道,請使用預設的直播電視應用程式。"</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"如要觀看這個節目,請使用預設的直播電視應用程式。"</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"這個節目的分級是「<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="tvview_msg_audio_only" msgid="1356866203687173329">"僅限音訊"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"這個搭載 Android Lollipop 的裝置不支援「直播頻道」。"</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"「直播頻道」需要權限才能讀取電視節目表。"</string>
</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index e93b4ea9..483f2625 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -146,11 +146,17 @@
<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="about_menu_improve" msgid="3712578027009311401">"Siza ukuthuthukisa Iziteshi Ezibukhoma"</string>
+ <string name="about_menu_improve_summary" msgid="7548489011760588571">"Yabelana ngedatha yokusebenza yokungaziwa neyokuhlola ne-Google ukuze sikwazi ukwenza kangcono iziteshi ezibukhoma futhi sivikele izinkinga ezifana nokusaphazeka nokjema."</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="tvview_channel_locked_no_permission" msgid="677653135227590620">"Ukuze ubuke lesi siteshi, sebenzisa uhlelo lokusebenza lwe-TV ebukhoma."</string>
+ <string name="tvview_content_locked_no_permission" msgid="2279126235895507764">"Ukuze ubuke lolu hlelo, sebenzisa uhlelo lokusebenza oluzenzakalelayo lwe-TV ebukhoma."</string>
+ <string name="tvview_content_locked_format_no_permission" msgid="5690794624572767106">"Lolu hlelo lulinganiselwe ngokungu-<xliff:g id="RATING">%1$s</xliff:g>.\nUkuze ubuke lolu hlelo, sebenzisa uhlelo lokusebenza lwe-TV ebukhoma."</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="tvview_msg_audio_only" msgid="1356866203687173329">"Umsindo kuphela"</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>
@@ -177,4 +183,6 @@
<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>
+ <string name="msg_not_supported_device" msgid="4469404625040284722">"Iziteshi ezibukhoma azisekelwa kule divayisi nge-Android Lollipop."</string>
+ <string name="msg_read_tv_listing_permission_denied" msgid="8882813301235518909">"Iziteshi ezibukhoma zidinga imvume ukuze zifunde ukufakwa kuhlu kwe-TV."</string>
</resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 0ae24d90..e887e6d0 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -50,4 +50,22 @@
<item>Premier</item>
<item>Tech/Science</item>
</string-array>
+
+ <!-- Titles in the onboarding page.
+ TODO: set translatable to true. -->
+ <string-array name="welcome_page_titles" translatable="false">
+ <item>Live TV</item>
+ <item>Download new channels</item>
+ <item>Customize your channel line-up</item>
+ <item>Watch, pause, and rewind live TV</item>
+ </string-array>
+
+ <!-- Descriptions in the onboarding page.
+ TODO: set translatable to true. -->
+ <string-array name="welcome_page_descriptions" translatable="false">
+ <item>Combine broadcast TV with streaming channels provided by your apps.</item>
+ <item>Customize your channel line up by downloading apps that offer streaming channels.\nYou can find compatible apps in the Google Play Store.</item>
+ <item>Set up newly installed channels from the Channel Sources option within the TV menu</item>
+ <item>Enjoy over-the-air channels like ABC, CBS, NBC, and FOX\nwhen you add a compatible TV tuner &amp; HDTV antenna to your Nexus Player.</item>
+ </string-array>
</resources>
diff --git a/res/values/attr.xml b/res/values/attr.xml
index 1261ea41..4592dbad 100644
--- a/res/values/attr.xml
+++ b/res/values/attr.xml
@@ -22,4 +22,4 @@
<declare-styleable name="ProgramTooWideState" >
<attr name="state_program_too_wide" format="boolean" />
</declare-styleable>
-</resources>
+</resources> \ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 67a75431..9cb121e8 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -135,4 +135,8 @@
<color name="setup_description">#B3EEEEEE</color>
<color name="setup_background">#E701579B</color>
<color name="setup_divider">#4DEEEEEE</color>
+
+ <!-- Onboarding screen -->
+ <color name="onboarding_dot_selected">#FFFFFFFF</color>
+ <color name="onboarding_dot_unselected">#4DFFFFFF</color>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 185d833b..c4c6018a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -17,24 +17,14 @@
<resources>
<!-- 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_margin_top">@dimen/menu_enter_offset_y_negative</dimen>
<dimen name="menu_margin_bottom_min">27dp</dimen>
<dimen name="menu_row_align_from_bottom">124dp</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>
<!-- This should be changed as the font change -->
<dimen name="menu_row_title_text_descender_height">4.5dp</dimen>
<!-- This value is 16dp in the redlines. Considering the descenders, it needs to be subtracted
@@ -44,15 +34,9 @@
<dimen name="menu_row_contents_padding_top">11.5dp</dimen>
<dimen name="menu_row_contents_padding_bottom_max">48dp</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.
+ <!-- NOTE: The top 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>
@@ -60,7 +44,6 @@
<dimen name="menu_enter_offset_y_negative">-32dp</dimen>
<dimen name="menu_exit_offset_y">32dp</dimen>
<dimen name="menu_row_scroll_up_anim_offset">32dp</dimen>
- <dimen name="menu_row_scroll_up_anim_offset_negative">-32dp</dimen>
<!-- Play controls -->
<dimen name="play_controls_width">696dp</dimen>
@@ -319,4 +302,8 @@
<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>
+
+ <!-- Onboarding screen -->
+ <dimen name="onboarding_dot_radius">4dp</dimen>
+ <dimen name="onboarding_dot_gap">12dp</dimen>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2c2bcd39..d25cd34b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -403,6 +403,15 @@
<!-- 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>
+ <!-- opt out preferences -->
+ <eat-comment />
+
+ <!--Title of option set to improve the Live TV app.
+ "Live TV" should match app_name -->
+ <string name="about_menu_improve">Help improve Live TV</string>
+ <!--Summary of option to improve the Live TV app. -->
+ <string name="about_menu_improve_summary">Share anonymous usage and diagnostics data with Google so we can make Live TV better and prevent issues like crashing and freezing.</string>
+
<!-- TunableTvView -->
<eat-comment />
<!-- Description on the locked screen when current channel is locked by parental control. [CHAR LIMIT=NONE] -->
@@ -411,12 +420,22 @@
<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 current channel is locked by parental control. [CHAR LIMIT=NONE] -->
+ <string name="tvview_channel_locked_no_permission">To watch this channel, use the default Live TV app.</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_no_permission">To watch this program, use the default Live TV app.</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_no_permission">This program is rated <xliff:g id="rating" example="TV_MA">%1$s</xliff:g>.\nTo watch this program, use the default Live TV app.</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>
+ <!-- The text message which is shown when the current program is audio only one.
+ i.e. The program dones't have a video. [CHAR LIMIT=NONE] -->
+ <string name="tvview_msg_audio_only">Audio only</string>
+
<!-- Channel Banner -->
<eat-comment />
<!-- The text used when there is no program title. [CHAR LIMIT=NONE] -->
@@ -483,6 +502,11 @@
<!-- 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>
+ <!-- Error message when Live channels has been launched in Lollipop devices without system
+ permission. [CHAR LIMIT=NONE] -->
+ <string name="msg_not_supported_device">Live TV is not supported on this device with Android Lollipop.</string>
+ <!-- Error message when a user denied to grant READ_TV_LISTING permission. [CHAR LIMIT=NONE] -->
+ <string name="msg_read_tv_listing_permission_denied">Live TV needs permission to read the TV listings.</string>
<!-- Strings for debug or not to be shown to users -->
<eat-comment />
@@ -498,4 +522,35 @@
<string name="search_result_no_result" translatable="false">No result</string>
<string name="search_result_title" translatable="false">Search results</string>
+ <!-- Onboarding experience.
+ TODO: Change translatable to true.-->
+ <!-- Text for the illustration area -->
+ <string name="illustration_area_text" translatable="false">ILLUSTRATION AREA</string>
+ <!-- Text for the button which is used to navigate to the next page -->
+ <string name="welcome_next_button_text" translatable="false">Next &gt;</string>
+ <!-- Text for the button which is used to start the channel source setup. -->
+ <string name="welcome_start_button_text" translatable="false">Get started &gt;</string>
+ <!-- Text for the Live TV overview screen in onboarding. -->
+ <string name="app_overview_text" translatable="false">Get Live TV</string>
+ <!-- Description for the Live TV overview screen in onboarding. -->
+ <string name="app_overview_description_has_ac3" translatable="false">Live channels combine the experience of traditional TV channels with streaming channels provided by apps. Get started by setting up the channel sources you already have available, or browse the Google Play Store for more apps that offer live channels.
+\n\nAlso, learn about getting over-the-air broadcast channels like ABC, CBS, NBC, and FOX when you add a compatible TV tuner &amp; HDTV antenna to your Nexus Player.</string>
+ <string name="app_overview_description_no_ac3" translatable="false">Live channels combine the experience of traditional TV channels with streaming channels provided by apps. Get started by setting up the channel sources you already have available, or browse the Google nPlay Store for more apps that offer live channels.</string>
+ <!-- Text for the action for setting up channel sources. It will show the setup page of the channel sources. -->
+ <string name="app_overview_action_text_setup_source" translatable="false">Set up your channel sources</string>
+ <!-- Description for the action for setting up channel sources. It will show the setup page of the channel sources. -->
+ <string name="app_overview_action_description_setup_source" translatable="false">View the channel sources already installed</string>
+ <!-- Text for the action for getting more channels from play store. It will show the play store page. -->
+ <string name="app_overview_action_text_play_store" translatable="false">Get streaming channels</string>
+ <!-- Description for the action for getting more channels from play store. It will show the play store page. -->
+ <string name="app_overview_action_description_play_store" translatable="false">Browse apps that offer live channels</string>
+ <!-- Text for the action for setting up channel of USB tuner. It will show the channel scan page. -->
+ <string name="app_overview_action_text_usb_tuner" translatable="false">Get over-the-air TV channels</string>
+ <!-- Description for the action for setting up channel of USB tuner. It will show the channel scan page. -->
+ <string name="app_overview_action_description_usb_tuner" translatable="false">Learn about compatible TV tuners</string>
+ <!-- Text for the channel sources screen in onboarding. -->
+ <string name="setup_sources_text" translatable="false">Channel sources</string>
+ <!-- Description for channel sources screen in onboarding. -->
+ <string name="setup_sources_description" translatable="false">Set up live channels from the available sources. This may take several minutes depending on the channel source.
+\n\nGet more channels by installing compatible apps from the Google Play Store.</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index d63a58cc..b6b0b2d9 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -61,8 +61,8 @@
<style name="menu_row_title_view">
<item name="android:layout_width">@dimen/menu_row_title_width</item>
<item name="android:layout_height">wrap_content</item>
- <item name="android:layout_marginStart">@dimen/menu_list_padding_start</item>
- <item name="android:layout_marginEnd">@dimen/menu_list_padding_end</item>
+ <item name="android:layout_marginStart">@dimen/menu_padding_start</item>
+ <item name="android:layout_marginEnd">@dimen/menu_padding_end</item>
<item name="android:fontFamily">@string/condensed_font</item>
<item name="android:textAlignment">gravity</item>
<item name="android:gravity">start</item>
@@ -75,8 +75,13 @@
<style name="menu_row_contents_view">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
- <item name="android:paddingStart">@dimen/menu_list_padding_start</item>
- <item name="android:paddingEnd">@dimen/menu_list_padding_end</item>
+ <item name="android:paddingStart">@dimen/menu_padding_start</item>
+ <item name="android:paddingEnd">@dimen/menu_padding_end</item>
<item name="android:paddingTop">@dimen/menu_row_contents_padding_top</item>
</style>
+
+ <style name="Animation.SelectInputActivity" parent="android:Animation.Activity">
+ <item name="android:windowEnterAnimation">@anim/select_input_activity_enter</item>
+ <item name="android:windowExitAnimation">@anim/select_input_activity_exit</item>
+ </style>
</resources>
diff --git a/res/values/themes.xml b/res/values/themes.xml
index da1a7472..5b45f2c7 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -32,10 +32,18 @@
<!-- 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>
+
+ <style name="Theme.SelectInputActivity" parent="@android:style/Theme.Material.NoActionBar">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowAnimationStyle">@style/Animation.SelectInputActivity</item>
+ <item name="android:backgroundDimEnabled">true</item>
+ </style>
</resources>
diff --git a/src/com/android/tv/ChannelTuner.java b/src/com/android/tv/ChannelTuner.java
index f5114193..d0d7c595 100644
--- a/src/com/android/tv/ChannelTuner.java
+++ b/src/com/android/tv/ChannelTuner.java
@@ -16,21 +16,19 @@
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.support.annotation.WorkerThread;
import android.util.Log;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.util.CollectionUtils;
+import com.android.tv.util.SoftPreconditions;
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;
@@ -42,9 +40,6 @@ import java.util.Set;
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<>();
@@ -56,7 +51,7 @@ public class ChannelTuner {
private final Handler mHandler = new Handler();
private final ChannelDataManager mChannelDataManager;
- private final Set<Listener> mListeners = new HashSet<>();
+ private final Set<Listener> mListeners = CollectionUtils.createSmallSet();
private Channel mCurrentChannel;
private final ChannelDataManager.Listener mChannelDataManagerListener =
@@ -84,8 +79,7 @@ public class ChannelTuner {
}
};
- public ChannelTuner(Context context, ChannelDataManager channelDataManager) {
- mContext = context;
+ public ChannelTuner(ChannelDataManager channelDataManager) {
mChannelDataManager = channelDataManager;
}
@@ -286,12 +280,11 @@ public class ChannelTuner {
setCurrentChannelAndNotify(channel);
return true;
}
+ SoftPreconditions.checkState(mChannelDataManagerLoaded, TAG, "Channel data is not loaded");
Channel newChannel = mChannelMap.get(channel.getId());
if (newChannel != null) {
setCurrentChannelAndNotify(newChannel);
return true;
- } else if (!mChannelDataManagerLoaded) {
- return loadChannel(channel.getId()) != null;
}
return false;
}
@@ -386,37 +379,4 @@ public class ChannelTuner {
}
}
}
-
- /**
- * 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.
- */
- @WorkerThread
- 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/Features.java b/src/com/android/tv/Features.java
new file mode 100644
index 00000000..f62987ab
--- /dev/null
+++ b/src/com/android/tv/Features.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv;
+
+import static com.android.tv.common.feature.FeatureUtils.AND;
+import static com.android.tv.common.feature.FeatureUtils.ON;
+import static com.android.tv.common.feature.FeatureUtils.OR;
+
+import android.support.annotation.VisibleForTesting;
+
+import com.android.tv.common.feature.Feature;
+import com.android.tv.common.feature.GServiceFeature;
+import com.android.tv.common.feature.PropertyFeature;
+import com.android.tv.util.EngOnlyFeature;
+
+/**
+ * List of {@link Feature} for the Live TV App.
+ *
+ * <p>Remove the {@code Feature} once it is launched.
+ */
+public final class Features {
+ /**
+ * UI for opting out of analytics.
+ *
+ * <p>See <a href="http://b/20228119">b/20228119</a>
+ */
+ public static Feature ANALYTICS_OPT_OUT = new EngOnlyFeature();
+
+ /**
+ * Analytics that include sensitive information such as channel or program identifiers.
+ *
+ * <p>See <a href="http://b/22062676">b/22062676</a>
+ */
+ public static Feature ANALYTICS_V2 = AND(ON, ANALYTICS_OPT_OUT);
+
+ public static Feature EPG_SEARCH = new PropertyFeature("feature_tv_use_epg_search", false);
+
+
+ /**
+ * A flag which indicates that the on-boarding experience is used or not.
+ *
+ * <p>See <a href="http://b/24070322">b/24070322</a>
+ */
+ public static Feature ONBOARDING_EXPERIENCE = new PropertyFeature(
+ "feature_tv_use_onboarding_exp", false);
+
+ @VisibleForTesting
+ public static Feature TEST_FEATURE = new PropertyFeature("test_feature", false);
+
+ private Features() {
+ }
+}
diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java
index db4cbffd..92cbd462 100644
--- a/src/com/android/tv/MainActivity.java
+++ b/src/com/android/tv/MainActivity.java
@@ -24,6 +24,7 @@ import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.PixelFormat;
import android.graphics.Point;
@@ -39,6 +40,7 @@ import android.media.tv.TvInputManager;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView.OnUnhandledInputEventListener;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -61,6 +63,7 @@ import android.widget.FrameLayout;
import android.widget.Toast;
import com.android.tv.analytics.DurationTimer;
+import com.android.tv.analytics.SendConfigInfoRunnable;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.WeakHandler;
import com.android.tv.data.Channel;
@@ -69,9 +72,11 @@ import com.android.tv.data.OnCurrentProgramUpdatedListener;
import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.data.StreamInfo;
+import com.android.tv.data.WatchedHistoryManager;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dialog.SafeDismissDialogFragment;
import com.android.tv.menu.Menu;
+import com.android.tv.onboarding.OnboardingActivity;
import com.android.tv.parental.ContentRatingsManager;
import com.android.tv.parental.ParentalControlSettings;
import com.android.tv.receiver.AudioCapabilitiesReceiver;
@@ -83,6 +88,7 @@ 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.SelectInputView.OnInputSelectedCallback;
import com.android.tv.ui.TunableTvView;
import com.android.tv.ui.TunableTvView.OnTuneListener;
import com.android.tv.ui.TvOverlayManager;
@@ -97,10 +103,15 @@ 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.MemoryManageable;
+import com.android.tv.util.OnboardingUtils;
+import com.android.tv.util.PermissionUtils;
import com.android.tv.util.PipInputManager;
import com.android.tv.util.PipInputManager.PipInput;
+import com.android.tv.util.RecurringRunner;
import com.android.tv.util.SearchManagerHelper;
import com.android.tv.util.SetupUtils;
+import com.android.tv.util.SoftPreconditions;
import com.android.tv.util.SystemProperties;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.TvSettings;
@@ -143,6 +154,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private static final float FRAME_RATE_EPSILON = 0.1f;
+ private static final int PERMISSIONS_REQUEST_READ_TV_LISTINGS = 1;
+ private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
+
// Tracker screen names.
public static final String SCREEN_NAME = "Main";
private static final String SCREEN_BEHIND_NAME = "Behind";
@@ -201,6 +215,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private AccessibilityManager mAccessibilityManager;
private ChannelDataManager mChannelDataManager;
private ProgramDataManager mProgramDataManager;
+ private WatchedHistoryManager mWatchedHistoryManager;
private TvInputManagerHelper mTvInputManagerHelper;
private ChannelTuner mChannelTuner;
private PipInputManager mPipInputManager;
@@ -271,6 +286,7 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
private final ArrayDeque<Long> mRecentChannels = new ArrayDeque<>(MAX_RECENT_CHANNELS);
private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver;
+ private RecurringRunner mSendConfigInfoRecurringRunner;
// A caller which started this activity. (e.g. TvSearch)
private String mSource;
@@ -375,20 +391,28 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG,"onCreate()");
- if(BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) {
- Toast.makeText(this, "Using Strict Mode for eng builds", Toast.LENGTH_SHORT).show();
- }
super.onCreate(savedInstanceState);
-
TvApplication tvApplication = (TvApplication) getApplication();
tvApplication.setMainActivity(this);
+
+ if (Features.ONBOARDING_EXPERIENCE.isEnabled(this)
+ && OnboardingUtils.needToShowOnboarding(this)) {
+ startActivity(OnboardingActivity.buildIntent(this, getIntent()));
+ finish();
+ return;
+ }
+
+ if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) {
+ Toast.makeText(this, "Using Strict Mode for eng builds", Toast.LENGTH_SHORT).show();
+ }
mTracker = tvApplication.getTracker();
mTvInputManagerHelper = tvApplication.getTvInputManagerHelper();
- mChannelDataManager = new ChannelDataManager(this, mTvInputManagerHelper, mTracker);
- mProgramDataManager = new ProgramDataManager(this);
+ mChannelDataManager = tvApplication.getChannelDataManager();
+ mProgramDataManager = tvApplication.getProgramDataManager();
mProgramDataManager.addOnCurrentProgramUpdatedListener(Channel.INVALID_ID,
mOnCurrentProgramUpdatedListener);
- mChannelTuner = new ChannelTuner(this, mChannelDataManager);
+ mProgramDataManager.setPrefetchEnabled(true);
+ mChannelTuner = new ChannelTuner(mChannelDataManager);
mChannelTuner.addListener(mChannelTunerListener);
mChannelTuner.start();
mPipInputManager = new PipInputManager(this, mTvInputManagerHelper, mChannelTuner);
@@ -458,6 +482,11 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mPipView.initialize((AppLayerTvView) findViewById(R.id.pip_tv_view), true, screenHeight,
shrunkenTvViewHeight);
+ if (!PermissionUtils.hasAccessWatchedHistory(this)) {
+ mWatchedHistoryManager = new WatchedHistoryManager(getApplicationContext());
+ mWatchedHistoryManager.start();
+ mTvView.setWatchedHistoryManager(mWatchedHistoryManager);
+ }
mTvViewUiManager = new TvViewUiManager(this, mTvView, mPipView,
(FrameLayout) findViewById(android.R.id.content), mTvOptionsManager);
@@ -473,6 +502,36 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
.inflate(R.layout.input_banner, sceneContainer, false);
SelectInputView selectInputView = (SelectInputView) getLayoutInflater()
.inflate(R.layout.select_input, sceneContainer, false);
+ selectInputView.setOnInputSelectedCallback(new OnInputSelectedCallback() {
+ @Override
+ public void onTunerInputSelected() {
+ Channel currentChannel = mChannelTuner.getCurrentChannel();
+ if (currentChannel != null && !currentChannel.isPassthrough()) {
+ hideOverlays();
+ } else {
+ tuneToLastWatchedChannelForTunerInput();
+ }
+ }
+
+ @Override
+ public void onPassthroughInputSelected(TvInputInfo input) {
+ Channel currentChannel = mChannelTuner.getCurrentChannel();
+ String currentInputId = currentChannel == null ? null : currentChannel.getInputId();
+ if (TextUtils.equals(input.getId(), currentInputId)) {
+ hideOverlays();
+ } else {
+ tuneToChannel(Channel.createPassthroughChannel(input.getId()));
+ }
+ }
+
+ private void hideOverlays() {
+ getOverlayManager().hideOverlays(
+ TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU);
+ }
+ });
mSearchFragment = new ProgramGuideSearchFragment();
mOverlayManager = new TvOverlayManager(this, mChannelTuner,
mKeypadChannelSwitchView, mChannelBannerView, inputBannerView,
@@ -486,27 +545,36 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
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 = new AudioCapabilitiesReceiver(this, null);
mAudioCapabilitiesReceiver.register();
mAccessibilityManager =
(AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
-
+ mSendConfigInfoRecurringRunner = new RecurringRunner(this, TimeUnit.DAYS.toMillis(1),
+ new SendConfigInfoRunnable(mTracker, mTvInputManagerHelper));
+ mSendConfigInfoRecurringRunner.start();
initForTest();
}
@Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ int[] grantResults) {
+ if (requestCode == PERMISSIONS_REQUEST_READ_TV_LISTINGS) {
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // Restart live channels.
+ Intent intent = getIntent();
+ finish();
+ startActivity(intent);
+ } else {
+ Toast.makeText(this, R.string.msg_read_tv_listing_permission_denied,
+ Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+ }
+
+ @Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams(
@@ -582,6 +650,16 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
protected void onResume() {
if (DEBUG) Log.d(TAG, "onResume()");
super.onResume();
+ if (!PermissionUtils.hasAccessAllEpg(this)) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ Toast.makeText(this, R.string.msg_not_supported_device, Toast.LENGTH_LONG).show();
+ finish();
+ } else if (checkSelfPermission(PERMISSION_READ_TV_LISTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(new String[]{PERMISSION_READ_TV_LISTINGS},
+ PERMISSIONS_REQUEST_READ_TV_LISTINGS);
+ }
+ }
mTracker.sendScreenView(SCREEN_NAME);
SystemProperties.updateSystemProperties();
@@ -600,9 +678,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
if (mChannelTuner.areAllChannelsLoaded()) {
markNewChannelsBrowsable();
+ resumeTvIfNeeded();
+ resumePipIfNeeded();
}
- resumeTvIfNeeded();
- resumePipIfNeeded();
mOverlayManager.showMenuWithTimeShiftPauseIfNeeded();
// Note: The following codes are related to pop up an overlay UI after resume.
@@ -692,6 +770,13 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
return mActivityResumed;
}
+ /**
+ * Returns true if {@link #onStart} is called and {@link #onStop} is not called yet.
+ */
+ public boolean isActivityStarted() {
+ return mActivityStarted;
+ }
+
@Override
public boolean requestVisibleBehind(boolean enable) {
boolean state = super.requestVisibleBehind(enable);
@@ -732,10 +817,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
Set<String> newInputsWithChannels = new HashSet<>();
for (TvInputInfo input : mTvInputManagerHelper.getTvInputInfos(true, true)) {
String inputId = input.getId();
- if (!setupUtils.hasSetupLaunched(inputId)
+ if (!setupUtils.isSetupDone(inputId)
&& mChannelDataManager.getChannelCountForInput(inputId) > 0) {
- setupUtils.onSetupLaunched(inputId);
- setupUtils.markAsKnownInput(inputId);
+ setupUtils.onSetupDone(inputId);
newInputsWithChannels.add(inputId);
if (DEBUG) {
Log.d(TAG, "New input " + inputId + " has "
@@ -754,32 +838,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
}
- 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))
@@ -789,6 +847,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
// is playing, we stop the passthrough TV input.
stopTv();
}
+ SoftPreconditions.checkState(TvContract.isChannelUriForPassthroughInput(channelUri)
+ || mChannelTuner.areAllChannelsLoaded(),
+ TAG, "startTV assumes that ChannelDataManager is already loaded.");
if (mTvView.isPlaying()) {
// TV has already started.
if (channelUri == null) {
@@ -822,11 +883,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
Channel channel = Channel.createPassthroughChannel(channelUri);
mChannelTuner.moveToChannel(channel);
} else {
- Channel channel = loadInitialChannel(channelUri);
+ long channelId = ContentUris.parseId(channelUri);
+ Channel channel = mChannelDataManager.getChannel(channelId);
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.");
}
@@ -880,8 +940,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mInputIdUnderSetup = input.getId();
mIsSetupActivityCalledByDialog = calledByDialog;
+ // Call requestVisibleBehind(false) before starting other activity.
+ // In Activity.requestVisibleBehind(false), this activity is scheduled to be stopped
+ // immediately if other activity is about to start. And this activity is scheduled to
+ // to be stopped again after onPause().
+ stopTv("startSetupActivity()", false);
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,
@@ -895,7 +959,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
| TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY);
}
- stopTv("startSetupActivity()", false);
}
public boolean hasCaptioningSettingsActivity() {
@@ -1120,31 +1183,12 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_START_SETUP_ACTIVITY:
- if (resultCode == Activity.RESULT_OK) {
+ if (resultCode == 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() {
+ SetupUtils.getInstance(this).onTvInputSetupFinished(inputId, 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();
- }
+ int count = mChannelDataManager.getChannelCountForInput(inputId);
String text;
if (count > 0) {
text = getResources().getQuantityString(R.plurals.msg_channel_added,
@@ -1502,6 +1546,20 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
}
+ /**
+ * Says {@code text} when accessibility is turned on.
+ */
+ public void sendAccessiblityText(String text) {
+ if (mAccessibilityManager.isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setClassName(getClass().getName());
+ event.setPackageName(getPackageName());
+ event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
+ event.getText().add(text);
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+ }
+
private void tune() {
if (DEBUG) Log.d(TAG, "tune()");
mTuneDurationTimer.start();
@@ -1596,6 +1654,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
if (TvContract.isChannelUriForPassthroughInput(channel.getUri())) {
TvInputInfo input = mTvInputManagerHelper.getTvInputInfo(channel.getInputId());
event.getText().add(Utils.loadLabel(this, input));
+ } else if (TextUtils.isEmpty(channel.getDisplayName())) {
+ event.getText().add(channel.getDisplayNumber());
} else {
event.getText().add(channel.getDisplayNumber() + " " + channel.getDisplayName());
}
@@ -1822,8 +1882,8 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
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;
+ boolean noOverlayUiWhenResume =
+ mInputToSetUp == null && !mShowProgramGuide && !mShowSelectInputView;
if (needToShowBanner && noOverlayUiWhenResume
&& mOverlayManager.getCurrentDialog() == null) {
if (mChannelTuner.getCurrentChannel() == null) {
@@ -1994,19 +2054,39 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
@Override
protected void onDestroy() {
if (DEBUG) Log.d(TAG, "onDestroy()");
- mChannelTuner.removeListener(mChannelTunerListener);
- mProgramDataManager.removeOnCurrentProgramUpdatedListener(
- Channel.INVALID_ID, mOnCurrentProgramUpdatedListener);
- mProgramDataManager.stop();
- mChannelDataManager.stop();
- mPipInputManager.stop();
- mOverlayManager.release();
- mChannelTuner.stop();
- mKeypadChannelSwitchView.setChannels(null);
+ if (mChannelTuner != null) {
+ mChannelTuner.removeListener(mChannelTunerListener);
+ mChannelTuner.stop();
+ }
+ TvApplication application = ((TvApplication) getApplication());
+ if (mProgramDataManager != null) {
+ mProgramDataManager.removeOnCurrentProgramUpdatedListener(
+ Channel.INVALID_ID, mOnCurrentProgramUpdatedListener);
+ if (application.isCurrentMainActivity(this)) {
+ mProgramDataManager.setPrefetchEnabled(false);
+ }
+ }
+ if (mPipInputManager != null) {
+ mPipInputManager.stop();
+ }
+ if (mOverlayManager != null) {
+ mOverlayManager.release();
+ }
+ if (mKeypadChannelSwitchView != null) {
+ mKeypadChannelSwitchView.setChannels(null);
+ }
mMemoryManageables.clear();
- ((TvApplication) getApplication()).setMainActivity(null);
- mAudioCapabilitiesReceiver.unregister();
+ if (mAudioCapabilitiesReceiver != null) {
+ mAudioCapabilitiesReceiver.unregister();
+ }
mHandler.removeCallbacksAndMessages(null);
+ if (application.isCurrentMainActivity(this)) {
+ application.setMainActivity(null);
+ }
+ if (mSendConfigInfoRecurringRunner != null) {
+ mSendConfigInfoRecurringRunner.stop();
+ mSendConfigInfoRecurringRunner = null;
+ }
super.onDestroy();
}
@@ -2130,6 +2210,10 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (!PermissionUtils.hasModifyParentalControls(this)) {
+ // TODO: support this feature for non-system LC app. b/23939816
+ return true;
+ }
PinDialogFragment dialog = null;
if (mTvView.isScreenBlocked()) {
dialog = new PinDialogFragment(
@@ -2284,7 +2368,9 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
@Override
public void onUserInteraction() {
super.onUserInteraction();
- mOverlayManager.onUserInteraction();
+ if (mOverlayManager != null) {
+ mOverlayManager.onUserInteraction();
+ }
}
public void togglePipView() {
@@ -2636,14 +2722,13 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
int stringId;
switch (info.getVideoUnavailableReason()) {
case TunableTvView.VIDEO_UNAVAILABLE_REASON_NOT_TUNED:
- return;
case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING:
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY:
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;
@@ -2712,19 +2797,6 @@ public class MainActivity extends Activity implements AudioManager.OnAudioFocusC
}
}
- /**
- * 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);
- }
-
private static class MainActivityHandler extends WeakHandler<MainActivity> {
MainActivityHandler(MainActivity mainActivity) {
super(mainActivity);
diff --git a/src/com/android/tv/SelectInputActivity.java b/src/com/android/tv/SelectInputActivity.java
new file mode 100644
index 00000000..c68a1ad0
--- /dev/null
+++ b/src/com/android/tv/SelectInputActivity.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;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+import com.android.tv.data.Channel;
+import com.android.tv.ui.SelectInputView;
+import com.android.tv.ui.SelectInputView.OnInputSelectedCallback;
+import com.android.tv.util.Utils;
+
+/**
+ * An activity to select input.
+ */
+public class SelectInputActivity extends Activity {
+ private SelectInputView mSelectInputView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ((TvApplication) getApplicationContext()).setSelectInputActivity(this);
+ setContentView(R.layout.activity_select_input);
+ mSelectInputView = (SelectInputView) findViewById(R.id.scene_transition_common);
+ mSelectInputView.setOnInputSelectedCallback(new OnInputSelectedCallback() {
+ @Override
+ public void onTunerInputSelected() {
+ startTvWithChannel(TvContract.Channels.CONTENT_URI);
+ }
+
+ @Override
+ public void onPassthroughInputSelected(TvInputInfo input) {
+ startTvWithChannel(TvContract.buildChannelUriForPassthroughInput(input.getId()));
+ }
+
+ private void startTvWithChannel(Uri channelUri) {
+ Intent intent = new Intent(Intent.ACTION_VIEW, channelUri,
+ SelectInputActivity.this, MainActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ finish();
+ }
+ });
+ String channelUriString = Utils.getLastWatchedChannelUri(this);
+ if (channelUriString != null) {
+ Uri channelUri = Uri.parse(channelUriString);
+ if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
+ mSelectInputView.setCurrentChannel(Channel.createPassthroughChannel(channelUri));
+ }
+ // No need to set the tuner channel because it's the default selection.
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mSelectInputView.onEnterAction(true);
+ }
+
+ @Override
+ protected void onPause() {
+ mSelectInputView.onExitAction();
+ super.onPause();
+ }
+
+ @Override
+ protected void onDestroy() {
+ ((TvApplication) getApplicationContext()).setSelectInputActivity(null);
+ super.onDestroy();
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_I || keyCode == KeyEvent.KEYCODE_TV_INPUT) {
+ mSelectInputView.onKeyUp(keyCode, event);
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+}
diff --git a/src/com/android/tv/SetupPassthroughActivity.java b/src/com/android/tv/SetupPassthroughActivity.java
new file mode 100644
index 00000000..45b99776
--- /dev/null
+++ b/src/com/android/tv/SetupPassthroughActivity.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Intent;
+import android.media.tv.TvInputInfo;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.tv.common.TvCommonConstants;
+import com.android.tv.util.SetupUtils;
+import com.android.tv.util.TvInputManagerHelper;
+
+/**
+ * An activity to launch a TV input setup activity.
+ *
+ * <p> After setup activity is finished, all channels will be browsable.
+ */
+public class SetupPassthroughActivity extends Activity {
+ private static final String TAG = "SetupPassthroughActivity";
+ private static final boolean DEBUG = false;
+
+ private static final int REQUEST_START_SETUP_ACTIVITY = 200;
+
+ private TvInputInfo mTvInputInfo;
+ private Intent mActivityAfterCompletion;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ TvApplication tvApplication = (TvApplication) getApplication();
+ TvInputManagerHelper inputManager = tvApplication.getTvInputManagerHelper();
+ // It is not only for setting the variable but also for early initialization of
+ // ChannelDataManager.
+ String inputId = getIntent().getStringExtra(TvCommonConstants.EXTRA_INPUT_ID);
+ mTvInputInfo = inputManager.getTvInputInfo(inputId);
+ if (DEBUG) Log.d(TAG, "TvInputId " + inputId + " / TvInputInfo " + mTvInputInfo);
+ if (mTvInputInfo == null) {
+ Log.w(TAG, "There is no input with the ID " + inputId + ".");
+ finish();
+ return;
+ }
+ Intent setupIntent = mTvInputInfo.createSetupIntent();
+ if (setupIntent == null) {
+ Log.w(TAG, "The input (" + mTvInputInfo.getId() + ") doesn't have setup.");
+ finish();
+ return;
+ }
+ SetupUtils.grantEpgPermission(this, mTvInputInfo.getServiceInfo().packageName);
+ mActivityAfterCompletion = getIntent().getParcelableExtra(
+ TvCommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION);
+ if (DEBUG) Log.d(TAG, "Activity after completion " + mActivityAfterCompletion);
+ setupIntent.putExtras(getIntent().getExtras());
+ startActivityForResult(setupIntent, REQUEST_START_SETUP_ACTIVITY);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode != REQUEST_START_SETUP_ACTIVITY || resultCode != Activity.RESULT_OK) {
+ finish();
+ return;
+ }
+ SetupUtils.getInstance(this).onTvInputSetupFinished(mTvInputInfo.getId(), new Runnable() {
+ @Override
+ public void run() {
+ if (mActivityAfterCompletion != null) {
+ try {
+ startActivity(mActivityAfterCompletion);
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Activity launch failed", e);
+ }
+ }
+ finish();
+ }
+ });
+ }
+}
diff --git a/src/com/android/tv/TvApplication.java b/src/com/android/tv/TvApplication.java
index d3a8facb..9c699389 100644
--- a/src/com/android/tv/TvApplication.java
+++ b/src/com/android/tv/TvApplication.java
@@ -16,6 +16,7 @@
package com.android.tv;
+import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
@@ -23,6 +24,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.StrictMode;
import android.util.Log;
@@ -30,25 +32,30 @@ import android.view.KeyEvent;
import com.android.tv.analytics.Analytics;
import com.android.tv.analytics.StubAnalytics;
+import com.android.tv.analytics.OptOutPreferenceHelper;
import com.android.tv.analytics.StubAnalytics;
import com.android.tv.analytics.Tracker;
-import com.android.tv.util.RecurringRunner;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.ProgramDataManager;
import com.android.tv.util.SystemProperties;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
import java.util.List;
-import java.util.concurrent.TimeUnit;
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 MainActivity mMainActivity;
+ private SelectInputActivity mSelectInputActivity;
+ private Analytics mAnalytics;
private Tracker mTracker;
private TvInputManagerHelper mTvInputManagerHelper;
- private RecurringRunner mSendConfigInfoRecurringRunner;
+ private ChannelDataManager mChannelDataManager;
+ private ProgramDataManager mProgramDataManager;
+ private OptOutPreferenceHelper mOptPreferenceHelper;
@Override
public void onCreate() {
@@ -66,13 +73,33 @@ public class TvApplication extends Application {
}
StrictMode.setVmPolicy(vmPolicyBuilder.build());
}
- Analytics analytics;
+
if (BuildConfig.ENG && !SystemProperties.ALLOW_ANALYTICS_IN_ENG.getValue()) {
- analytics = StubAnalytics.getInstance(this);
+ mAnalytics = StubAnalytics.getInstance(this);
} else {
- analytics = StubAnalytics.getInstance(this);
+ mAnalytics = StubAnalytics.getInstance(this);
+ }
+ mTracker = mAnalytics.getDefaultTracker();
+ if(Features.ANALYTICS_OPT_OUT.isEnabled(this)) {
+ mOptPreferenceHelper = new OptOutPreferenceHelper(this);
+ mOptPreferenceHelper.registerChangeListener(mAnalytics,
+ OptOutPreferenceHelper.ANALYTICS_OPT_OUT_DEFAULT_VALUE);
+ // always start with analytics off
+ mAnalytics.setAppOptOut(true);
+ // then update with the saved preference in an AsyncTask.
+ new AsyncTask<Void, Void, Boolean>() {
+ @Override
+ protected Boolean doInBackground(Void... voids) {
+ return mOptPreferenceHelper.getOptOutPreference(
+ OptOutPreferenceHelper.ANALYTICS_OPT_OUT_DEFAULT_VALUE);
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ mAnalytics.setAppOptOut(result);
+ }
+ }.execute();
}
- mTracker = analytics.getDefaultTracker();
try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
versionName = pInfo.versionName;
@@ -82,16 +109,52 @@ public class TvApplication extends Application {
}
mTvInputManagerHelper = new TvInputManagerHelper(this);
mTvInputManagerHelper.start();
- mSendConfigInfoRecurringRunner = new RecurringRunner(this, TimeUnit.DAYS.toMillis(1),
- new SendConfigInfoRunnable());
- mSendConfigInfoRecurringRunner.start();
if (DEBUG) Log.i(TAG, "Started Live TV " + versionName);
}
+ /**
+ * Returns the {@link Analytics}.
+ */
+ public Analytics getAnalytics() {
+ return mAnalytics;
+ }
+
+ /**
+ * Returns the default tracker.
+ */
public Tracker getTracker() {
return mTracker;
}
+ public OptOutPreferenceHelper getOptPreferenceHelper(){
+ return mOptPreferenceHelper;
+ }
+
+ /**
+ * Returns {@link ChannelDataManager}.
+ */
+ public ChannelDataManager getChannelDataManager() {
+ if (mChannelDataManager == null) {
+ mChannelDataManager = new ChannelDataManager(this, mTvInputManagerHelper, mTracker);
+ mChannelDataManager.start();
+ }
+ return mChannelDataManager;
+ }
+
+ /**
+ * Returns {@link ProgramDataManager}.
+ */
+ public ProgramDataManager getProgramDataManager() {
+ if (mProgramDataManager == null) {
+ mProgramDataManager = new ProgramDataManager(this);
+ mProgramDataManager.start();
+ }
+ return mProgramDataManager;
+ }
+
+ /**
+ * Returns {@link TvInputManagerHelper}.
+ */
public TvInputManagerHelper getTvInputManagerHelper() {
return mTvInputManagerHelper;
}
@@ -101,21 +164,39 @@ public class TvApplication extends Application {
* {@link MainActivity#onDestroy}.
*/
public void setMainActivity(MainActivity activity) {
- mActivity = activity;
+ mMainActivity = activity;
+ }
+
+ /**
+ * SelectInputActivity is set in {@link SelectInputActivity#onCreate} and cleared in
+ * {@link SelectInputActivity#onDestroy}.
+ */
+ public void setSelectInputActivity(SelectInputActivity activity) {
+ mSelectInputActivity = activity;
}
/**
* Checks if MainActivity is set or not.
*/
public boolean hasMainActivity() {
- return (mActivity != null);
+ return (mMainActivity != null);
+ }
+
+ /**
+ * Returns true, if {@code activity} is the current activity.
+ *
+ * Note: MainActivity can start while another MainActivity destroys. In this case, the current
+ * activity is the newly created activity.
+ */
+ public boolean isCurrentMainActivity(MainActivity activity) {
+ return mMainActivity == activity;
}
/**
* Handles the global key KEYCODE_TV.
*/
public void handleTvKey() {
- if (mActivity == null || !mActivity.isActivityResumed()) {
+ if (mMainActivity == null || !mMainActivity.isActivityResumed()) {
startMainActivity(null);
}
}
@@ -139,16 +220,22 @@ public class TvApplication extends Application {
if (inputCount < 2) {
return;
}
- if (mActivity != null && mActivity.isActivityResumed()) {
+ Activity activityToHandle = mMainActivity != null && mMainActivity.isActivityResumed()
+ ? mMainActivity : mSelectInputActivity;
+ if (activityToHandle != null) {
// If startActivity is called, MainActivity.onPause is unnecessarily called. To
// prevent it, MainActivity.dispatchKeyEvent is directly called.
- mActivity.dispatchKeyEvent(
+ activityToHandle.dispatchKeyEvent(
new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TV_INPUT));
- mActivity.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_TV_INPUT));
- } else {
+ activityToHandle.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_TV_INPUT));
+ } else if (mMainActivity != null && mMainActivity.isActivityStarted()) {
Bundle extras = new Bundle();
extras.putString(Utils.EXTRA_KEY_ACTION, Utils.EXTRA_ACTION_SHOW_TV_INPUT);
startMainActivity(extras);
+ } else {
+ startActivity(new Intent(this, SelectInputActivity.class).setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK));
}
}
@@ -168,35 +255,4 @@ public class TvApplication extends Application {
return versionName;
}
- /**
- * Data useful for tracking that doesn't change often.
- */
- public static class ConfigurationInfo {
- public final int systemInputCount;
- public final int nonSystemInputCount;
-
- public ConfigurationInfo(int systemInputCount, int nonSystemInputCount) {
- this.systemInputCount = systemInputCount;
- this.nonSystemInputCount = nonSystemInputCount;
- }
- }
-
- private class SendConfigInfoRunnable implements Runnable {
- @Override
- public void run() {
- List<TvInputInfo> infoList = mTvInputManagerHelper.getTvInputInfos(false, false);
- int systemInputCount = 0;
- int nonSystemInputCount = 0;
- for (TvInputInfo info : infoList) {
- if (mTvInputManagerHelper.isSystemInput(info)) {
- systemInputCount++;
- } else {
- nonSystemInputCount++;
- }
- }
- ConfigurationInfo configurationInfo = new ConfigurationInfo(systemInputCount,
- nonSystemInputCount);
- mTracker.sendConfigurationInfo(configurationInfo);
- }
- }
}
diff --git a/src/com/android/tv/analytics/Analytics.java b/src/com/android/tv/analytics/Analytics.java
index d5b99247..27085de7 100644
--- a/src/com/android/tv/analytics/Analytics.java
+++ b/src/com/android/tv/analytics/Analytics.java
@@ -17,8 +17,22 @@
package com.android.tv.analytics;
/**
- * Provides a Tracker used for user activity analysis.
+ * Provides Trackers used for user activity analysis.
*/
public interface Analytics {
- Tracker getDefaultTracker();
+ Tracker getDefaultTracker();
+
+ /**
+ * Returns whether the state of the application-level opt is on.
+ */
+ boolean isAppOptOut();
+
+ /**
+ * Sets or resets the application-level opt out flag. If set, no hits will be sent.
+ * The value of this flag will <i>not</i> persist across application starts. The
+ * correct value should thus be set in application initialization code.
+ *
+ * @param optOut {@code true} if application-level opt out should be enforced.
+ */
+ void setAppOptOut(boolean optOut);
}
diff --git a/src/com/android/tv/analytics/ConfigurationInfo.java b/src/com/android/tv/analytics/ConfigurationInfo.java
new file mode 100644
index 00000000..41e8baeb
--- /dev/null
+++ b/src/com/android/tv/analytics/ConfigurationInfo.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+
+/**
+ * Data useful for tracking that doesn't change often.
+ */
+public class ConfigurationInfo {
+ public final int systemInputCount;
+ public final int nonSystemInputCount;
+
+ public ConfigurationInfo(int systemInputCount, int nonSystemInputCount) {
+ this.systemInputCount = systemInputCount;
+ this.nonSystemInputCount = nonSystemInputCount;
+ }
+}
diff --git a/src/com/android/tv/analytics/OptOutPreferenceHelper.java b/src/com/android/tv/analytics/OptOutPreferenceHelper.java
new file mode 100644
index 00000000..7fefaa46
--- /dev/null
+++ b/src/com/android/tv/analytics/OptOutPreferenceHelper.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.analytics;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.preference.PreferenceManager;
+
+/**
+ * Handles the opt out preference for analytics, including updating {@link Analytics} with the
+ * preference changes.
+ */
+public final class OptOutPreferenceHelper {
+ /**
+ * The {@link SharedPreferences SharedPreferences} key
+ * "{@value #ANALYTICS_OPT_OUT_KEY}", true means the user has chosen NOT to send
+ * analytics.
+ */
+ public static final String ANALYTICS_OPT_OUT_KEY = "analytics_opt_out";
+
+ /**
+ * The default value for the {@link SharedPreferences SharedPreferences} key
+ * "{@value #ANALYTICS_OPT_OUT_KEY}" is
+ * {@value #ANALYTICS_OPT_OUT_DEFAULT_VALUE}
+ */
+ public static final boolean ANALYTICS_OPT_OUT_DEFAULT_VALUE = false;
+
+ private final SharedPreferences userPrefs;
+
+ public OptOutPreferenceHelper(Context context) {
+ userPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+ }
+
+ /**
+ * Creates and registers a change listener that will update analytics.
+ *
+ * @param analytics the analytics to update when opt out settings change.
+ * @param defaultValue the default opt out values
+ * @return the newly created OptOutChangeListener, keep this pass to
+ * {@link #unRegisterChangeListener(OptOutChangeListener)}
+ */
+ public OptOutChangeListener registerChangeListener(Analytics analytics, boolean defaultValue) {
+ OptOutChangeListener changeListener = new OptOutChangeListener(analytics, defaultValue);
+ userPrefs.registerOnSharedPreferenceChangeListener(changeListener);
+ return changeListener;
+ }
+
+ /**
+ * Unregister a {@link OptOutChangeListener} created by
+ * {@link #registerChangeListener(Analytics, boolean)}
+ */
+ public void unRegisterChangeListener(OptOutChangeListener changeListener) {
+ userPrefs.registerOnSharedPreferenceChangeListener(changeListener);
+ }
+
+ /**
+ * Returns the saved opt out preference or {@code defaultValue} if it has been set.
+ */
+ public boolean getOptOutPreference(boolean defaultValue) {
+ return userPrefs.getBoolean(ANALYTICS_OPT_OUT_KEY, defaultValue);
+ }
+
+ /**
+ * Sets the opt out preference.
+ */
+ public void setOptOutPreference(boolean optOut) {
+ userPrefs.edit().putBoolean(ANALYTICS_OPT_OUT_KEY, optOut).apply();
+ }
+
+ /**
+ * Updates Analytics when opt out preference is changed.
+ *
+ * <p>{@link OnSharedPreferenceChangeListener} is used so the {@code analytics} object is
+ * updated even if the preference are modified directly and not by
+ * {@link OptOutPreferenceHelper}.
+ */
+ public static final class OptOutChangeListener implements OnSharedPreferenceChangeListener {
+ private final Analytics mAnalytics;
+ private final boolean mDefaultValue;
+
+ private OptOutChangeListener(Analytics analytics, boolean defaultValue) {
+ mAnalytics = analytics;
+ mDefaultValue = defaultValue;
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ switch (key) {
+ case ANALYTICS_OPT_OUT_KEY:
+ mAnalytics.setAppOptOut(
+ sharedPreferences.getBoolean(ANALYTICS_OPT_OUT_KEY, mDefaultValue));
+ break;
+ default:
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/analytics/SendConfigInfoRunnable.java b/src/com/android/tv/analytics/SendConfigInfoRunnable.java
new file mode 100644
index 00000000..c2d5c5fb
--- /dev/null
+++ b/src/com/android/tv/analytics/SendConfigInfoRunnable.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.analytics;
+
+import android.media.tv.TvInputInfo;
+
+import com.android.tv.util.TvInputManagerHelper;
+
+import java.util.List;
+
+/**
+ * Sends ConfigurationInfo once a day.
+ */
+public class SendConfigInfoRunnable implements Runnable {
+ private Tracker mTracker;
+ private TvInputManagerHelper mTvInputManagerHelper;
+
+ public SendConfigInfoRunnable(Tracker tracker, TvInputManagerHelper tvInputManagerHelper) {
+ this.mTracker = tracker;
+ this.mTvInputManagerHelper = tvInputManagerHelper;
+ }
+
+ @Override
+ public void run() {
+ List<TvInputInfo> infoList = mTvInputManagerHelper.getTvInputInfos(false, false);
+ int systemInputCount = 0;
+ int nonSystemInputCount = 0;
+ for (TvInputInfo info : infoList) {
+ if (mTvInputManagerHelper.isSystemInput(info)) {
+ systemInputCount++;
+ } else {
+ nonSystemInputCount++;
+ }
+ }
+ ConfigurationInfo configurationInfo = new ConfigurationInfo(systemInputCount,
+ nonSystemInputCount);
+ mTracker.sendConfigurationInfo(configurationInfo);
+ }
+}
diff --git a/src/com/android/tv/analytics/StubAnalytics.java b/src/com/android/tv/analytics/StubAnalytics.java
index 9b54e7d2..ae4cdafa 100644
--- a/src/com/android/tv/analytics/StubAnalytics.java
+++ b/src/com/android/tv/analytics/StubAnalytics.java
@@ -17,23 +17,34 @@
package com.android.tv.analytics;
import android.app.Application;
+import android.content.Context;
/**
* 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;
+ return new StubAnalytics(application);
}
private final Tracker mTracker = new StubTracker();
+ private boolean mOptOut = OptOutPreferenceHelper.ANALYTICS_OPT_OUT_DEFAULT_VALUE;
- private StubAnalytics() { }
+ private StubAnalytics(Context context) {
+ }
@Override
public Tracker getDefaultTracker() {
return mTracker;
}
+
+ @Override
+ public boolean isAppOptOut() {
+ return mOptOut;
+ }
+
+ @Override
+ public void setAppOptOut(boolean optOut) {
+ mOptOut = optOut;
+ }
}
diff --git a/src/com/android/tv/analytics/StubTracker.java b/src/com/android/tv/analytics/StubTracker.java
index f7efcb92..6e64ebca 100644
--- a/src/com/android/tv/analytics/StubTracker.java
+++ b/src/com/android/tv/analytics/StubTracker.java
@@ -19,7 +19,6 @@ package com.android.tv.analytics;
import android.support.annotation.VisibleForTesting;
import com.android.tv.TimeShiftManager;
-import com.android.tv.TvApplication;
import com.android.tv.data.Channel;
/**
@@ -31,7 +30,7 @@ public class StubTracker implements Tracker {
public void sendChannelCount(int browsableChannelCount, int totalChannelCount) { }
@Override
- public void sendConfigurationInfo(TvApplication.ConfigurationInfo info) { }
+ public void sendConfigurationInfo(ConfigurationInfo info) { }
@Override
public void sendMainStart() { }
@@ -97,9 +96,18 @@ public class StubTracker implements Tracker {
public void sendChannelNumberItemChosenByTimeout() { }
@Override
+ public void sendChannelVideoUnavailable(Channel channel, int reason) { }
+
+ @Override
public void sendAc3PassthroughCapabilities(boolean isSupported) { }
@Override
+ public void sendInputConnectionFailure(String inputId) { }
+
+ @Override
+ public void sendInputDisconnected(String inputId) { }
+
+ @Override
public void sendShowInputSelection() { }
@Override
diff --git a/src/com/android/tv/analytics/Tracker.java b/src/com/android/tv/analytics/Tracker.java
index 05638871..291fc9ce 100644
--- a/src/com/android/tv/analytics/Tracker.java
+++ b/src/com/android/tv/analytics/Tracker.java
@@ -17,7 +17,6 @@
package com.android.tv.analytics;
import com.android.tv.TimeShiftManager;
-import com.android.tv.TvApplication;
import com.android.tv.data.Channel;
/**
@@ -44,7 +43,7 @@ public interface Tracker {
*
* @param info the configuration info.
*/
- void sendConfigurationInfo(TvApplication.ConfigurationInfo info);
+ void sendConfigurationInfo(ConfigurationInfo info);
/**
* Sends tracking information for starting the MainActivity.
@@ -179,6 +178,11 @@ public interface Tracker {
void sendChannelNumberItemChosenByTimeout();
/**
+ * Sends tracking for the reason video is unavailable on a channel.
+ */
+ void sendChannelVideoUnavailable(Channel channel, int reason);
+
+ /**
* Sends HDMI AC3 passthrough capabilities.
*
* @param isSupported {@code true} if the feature is supported; otherwise {@code false}.
@@ -186,6 +190,22 @@ public interface Tracker {
void sendAc3PassthroughCapabilities(boolean isSupported);
/**
+ * Sends tracking for input a connection failure.
+ * <p><strong>WARNING</strong> callers must ensure no PII is included in the inputId.
+ *
+ * @param inputId the input the failure happened on
+ */
+ void sendInputConnectionFailure(String inputId);
+
+ /**
+ * Sends tracking for input disconnected.
+ * <p><strong>WARNING</strong> callers must ensure no PII is included in the inputId.
+ *
+ * @param inputId the input the failure happened on
+ */
+ void sendInputDisconnected(String inputId);
+
+ /**
* Sends tracking information for showing the input selection view.
*/
void sendShowInputSelection();
@@ -224,7 +244,7 @@ public interface Tracker {
/**
* Sends time shift action (pause, ff, etc).
*
- * @param actionId The label of the side panel
+ * @param actionId The {@link com.android.tv.TimeShiftManager.TimeShiftActionId}
*/
void sendTimeShiftAction(@TimeShiftManager.TimeShiftActionId int actionId);
}
diff --git a/src/com/android/tv/data/Channel.java b/src/com/android/tv/data/Channel.java
index 49244c14..7901a01f 100644
--- a/src/com/android/tv/data/Channel.java
+++ b/src/com/android/tv/data/Channel.java
@@ -25,6 +25,7 @@ import android.media.tv.TvContract;
import android.media.tv.TvInputInfo;
import android.net.Uri;
import android.os.Build;
+import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
@@ -198,6 +199,7 @@ public final class Channel {
return mDisplayNumber;
}
+ @Nullable
public String getDisplayName() {
return mDisplayName;
}
diff --git a/src/com/android/tv/data/ChannelDataManager.java b/src/com/android/tv/data/ChannelDataManager.java
index 2325952f..067f2583 100644
--- a/src/com/android/tv/data/ChannelDataManager.java
+++ b/src/com/android/tv/data/ChannelDataManager.java
@@ -19,6 +19,8 @@ package com.android.tv.data;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
import android.database.ContentObserver;
import android.media.tv.TvContract;
import android.media.tv.TvContract.Channels;
@@ -34,6 +36,8 @@ import android.util.MutableInt;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.WeakHandler;
import com.android.tv.util.AsyncDbTask;
+import com.android.tv.util.CollectionUtils;
+import com.android.tv.util.PermissionUtils;
import com.android.tv.util.RecurringRunner;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
@@ -60,6 +64,7 @@ public class ChannelDataManager {
private static final int MSG_UPDATE_CHANNELS = 1000;
private static final long SEND_CHANNEL_STATUS_INTERVAL_MS = TimeUnit.DAYS.toMillis(1);
+ private static final String SHARED_PREF_BROWSABLE = "browsable_shared_preference";
private final Context mContext;
private final TvInputManagerHelper mInputManager;
@@ -71,7 +76,7 @@ public class ChannelDataManager {
private RecurringRunner mRecurringRunner;
private final Tracker mTracker;
- private final Set<Listener> mListeners = new HashSet<>();
+ private final Set<Listener> mListeners = CollectionUtils.createSmallSet();
private final Map<Long, ChannelWrapper> mChannelWrapperMap = new HashMap<>();
private final Map<String, MutableInt> mChannelCountMap = new HashMap<>();
private final Channel.DefaultComparator mChannelComparator;
@@ -83,6 +88,8 @@ public class ChannelDataManager {
private final ContentResolver mContentResolver;
private final ContentObserver mChannelObserver;
+ private final boolean mStoreBrowsableInSharedPreferences;
+ private final SharedPreferences mBrowsableSharedPreferences;
private final TvInputCallback mTvInputCallback = new TvInputCallback() {
@Override
@@ -134,19 +141,19 @@ public class ChannelDataManager {
public ChannelDataManager(Context context, TvInputManagerHelper inputManager,
Tracker tracker) {
- this(context, inputManager, tracker, context.getContentResolver(), Looper.myLooper());
+ this(context, inputManager, tracker, context.getContentResolver());
}
@VisibleForTesting
ChannelDataManager(Context context, TvInputManagerHelper inputManager, Tracker tracker,
- ContentResolver contentResolver, Looper looper) {
+ ContentResolver contentResolver) {
mContext = context;
mInputManager = inputManager;
mContentResolver = contentResolver;
mChannelComparator = new Channel.DefaultComparator(context, inputManager);
// Detect duplicate channels while sorting.
mChannelComparator.setDetectDuplicatesEnabled(true);
- mHandler = new ChannelDataManagerHandler(looper, this);
+ mHandler = new ChannelDataManagerHandler(this);
mChannelObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -158,6 +165,9 @@ public class ChannelDataManager {
mTracker = tracker;
mRecurringRunner = new RecurringRunner(mContext, SEND_CHANNEL_STATUS_INTERVAL_MS,
new SendChannelStatusRunnable());
+ mStoreBrowsableInSharedPreferences = !PermissionUtils.hasAccessAllEpg(mContext);
+ mBrowsableSharedPreferences = context.getSharedPreferences(SHARED_PREF_BROWSABLE,
+ Context.MODE_PRIVATE);
}
@VisibleForTesting
@@ -185,6 +195,7 @@ public class ChannelDataManager {
* Stops the manager. It clears manager states and runs pending DB operations. Added listeners
* aren't automatically removed by this method.
*/
+ @VisibleForTesting
public void stop() {
if (!mStarted) {
return;
@@ -413,11 +424,22 @@ public class ChannelDataManager {
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);
+ if (mStoreBrowsableInSharedPreferences) {
+ Editor editor = mBrowsableSharedPreferences.edit();
+ for (Long id : browsableIds) {
+ editor.putBoolean(getBrowsableKey(getChannel(id)), true);
+ }
+ for (Long id : unbrowsableIds) {
+ editor.putBoolean(getBrowsableKey(getChannel(id)), false);
+ }
+ editor.apply();
+ } else {
+ if (browsableIds.size() != 0) {
+ updateOneColumnValue(column, 1, browsableIds);
+ }
+ if (unbrowsableIds.size() != 0) {
+ updateOneColumnValue(column, 0, unbrowsableIds);
+ }
}
mBrowsableUpdateChannelIds.clear();
@@ -507,7 +529,7 @@ public class ChannelDataManager {
}
private class ChannelWrapper {
- final Set<ChannelListener> mChannelListeners = new HashSet<>();
+ final Set<ChannelListener> mChannelListeners = CollectionUtils.createSmallSet();
final Channel mChannel;
boolean mBrowsableInDb;
boolean mLockedInDb;
@@ -561,7 +583,17 @@ public class ChannelDataManager {
boolean channelAdded = false;
boolean channelUpdated = false;
boolean channelRemoved = false;
+ Map<String, ?> deletedBrowsableMap = null;
+ if (mStoreBrowsableInSharedPreferences) {
+ deletedBrowsableMap = new HashMap<>(mBrowsableSharedPreferences.getAll());
+ }
for (Channel channel : channels) {
+ if (mStoreBrowsableInSharedPreferences) {
+ String browsableKey = getBrowsableKey(channel);
+ channel.setBrowsable(mBrowsableSharedPreferences.getBoolean(browsableKey,
+ false));
+ deletedBrowsableMap.remove(browsableKey);
+ }
long channelId = channel.getId();
boolean newlyAdded = !removedChannelIds.remove(channelId);
ChannelWrapper channelWrapper;
@@ -591,6 +623,17 @@ public class ChannelDataManager {
}
}
}
+ if (mStoreBrowsableInSharedPreferences && !deletedBrowsableMap.isEmpty()
+ && PermissionUtils.hasReadTvListings(mContext)) {
+ // If hasReadTvListings(mContext) is false, the given channel list would
+ // empty. In this case, we skip the browsable data clean up process.
+ Editor editor = mBrowsableSharedPreferences.edit();
+ for (String key : deletedBrowsableMap.keySet()) {
+ if (DEBUG) Log.d(TAG, "remove key: " + key);
+ editor.remove(key);
+ }
+ editor.apply();
+ }
for (long id : removedChannelIds) {
ChannelWrapper channelWrapper = mChannelWrapperMap.remove(id);
@@ -639,6 +682,10 @@ public class ChannelDataManager {
*/
private void updateOneColumnValue(
final String columnName, final int columnValue, final List<Long> ids) {
+ if (!PermissionUtils.hasAccessAllEpg(mContext)) {
+ // TODO: support this feature for non-system LC app. b/23939816
+ return;
+ }
AsyncDbTask.execute(new Runnable() {
@Override
public void run() {
@@ -650,9 +697,13 @@ public class ChannelDataManager {
});
}
+ private String getBrowsableKey(Channel channel) {
+ return channel.getInputId() + "|" + channel.getId();
+ }
+
private static class ChannelDataManagerHandler extends WeakHandler<ChannelDataManager> {
- public ChannelDataManagerHandler(Looper looper, ChannelDataManager channelDataManager) {
- super(looper, channelDataManager);
+ public ChannelDataManagerHandler(ChannelDataManager channelDataManager) {
+ super(Looper.getMainLooper(), channelDataManager);
}
@Override
diff --git a/src/com/android/tv/data/ChannelLogoFetcher.java b/src/com/android/tv/data/ChannelLogoFetcher.java
index 166b1d87..5a549f83 100644
--- a/src/com/android/tv/data/ChannelLogoFetcher.java
+++ b/src/com/android/tv/data/ChannelLogoFetcher.java
@@ -30,6 +30,7 @@ import android.util.Log;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.BitmapUtils;
import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
+import com.android.tv.util.PermissionUtils;
import java.io.BufferedReader;
import java.io.IOException;
@@ -78,6 +79,10 @@ public class ChannelLogoFetcher {
* The previous task is canceled and a new task starts.
*/
public static void startFetchingChannelLogos(Context context) {
+ if (!PermissionUtils.hasAccessAllEpg(context)) {
+ // TODO: support this feature for non-system LC app. b/23939816
+ return;
+ }
synchronized (sLock) {
stopFetchingChannelLogos();
if (DEBUG) Log.d(TAG, "Request to start fetching logos.");
@@ -201,6 +206,14 @@ public class ChannelLogoFetcher {
return null;
}
// Download the channel logo.
+ if (TextUtils.isEmpty(channel.getDisplayName())) {
+ if (DEBUG) {
+ Log.d(TAG, "The channel with ID (" + channel.getId()
+ + ") doesn't have the display name.");
+ }
+ sChannelIdBlackListSet.add(channel.getId());
+ continue;
+ }
String channelName = channel.getDisplayName().trim();
String logoUri = channelNameLogoUriMap.get(channelName);
if (TextUtils.isEmpty(logoUri)) {
@@ -216,16 +229,21 @@ public class ChannelLogoFetcher {
sb.append(splitName);
}
logoUri = channelNameLogoUriMap.get(sb.toString());
- if (DEBUG && TextUtils.isEmpty(logoUri)) {
- Log.d(TAG, "Can't find a logo URI for channel '" + sb.toString() + "'");
+ if (DEBUG) {
+ if (TextUtils.isEmpty(logoUri)) {
+ Log.d(TAG, "Can't find a logo URI for channel '" + sb.toString()
+ + "'");
+ }
}
}
if (TextUtils.isEmpty(logoUri)
&& splitNames[0].length() != channelName.length()) {
logoUri = channelNameLogoUriMap.get(splitNames[0]);
- if (DEBUG && TextUtils.isEmpty(logoUri)) {
- Log.d(TAG, "Can't find a logo URI for channel '" + splitNames[0]
- + "'");
+ if (DEBUG) {
+ if (TextUtils.isEmpty(logoUri)) {
+ Log.d(TAG, "Can't find a logo URI for channel '" + splitNames[0]
+ + "'");
+ }
}
}
}
diff --git a/src/com/android/tv/data/ChannelNumber.java b/src/com/android/tv/data/ChannelNumber.java
index 8e9c3cb2..59021609 100644
--- a/src/com/android/tv/data/ChannelNumber.java
+++ b/src/com/android/tv/data/ChannelNumber.java
@@ -101,22 +101,41 @@ public final class ChannelNumber implements Comparable<ChannelNumber> {
}
if (indexOfDelimiter < 0) {
ret.majorNumber = number;
- return ret;
+ if (!isInteger(ret.majorNumber)) {
+ return null;
+ }
+ } else {
+ ret.hasDelimiter = true;
+ ret.majorNumber = number.substring(0, indexOfDelimiter);
+ ret.minorNumber = number.substring(indexOfDelimiter + 1);
+ if (!isInteger(ret.majorNumber) || !isInteger(ret.minorNumber)) {
+ return null;
+ }
}
- 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) {
+ ChannelNumber lhsNumber = parseChannelNumber(lhs);
+ ChannelNumber rhsNumber = parseChannelNumber(rhs);
+ if (lhsNumber == null && rhsNumber == null) {
return 0;
- } else if (lhs == null /* && rhs != null */) {
+ } else if (lhsNumber == null /* && rhsNumber != null */) {
return -1;
- } else if (lhs != null && rhs == null) {
+ } else if (lhsNumber != null && rhsNumber == null) {
return 1;
}
- return parseChannelNumber(lhs).compareTo(parseChannelNumber(rhs));
+ return lhsNumber.compareTo(rhsNumber);
+ }
+
+ public static boolean isInteger(String string) {
+ try {
+ Integer.parseInt(string);
+ } catch(NumberFormatException e) {
+ return false;
+ } catch(NullPointerException e) {
+ return false;
+ }
+ return true;
}
}
diff --git a/src/com/android/tv/data/ProgramDataManager.java b/src/com/android/tv/data/ProgramDataManager.java
index c12e7094..3f527433 100644
--- a/src/com/android/tv/data/ProgramDataManager.java
+++ b/src/com/android/tv/data/ProgramDataManager.java
@@ -32,20 +32,21 @@ import android.util.LongSparseArray;
import android.util.LruCache;
import com.android.tv.BuildConfig;
-import com.android.tv.MainActivity.MemoryManageable;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.Clock;
+import com.android.tv.util.CollectionUtils;
+import com.android.tv.util.MemoryManageable;
+import com.android.tv.util.MultiLongSparseArray;
+import com.android.tv.util.SoftPreconditions;
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;
@@ -81,20 +82,21 @@ public class ProgramDataManager implements MemoryManageable {
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 MultiLongSparseArray<OnCurrentProgramUpdatedListener>
+ mChannelId2ProgramUpdatedListeners = new MultiLongSparseArray<>();
private final Handler mHandler;
- private final List<Listener> mListeners = new ArrayList<>();
+ private final Set<Listener> mListeners = CollectionUtils.createSmallSet();
private final ContentObserver mProgramObserver;
+ private boolean mPrefetchEnabled;
+ private long mProgramPrefetchUpdateWaitMs;
+ private long mLastPrefetchTaskRunMs;
+ private ProgramsPrefetchTask mProgramsPrefetchTask;
private Map<Long, ArrayList<Program>> mChannelIdProgramCache = new HashMap<>();
// Any program that ends prior to this time will be removed from the cache
@@ -123,11 +125,13 @@ public class ProgramDataManager implements MemoryManageable {
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);
+ if (mPrefetchEnabled) {
+ // The delay time of an existing MSG_UPDATE_PREFETCH_PROGRAM could be quite long
+ // up to PROGRAM_GUIDE_SNAP_TIME_MS. So we need to remove the existing message and
+ // send MSG_UPDATE_PREFETCH_PROGRAM again.
+ mHandler.removeMessages(MSG_UPDATE_PREFETCH_PROGRAM);
+ mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
+ }
}
};
mProgramPrefetchUpdateWaitMs = PROGRAM_PREFETCH_UPDATE_WAIT_MS;
@@ -159,7 +163,9 @@ public class ProgramDataManager implements MemoryManageable {
// 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);
+ if (mPrefetchEnabled) {
+ mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
+ }
mContentResolver.registerContentObserver(Programs.CONTENT_URI,
true, mProgramObserver);
}
@@ -168,6 +174,7 @@ public class ProgramDataManager implements MemoryManageable {
* Stops the manager. It clears manager states and runs pending DB operations. Added listeners
* aren't automatically removed by this method.
*/
+ @VisibleForTesting
public void stop() {
if (!mStarted) {
return;
@@ -219,12 +226,36 @@ public class ProgramDataManager implements MemoryManageable {
}
/**
+ * Enables or Disables program prefetch.
+ */
+ public void setPrefetchEnabled(boolean enable) {
+ if (mPrefetchEnabled == enable) {
+ return;
+ }
+ if (enable) {
+ mPrefetchEnabled = true;
+ mLastPrefetchTaskRunMs = 0;
+ if (mStarted) {
+ mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
+ }
+ } else {
+ mPrefetchEnabled = false;
+ cancelPrefetchTask();
+ mChannelIdProgramCache.clear();
+ mHandler.removeMessages(MSG_UPDATE_PREFETCH_PROGRAM);
+ }
+ }
+
+ /**
* Returns the programs for the given channel which ends after the given start time.
*
+ * <p> Prefetch should be enabled to call it.
+ *
* @return {@link List} with Programs. It may includes dummy program if the entry needs DB
* operations to get.
*/
public List<Program> getPrograms(long channelId, long startTime) {
+ SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled.");
ArrayList<Program> cachedPrograms = mChannelIdProgramCache.get(channelId);
if (cachedPrograms == null) {
return Collections.emptyList();
@@ -267,13 +298,8 @@ public class ProgramDataManager implements MemoryManageable {
*/
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);
+ mChannelId2ProgramUpdatedListeners
+ .put(channelId, listener);
}
/**
@@ -282,36 +308,28 @@ public class ProgramDataManager implements MemoryManageable {
*/
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.
- }
+ mChannelId2ProgramUpdatedListeners
+ .remove(channelId, listener);
}
private void notifyCurrentProgramUpdate(long channelId, Program program) {
- List<OnCurrentProgramUpdatedListener> listeners =
- mOnCurrentProgramUpdatedListenersMap.get(channelId);
- if (listeners != null) {
- for (OnCurrentProgramUpdatedListener listener : listeners) {
- listener.onCurrentProgramUpdated(channelId, program);
+
+ for (OnCurrentProgramUpdatedListener listener : mChannelId2ProgramUpdatedListeners
+ .get(channelId)) {
+ listener.onCurrentProgramUpdated(channelId, program);
}
- }
- listeners = mOnCurrentProgramUpdatedListenersMap.get(Channel.INVALID_ID);
- if (listeners != null) {
- for (OnCurrentProgramUpdatedListener listener : listeners) {
- listener.onCurrentProgramUpdated(channelId, program);
+ for (OnCurrentProgramUpdatedListener listener : mChannelId2ProgramUpdatedListeners
+ .get(Channel.INVALID_ID)) {
+ listener.onCurrentProgramUpdated(channelId, program);
}
- }
}
private void updateCurrentProgram(long channelId, Program program) {
Program previousProgram = mChannelIdCurrentProgramMap.put(channelId, program);
if (!Objects.equals(program, previousProgram)) {
- removePreviousProgramsAndUpdateCurrentProgramInCache(channelId, program);
+ if (mPrefetchEnabled) {
+ removePreviousProgramsAndUpdateCurrentProgramInCache(channelId, program);
+ }
notifyCurrentProgramUpdate(channelId, program);
}
@@ -329,6 +347,7 @@ public class ProgramDataManager implements MemoryManageable {
private void removePreviousProgramsAndUpdateCurrentProgramInCache(
long channelId, Program currentProgram) {
+ SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled.");
if (!Program.isValid(currentProgram)) {
return;
}
@@ -369,6 +388,11 @@ public class ProgramDataManager implements MemoryManageable {
}
break;
}
+ if (cachedPrograms.isEmpty()) {
+ // If all the cached programs finish before mPrefetchTimeRangeStartMs, the
+ // currentProgram would not have a chance to be inserted to the cache.
+ cachedPrograms.add(currentProgram);
+ }
mChannelIdProgramCache.put(channelId, cachedPrograms);
}
@@ -452,6 +476,8 @@ public class ProgramDataManager implements MemoryManageable {
if (DEBUG) {
Log.d(TAG, "Database is changed while querying. Will retry.");
}
+ } catch (SecurityException e) {
+ Log.d(TAG, "Security exception during program data query", e);
}
}
if (DEBUG) {
@@ -540,7 +566,9 @@ public class ProgramDataManager implements MemoryManageable {
removedChannelIds.remove(channelId);
}
for (Long channelId : removedChannelIds) {
- mChannelIdProgramCache.remove(channelId);
+ if (mPrefetchEnabled) {
+ mChannelIdProgramCache.remove(channelId);
+ }
mChannelIdCurrentProgramMap.remove(channelId);
notifyCurrentProgramUpdate(channelId, null);
}
@@ -623,8 +651,11 @@ public class ProgramDataManager implements MemoryManageable {
* 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.
+ *
+ * <p> Prefetch should be enabled to call it.
*/
public void setPauseProgramUpdate(boolean pauseProgramUpdate) {
+ SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled.");
if (mPauseProgramUpdate && !pauseProgramUpdate) {
if (!mHandler.hasMessages(MSG_UPDATE_PREFETCH_PROGRAM)) {
// MSG_UPDATE_PRFETCH_PROGRAM can be empty
@@ -645,11 +676,16 @@ public class ProgramDataManager implements MemoryManageable {
* Sets program data prefetch time range.
* Any program data that ends before the start time will be removed from the cache later.
* Note that there's no limit for end time.
+ *
+ * <p> Prefetch should be enabled to call it.
*/
public void setPrefetchTimeRange(long startTimeMs) {
+ SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled.");
if (mPrefetchTimeRangeStartMs > startTimeMs) {
- // TODO: Do not throw exception and simply refresh the cache.
- throw new IllegalArgumentException("");
+ // Fetch the programs immediately to re-create the cache.
+ if (!mHandler.hasMessages(MSG_UPDATE_PREFETCH_PROGRAM)) {
+ mHandler.sendEmptyMessage(MSG_UPDATE_PREFETCH_PROGRAM);
+ }
}
mPrefetchTimeRangeStartMs = startTimeMs;
}
@@ -692,13 +728,6 @@ public class ProgramDataManager implements MemoryManageable {
@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();
- }
- }
+ mChannelId2ProgramUpdatedListeners.clearEmptyCache();
}
}
diff --git a/src/com/android/tv/data/WatchedHistoryManager.java b/src/com/android/tv/data/WatchedHistoryManager.java
new file mode 100644
index 00000000..13707ae6
--- /dev/null
+++ b/src/com/android/tv/data/WatchedHistoryManager.java
@@ -0,0 +1,323 @@
+package com.android.tv.data;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.MainThread;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Scanner;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class to manage watched history.
+ *
+ * <p>When there is no access to watched table of TvProvider,
+ * this class is used to build up watched history and to compute recent channels.
+ */
+public class WatchedHistoryManager {
+ private final static String TAG = "WatchedHistoryManager";
+ private final boolean DEBUG = false;
+
+ private static final int MAX_HISTORY_SIZE = 10000;
+ private static final String SHARED_PREF_WATCHED_HISTORY = "watched_history_shared_preference";
+ private static final String PREF_KEY_LAST_INDEX = "last_index";
+ private static final long MIN_DURATION_MS = TimeUnit.SECONDS.toMillis(10);
+ private static final long RECENT_CHANNEL_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(5);
+
+ private final List<WatchedRecord> mWatchedHistory = new ArrayList<>();
+ private final List<WatchedRecord> mPendingRecords = new ArrayList<>();
+ private long mLastIndex;
+ private boolean mStarted;
+ private boolean mLoaded;
+ private SharedPreferences mSharedPreferences;
+ private SharedPreferences.OnSharedPreferenceChangeListener mOnSharedPreferenceChangeListener =
+ new SharedPreferences.OnSharedPreferenceChangeListener() {
+ @Override
+ @MainThread
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+ String key) {
+ if (key.equals(PREF_KEY_LAST_INDEX)) {
+ final long lastIndex = mSharedPreferences.getLong(PREF_KEY_LAST_INDEX, -1);
+ if (lastIndex <= mLastIndex) {
+ return;
+ }
+ // onSharedPreferenceChanged is always called in a main thread.
+ // onNewRecordAdded will be called in the same thread as the thread
+ // which created this instance.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ for (long i = mLastIndex + 1; i <= lastIndex; ++i) {
+ WatchedRecord record = decode(
+ mSharedPreferences.getString(getSharedPreferencesKey(i),
+ null));
+ if (record != null) {
+ mWatchedHistory.add(record);
+ if (mListener != null) {
+ mListener.onNewRecordAdded(record);
+ }
+ }
+ }
+ mLastIndex = lastIndex;
+ }
+ });
+ }
+ }
+ };
+
+ private final Context mContext;
+ private Listener mListener;
+ private final int mMaxHistorySize;
+ private Handler mHandler;
+
+ public WatchedHistoryManager(Context context) {
+ this(context, MAX_HISTORY_SIZE);
+ }
+
+ @VisibleForTesting
+ WatchedHistoryManager(Context context, int maxHistorySize) {
+ mContext = context.getApplicationContext();
+ mMaxHistorySize = maxHistorySize;
+ if (Looper.myLooper() == null) {
+ mHandler = new Handler(Looper.getMainLooper());
+ } else {
+ mHandler = new Handler();
+ }
+ }
+
+ /**
+ * Starts the manager. It loads history data from {@link SharedPreferences}.
+ */
+ public void start() {
+ if (mStarted) {
+ return;
+ }
+ mStarted = true;
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ mSharedPreferences = mContext.getSharedPreferences(
+ SHARED_PREF_WATCHED_HISTORY, Context.MODE_PRIVATE);
+ mLastIndex = mSharedPreferences.getLong(PREF_KEY_LAST_INDEX, -1);
+ if (mLastIndex >= 0 && mLastIndex < mMaxHistorySize) {
+ for (int i = 0; i <= mLastIndex; ++i) {
+ WatchedRecord record =
+ decode(mSharedPreferences.getString(getSharedPreferencesKey(i),
+ null));
+ if (record != null) {
+ mWatchedHistory.add(record);
+ }
+ }
+ } else if (mLastIndex >= mMaxHistorySize) {
+ for (long i = mLastIndex - mMaxHistorySize + 1; i <= mLastIndex; ++i) {
+ WatchedRecord record = decode(mSharedPreferences.getString(
+ getSharedPreferencesKey(i), null));
+ if (record != null) {
+ mWatchedHistory.add(record);
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void params) {
+ mLoaded = true;
+ if (DEBUG) {
+ Log.d(TAG, "Loaded: size=" + mWatchedHistory.size() + " index=" + mLastIndex);
+ }
+ if (!mPendingRecords.isEmpty()) {
+ Editor editor = mSharedPreferences.edit();
+ for (WatchedRecord record : mPendingRecords) {
+ mWatchedHistory.add(record);
+ ++mLastIndex;
+ editor.putString(getSharedPreferencesKey(mLastIndex), encode(record));
+ }
+ editor.putLong(PREF_KEY_LAST_INDEX, mLastIndex).apply();
+ mPendingRecords.clear();
+ }
+ if (mListener != null) {
+ mListener.onLoadFinished();
+ }
+ mSharedPreferences.registerOnSharedPreferenceChangeListener(
+ mOnSharedPreferenceChangeListener);
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ @VisibleForTesting
+ public boolean isLoaded() {
+ return mLoaded;
+ }
+
+ /**
+ * Logs the record of the watched channel.
+ */
+ public void logChannelViewStop(Channel channel, long endTime, long duration) {
+ if (duration < MIN_DURATION_MS) {
+ return;
+ }
+ WatchedRecord record = new WatchedRecord(channel.getId(), endTime - duration, duration);
+ if (mLoaded) {
+ if (DEBUG) Log.d(TAG, "Log a watched record. " + record);
+ mWatchedHistory.add(record);
+ ++mLastIndex;
+ mSharedPreferences.edit()
+ .putString(getSharedPreferencesKey(mLastIndex), encode(record))
+ .putLong(PREF_KEY_LAST_INDEX, mLastIndex)
+ .apply();
+ if (mListener != null) {
+ mListener.onNewRecordAdded(record);
+ }
+ } else {
+ mPendingRecords.add(record);
+ }
+ }
+
+ /**
+ * Sets {@link Listener}.
+ */
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Returns watched history in the ascending order of time. In other words, the first element
+ * is the oldest and the last element is the latest record.
+ */
+ public List<WatchedRecord> getWatchedHistory() {
+ return Collections.unmodifiableList(mWatchedHistory);
+ }
+
+ /**
+ * Returns the list of recently watched channels.
+ */
+ public List<Channel> buildRecentChannel(ChannelDataManager channelDataManager, int maxCount) {
+ List<Channel> list = new ArrayList<>();
+ Map<Long, Long> durationMap = new HashMap<>();
+ for (int i = mWatchedHistory.size() - 1; i >= 0; --i) {
+ WatchedRecord record = mWatchedHistory.get(i);
+ long channelId = record.channelId;
+ Channel channel = channelDataManager.getChannel(channelId);
+ if (channel == null || !channel.isBrowsable()) {
+ continue;
+ }
+ Long duration = durationMap.get(channelId);
+ if (duration == null) {
+ duration = 0l;
+ }
+ if (duration >= RECENT_CHANNEL_THRESHOLD_MS) {
+ continue;
+ }
+ if (list.isEmpty()) {
+ // We put the first recent channel regardless of RECENT_CHANNEL_THREASHOLD.
+ // It has the similar functionality as the previous channel in a usual remote
+ // controller.
+ list.add(channel);
+ durationMap.put(channelId, RECENT_CHANNEL_THRESHOLD_MS);
+ } else {
+ duration += record.duration;
+ durationMap.put(channelId, duration);
+ if (duration >= RECENT_CHANNEL_THRESHOLD_MS) {
+ list.add(channel);
+ }
+ }
+ if (list.size() >= maxCount) {
+ break;
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Build recent channel");
+ for (Channel channel : list) {
+ Log.d(TAG, "recent channel: " + channel);
+ }
+ }
+ return list;
+ }
+
+ @VisibleForTesting
+ WatchedRecord getRecord(int reverseIndex) {
+ return mWatchedHistory.get(mWatchedHistory.size() - 1 - reverseIndex);
+ }
+
+ @VisibleForTesting
+ WatchedRecord getRecordFromSharedPreferences(int reverseIndex) {
+ long lastIndex = mSharedPreferences.getLong(PREF_KEY_LAST_INDEX, -1);
+ long index = lastIndex - reverseIndex;
+ return decode(mSharedPreferences.getString(getSharedPreferencesKey(index), null));
+ }
+
+ private String getSharedPreferencesKey(long index) {
+ return Long.toString(index % mMaxHistorySize);
+ }
+
+ public static class WatchedRecord {
+ public final long channelId;
+ public final long watchedStartTime;
+ public final long duration;
+
+ WatchedRecord(long channelId, long watchedStartTime, long duration) {
+ this.channelId = channelId;
+ this.watchedStartTime = watchedStartTime;
+ this.duration = duration;
+ }
+
+ @Override
+ public String toString() {
+ return "WatchedRecord: id=" + channelId + ",watchedStartTime=" + watchedStartTime
+ + ",duration=" + duration;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof WatchedRecord) {
+ WatchedRecord that = (WatchedRecord) o;
+ return Objects.equals(channelId, that.channelId)
+ && Objects.equals(watchedStartTime, that.watchedStartTime)
+ && Objects.equals(duration, that.duration);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(channelId, watchedStartTime, duration);
+ }
+ }
+
+ @VisibleForTesting
+ String encode(WatchedRecord record) {
+ return record.channelId + " " + record.watchedStartTime + " " + record.duration;
+ }
+
+ @VisibleForTesting
+ WatchedRecord decode(String encodedString) {
+ try (Scanner scanner = new Scanner(encodedString)) {
+ long channelId = scanner.nextLong();
+ long watchedStartTime = scanner.nextLong();
+ long duration = scanner.nextLong();
+ return new WatchedRecord(channelId, watchedStartTime, duration);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public interface Listener {
+ /**
+ * Called when history is loaded.
+ */
+ void onLoadFinished();
+ void onNewRecordAdded(WatchedRecord watchedRecord);
+ }
+}
diff --git a/src/com/android/tv/guide/ProgramGrid.java b/src/com/android/tv/guide/ProgramGrid.java
index 99da84b0..1339ddf8 100644
--- a/src/com/android/tv/guide/ProgramGrid.java
+++ b/src/com/android/tv/guide/ProgramGrid.java
@@ -16,20 +16,19 @@
package com.android.tv.guide;
-import com.android.tv.R;
-import com.android.tv.ui.OnRepeatedKeyInterceptListener;
-
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.support.v17.leanback.widget.VerticalGridView;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import com.android.tv.R;
+import com.android.tv.ui.OnRepeatedKeyInterceptListener;
+
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
diff --git a/src/com/android/tv/guide/ProgramGuide.java b/src/com/android/tv/guide/ProgramGuide.java
index 468f10e0..d4e4a99d 100644
--- a/src/com/android/tv/guide/ProgramGuide.java
+++ b/src/com/android/tv/guide/ProgramGuide.java
@@ -38,12 +38,12 @@ import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.View.MeasureSpec;
-import android.view.View.OnScrollChangeListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
import com.android.tv.ChannelTuner;
+import com.android.tv.Features;
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.analytics.DurationTimer;
@@ -53,8 +53,6 @@ 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.ui.OnRepeatedKeyInterceptListener;
-import com.android.tv.util.SystemProperties;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
@@ -214,7 +212,7 @@ public class ProgramGuide implements ProgramGrid.ChildFocusListener {
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()) {
+ if (Features.EPG_SEARCH.isEnabled(mActivity)) {
mSearchOrb = (SearchOrbView) mContainer.findViewById(
R.id.program_guide_side_panel_search_orb);
mSearchOrb.setVisibility(View.VISIBLE);
diff --git a/src/com/android/tv/guide/ProgramItemView.java b/src/com/android/tv/guide/ProgramItemView.java
index 1babc255..57678c05 100644
--- a/src/com/android/tv/guide/ProgramItemView.java
+++ b/src/com/android/tv/guide/ProgramItemView.java
@@ -151,13 +151,13 @@ public class ProgramItemView extends TextView {
sItemPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_item_padding);
- ColorStateList programTitleColor = ColorStateList.valueOf(res.getColor(
+ ColorStateList programTitleColor = ColorStateList.valueOf(Utils.getColor(res,
R.color.program_guide_table_item_program_title_text_color));
- ColorStateList grayedOutProgramTitleColor = res.getColorStateList(
+ ColorStateList grayedOutProgramTitleColor = Utils.getColorStateList(res,
R.color.program_guide_table_item_grayed_out_program_text_color);
- ColorStateList episodeTitleColor = ColorStateList.valueOf(res.getColor(
+ ColorStateList episodeTitleColor = ColorStateList.valueOf(Utils.getColor(res,
R.color.program_guide_table_item_program_episode_title_text_color));
- ColorStateList grayedOutEpisodeTitleColor = ColorStateList.valueOf(res.getColor(
+ ColorStateList grayedOutEpisodeTitleColor = ColorStateList.valueOf(Utils.getColor(res,
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);
diff --git a/src/com/android/tv/guide/ProgramManager.java b/src/com/android/tv/guide/ProgramManager.java
index fde903d1..216fcf3c 100644
--- a/src/com/android/tv/guide/ProgramManager.java
+++ b/src/com/android/tv/guide/ProgramManager.java
@@ -23,6 +23,7 @@ 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.CollectionUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
@@ -30,6 +31,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
@@ -163,43 +165,53 @@ public class ProgramManager {
// 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<>();
+ private final Set<Listener> mListeners = CollectionUtils.createSmallSet();
+ private final Set<TableEntriesUpdatedListener> mTableEntriesUpdatedListeners = CollectionUtils
+ .createSmallSet();
+
+ private final ChannelDataManager.Listener mChannelDataManagerListener =
+ new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ updateChannels(true, false);
+ }
+
+ @Override
+ public void onChannelListUpdated() {
+ updateChannels(true, false);
+ }
+
+ @Override
+ public void onChannelBrowsableChanged() {
+ updateChannels(true, false);
+ }
+ };
+
+ private final ProgramDataManager.Listener mProgramDataManagerListener =
+ new ProgramDataManager.Listener() {
+ @Override
+ public void onProgramUpdated() {
+ updateTableEntries(true, true);
+ }
+ };
public ProgramManager(TvInputManagerHelper tvInputManagerHelper,
ChannelDataManager channelDataManager,
ProgramDataManager programDataManager) {
mTvInputManagerHelper = tvInputManagerHelper;
mChannelDataManager = channelDataManager;
- mChannelDataManager.addListener(new ChannelDataManager.Listener() {
- @Override
- public void onLoadFinished() {
- updateChannels(true, false);
- }
-
- @Override
- public void onChannelListUpdated() {
- updateChannels(true, false);
- }
-
- @Override
- public void onChannelBrowsableChanged() {
- updateChannels(true, false);
- }
- });
-
mProgramDataManager = programDataManager;
- mProgramDataManager.addListener(new ProgramDataManager.Listener() {
- @Override
- public void onProgramUpdated() {
- updateTableEntries(true, true);
- }
- });
}
public void programGuideVisibilityChanged(boolean visible) {
mProgramDataManager.setPauseProgramUpdate(visible);
+ if (visible) {
+ mChannelDataManager.addListener(mChannelDataManagerListener);
+ mProgramDataManager.addListener(mProgramDataManagerListener);
+ } else {
+ mChannelDataManager.removeListener(mChannelDataManagerListener);
+ mProgramDataManager.removeListener(mProgramDataManagerListener);
+ }
}
/**
diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java
index df764668..cd0e9611 100644
--- a/src/com/android/tv/guide/ProgramTableAdapter.java
+++ b/src/com/android/tv/guide/ProgramTableAdapter.java
@@ -101,13 +101,13 @@ public class ProgramTableAdapter extends
R.string.program_title_for_no_information);
mProgramTitleForBlockedChannel = res.getString(
R.string.program_title_for_blocked_channel);
- mChannelTextColor = res.getColor(
+ mChannelTextColor = Utils.getColor(res,
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(
+ mChannelBlockedTextColor = Utils.getColor(res,
+ R.color.program_guide_table_header_column_channel_number_blocked_text_color);
+ mDetailTextColor = Utils.getColor(res,
R.color.program_guide_table_detail_title_text_color);
- mDetailGrayedTextColor = res.getColor(
+ mDetailGrayedTextColor = Utils.getColor(res,
R.color.program_guide_table_detail_title_grayed_text_color);
mAnimationDuration =
res.getInteger(R.integer.program_guide_table_detail_fade_anim_duration);
@@ -117,7 +117,7 @@ public class ProgramTableAdapter extends
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));
+ Utils.getColor(res, R.color.program_guide_table_detail_episode_title_text_color));
mEpisodeTitleStyle = new TextAppearanceSpan(null, 0, episodeTitleSize,
episodeTitleColor, null);
diff --git a/src/com/android/tv/menu/AppLinkCardView.java b/src/com/android/tv/menu/AppLinkCardView.java
index 19f22cc1..54564403 100644
--- a/src/com/android/tv/menu/AppLinkCardView.java
+++ b/src/com/android/tv/menu/AppLinkCardView.java
@@ -38,6 +38,7 @@ import com.android.tv.R;
import com.android.tv.data.Channel;
import com.android.tv.util.BitmapUtils;
import com.android.tv.util.TvInputManagerHelper;
+import com.android.tv.util.Utils;
/**
* A view to render an app link card.
@@ -94,7 +95,7 @@ public class AppLinkCardView extends BaseCardView<Channel> implements Channel.Lo
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);
+ mIconColorFilter = Utils.getColor(getResources(), R.color.app_link_card_icon_color_filter);
}
/**
@@ -107,7 +108,7 @@ public class AppLinkCardView extends BaseCardView<Channel> implements Channel.Lo
@Override
public void onBind(Channel channel, boolean selected) {
if (DEBUG) {
- Log.d(TAG, "onBind(channel=" + channel.getDisplayName() + ", selected=" + selected
+ Log.d(TAG, "onBind(channelName=" + channel.getDisplayName() + ", selected=" + selected
+ ")");
}
mChannel = channel;
@@ -241,27 +242,26 @@ public class AppLinkCardView extends BaseCardView<Channel> implements Channel.Lo
}
// 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.
+ // 1) Provided poster art image, 2) Activity banner, 3) Activity icon, 4) Application banner,
+ // 5) Application icon, and 6) default image.
private void setCardImageWithBanner(ApplicationInfo appInfo) {
Drawable banner = null;
try {
banner = mPackageManager.getActivityBanner(mIntent);
+ if (banner == null) {
+ banner = mPackageManager.getActivityIcon(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) {
+ if (appInfo.banner != 0) {
+ banner = mPackageManager.getApplicationBanner(appInfo);
+ }
+ if (banner == null && appInfo.icon != 0) {
+ banner = mPackageManager.getApplicationIcon(appInfo);
}
- }
- if (banner == null && appInfo != null && appInfo.logo != 0) {
- banner = mPackageManager.getApplicationLogo(appInfo);
}
if (banner == null) {
@@ -285,7 +285,7 @@ public class AppLinkCardView extends BaseCardView<Channel> implements Channel.Lo
@Override
public void onGenerated(Palette palette) {
mMetaViewHolder.setBackgroundColor(palette.getDarkVibrantColor(
- getResources().getColor(R.color.channel_card_meta_background, null)));
+ Utils.getColor(getResources(), R.color.channel_card_meta_background)));
}
});
}
diff --git a/src/com/android/tv/menu/ChannelCardView.java b/src/com/android/tv/menu/ChannelCardView.java
index d6086910..ea4f31e9 100644
--- a/src/com/android/tv/menu/ChannelCardView.java
+++ b/src/com/android/tv/menu/ChannelCardView.java
@@ -96,13 +96,17 @@ public class ChannelCardView extends BaseCardView<Channel> implements
@Override
public void onBind(Channel channel, boolean selected) {
if (DEBUG) {
- Log.d(TAG, "onBind(channel=" + channel.getDisplayName() + ", selected=" + selected
+ Log.d(TAG, "onBind(channelName=" + channel.getDisplayName() + ", selected=" + selected
+ ")");
}
mChannel = channel;
mProgram = null;
- mChannelNumberNameView.setText(mChannel.getDisplayNumber() + " "
- + mChannel.getDisplayName());
+ if (TextUtils.isEmpty(mChannel.getDisplayName())) {
+ mChannelNumberNameView.setText(mChannel.getDisplayNumber());
+ } else {
+ mChannelNumberNameView.setText(mChannel.getDisplayNumber() + " "
+ + mChannel.getDisplayName());
+ }
mChannelNumberNameView.setVisibility(VISIBLE);
mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default);
mImageView.setBackgroundResource(R.color.channel_card);
diff --git a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java
index b008fa65..d576342c 100644
--- a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java
+++ b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java
@@ -27,7 +27,7 @@ import com.android.tv.common.WeakHandler;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
import com.android.tv.data.ProgramDataManager;
-import com.android.tv.util.Utils;
+import com.android.tv.util.SoftPreconditions;
import java.util.List;
@@ -68,8 +68,8 @@ public class ChannelsPosterPrefetcher {
* Start prefetching of program poster art of recommendation.
*/
public void prefetch() {
+ SoftPreconditions.checkState(!isCanceled, TAG, "Prefetch called after cancel was called.");
if (isCanceled) {
- Utils.engThrowElseWarn(TAG, "Prefetch called after cancel was called.");
return;
}
if (DEBUG) {
diff --git a/src/com/android/tv/menu/ChannelsRowAdapter.java b/src/com/android/tv/menu/ChannelsRowAdapter.java
index 8190c976..7a1eacf3 100644
--- a/src/com/android/tv/menu/ChannelsRowAdapter.java
+++ b/src/com/android/tv/menu/ChannelsRowAdapter.java
@@ -30,7 +30,6 @@ import com.android.tv.recommendation.Recommender;
import com.android.tv.util.SetupUtils;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
@@ -187,7 +186,6 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel>
}
}
}
-
- return Collections.unmodifiableList(channelList);
+ return channelList;
}
}
diff --git a/src/com/android/tv/menu/Menu.java b/src/com/android/tv/menu/Menu.java
index 323ce9c5..1f33bd67 100644
--- a/src/com/android/tv/menu/Menu.java
+++ b/src/com/android/tv/menu/Menu.java
@@ -132,7 +132,7 @@ public class Menu {
mHideAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mMenuView.onHide();
+ hideInternal();
}
});
mHideAnimator.setTarget(mMenuView);
@@ -222,11 +222,15 @@ public class Menu {
// mMenuView.onHide() is called in AnimatorListener.
mHideAnimator.end();
} else {
- mMenuView.onHide();
- mTracker.sendHideMenu(mVisibleTimer.reset());
- if (mOnMenuVisibilityChangeListener != null) {
- mOnMenuVisibilityChangeListener.onMenuVisibilityChange(false);
- }
+ hideInternal();
+ }
+ }
+
+ private void hideInternal() {
+ mMenuView.onHide();
+ mTracker.sendHideMenu(mVisibleTimer.reset());
+ if (mOnMenuVisibilityChangeListener != null) {
+ mOnMenuVisibilityChangeListener.onMenuVisibilityChange(false);
}
}
diff --git a/src/com/android/tv/menu/MenuLayoutManager.java b/src/com/android/tv/menu/MenuLayoutManager.java
index 187d0e14..265ad840 100644
--- a/src/com/android/tv/menu/MenuLayoutManager.java
+++ b/src/com/android/tv/menu/MenuLayoutManager.java
@@ -35,6 +35,7 @@ import android.view.ViewGroup.MarginLayoutParams;
import android.widget.TextView;
import com.android.tv.R;
+import com.android.tv.util.SoftPreconditions;
import com.android.tv.util.Utils;
import java.util.ArrayList;
@@ -312,16 +313,16 @@ public class MenuLayoutManager {
if (mSelectedPosition == position) {
return;
}
- if (position < 0 || position >= mMenuRowViews.size()) {
- String msg = "Invalid position: " + position;
- Utils.engThrowElseWarn(TAG, msg, new IllegalArgumentException(msg));
+ boolean indexValid = Utils.isIndexValid(mMenuRowViews, position);
+ SoftPreconditions.checkArgument(indexValid, TAG, "position " + position);
+ if (!indexValid) {
return;
}
- if (mSelectedPosition >= 0 && mSelectedPosition < mMenuRowViews.size()) {
+ if (Utils.isIndexValid(mMenuRowViews, mSelectedPosition)) {
mMenuRowViews.get(mSelectedPosition).onDeselected();
}
mSelectedPosition = position;
- if (mSelectedPosition >= 0 && mSelectedPosition < mMenuRowViews.size()) {
+ if (Utils.isIndexValid(mMenuRowViews, mSelectedPosition)) {
mMenuRowViews.get(mSelectedPosition).onSelected(false);
}
if (mMenuView.getVisibility() == View.VISIBLE) {
@@ -348,14 +349,15 @@ public class MenuLayoutManager {
if (mSelectedPosition == position) {
return;
}
- if (mSelectedPosition < 0 || mSelectedPosition >= mMenuRowViews.size()) {
- String msg = "No previous selection: " + mSelectedPosition;
- Utils.engThrowElseWarn(TAG, msg, new IllegalStateException(msg));
+ boolean oldIndexValid = Utils.isIndexValid(mMenuRowViews, mSelectedPosition);
+ SoftPreconditions
+ .checkState(oldIndexValid, TAG, "No previous selection: " + mSelectedPosition);
+ if (!oldIndexValid) {
return;
}
- if (position < 0 || position >= mMenuRowViews.size()) {
- String msg = "Invalid position: " + position;
- Utils.engThrowElseWarn(TAG, msg, new IllegalArgumentException(msg));
+ boolean newIndexValid = Utils.isIndexValid(mMenuRowViews, position);
+ SoftPreconditions.checkArgument(newIndexValid, TAG, "position " + position);
+ if (!newIndexValid) {
return;
}
if (mAnimatorSet != null) {
@@ -629,8 +631,8 @@ public class MenuLayoutManager {
if (mMenuView.getVisibility() != View.VISIBLE) {
int count = mMenuRowViews.size();
for (int i = 0; i < count; ++i) {
- mMenuRowViews.get(i).setVisibility(mMenuRows.get(i).isVisible() ? View.VISIBLE
- : View.GONE);
+ mMenuRowViews.get(i)
+ .setVisibility(mMenuRows.get(i).isVisible() ? View.VISIBLE : View.GONE);
}
return;
}
diff --git a/src/com/android/tv/menu/MenuRowView.java b/src/com/android/tv/menu/MenuRowView.java
index a6d8c990..6b3b6b5f 100644
--- a/src/com/android/tv/menu/MenuRowView.java
+++ b/src/com/android/tv/menu/MenuRowView.java
@@ -236,7 +236,11 @@ public abstract class MenuRowView extends LinearLayout {
mTitleView.setScaleX(mTitleViewScaleSelected);
mTitleView.setScaleY(mTitleViewScaleSelected);
}
+ // Making the content view visible will cause it to set a focus item
+ // So we store mLastFocusView and reset it
+ View lastFocusView = mLastFocusView;
mContentsView.setVisibility(VISIBLE);
+ mLastFocusView = lastFocusView;
}
/**
diff --git a/src/com/android/tv/menu/TvOptionsRowAdapter.java b/src/com/android/tv/menu/TvOptionsRowAdapter.java
index b7814fa5..1977dde1 100644
--- a/src/com/android/tv/menu/TvOptionsRowAdapter.java
+++ b/src/com/android/tv/menu/TvOptionsRowAdapter.java
@@ -29,6 +29,7 @@ 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.PermissionUtils;
import com.android.tv.util.PipInputManager;
import java.util.ArrayList;
@@ -54,7 +55,13 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter {
mPositionPipAction = actionList.size() - 1;
actionList.add(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION);
actionList.add(MenuAction.CHANNEL_SOURCES_ACTION);
- actionList.add(MenuAction.PARENTAL_CONTROLS_ACTION);
+ if (PermissionUtils.hasModifyParentalControls(getMainActivity())) {
+ actionList.add(MenuAction.PARENTAL_CONTROLS_ACTION);
+ } else {
+ // Note: parental control is turned off, when MODIFY_PARENTAL_CONTROLS is not granted.
+ // But, we may be able to turn on channel lock feature regardless of the permission.
+ // It's TBD.
+ }
actionList.add(MenuAction.ABOUT_ACTION);
for (MenuAction action : actionList) {
diff --git a/src/com/android/tv/onboarding/AppOverviewFragment.java b/src/com/android/tv/onboarding/AppOverviewFragment.java
new file mode 100644
index 00000000..3427b122
--- /dev/null
+++ b/src/com/android/tv/onboarding/AppOverviewFragment.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.onboarding;
+
+import android.app.Fragment;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.tv.R;
+import com.android.tv.TvApplication;
+import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
+import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
+
+import java.util.List;
+
+/**
+ * A fragment for channel source info/setup.
+ */
+public class AppOverviewFragment extends SetupMultiPaneFragment {
+ public static final int ACTION_SETUP_SOURCE = 1;
+ public static final int ACTION_GET_MORE_CHANNELS = 2;
+ public static final int ACTION_SETUP_USB_TUNER = 3;
+
+ public static final String KEY_AC3_SUPPORT = "key_ac3_support";
+
+ private boolean mAc3Supported;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = super.onCreateView(inflater, container, savedInstanceState);
+ Bundle bundle = getArguments();
+ mAc3Supported = bundle.getBoolean(KEY_AC3_SUPPORT);
+ return view;
+ }
+
+ @Override
+ protected Fragment getContentFragment() {
+ return new ContentFragment();
+ }
+
+ @Override
+ protected boolean needsDoneButton() {
+ return false;
+ }
+
+ // AppOverviewFragment should inherit OnboardingPageFragment for animation and command execution
+ // purpose. So child fragment which inherits GuidedStepFragment is needed.
+ private class ContentFragment extends SetupGuidedStepFragment {
+ @Override
+ public Guidance onCreateGuidance(Bundle savedInstanceState) {
+ String title = getString(R.string.app_overview_text);
+ String description = mAc3Supported
+ ? getString(R.string.app_overview_description_has_ac3)
+ : getString(R.string.app_overview_description_no_ac3);
+ return new Guidance(title, description, null, null);
+ }
+
+ @Override
+ public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+ boolean hasTvInput = ((TvApplication) getActivity().getApplicationContext())
+ .getTvInputManagerHelper().getTunerTvInputSize() > 0;
+ Resources res = getResources();
+ if (hasTvInput) {
+ actions.add(new GuidedAction.Builder()
+ .id(ACTION_SETUP_SOURCE)
+ .title(res.getString(R.string.app_overview_action_text_setup_source))
+ .description(res.getString(
+ R.string.app_overview_action_description_setup_source))
+ .build());
+ }
+ actions.add(new GuidedAction.Builder()
+ .id(ACTION_GET_MORE_CHANNELS)
+ .title(res.getString(R.string.app_overview_action_text_play_store))
+ .description(res.getString(R.string.app_overview_action_description_play_store))
+ .build());
+ if (mAc3Supported) {
+ actions.add(new GuidedAction.Builder()
+ .id(ACTION_SETUP_USB_TUNER)
+ .title(res.getString(R.string.app_overview_action_text_usb_tuner))
+ .description(res.getString(
+ R.string.app_overview_action_description_usb_tuner))
+ .build());
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/onboarding/OnboardingActivity.java b/src/com/android/tv/onboarding/OnboardingActivity.java
new file mode 100644
index 00000000..4176a70f
--- /dev/null
+++ b/src/com/android/tv/onboarding/OnboardingActivity.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.onboarding;
+
+import android.app.Fragment;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.media.tv.TvInputInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.tv.R;
+import com.android.tv.common.WeakHandler;
+import com.android.tv.common.ui.setup.SetupStep;
+import com.android.tv.common.ui.setup.SteppedSetupActivity;
+import com.android.tv.receiver.AudioCapabilitiesReceiver;
+import com.android.tv.util.OnboardingUtils;
+import com.android.tv.util.SetupUtils;
+import com.android.tv.util.SoftPreconditions;
+import com.android.tv.util.Utils;
+
+import java.util.concurrent.TimeUnit;
+
+public class OnboardingActivity extends SteppedSetupActivity {
+ private static final String TAG = "OnboardingActivity";
+
+ private static final String KEY_INTENT_AFTER_COMPLETION = "key_intent_after_completion";
+
+ private static final int MSG_CHECK_RECEIVED_AC3_CAPABILITY_NOTIFICATION = 1;
+ private static final long AC3_CHECK_WAIT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+
+ private static final int REQUEST_CODE_SETUP_USB_TUNER = 1;
+
+ private Handler mHandler = new OnboardingActivityHandler(this);
+ private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver;
+ private Boolean mAc3Supported;
+
+ /**
+ * Returns an intent to start {@link OnboardingActivity}.
+ *
+ * @param context context to create an intent. Should not be {@code null}.
+ * @param intentAfterCompletion intent which will be used to start a new activity when this
+ * activity finishes. Should not be {@code null}.
+ */
+ public static Intent buildIntent(@NonNull Context context,
+ @NonNull Intent intentAfterCompletion) {
+ return new Intent(context, OnboardingActivity.class)
+ .putExtra(OnboardingActivity.KEY_INTENT_AFTER_COMPLETION, intentAfterCompletion);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Register a receiver for HDMI audio plug and wait for the response.
+ mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(this,
+ new AudioCapabilitiesReceiver.OnAc3PassthroughCapabilityChangeListener() {
+ @Override
+ public void onAc3PassthroughCapabilityChange(boolean capability) {
+ mAudioCapabilitiesReceiver.unregister();
+ mAudioCapabilitiesReceiver = null;
+ mHandler.removeMessages(MSG_CHECK_RECEIVED_AC3_CAPABILITY_NOTIFICATION);
+ mAc3Supported = capability;
+ startFirstStep();
+ }
+ });
+ mAudioCapabilitiesReceiver.register();
+ mHandler.sendEmptyMessageDelayed(MSG_CHECK_RECEIVED_AC3_CAPABILITY_NOTIFICATION,
+ AC3_CHECK_WAIT_TIMEOUT);
+ }
+
+ @Override
+ protected SetupStep onCreateInitialStep() {
+ if (mAc3Supported == null) {
+ return null;
+ }
+ if (OnboardingUtils.isFirstRun(this)) {
+ return new WelcomeStep(null);
+ }
+ return new AppOverviewStep(null);
+ }
+
+ @Override
+ protected void onDestroy() {
+ mHandler.removeCallbacksAndMessages(null);
+ if (mAudioCapabilitiesReceiver != null) {
+ mAudioCapabilitiesReceiver.unregister();
+ mAudioCapabilitiesReceiver = null;
+ }
+ super.onDestroy();
+ }
+
+ void startFirstStep() {
+ SoftPreconditions.checkNotNull(mAc3Supported, TAG,
+ "AC3 passthrough support check hasn't been completed yet.");
+ startInitialStep();
+ }
+
+
+ private static class OnboardingActivityHandler extends WeakHandler<OnboardingActivity> {
+ OnboardingActivityHandler(OnboardingActivity activity) {
+ // Should run on main thread because onAc3SupportChanged will be called on main thread.
+ super(Looper.getMainLooper(), activity);
+ }
+
+ @Override
+ protected void handleMessage(Message msg, OnboardingActivity activity) {
+ if (msg.what == MSG_CHECK_RECEIVED_AC3_CAPABILITY_NOTIFICATION) {
+ activity.mAudioCapabilitiesReceiver.unregister();
+ activity.mAudioCapabilitiesReceiver = null;
+ activity.startFirstStep();
+ }
+ }
+ }
+
+ void finishActivity() {
+ Intent intentForNextActivity = (Intent) getIntent().getParcelableExtra(
+ KEY_INTENT_AFTER_COMPLETION);
+ if (intentForNextActivity != null) {
+ startActivity(intentForNextActivity);
+ }
+ finish();
+ }
+
+ private class WelcomeStep extends SetupStep {
+ public WelcomeStep(@Nullable SetupStep previousStep) {
+ super(getFragmentManager(), previousStep);
+ }
+
+ @Override
+ public Fragment onCreateFragment() {
+ return new WelcomeFragment();
+ }
+
+ @Override
+ protected boolean needsToBeAddedToBackStack() {
+ return false;
+ }
+
+ @Override
+ protected boolean needsFragmentTransitionAnimation() {
+ return false;
+ }
+
+ @Override
+ public void executeAction(int actionId) {
+ switch (actionId) {
+ case WelcomeFragment.ACTION_NEXT:
+ OnboardingUtils.setFirstRunCompleted(OnboardingActivity.this);
+ if (!OnboardingUtils.areChannelsAvailable(OnboardingActivity.this)) {
+ startStep(new AppOverviewStep(this));
+ } else {
+ // TODO: Go to the correct step.
+ finishActivity();
+ }
+ break;
+ }
+ }
+ }
+
+ private class AppOverviewStep extends SetupStep {
+ public AppOverviewStep(@Nullable SetupStep previousStep) {
+ super(getFragmentManager(), previousStep);
+ }
+
+ @Override
+ public Fragment onCreateFragment() {
+ Fragment fragment = new AppOverviewFragment();
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(AppOverviewFragment.KEY_AC3_SUPPORT, mAc3Supported);
+ fragment.setArguments(bundle);
+ return fragment;
+ }
+
+ @Override
+ protected boolean needsToBeAddedToBackStack() {
+ return false;
+ }
+
+ @Override
+ public void executeAction(int actionId) {
+ switch (actionId) {
+ case AppOverviewFragment.ACTION_SETUP_SOURCE:
+ startStep(new SetupSourcesStep(this));
+ break;
+ case AppOverviewFragment.ACTION_GET_MORE_CHANNELS:
+ // TODO: Implement this.
+ Toast.makeText(OnboardingActivity.this, "Not implemented yet.",
+ Toast.LENGTH_SHORT).show();
+ break;
+ }
+ }
+ }
+
+ private class SetupSourcesStep extends SetupStep {
+ public SetupSourcesStep(@Nullable SetupStep previousStep) {
+ super(getFragmentManager(), previousStep);
+ }
+
+ @Override
+ public Fragment onCreateFragment() {
+ return new SetupSourcesFragment();
+ }
+
+ @Override
+ public void executeAction(int actionId) {
+ switch (actionId) {
+ case SetupSourcesFragment.ACTION_DONE:
+ finishActivity();
+ break;
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/onboarding/PagingIndicator.java b/src/com/android/tv/onboarding/PagingIndicator.java
new file mode 100644
index 00000000..128fa996
--- /dev/null
+++ b/src/com/android/tv/onboarding/PagingIndicator.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.onboarding;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+
+import com.android.tv.R;
+import com.android.tv.util.Utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A page indicator with dots.
+ */
+public class PagingIndicator extends View {
+ // attribute
+ private final int mDotDiameter;
+ private final int mDotRadius;
+ private final int mDotGap;
+ private int[] mDotCenterX;
+ private int mDotCenterY;
+
+ // state
+ private int mPageCount;
+ private int mCurrentPage;
+ private int mPreviousPage;
+
+ // drawing
+ private final Paint mUnselectedPaint;
+ private final Paint mSelectedPaint;
+ private final Paint mUnselectingPaint;
+ private final Paint mSelectingPaint;
+ private final AnimatorSet mAnimator = new AnimatorSet();
+
+ public PagingIndicator(Context context) {
+ this(context, null, 0);
+ }
+
+ public PagingIndicator(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagingIndicator(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ Resources res = getResources();
+ mDotRadius = res.getDimensionPixelSize(R.dimen.onboarding_dot_radius);
+ mDotDiameter = mDotRadius * 2;
+ mDotGap = res.getDimensionPixelSize(R.dimen.onboarding_dot_gap);
+ mUnselectedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ // Deprecated method is used because this code should run on L platform.
+ int unselectedColor = Utils.getColor(res, R.color.onboarding_dot_unselected);
+ int selectedColor = Utils.getColor(res, R.color.onboarding_dot_selected);
+ mUnselectedPaint.setColor(unselectedColor);
+ mSelectedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mSelectedPaint.setColor(selectedColor);
+ mUnselectingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mSelectingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ // Initialize animations.
+ int duration = res.getInteger(R.integer.setup_slide_anim_duration);
+ List<Animator> animators = new ArrayList<>();
+ animators.add(createColorAnimator(selectedColor, unselectedColor, duration,
+ mUnselectingPaint));
+ animators.add(createColorAnimator(unselectedColor, selectedColor, duration,
+ mSelectingPaint));
+ mAnimator.playTogether(animators);
+ }
+
+ private Animator createColorAnimator(int fromColor, int toColor, int duration,
+ final Paint paint) {
+ ValueAnimator animator = ValueAnimator.ofArgb(fromColor, toColor);
+ animator.setDuration(duration);
+ animator.setInterpolator(new DecelerateInterpolator());
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ invalidate();
+ }
+ });
+ animator.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ paint.setColor((int) animator.getAnimatedValue());
+ invalidate();
+ }
+ });
+ return animator;
+ }
+
+ /**
+ * Sets the page count.
+ */
+ public void setPageCount(int pages) {
+ mPageCount = pages;
+ calculateDotPositions();
+ setSelectedPage(0);
+ }
+
+ /**
+ * Called when the page has been selected.
+ */
+ public void onPageSelected(int pageIndex, boolean withAnimation) {
+ if (mAnimator.isStarted()) {
+ mAnimator.end();
+ }
+ if (withAnimation) {
+ mPreviousPage = mCurrentPage;
+ mAnimator.start();
+ }
+ setSelectedPage(pageIndex);
+ }
+
+ private void calculateDotPositions() {
+ int left = getPaddingLeft();
+ int top = getPaddingTop();
+ int right = getWidth() - getPaddingRight();
+ int requiredWidth = getRequiredWidth();
+ int startLeft = left + ((right - left - requiredWidth) / 2) + mDotRadius;
+ mDotCenterX = new int[mPageCount];
+ for (int i = 0; i < mPageCount; i++) {
+ mDotCenterX[i] = startLeft + i * (mDotDiameter + mDotGap);
+ }
+ mDotCenterY = top + mDotRadius;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int desiredHeight = getDesiredHeight();
+ int height;
+ switch (MeasureSpec.getMode(heightMeasureSpec)) {
+ case MeasureSpec.EXACTLY:
+ height = MeasureSpec.getSize(heightMeasureSpec);
+ break;
+ case MeasureSpec.AT_MOST:
+ height = Math.min(desiredHeight, MeasureSpec.getSize(heightMeasureSpec));
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ default:
+ height = desiredHeight;
+ break;
+ }
+ int desiredWidth = getDesiredWidth();
+ int width;
+ switch (MeasureSpec.getMode(widthMeasureSpec)) {
+ case MeasureSpec.EXACTLY:
+ width = MeasureSpec.getSize(widthMeasureSpec);
+ break;
+ case MeasureSpec.AT_MOST:
+ width = Math.min(desiredWidth, MeasureSpec.getSize(widthMeasureSpec));
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ default:
+ width = desiredWidth;
+ break;
+ }
+ setMeasuredDimension(width, height);
+ calculateDotPositions();
+ }
+
+ @Override
+ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
+ setMeasuredDimension(width, height);
+ calculateDotPositions();
+ }
+
+ private int getDesiredHeight() {
+ return getPaddingTop() + mDotDiameter + getPaddingBottom();
+ }
+
+ private int getRequiredWidth() {
+ return mPageCount * mDotDiameter + (mPageCount - 1) * mDotGap;
+ }
+
+ private int getDesiredWidth() {
+ return getPaddingLeft() + getRequiredWidth() + getPaddingRight();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ drawUnselected(canvas);
+ if (mAnimator.isStarted()) {
+ drawAnimator(canvas);
+ } else {
+ drawSelected(canvas);
+ }
+ }
+
+ private void drawUnselected(Canvas canvas) {
+ for (int page = 0; page < mPageCount; page++) {
+ canvas.drawCircle(mDotCenterX[page], mDotCenterY, mDotRadius, mUnselectedPaint);
+ }
+ }
+
+ private void drawSelected(Canvas canvas) {
+ canvas.drawCircle(mDotCenterX[mCurrentPage], mDotCenterY, mDotRadius, mSelectedPaint);
+ }
+
+ private void drawAnimator(Canvas canvas) {
+ canvas.drawCircle(mDotCenterX[mPreviousPage], mDotCenterY, mDotRadius, mUnselectingPaint);
+ canvas.drawCircle(mDotCenterX[mCurrentPage], mDotCenterY, mDotRadius, mSelectingPaint);
+ }
+
+ private void setSelectedPage(int now) {
+ if (now == mCurrentPage) {
+ return;
+ }
+ mCurrentPage = now;
+ invalidate();
+ }
+}
diff --git a/src/com/android/tv/onboarding/SetupSourcesFragment.java b/src/com/android/tv/onboarding/SetupSourcesFragment.java
new file mode 100644
index 00000000..3572a209
--- /dev/null
+++ b/src/com/android/tv/onboarding/SetupSourcesFragment.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.onboarding;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.tv.R;
+import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
+import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
+
+import java.util.List;
+
+/**
+ * A fragment for channel source info/setup.
+ */
+public class SetupSourcesFragment extends SetupMultiPaneFragment {
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = super.onCreateView(inflater, container, savedInstanceState);
+ setOnClickAction(view.findViewById(R.id.button_done), ACTION_DONE);
+ return view;
+ }
+
+ @Override
+ protected Fragment getContentFragment() {
+ return new ContentFragment();
+ }
+
+ private class ContentFragment extends SetupGuidedStepFragment {
+ @Override
+ public Guidance onCreateGuidance(Bundle savedInstanceState) {
+ String title = getString(R.string.setup_sources_text);
+ String description = getString(R.string.setup_sources_description);
+ return new Guidance(title, description, null, null);
+ }
+
+ @Override
+ public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+ actions.add(new GuidedAction.Builder().id(ACTION_DONE).title("Done")
+ .description("Return to previous page").build());
+ }
+ }
+}
diff --git a/src/com/android/tv/onboarding/WelcomeFragment.java b/src/com/android/tv/onboarding/WelcomeFragment.java
new file mode 100644
index 00000000..baeb1b29
--- /dev/null
+++ b/src/com/android/tv/onboarding/WelcomeFragment.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.onboarding;
+
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import com.android.tv.R;
+import com.android.tv.common.ui.setup.SetupFragment;
+
+/**
+ * A fragment for the onboarding screen.
+ */
+public class WelcomeFragment extends SetupFragment {
+ public static final int ACTION_NEXT = 1;
+
+ private int mNumPages;
+ private String[] mPageTitles;
+ private String[] mPageDescriptions;
+ private int mCurrentPageIndex;
+
+ private PagingIndicator mPageIndicator;
+ private Button mButton;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = super.onCreateView(inflater, container, savedInstanceState);
+ mPageTitles = getResources().getStringArray(R.array.welcome_page_titles);
+ mPageDescriptions = getResources().getStringArray(R.array.welcome_page_descriptions);
+ mNumPages = mPageTitles.length;
+ mCurrentPageIndex = 0;
+ mPageIndicator = (PagingIndicator) view.findViewById(R.id.page_indicator);
+ mPageIndicator.setPageCount(mNumPages);
+ mButton = (Button) view.findViewById(R.id.button);
+ mButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mCurrentPageIndex == mNumPages - 1) {
+ onActionClick(ACTION_NEXT);
+ } else {
+ showPage(++mCurrentPageIndex);
+ }
+ }
+ });
+ showPage(mCurrentPageIndex);
+ return view;
+ }
+
+ @Override
+ protected int getLayoutResourceId() {
+ return R.layout.fragment_welcome;
+ }
+
+ /*
+ * Should return {@link SetupFragment} for the custom animations.
+ */
+ private SetupFragment getPage(int index) {
+ Bundle args = new Bundle();
+ args.putString(WelcomePageFragment.KEY_TITLE, mPageTitles[index]);
+ args.putString(WelcomePageFragment.KEY_DESCRIPTION, mPageDescriptions[index]);
+ SetupFragment fragment = new WelcomePageFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ private void showPage(int pageIndex) {
+ SetupFragment fragment = getPage(pageIndex);
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ if (pageIndex != 0) {
+ ft.setCustomAnimations(SetupFragment.ANIM_ENTER,
+ SetupFragment.ANIM_EXIT);
+ }
+ ft.replace(R.id.page_container, fragment).commit();
+ if (pageIndex == mNumPages - 1) {
+ mButton.setText(R.string.welcome_start_button_text);
+ } else {
+ mButton.setText(R.string.welcome_next_button_text);
+ }
+ mPageIndicator.onPageSelected(pageIndex, pageIndex != 0);
+ }
+}
diff --git a/src/com/android/tv/onboarding/WelcomePageFragment.java b/src/com/android/tv/onboarding/WelcomePageFragment.java
new file mode 100644
index 00000000..3c6cd679
--- /dev/null
+++ b/src/com/android/tv/onboarding/WelcomePageFragment.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.onboarding;
+
+import android.os.Bundle;
+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.common.ui.setup.SetupFragment;
+
+/**
+ * A fragment for the onboarding screen.
+ */
+public class WelcomePageFragment extends SetupFragment {
+ public static final String KEY_TITLE = "key_title";
+ public static final String KEY_DESCRIPTION = "key_description";
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = super.onCreateView(inflater, container, savedInstanceState);
+ Bundle args = getArguments();
+ ((TextView) view.findViewById(R.id.title)).setText(args.getString(KEY_TITLE));
+ ((TextView) view.findViewById(R.id.description)).setText(args.getString(KEY_DESCRIPTION));
+ return view;
+ }
+
+ @Override
+ protected int getLayoutResourceId() {
+ return R.layout.fragment_welcome_page;
+ }
+}
diff --git a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
index 949222a9..55d3cf3a 100644
--- a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
+++ b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
@@ -22,15 +22,17 @@ import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.media.AudioFormat;
import android.media.AudioManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import com.android.tv.TvApplication;
+import com.android.tv.analytics.Analytics;
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.
+ * Creates HDMI plug broadcast receiver, and reports AC3 passthrough capabilities to Google
+ * Analytics and listeners. Call {@link #register} to start receiving notifications, and
+ * {@link #unregister} to stop.
*/
public final class AudioCapabilitiesReceiver {
private static final String PREFS_NAME = "com.android.tv.audio_capabilities";
@@ -45,18 +47,25 @@ public final class AudioCapabilitiesReceiver {
private static final int REPORT_REVISION = 1;
private final Context mContext;
+ private final Analytics mAnalytics;
private final Tracker mTracker;
+ @Nullable
+ private final OnAc3PassthroughCapabilityChangeListener mListener;
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
+ * @param listener listener which receives AC3 passthrough capability change notification
*/
- public AudioCapabilitiesReceiver(Context context, Tracker tracker) {
+ public AudioCapabilitiesReceiver(@NonNull Context context,
+ @Nullable OnAc3PassthroughCapabilityChangeListener listener) {
mContext = context;
- mTracker = tracker;
+ TvApplication tvApplication = (TvApplication) context.getApplicationContext();
+ mAnalytics = tvApplication.getAnalytics();
+ mTracker = tvApplication.getTracker();
+ mListener = listener;
}
public void register() {
@@ -74,23 +83,36 @@ public final class AudioCapabilitiesReceiver {
if (!action.equals(AudioManager.ACTION_HDMI_AUDIO_PLUG)) {
return;
}
- reportAudioCapabilities(intent.getIntArrayExtra(AudioManager.EXTRA_ENCODINGS));
+ boolean supported = false;
+ int[] supportedEncodings = intent.getIntArrayExtra(AudioManager.EXTRA_ENCODINGS);
+ if (supportedEncodings != null) {
+ for (int supportedEncoding : supportedEncodings) {
+ if (supportedEncoding == AudioFormat.ENCODING_AC3) {
+ supported = true;
+ break;
+ }
+ }
+ }
+ if (mListener != null) {
+ mListener.onAc3PassthroughCapabilityChange(supported);
+ }
+ if (!mAnalytics.isAppOptOut()) {
+ reportAudioCapabilities(supported);
+ }
}
}
- private void reportAudioCapabilities(int[] supportedEncodings) {
- boolean newVal = supportedEncodings != null
- && Arrays.binarySearch(supportedEncodings, AudioFormat.ENCODING_AC3) >= 0;
+ private void reportAudioCapabilities(boolean ac3Supported) {
boolean oldVal = getBoolean(SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES, false);
boolean reported = getBoolean(SETTINGS_KEY_AC3_PASSTHRU_REPORTED, false);
int revision = getInt(SETTINGS_KEY_AC3_REPORT_REVISION, 0);
// 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 || REPORT_REVISION > revision) {
- mTracker.sendAc3PassthroughCapabilities(newVal);
+ if (!reported || oldVal != ac3Supported || REPORT_REVISION > revision) {
+ mTracker.sendAc3PassthroughCapabilities(ac3Supported);
setBoolean(SETTINGS_KEY_AC3_PASSTHRU_REPORTED, true);
- setBoolean(SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES, newVal);
+ setBoolean(SETTINGS_KEY_AC3_PASSTHRU_CAPABILITIES, ac3Supported);
if (REPORT_REVISION > revision) {
setInt(SETTINGS_KEY_AC3_REPORT_REVISION, REPORT_REVISION);
}
@@ -116,4 +138,14 @@ public final class AudioCapabilitiesReceiver {
private void setInt(String key, int val) {
getSharedPreferences().edit().putInt(key, val).apply();
}
+
+ /**
+ * Listener notified when AC3 passthrough capability changes.
+ */
+ public interface OnAc3PassthroughCapabilityChangeListener {
+ /**
+ * Called when the AC3 passthrough capability changes.
+ */
+ void onAc3PassthroughCapabilityChange(boolean capability);
+ }
}
diff --git a/src/com/android/tv/receiver/BootCompletedReceiver.java b/src/com/android/tv/receiver/BootCompletedReceiver.java
new file mode 100644
index 00000000..61a9baa9
--- /dev/null
+++ b/src/com/android/tv/receiver/BootCompletedReceiver.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.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+
+import com.android.tv.Features;
+import com.android.tv.TvActivity;
+import com.android.tv.recommendation.NotificationService;
+import com.android.tv.util.OnboardingUtils;
+import com.android.tv.util.SetupUtils;
+
+/**
+ * Boot completed receiver. It's used to start the {@code NotificationService} for recommendation,
+ * grant permission to the TIS's and enable {@code TvActivity} if necessary.
+ */
+public class BootCompletedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Start {@link NotificationService}.
+ Intent notificationIntent = new Intent(context, NotificationService.class);
+ notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION);
+ context.startService(notificationIntent);
+
+ // Grant permission to already set up packages after the system has finished booting.
+ SetupUtils.grantEpgPermissionToSetUpPackages(context);
+
+ // On-boarding experience.
+ if (Features.ONBOARDING_EXPERIENCE.isEnabled(context)) {
+ if (OnboardingUtils.isFirstBoot(context)) {
+ // Enable the application if this is the first run after the on-boarding experience
+ // is applied just in case when the app is disabled before.
+ PackageManager pm = context.getPackageManager();
+ ComponentName name = new ComponentName(context, TvActivity.class);
+ if (pm.getComponentEnabledSetting(name)
+ != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ pm.setComponentEnabledSetting(name,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+ }
+ OnboardingUtils.setFirstBootCompleted(context);
+ }
+ }
+ }
+}
diff --git a/src/com/android/tv/receiver/PackageIntentsReceiver.java b/src/com/android/tv/receiver/PackageIntentsReceiver.java
index fd9d7baf..a6ab858d 100644
--- a/src/com/android/tv/receiver/PackageIntentsReceiver.java
+++ b/src/com/android/tv/receiver/PackageIntentsReceiver.java
@@ -25,6 +25,7 @@ import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.os.Handler;
+import com.android.tv.Features;
import com.android.tv.TvActivity;
import com.android.tv.util.SetupUtils;
@@ -42,7 +43,8 @@ public class PackageIntentsReceiver extends BroadcastReceiver {
private TvInputManager mTvInputManager;
private final Handler mHandler = new Handler();
private Runnable mOnPackageUpdatedRunnable;
- private boolean mPermissionGranted;
+ private PackageManager mPackageManager;
+ private ComponentName mTvActivityComponentName;
private void init(Context context) {
mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
@@ -51,27 +53,25 @@ public class PackageIntentsReceiver extends BroadcastReceiver {
mOnPackageUpdatedRunnable = new Runnable() {
@Override
public void run() {
- 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;
+ if (!Features.ONBOARDING_EXPERIENCE.isEnabled(applicationContext)) {
+ 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);
}
- enableTvActivityWithinPackageManager(applicationContext, enable);
SetupUtils.getInstance(applicationContext).onInputListUpdated(mTvInputManager);
}
};
- // 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;
- }
+ mPackageManager = applicationContext.getPackageManager();
+ mTvActivityComponentName = new ComponentName(applicationContext, TvActivity.class);
}
@Override
@@ -82,8 +82,13 @@ public class PackageIntentsReceiver extends BroadcastReceiver {
mHandler.removeCallbacks(mOnPackageUpdatedRunnable);
mHandler.postDelayed(mOnPackageUpdatedRunnable, TV_INPUT_UPDATE_DELAY_MS);
+
}
+
+ /**
+ * Enables/Disables {@link TvActivity}.
+ */
private void enableTvActivityWithinPackageManager(Context context, boolean enable) {
PackageManager pm = context.getPackageManager();
ComponentName name = new ComponentName(context, TvActivity.class);
diff --git a/src/com/android/tv/recommendation/NotificationService.java b/src/com/android/tv/recommendation/NotificationService.java
index 835a3e53..c00a508e 100644
--- a/src/com/android/tv/recommendation/NotificationService.java
+++ b/src/com/android/tv/recommendation/NotificationService.java
@@ -141,11 +141,16 @@ public class NotificationService extends Service implements Recommender.Listener
getResources().getDimensionPixelOffset(R.dimen.notif_ch_logo_padding_bottom);
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- mTvInputManagerHelper = ((TvApplication) getApplicationContext()).getTvInputManagerHelper();
+ TvApplication application = ((TvApplication) getApplicationContext());
+ mTvInputManagerHelper = application.getTvInputManagerHelper();
mHandlerThread = new HandlerThread("tv notification");
mHandlerThread.start();
mHandler = new NotificationHandler(mHandlerThread.getLooper(), this);
mHandler.sendEmptyMessage(MSG_INITIALIZE_RECOMMENDER);
+
+ // Just called for early initialization.
+ application.getChannelDataManager();
+ application.getProgramDataManager();
}
private void handleInitializeRecommender() {
@@ -309,8 +314,10 @@ public class NotificationService extends Service implements Recommender.Listener
return false;
}
final Channel channel = cr.getChannel();
- if (DEBUG) Log.d(TAG, "sendNotification (" + channel.getDisplayName()
- + " notifyId=" + notificationId + ")");
+ if (DEBUG) {
+ Log.d(TAG, "sendNotification (channelName=" + 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);
@@ -359,13 +366,15 @@ public class NotificationService extends Service implements Recommender.Listener
// This callback will run on the main thread.
Bitmap largeIconBitmap = (channelLogo == null) ? posterArtBitmap
: overlayChannelLogo(channelLogo, posterArtBitmap);
+ String channelDisplayName = channel.getDisplayName();
Notification notification =
new Notification.Builder(NotificationService.this)
.setContentIntent(notificationIntent)
.setContentTitle(program.getTitle())
- .setContentText(inputDisplayName + " "
- + channel.getDisplayName())
- .setContentInfo(channel.getDisplayName())
+ .setContentText(inputDisplayName + " " +
+ (TextUtils.isEmpty(channelDisplayName)
+ ? channel.getDisplayNumber() : channelDisplayName))
+ .setContentInfo(channelDisplayName)
.setAutoCancel(true)
.setLargeIcon(largeIconBitmap)
.setSmallIcon(R.drawable.ic_launcher_s)
@@ -375,8 +384,8 @@ public class NotificationService extends Service implements Recommender.Listener
false)
.setSortKey(mRecommender.getChannelSortKey(channelId))
.build();
- notification.color =
- getResources().getColor(R.color.recommendation_card_background);
+ notification.color = Utils.getColor(getResources(),
+ R.color.recommendation_card_background);
if (!TextUtils.isEmpty(program.getThumbnailUri())) {
notification.extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI,
program.getThumbnailUri());
diff --git a/src/com/android/tv/recommendation/RecentChannelEvaluator.java b/src/com/android/tv/recommendation/RecentChannelEvaluator.java
index c3482af9..e724f4ce 100644
--- a/src/com/android/tv/recommendation/RecentChannelEvaluator.java
+++ b/src/com/android/tv/recommendation/RecentChannelEvaluator.java
@@ -61,4 +61,4 @@ public class RecentChannelEvaluator extends Recommender.Evaluator {
}
return (maxScore > 0.0) ? maxScore : NOT_RECOMMENDED;
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/recommendation/RecommendationDataManager.java b/src/com/android/tv/recommendation/RecommendationDataManager.java
index 0f59e2bd..693380df 100644
--- a/src/com/android/tv/recommendation/RecommendationDataManager.java
+++ b/src/com/android/tv/recommendation/RecommendationDataManager.java
@@ -31,11 +31,14 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import com.android.tv.common.WeakHandler;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
+import com.android.tv.data.WatchedHistoryManager;
+import com.android.tv.util.PermissionUtils;
import java.util.ArrayList;
import java.util.Collection;
@@ -46,7 +49,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-public class RecommendationDataManager {
+public class RecommendationDataManager implements WatchedHistoryManager.Listener {
private static final String TAG = "RecommendationDataManager";
private static final UriMatcher sUriMatcher;
@@ -93,8 +96,9 @@ public class RecommendationDataManager {
private final Set<String> mInputs = new HashSet<>();
private final HandlerThread mHandlerThread;
-
private final Handler mHandler;
+ @Nullable
+ private WatchedHistoryManager mWatchedHistoryManager;
private final List<ListenerRecord> mListeners = new ArrayList<>();
@@ -257,12 +261,19 @@ public class RecommendationDataManager {
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();
+ if (!PermissionUtils.hasAccessWatchedHistory(mContext)) {
+ mWatchedHistoryManager = new WatchedHistoryManager(mContext);
+ mWatchedHistoryManager.setListener(this);
+ mWatchedHistoryManager.start();
+ } else {
+ mContext.getContentResolver().registerContentObserver(
+ TvContract.WatchedPrograms.CONTENT_URI, true, mContentObserver);
+ mHandler.obtainMessage(MSG_UPDATE_WATCH_HISTORY,
+ TvContract.WatchedPrograms.CONTENT_URI)
+ .sendToTarget();
+ }
mTvInputManager = (TvInputManager) mContext.getSystemService(Context.TV_INPUT_SERVICE);
mTvInputManager.registerCallback(mInternalCallback, mHandler);
for (TvInputInfo input : mTvInputManager.getTvInputList()) {
@@ -374,6 +385,41 @@ public class RecommendationDataManager {
}
}
+ private WatchedProgram convertFromWatchedHistoryManagerRecords(
+ WatchedHistoryManager.WatchedRecord watchedRecord) {
+ long endTime = watchedRecord.watchedStartTime + watchedRecord.duration;
+ Program program = new Program.Builder()
+ .setChannelId(watchedRecord.channelId)
+ .setTitle("")
+ .setStartTimeUtcMillis(watchedRecord.watchedStartTime)
+ .setEndTimeUtcMillis(endTime)
+ .build();
+ return new WatchedProgram(program, watchedRecord.watchedStartTime, endTime);
+ }
+
+ @Override
+ public void onLoadFinished() {
+ for (WatchedHistoryManager.WatchedRecord record
+ : mWatchedHistoryManager.getWatchedHistory()) {
+ updateChannelRecordFromWatchedProgram(
+ convertFromWatchedHistoryManagerRecords(record));
+ }
+ mHandler.sendEmptyMessage(MSG_NOTIFY_CHANNEL_RECORD_MAP_LOADED);
+ }
+
+ @Override
+ public void onNewRecordAdded(WatchedHistoryManager.WatchedRecord watchedRecord) {
+ ChannelRecord channelRecord = updateChannelRecordFromWatchedProgram(
+ convertFromWatchedHistoryManagerRecords(watchedRecord));
+ if (mChannelRecordMapLoaded && channelRecord != null) {
+ synchronized (sListenerLock) {
+ for (ListenerRecord l : mListeners) {
+ l.postNewWatchLog(channelRecord);
+ }
+ }
+ }
+ }
+
private WatchedProgram createWatchedProgramFromWatchedProgramCursor(Cursor cursor) {
// Have to initiate the indexes of WatchedProgram Columns.
if (mIndexWatchChannelId == -1) {
diff --git a/src/com/android/tv/search/DataManagerSearch.java b/src/com/android/tv/search/DataManagerSearch.java
new file mode 100644
index 00000000..4c85af67
--- /dev/null
+++ b/src/com/android/tv/search/DataManagerSearch.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Context;
+import android.content.Intent;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvContract;
+import android.media.tv.TvContract.Programs;
+import android.media.tv.TvInputManager;
+import android.support.annotation.UiThread;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.tv.TvApplication;
+import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.Program;
+import com.android.tv.data.ProgramDataManager;
+import com.android.tv.search.LocalSearchProvider.SearchResult;
+import com.android.tv.util.MainThreadExecutor;
+import com.android.tv.util.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * An implementation of {@link SearchInterface} to search query from {@link ChannelDataManager}
+ * and {@link ProgramDataManager}.
+ */
+public class DataManagerSearch implements SearchInterface {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "TvProviderSearch";
+
+ private final Context mContext;
+ private final TvInputManager mTvInputManager;
+ private final ChannelDataManager mChannelDataManager;
+ private final ProgramDataManager mProgramDataManager;
+
+ DataManagerSearch(Context context) {
+ mContext = context;
+ mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
+ TvApplication application = (TvApplication) context.getApplicationContext();
+ mChannelDataManager = application.getChannelDataManager();
+ mProgramDataManager = application.getProgramDataManager();
+ }
+
+ @Override
+ public List<SearchResult> search(final String query, final int limit, final int action) {
+ Future<List<SearchResult>> future = MainThreadExecutor.getInstance()
+ .submit(new Callable<List<SearchResult>>() {
+ @Override
+ public List<SearchResult> call() throws Exception {
+ return searchFromDataManagers(query, limit, action);
+ }
+ });
+
+ try {
+ return future.get();
+ } catch (InterruptedException e) {
+ Thread.interrupted();
+ return Collections.EMPTY_LIST;
+ } catch (ExecutionException e) {
+ Log.w(TAG, "Error searching for " + query, e);
+ return Collections.EMPTY_LIST;
+ }
+ }
+
+ @UiThread
+ private List<SearchResult> searchFromDataManagers(String query, int limit, int action) {
+ List<SearchResult> results = new ArrayList<>();
+ if (!mChannelDataManager.isDbLoadFinished()) {
+ return results;
+ }
+ if (action == ACTION_TYPE_SWITCH_CHANNEL
+ || action == ACTION_TYPE_SWITCH_INPUT) {
+ // Voice search query should be handled by the a system TV app.
+ return results;
+ }
+ Set<Long> channelsFound = new HashSet<>();
+ List<Channel> channelList = mChannelDataManager.getBrowsableChannelList();
+ query = query.toLowerCase();
+ if (TextUtils.isDigitsOnly(query)) {
+ for (Channel channel : channelList) {
+ if (channelsFound.contains(channel.getId())) {
+ continue;
+ }
+ if (contains(channel.getDisplayNumber(), query)) {
+ addResult(results, channelsFound, channel, null);
+ }
+ if (results.size() >= limit) {
+ return results;
+ }
+ }
+ // TODO: recently watched channels may have higher priority.
+ }
+ for (Channel channel : channelList) {
+ if (channelsFound.contains(channel.getId())) {
+ continue;
+ }
+ if (contains(channel.getDisplayName(), query)
+ || contains(channel.getDescription(), query)) {
+ addResult(results, channelsFound, channel, null);
+ }
+ if (results.size() >= limit) {
+ return results;
+ }
+ }
+ for (Channel channel : channelList) {
+ if (channelsFound.contains(channel.getId())) {
+ continue;
+ }
+ Program program = mProgramDataManager.getCurrentProgram(channel.getId());
+ if (program == null) {
+ continue;
+ }
+ if (contains(program.getTitle(), query)
+ && !isRatingBlocked(program.getContentRatings())) {
+ addResult(results, channelsFound, channel, program);
+ }
+ if (results.size() >= limit) {
+ return results;
+ }
+ }
+ for (Channel channel : channelList) {
+ if (channelsFound.contains(channel.getId())) {
+ continue;
+ }
+ Program program = mProgramDataManager.getCurrentProgram(channel.getId());
+ if (program == null) {
+ continue;
+ }
+ if (contains(program.getDescription(), query)
+ && !isRatingBlocked(program.getContentRatings())) {
+ addResult(results, channelsFound, channel, program);
+ }
+ if (results.size() >= limit) {
+ return results;
+ }
+ }
+ return results;
+ }
+
+ // It assumes that query is already lower cases.
+ private boolean contains(String string, String query) {
+ return string != null && string.toLowerCase().contains(query);
+ }
+
+ /**
+ * If query is matched to channel, {@code program} should be null.
+ */
+ private void addResult(List<SearchResult> results, Set<Long> channelsFound, Channel channel,
+ Program program) {
+ if (program == null) {
+ program = mProgramDataManager.getCurrentProgram(channel.getId());
+ if (program != null && isRatingBlocked(program.getContentRatings())) {
+ program = null;
+ }
+ }
+
+ SearchResult result = new SearchResult();
+
+ long channelId = channel.getId();
+ result.channelId = channelId;
+ result.channelNumber = channel.getDisplayNumber();
+ if (program == null) {
+ result.title = channel.getDisplayName();
+ result.description = channel.getDescription();
+ result.imageUri = TvContract.buildChannelLogoUri(channelId).toString();
+ result.intentAction = Intent.ACTION_VIEW;
+ result.intentData = buildIntentData(channelId);
+ result.contentType = Programs.CONTENT_ITEM_TYPE;
+ result.isLive = true;
+ result.progressPercentage = LocalSearchProvider.PROGRESS_PERCENTAGE_HIDE;
+ } else {
+ result.title = program.getTitle();
+ result.description = buildProgramDescription(channel.getDisplayNumber(),
+ channel.getDisplayName(), program.getStartTimeUtcMillis(),
+ program.getEndTimeUtcMillis());
+ result.imageUri = program.getPosterArtUri();
+ result.intentAction = Intent.ACTION_VIEW;
+ result.intentData = buildIntentData(channelId);
+ result.contentType = Programs.CONTENT_ITEM_TYPE;
+ result.isLive = true;
+ result.videoWidth = program.getVideoWidth();
+ result.videoHeight = program.getVideoHeight();
+ result.duration = program.getDurationMillis();
+ result.progressPercentage = getProgressPercentage(
+ program.getStartTimeUtcMillis(), program.getEndTimeUtcMillis());
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Add a result : channel=" + channel + " program=" + program);
+ }
+ results.add(result);
+ channelsFound.add(channel.getId());
+ }
+
+ private String buildProgramDescription(String channelNumber, String channelName,
+ long programStartUtcMillis, long programEndUtcMillis) {
+ return Utils.getDurationString(mContext, programStartUtcMillis, programEndUtcMillis, false)
+ + System.lineSeparator() + channelNumber + " " + channelName;
+ }
+
+ 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 String buildIntentData(long channelId) {
+ return TvContract.buildChannelUri(channelId).buildUpon()
+ .appendQueryParameter(Utils.PARAM_SOURCE, SOURCE_TV_SEARCH)
+ .build().toString();
+ }
+
+ private boolean isRatingBlocked(TvContentRating[] ratings) {
+ if (ratings == null || ratings.length == 0
+ || !mTvInputManager.isParentalControlsEnabled()) {
+ return false;
+ }
+ for (TvContentRating rating : ratings) {
+ try {
+ if (mTvInputManager.isRatingBlocked(rating)) {
+ return true;
+ }
+ } catch (IllegalArgumentException e) {
+ // Do nothing.
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/tv/search/LocalSearchProvider.java b/src/com/android/tv/search/LocalSearchProvider.java
index e9cffa31..3cc21ace 100644
--- a/src/com/android/tv/search/LocalSearchProvider.java
+++ b/src/com/android/tv/search/LocalSearchProvider.java
@@ -25,6 +25,8 @@ import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
+import com.android.tv.util.PermissionUtils;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -60,13 +62,12 @@ public class LocalSearchProvider extends ContentProvider {
private static final String LIVE_CONTENTS = "1";
static final String SUGGEST_PARAMETER_ACTION = "action";
- static final int DEFAULT_SEARCH_ACTION = TvProviderSearch.ACTION_TYPE_AMBIGUOUS;
+ static final int DEFAULT_SEARCH_ACTION = SearchInterface.ACTION_TYPE_AMBIGUOUS;
- private TvProviderSearch mTvProviderSearch;
+ private SearchInterface mSearch;
@Override
public boolean onCreate() {
- mTvProviderSearch = new TvProviderSearch(getContext());
return true;
}
@@ -77,6 +78,11 @@ public class LocalSearchProvider extends ContentProvider {
Log.d(TAG, "query(" + uri + ", " + Arrays.toString(projection) + ", " + selection + ", "
+ Arrays.toString(selectionArgs) + ", " + sortOrder + ")");
}
+ if (PermissionUtils.hasAccessAllEpg(getContext())) {
+ mSearch = new TvProviderSearch(getContext());
+ } else {
+ mSearch = new DataManagerSearch(getContext());
+ }
String query = uri.getLastPathSegment();
int limit = DEFAULT_SEARCH_LIMIT;
int action = DEFAULT_SEARCH_ACTION;
@@ -88,7 +94,7 @@ public class LocalSearchProvider extends ContentProvider {
}
List<SearchResult> results = new ArrayList<>();
if (!TextUtils.isEmpty(query)) {
- results.addAll(mTvProviderSearch.search(query, limit, action));
+ results.addAll(mSearch.search(query, limit, action));
}
return createSuggestionsCursor(results);
}
@@ -165,4 +171,4 @@ public class LocalSearchProvider extends ContentProvider {
", title: " + title;
}
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/search/ProgramGuideSearchFragment.java b/src/com/android/tv/search/ProgramGuideSearchFragment.java
index bb6cdc69..7d6efcb3 100644
--- a/src/com/android/tv/search/ProgramGuideSearchFragment.java
+++ b/src/com/android/tv/search/ProgramGuideSearchFragment.java
@@ -42,6 +42,7 @@ import android.view.ViewGroup;
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.util.ImageLoader;
+import com.android.tv.util.PermissionUtils;
import java.util.List;
@@ -129,7 +130,7 @@ public class ProgramGuideSearchFragment extends SearchFragment {
private final ArrayObjectAdapter mResultAdapter =
new ArrayObjectAdapter(new ListRowPresenter());
private MainActivity mMainActivity;
- private TvProviderSearch mTvProviderSearch;
+ private SearchInterface mSearch;
private int mMainCardWidth;
private int mMainCardHeight;
private SearchTask mSearchTask;
@@ -139,7 +140,11 @@ public class ProgramGuideSearchFragment extends SearchFragment {
super.onCreate(savedInstanceState);
mMainActivity = (MainActivity) getActivity();
- mTvProviderSearch = new TvProviderSearch(mMainActivity);
+ if (PermissionUtils.hasAccessAllEpg(mMainActivity)) {
+ mSearch = new TvProviderSearch(mMainActivity);
+ } else {
+ mSearch = new DataManagerSearch(mMainActivity);
+ }
Resources res = getResources();
mMainCardWidth = res.getDimensionPixelSize(R.dimen.card_image_layout_width);
mMainCardHeight = res.getDimensionPixelSize(R.dimen.card_image_layout_height);
@@ -186,7 +191,7 @@ public class ProgramGuideSearchFragment extends SearchFragment {
@Override
protected List<LocalSearchProvider.SearchResult> doInBackground(Void... params) {
- return mTvProviderSearch.search(mQuery, SEARCH_RESULT_MAX,
+ return mSearch.search(mQuery, SEARCH_RESULT_MAX,
TvProviderSearch.ACTION_TYPE_AMBIGUOUS);
}
diff --git a/src/com/android/tv/search/SearchInterface.java b/src/com/android/tv/search/SearchInterface.java
new file mode 100644
index 00000000..7394150e
--- /dev/null
+++ b/src/com/android/tv/search/SearchInterface.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 com.android.tv.search.LocalSearchProvider.SearchResult;
+
+import java.util.List;
+
+/**
+ * Interface for channel and program search.
+ */
+public interface SearchInterface {
+ String SOURCE_TV_SEARCH = "TvSearch";
+
+ int ACTION_TYPE_AMBIGUOUS = 1;
+ int ACTION_TYPE_SWITCH_CHANNEL = 2;
+ int ACTION_TYPE_SWITCH_INPUT = 3;
+
+ /**
+ * Search channels, inputs, or programs.
+ * This assumes that parental control settings will not be change while searching.
+ *
+ * @param action One of {@link #ACTION_TYPE_SWITCH_CHANNEL}, {@link #ACTION_TYPE_SWITCH_INPUT},
+ * or {@link #ACTION_TYPE_AMBIGUOUS},
+ */
+ public List<SearchResult> search(String query, int limit, int action);
+}
diff --git a/src/com/android/tv/search/TvProviderSearch.java b/src/com/android/tv/search/TvProviderSearch.java
index 00eb68bb..a5ad00ff 100644
--- a/src/com/android/tv/search/TvProviderSearch.java
+++ b/src/com/android/tv/search/TvProviderSearch.java
@@ -33,6 +33,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.tv.search.LocalSearchProvider.SearchResult;
+import com.android.tv.util.PermissionUtils;
import com.android.tv.util.Utils;
import junit.framework.Assert;
@@ -48,18 +49,15 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
-public class TvProviderSearch {
+/**
+ * An implementation of {@link SearchInterface} to search query from TvProvider directly.
+ */
+public class TvProviderSearch implements SearchInterface {
private static final boolean DEBUG = false;
private static final String TAG = "TvProviderSearch";
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;
@@ -77,9 +75,14 @@ public class TvProviderSearch {
* @param action One of {@link #ACTION_TYPE_SWITCH_CHANNEL}, {@link #ACTION_TYPE_SWITCH_INPUT},
* or {@link #ACTION_TYPE_AMBIGUOUS},
*/
+ @Override
@WorkerThread
public List<SearchResult> search(String query, int limit, int action) {
List<SearchResult> results = new ArrayList<>();
+ if (!PermissionUtils.hasAccessAllEpg(mContext)) {
+ // TODO: support this feature for non-system LC app. b/23939816
+ return results;
+ }
Set<Long> channelsFound = new HashSet<>();
if (action == ACTION_TYPE_SWITCH_CHANNEL) {
results.addAll(searchChannels(query, channelsFound, limit));
@@ -464,7 +467,6 @@ public class TvProviderSearch {
return result;
}
-
@WorkerThread
private class ChannelComparatorWithSameDisplayNumber implements Comparator<SearchResult> {
private final Map<Long, Long> mMaxWatchStartTimeMap = new HashMap<>();
diff --git a/src/com/android/tv/ui/ChannelBannerView.java b/src/com/android/tv/ui/ChannelBannerView.java
index 683e6c3a..bf8e69c7 100644
--- a/src/com/android/tv/ui/ChannelBannerView.java
+++ b/src/com/android/tv/ui/ChannelBannerView.java
@@ -200,8 +200,9 @@ public class ChannelBannerView extends FrameLayout implements Channel.LoadImageC
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);
+ mChannelBannerTextColor = Utils.getColor(mResources, R.color.channel_banner_text_color);
+ mChannelBannerDimTextColor = Utils.getColor(mResources,
+ R.color.channel_banner_dim_text_color);
mResizeAnimDuration = mResources.getInteger(R.integer.channel_banner_fast_anim_duration);
mResizeInterpolator = AnimationUtils.loadInterpolator(context,
diff --git a/src/com/android/tv/ui/KeypadChannelSwitchView.java b/src/com/android/tv/ui/KeypadChannelSwitchView.java
index 3ba2738e..722a3759 100644
--- a/src/com/android/tv/ui/KeypadChannelSwitchView.java
+++ b/src/com/android/tv/ui/KeypadChannelSwitchView.java
@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
+import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
@@ -42,8 +43,7 @@ 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 com.android.tv.util.SoftPreconditions;
import java.util.ArrayList;
import java.util.List;
@@ -62,6 +62,7 @@ public class KeypadChannelSwitchView extends LinearLayout implements
private final Tracker mTracker;
private final DurationTimer mViewDurationTimer = new DurationTimer();
private boolean mNavigated = false;
+ @Nullable //Once mChannels is set to null it should not be used again.
private List<Channel> mChannels;
private TextView mChannelNumberView;
private ListView mChannelItemListView;
@@ -157,7 +158,7 @@ public class KeypadChannelSwitchView extends LinearLayout implements
} else {
mSelectedChannel = (Channel) mAdapter.getItem(position);
}
- if(position!=0 && !mNavigated) {
+ if (position != 0 && !mNavigated) {
mNavigated = true;
mTracker.sendChannelInputNavigated();
}
@@ -178,7 +179,7 @@ public class KeypadChannelSwitchView extends LinearLayout implements
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
- Assert.assertNotNull(mChannels);
+ SoftPreconditions.checkNotNull(mChannels, TAG, "mChannels");
if (isChannelNumberKey(keyCode)) {
onNumberKeyUp(keyCode - KeyEvent.KEYCODE_0);
return true;
@@ -227,7 +228,7 @@ public class KeypadChannelSwitchView extends LinearLayout implements
mAdapter.notifyDataSetChanged();
}
- public void setChannels(List<Channel> channels) {
+ public void setChannels(@Nullable List<Channel> channels) {
mChannels = channels;
}
diff --git a/src/com/android/tv/ui/SelectInputView.java b/src/com/android/tv/ui/SelectInputView.java
index e347fbb1..b5b6ef34 100644
--- a/src/com/android/tv/ui/SelectInputView.java
+++ b/src/com/android/tv/ui/SelectInputView.java
@@ -22,6 +22,7 @@ import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
+import android.support.annotation.NonNull;
import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
@@ -33,13 +34,13 @@ 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 com.android.tv.util.Utils;
import java.util.ArrayList;
import java.util.Collections;
@@ -53,36 +54,71 @@ public class SelectInputView extends VerticalGridView implements
private static final String TAG = "SelectInputView";
private static final boolean DEBUG = false;
public static final String SCREEN_NAME = "Input selection";
+ private static final int TUNER_INPUT_POSITION = 0;
- private final MainActivity mMainActivity;
+ private final TvApplication mApplication;
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 TvInputCallback mTvInputCallback = 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(TUNER_INPUT_POSITION);
+ return;
+ }
+ if (getInputPosition(mSelectedInput.getId()) != getSelectedPosition()) {
+ setSelectedPosition(getInputPosition(mSelectedInput.getId()));
+ }
+ }
+ };
+
+ private Channel mCurrentChannel;
+ private OnInputSelectedCallback mCallback;
private final Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
- // Just dismiss the view when no action is required.
- if (mSelectedInput == null
- || TextUtils.equals(mSelectedInput.getId(), mCurrentInputId)
- || (!mSelectedInput.isPassthroughInput() && mCurrentInputId == null)) {
- mMainActivity.getOverlayManager().hideOverlays(
- TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU);
+ if (mSelectedInput == null) {
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();
+ if (mCallback != null) {
+ if (mSelectedInput.isPassthroughInput()) {
+ mCallback.onPassthroughInputSelected(mSelectedInput);
+ } else {
+ mCallback.onTunerInputSelected();
+ }
}
}
};
@@ -97,9 +133,6 @@ public class SelectInputView extends VerticalGridView implements
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) {
@@ -114,59 +147,21 @@ public class SelectInputView extends VerticalGridView implements
super(context, attrs, defStyleAttr);
setAdapter(new InputListAdapter());
- mMainActivity = (MainActivity) context;
+ mApplication = (TvApplication) context.getApplicationContext();
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()));
- }
- }
- });
+ mTvInputManagerHelper = mApplication.getTvInputManagerHelper();
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);
+ mTextColorPrimary = Utils.getColor(resources, R.color.select_input_text_color_primary);
+ mTextColorSecondary = Utils.getColor(resources, R.color.select_input_text_color_secondary);
+ mTextColorDisabled = Utils.getColor(resources, R.color.select_input_text_color_disabled);
mItemViewForMeasure = LayoutInflater.from(context).inflate(
- R.layout.select_input_item, null, false);
+ R.layout.select_input_item, this, false);
buildInputListAndNotify();
}
@@ -204,15 +199,15 @@ public class SelectInputView extends VerticalGridView implements
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);
+ mTvInputManagerHelper.addCallback(mTvInputCallback);
+ String currentInputId = mCurrentChannel != null && mCurrentChannel.isPassthrough() ?
+ mCurrentChannel.getInputId() : null;
+ if (currentInputId != null
+ && !isInputEnabled(mTvInputManagerHelper.getTvInputInfo(currentInputId))) {
+ // If current input is disabled, the tuner input will be focused.
+ setSelectedPosition(TUNER_INPUT_POSITION);
} else {
- setSelectedPosition(getInputPosition(mCurrentInputId));
+ setSelectedPosition(getInputPosition(currentInputId));
}
setFocusable(true);
requestFocus();
@@ -226,12 +221,13 @@ public class SelectInputView extends VerticalGridView implements
}
}
}
- return 0;
+ return TUNER_INPUT_POSITION;
}
@Override
public void onExitAction() {
mTracker.sendHideInputSelection(mViewDurationTimer.reset());
+ mTvInputManagerHelper.removeCallback(mTvInputCallback);
removeCallbacks(mHideRunnable);
}
@@ -302,6 +298,21 @@ public class SelectInputView extends VerticalGridView implements
!= TvInputManager.INPUT_STATE_DISCONNECTED;
}
+ /**
+ * Sets a callback which receives the notifications of input selection.
+ */
+ public void setOnInputSelectedCallback(OnInputSelectedCallback callback) {
+ mCallback = callback;
+ }
+
+ /**
+ * Sets the current channel. The initial selection will be the input which contains the
+ * {@code channel}.
+ */
+ public void setCurrentChannel(Channel channel) {
+ mCurrentChannel = channel;
+ }
+
class InputListAdapter extends RecyclerView.Adapter<InputListAdapter.ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
@@ -438,4 +449,19 @@ public class SelectInputView extends VerticalGridView implements
}
}
}
+
+ /**
+ * A callback interface for the input selection.
+ */
+ public static interface OnInputSelectedCallback {
+ /**
+ * Called when the tuner input is selected.
+ */
+ void onTunerInputSelected();
+
+ /**
+ * Called when the passthrough input is selected.
+ */
+ void onPassthroughInputSelected(@NonNull TvInputInfo input);
+ }
}
diff --git a/src/com/android/tv/ui/SetupView.java b/src/com/android/tv/ui/SetupView.java
index 330b7e9f..cb25f6f9 100644
--- a/src/com/android/tv/ui/SetupView.java
+++ b/src/com/android/tv/ui/SetupView.java
@@ -378,7 +378,7 @@ public class SetupView extends FullscreenDialogView {
final TvInputInfo input = mInputList.get(position);
viewHolder.mTitle.setText(input.loadLabel(getContext()));
int channelCount = mChannelDataManager.getChannelCountForInput(input.getId());
- if (mSetupUtils.hasSetupLaunched(input.getId())) {
+ if (mSetupUtils.isSetupDone(input.getId())) {
if (channelCount == 0) {
viewHolder.mDescription.setText(R.string.setup_input_no_channels);
} else {
diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java
index eba43594..f526c33c 100644
--- a/src/com/android/tv/ui/TunableTvView.java
+++ b/src/com/android/tv/ui/TunableTvView.java
@@ -34,6 +34,7 @@ import android.media.tv.TvView.TvInputCallback;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -43,6 +44,7 @@ import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.TextView;
import com.android.tv.R;
@@ -52,8 +54,10 @@ 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.data.WatchedHistoryManager;
import com.android.tv.parental.ContentRatingsManager;
import com.android.tv.recommendation.NotificationService;
+import com.android.tv.util.PermissionUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
@@ -98,6 +102,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private Channel mCurrentChannel;
private TvInputManagerHelper mInputManagerHelper;
private ContentRatingsManager mContentRatingsManager;
+ @Nullable
+ private WatchedHistoryManager mWatchedHistoryManager;
private boolean mStarted;
private TvInputInfo mInputInfo;
private OnTuneListener mOnTuneListener;
@@ -121,6 +127,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private boolean mIsPip;
private int mScreenHeight;
private int mShrunkenTvViewHeight;
+ private boolean mCanModifyParentalControls;
@TimeShiftState private int mTimeShiftState = TIME_SHIFT_STATE_NONE;
private TimeShiftListener mTimeShiftListener;
@@ -135,7 +142,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private final View mBlockScreenView;
private final View mBlockScreenDescriptionView;
- private final View mBlockScreenIconView;
+ private final ImageView mBlockScreenIconView;
private final View mBlockScreenShrunkenIconView;
private final TextView mBlockScreenTextView;
@@ -144,7 +151,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private final Animator mBlockScreenDescriptionFadeOut;
// A View to hide screen when there's problem in video playback.
- private final View mHideScreenView;
+ private final TextView mHideScreenView;
// A View to block screen until onContentAllowed is received if parental control is on.
private final View mBlockScreenForTuneView;
@@ -164,6 +171,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
public void onConnectionFailed(String inputId) {
Log.w(TAG, "Failed to bind an input");
+ mTracker.sendInputConnectionFailure(inputId);
Channel channel = mCurrentChannel;
mCurrentChannel = null;
mInputInfo = null;
@@ -182,6 +190,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
public void onDisconnected(String inputId) {
Log.w(TAG, "Session is released by crash");
+ mTracker.sendInputDisconnected(inputId);
Channel channel = mCurrentChannel;
mCurrentChannel = null;
mInputInfo = null;
@@ -273,6 +282,14 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
if (mOnTuneListener != null) {
mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
}
+ switch (reason) {
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
+ mTracker.sendChannelVideoUnavailable(mCurrentChannel, reason);
+ default:
+ // do nothing
+ }
}
@Override
@@ -314,12 +331,18 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
super(context, attrs, defStyleAttr, defStyleRes);
inflate(getContext(), R.layout.tunable_tv_view, this);
- mTracker = ((TvApplication) context.getApplicationContext()).getTracker();
+ TvApplication tvApplication = (TvApplication) context.getApplicationContext();
+ mCanModifyParentalControls = PermissionUtils.hasModifyParentalControls(context);
+ mTracker = tvApplication.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);
+ mBlockScreenIconView = (ImageView) mBlockScreenView.findViewById(R.id.block_screen_icon);
+ if (!mCanModifyParentalControls) {
+ mBlockScreenIconView.setImageResource(R.drawable.ic_message_lock_no_permission);
+ mBlockScreenIconView.setScaleType(ImageView.ScaleType.CENTER);
+ }
mBlockScreenShrunkenIconView = mBlockScreenView.findViewById(
R.id.block_screen_shrunken_icon);
mBlockScreenTextView = (TextView) mBlockScreenView.findViewById(R.id.block_screen_text);
@@ -355,7 +378,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
});
- mHideScreenView = findViewById(R.id.hide_screen);
+ mHideScreenView = (TextView) findViewById(R.id.hide_screen);
mBufferingSpinnerView = findViewById(R.id.buffering_spinner);
mBlockScreenForTuneView = findViewById(R.id.block_screen_for_tune);
mDimScreenView = findViewById(R.id.dim);
@@ -401,7 +424,12 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
mStarted = false;
if (mCurrentChannel != null) {
- mTracker.sendChannelViewStop(mCurrentChannel, mChannelViewTimer.reset());
+ long duration = mChannelViewTimer.reset();
+ mTracker.sendChannelViewStop(mCurrentChannel, duration);
+ if (mWatchedHistoryManager != null && !mCurrentChannel.isPassthrough()) {
+ mWatchedHistoryManager.logChannelViewStop(mCurrentChannel,
+ System.currentTimeMillis(), duration);
+ }
}
reset();
}
@@ -420,6 +448,10 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mTvView.setMain();
}
+ public void setWatchedHistoryManager(WatchedHistoryManager watchedHistoryManager) {
+ mWatchedHistoryManager = watchedHistoryManager;
+ }
+
public boolean isPlaying() {
return mStarted;
}
@@ -451,14 +483,17 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
return false;
}
if (mCurrentChannel != null) {
- mTracker.sendChannelViewStop(mCurrentChannel, mChannelViewTimer.reset());
+ long duration = mChannelViewTimer.reset();
+ mTracker.sendChannelViewStop(mCurrentChannel, duration);
+ if (mWatchedHistoryManager != null && !mCurrentChannel.isPassthrough()) {
+ mWatchedHistoryManager.logChannelViewStop(mCurrentChannel,
+ System.currentTimeMillis(), duration);
+ }
}
mOnTuneListener = listener;
mCurrentChannel = channel;
boolean tunedByRecommendation = params != null
&& params.getString(NotificationService.TUNE_PARAMS_RECOMMENDATION_TYPE) != null;
- mTracker.sendChannelViewStart(mCurrentChannel, tunedByRecommendation);
- mChannelViewTimer.start();
boolean needSurfaceSizeUpdate = false;
if (!inputInfo.equals(mInputInfo)) {
mInputInfo = inputInfo;
@@ -471,6 +506,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
needSurfaceSizeUpdate = true;
}
+ mTracker.sendChannelViewStart(mCurrentChannel, tunedByRecommendation);
+ mChannelViewTimer.start();
mVideoWidth = 0;
mVideoHeight = 0;
mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
@@ -840,7 +877,11 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mBlockScreenTextView.setText("");
break;
case BLOCK_SCREEN_TYPE_NORMAL:
- mBlockScreenTextView.setText(R.string.tvview_channel_locked);
+ if (mCanModifyParentalControls) {
+ mBlockScreenTextView.setText(R.string.tvview_channel_locked);
+ } else {
+ mBlockScreenTextView.setText(R.string.tvview_channel_locked_no_permission);
+ }
break;
}
} else if (mBlockedContentRating != null) {
@@ -859,10 +900,20 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
break;
case BLOCK_SCREEN_TYPE_NORMAL:
if (TextUtils.isEmpty(name)) {
- mBlockScreenTextView.setText(R.string.tvview_content_locked);
+ if (mCanModifyParentalControls) {
+ mBlockScreenTextView.setText(R.string.tvview_content_locked);
+ } else {
+ mBlockScreenTextView.setText(
+ R.string.tvview_content_locked_no_permission);
+ }
} else {
- mBlockScreenTextView.setText(getContext().getString(
- R.string.tvview_content_locked_format, name));
+ if (mCanModifyParentalControls) {
+ mBlockScreenTextView.setText(getContext().getString(
+ R.string.tvview_content_locked_format, name));
+ } else {
+ mBlockScreenTextView.setText(getContext().getString(
+ R.string.tvview_content_locked_format_no_permission, name));
+ }
}
break;
}
@@ -903,8 +954,15 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private void hideScreenByVideoAvailability(int reason) {
switch (reason) {
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY:
+ mHideScreenView.setVisibility(VISIBLE);
+ mHideScreenView.setText(R.string.tvview_msg_audio_only);
+ mBufferingSpinnerView.setVisibility(GONE);
+ unmuteIfPossible();
+ break;
case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
mBufferingSpinnerView.setVisibility(VISIBLE);
+ mute();
break;
case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING:
@@ -912,12 +970,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
case VIDEO_UNAVAILABLE_REASON_NOT_TUNED:
default:
mHideScreenView.setVisibility(VISIBLE);
+ mHideScreenView.setText(null);
mBufferingSpinnerView.setVisibility(GONE);
+ mute();
break;
}
mVideoAvailable = false;
mVideoUnavailableReason = reason;
- mute();
}
private void unhideScreenByVideoAvailability() {
diff --git a/src/com/android/tv/ui/TvTransitionManager.java b/src/com/android/tv/ui/TvTransitionManager.java
index 7096893f..444b5c0c 100644
--- a/src/com/android/tv/ui/TvTransitionManager.java
+++ b/src/com/android/tv/ui/TvTransitionManager.java
@@ -123,6 +123,7 @@ public class TvTransitionManager extends TransitionManager {
initIfNeeded();
if (mCurrentScene != mSelectInputScene) {
transitionTo(mSelectInputScene);
+ mSelectInputView.setCurrentChannel(mMainActivity.getCurrentChannel());
}
}
diff --git a/src/com/android/tv/ui/TvViewUiManager.java b/src/com/android/tv/ui/TvViewUiManager.java
index f93cf45c..d767906b 100644
--- a/src/com/android/tv/ui/TvViewUiManager.java
+++ b/src/com/android/tv/ui/TvViewUiManager.java
@@ -47,6 +47,7 @@ import com.android.tv.R;
import com.android.tv.TvOptionsManager;
import com.android.tv.data.DisplayMode;
import com.android.tv.util.TvSettings;
+import com.android.tv.util.Utils;
/**
* The TvViewUiManager is responsible for handling UI layouting and animation of main and PIP
@@ -778,8 +779,9 @@ public class TvViewUiManager {
// 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);
+ setBackgroundColor(Utils.getColor(mResources, isTvViewFullScreen()
+ ? R.color.tvactivity_background : R.color.tvactivity_background_on_shrunken_tvview),
+ layoutParams, animate);
setTvViewPosition(layoutParams, tvViewFrame, animate);
// Update the current display mode.
diff --git a/src/com/android/tv/ui/sidepanel/AboutFragment.java b/src/com/android/tv/ui/sidepanel/AboutFragment.java
index 5b7444b2..e880fe37 100644
--- a/src/com/android/tv/ui/sidepanel/AboutFragment.java
+++ b/src/com/android/tv/ui/sidepanel/AboutFragment.java
@@ -16,12 +16,17 @@
package com.android.tv.ui.sidepanel;
+import android.app.Activity;
+import android.content.Context;
import android.view.View;
+import android.widget.CompoundButton;
import android.widget.TextView;
+import com.android.tv.Features;
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TvApplication;
+import com.android.tv.analytics.OptOutPreferenceHelper;
import com.android.tv.dialog.WebDialogFragment;
import com.android.tv.license.LicenseUtils;
@@ -29,16 +34,15 @@ import java.util.ArrayList;
import java.util.List;
/**
- * Shows version and optional license information.
+ * Shows version, optional license information and Analytics OptOut.
*/
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 {
+ private static final class VersionItem extends Item {
@Override
protected int getResourceId() {
return R.layout.option_item_simple;
@@ -61,7 +65,7 @@ public class AboutFragment extends SideFragment {
/**
* Opens a dialog showing open source licenses.
*/
- public static class LicenseActionItem extends ActionItem {
+ public static final class LicenseActionItem extends ActionItem {
public final static String DIALOG_TAG = LicenseActionItem.class.getSimpleName();
public static final String TRACKER_LABEL = "Open Source Licenses";
private final MainActivity mMainActivity;
@@ -79,6 +83,70 @@ public class AboutFragment extends SideFragment {
}
}
+ /**
+ * Sets the users preference for allowing analytics.
+ */
+ private static final class AllowAnalyticsItem extends SwitchItem {
+ //TODO: change this to use SwitchPreference
+ private final OptOutPreferenceHelper mPreferenceHelper;
+ private TextView mDescriptionView;
+ private int mOriginalMaxDescriptionLine;
+ private MainActivity mMainActivity;
+ private View mBoundView;
+
+ public AllowAnalyticsItem(Context context) {
+ super(context.getResources().getString(R.string.about_menu_improve),
+ context.getResources().getString(R.string.about_menu_improve),
+ context.getResources().getString(R.string.about_menu_improve_summary));
+ mPreferenceHelper = ((TvApplication) context.getApplicationContext())
+ .getOptPreferenceHelper();
+ }
+
+ @Override
+ protected void onBind(View view) {
+ super.onBind(view);
+ mDescriptionView = (TextView) view.findViewById(getDescriptionViewId());
+ mOriginalMaxDescriptionLine = mDescriptionView.getMaxLines();
+ mDescriptionView.setMaxLines(Integer.MAX_VALUE);
+ mMainActivity = (MainActivity) view.getContext();
+ mBoundView = view;
+ }
+
+ @Override
+ protected void onUnbind() {
+ super.onUnbind();
+ mDescriptionView.setMaxLines(mOriginalMaxDescriptionLine);
+ mDescriptionView = null;
+ mMainActivity = null;
+ mBoundView = null;
+ }
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ setChecked(!mPreferenceHelper
+ .getOptOutPreference(OptOutPreferenceHelper.ANALYTICS_OPT_OUT_DEFAULT_VALUE));
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ mPreferenceHelper.setOptOutPreference(!isChecked());
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ super.setChecked(checked);
+ if (mMainActivity != null && mBoundView != null && mBoundView.hasFocus()) {
+ // Quick fix for accessibility
+ // TODO: Need to change the resource in the future.
+ mMainActivity.sendAccessiblityText(checked ?
+ mMainActivity.getString(R.string.options_item_pip_on)
+ : mMainActivity.getString(R.string.options_item_pip_off));
+ }
+ }
+ }
+
@Override
protected String getTitle() {
return getResources().getString(R.string.side_panel_title_about);
@@ -93,8 +161,12 @@ public class AboutFragment extends SideFragment {
protected List<Item> getItemList() {
List<Item> items = new ArrayList<>();
items.add(new VersionItem());
- if (LicenseUtils.hasLicenses(getActivity().getAssets())) {
- items.add(new LicenseActionItem((MainActivity) getActivity()));
+ Activity activity = getActivity();
+ if (LicenseUtils.hasLicenses(activity.getAssets())) {
+ items.add(new LicenseActionItem((MainActivity) activity));
+ }
+ if (Features.ANALYTICS_OPT_OUT.isEnabled(activity)) {
+ items.add(new AllowAnalyticsItem(activity));
}
return items;
}
diff --git a/src/com/android/tv/ui/sidepanel/ActionItem.java b/src/com/android/tv/ui/sidepanel/ActionItem.java
index c75eff9b..23aff91c 100644
--- a/src/com/android/tv/ui/sidepanel/ActionItem.java
+++ b/src/com/android/tv/ui/sidepanel/ActionItem.java
@@ -70,4 +70,4 @@ public abstract class ActionItem extends Item {
iconView.setVisibility(View.GONE);
}
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/ui/sidepanel/ChannelSourcesFragment.java b/src/com/android/tv/ui/sidepanel/ChannelSourcesFragment.java
index a95b8149..7289034f 100644
--- a/src/com/android/tv/ui/sidepanel/ChannelSourcesFragment.java
+++ b/src/com/android/tv/ui/sidepanel/ChannelSourcesFragment.java
@@ -101,4 +101,4 @@ public class ChannelSourcesFragment extends SideFragment {
.show();
}
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/ui/sidepanel/CheckBoxItem.java b/src/com/android/tv/ui/sidepanel/CheckBoxItem.java
index 205f1bc8..79c2b0a7 100644
--- a/src/com/android/tv/ui/sidepanel/CheckBoxItem.java
+++ b/src/com/android/tv/ui/sidepanel/CheckBoxItem.java
@@ -77,4 +77,4 @@ public class CheckBoxItem extends CompoundButtonItem {
protected void onSelected() {
setChecked(!isChecked());
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/ui/sidepanel/Item.java b/src/com/android/tv/ui/sidepanel/Item.java
index da9b39b0..4ae6e523 100644
--- a/src/com/android/tv/ui/sidepanel/Item.java
+++ b/src/com/android/tv/ui/sidepanel/Item.java
@@ -86,4 +86,4 @@ public abstract class Item {
}
}
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/ui/sidepanel/RadioButtonItem.java b/src/com/android/tv/ui/sidepanel/RadioButtonItem.java
index b6c36795..e0477493 100644
--- a/src/com/android/tv/ui/sidepanel/RadioButtonItem.java
+++ b/src/com/android/tv/ui/sidepanel/RadioButtonItem.java
@@ -41,4 +41,4 @@ public class RadioButtonItem extends CompoundButtonItem {
protected void onSelected() {
setChecked(true);
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/ui/sidepanel/SwitchItem.java b/src/com/android/tv/ui/sidepanel/SwitchItem.java
index c04a72ac..ef9966a5 100644
--- a/src/com/android/tv/ui/sidepanel/SwitchItem.java
+++ b/src/com/android/tv/ui/sidepanel/SwitchItem.java
@@ -20,11 +20,15 @@ import com.android.tv.R;
public class SwitchItem extends CompoundButtonItem {
public SwitchItem(String title) {
- super(title, null);
+ this(title, null, null);
}
public SwitchItem(String checkedTitle, String uncheckedTitle) {
- super(checkedTitle, uncheckedTitle, null);
+ this(checkedTitle, uncheckedTitle, null);
+ }
+
+ public SwitchItem(String checkedTitle, String uncheckedTitle, String description) {
+ super(checkedTitle, uncheckedTitle, description);
}
@Override
@@ -41,4 +45,4 @@ public class SwitchItem extends CompoundButtonItem {
protected void onSelected() {
setChecked(!isChecked());
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java
index c878900a..1df7fe59 100644
--- a/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ProgramRestrictionsFragment.java
@@ -82,4 +82,4 @@ public class ProgramRestrictionsFragment extends SideFragment {
items.add(ratingsItem);
return items;
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/util/AsyncDbTask.java b/src/com/android/tv/util/AsyncDbTask.java
index 82d42377..fbc93f3a 100644
--- a/src/com/android/tv/util/AsyncDbTask.java
+++ b/src/com/android/tv/util/AsyncDbTask.java
@@ -162,6 +162,9 @@ public abstract class AsyncDbTask<Params, Progress, Result>
}
return null;
}
+ } catch (SecurityException e) {
+ Log.d(TAG, "Security exception during query", e);
+ return null;
}
}
diff --git a/src/com/android/tv/util/Clock.java b/src/com/android/tv/util/Clock.java
index 58653068..f6c3782e 100644
--- a/src/com/android/tv/util/Clock.java
+++ b/src/com/android/tv/util/Clock.java
@@ -31,4 +31,4 @@ public interface Clock {
return System.currentTimeMillis();
}
};
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/util/CollectionUtils.java b/src/com/android/tv/util/CollectionUtils.java
new file mode 100644
index 00000000..e07bac90
--- /dev/null
+++ b/src/com/android/tv/util/CollectionUtils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.util;
+
+import android.os.Build;
+import android.util.ArraySet;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Static utilities for collections
+ */
+public class CollectionUtils {
+ /**
+ * Returns a new Set suitable for small data sets.
+ *
+ * <p>In M and above this is a ArraySet otherwise it is a HashSet
+ */
+ public static <T> Set<T> createSmallSet() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return new ArraySet<T>();
+ } else {
+ return new HashSet<T>();
+ }
+ }
+}
diff --git a/src/com/android/tv/receiver/NotificationReceiver.java b/src/com/android/tv/util/EngOnlyFeature.java
index 0bcb44c4..904e2369 100644
--- a/src/com/android/tv/receiver/NotificationReceiver.java
+++ b/src/com/android/tv/util/EngOnlyFeature.java
@@ -11,22 +11,22 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
*/
-package com.android.tv.receiver;
+package com.android.tv.util;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import com.android.tv.recommendation.NotificationService;
+import com.android.tv.BuildConfig;
+import com.android.tv.common.feature.Feature;
-public class NotificationReceiver extends BroadcastReceiver {
+/**
+ * A feature that is only available on {@link BuildConfig#ENG} builds.
+ */
+public final class EngOnlyFeature implements Feature {
@Override
- public void onReceive(Context context, Intent intent) {
- Intent notificationIntent = new Intent(context, NotificationService.class);
- notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION);
- context.startService(notificationIntent);
+ public boolean isEnabled(Context context) {
+ return BuildConfig.ENG;
}
}
diff --git a/src/com/android/tv/util/ImageCache.java b/src/com/android/tv/util/ImageCache.java
index 67a63a59..e849da89 100644
--- a/src/com/android/tv/util/ImageCache.java
+++ b/src/com/android/tv/util/ImageCache.java
@@ -20,13 +20,12 @@ 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 {
+public class ImageCache implements MemoryManageable {
private static final float MAX_CACHE_SIZE_PERCENT = 0.8f;
private static final float MIN_CACHE_SIZE_PERCENT = 0.05f;
private static final float DEFAULT_CACHE_SIZE_PERCENT = 0.1f;
diff --git a/src/com/android/tv/util/MainThreadExecutor.java b/src/com/android/tv/util/MainThreadExecutor.java
new file mode 100644
index 00000000..817286f7
--- /dev/null
+++ b/src/com/android/tv/util/MainThreadExecutor.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.util;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.List;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An executor service that executes its tasks on the main thread.
+ *
+ * Shutting down this executor is not supported.
+ */
+public class MainThreadExecutor extends AbstractExecutorService {
+
+ private final static MainThreadExecutor INSTANCE = new MainThreadExecutor();
+
+ public final static MainThreadExecutor getInstance() {
+ return INSTANCE;
+ }
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @Override
+ public void execute(Runnable runnable) {
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ runnable.run();
+ } else {
+ mHandler.post(runnable);
+ }
+ }
+
+ /**
+ * Not supported and throws an exception when used.
+ */
+ @Override
+ @Deprecated
+ public void shutdown() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not supported and throws an exception when used.
+ */
+ @Override
+ @Deprecated
+ public List<Runnable> shutdownNow() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return false;
+ }
+
+ /**
+ * Not supported and throws an exception when used.
+ */
+ @Override
+ @Deprecated
+ public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+} \ No newline at end of file
diff --git a/src/com/android/tv/util/MemoryManageable.java b/src/com/android/tv/util/MemoryManageable.java
new file mode 100644
index 00000000..c5e5d869
--- /dev/null
+++ b/src/com/android/tv/util/MemoryManageable.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+
+/**
+ * Interface for the fine-grained memory management.
+ * The class which wants to release memory based on the system constraints should inherit
+ * this interface and implement {@link #performTrimMemory}.
+ */
+public interface MemoryManageable {
+ /**
+ * For more information, see {@link android.content.ComponentCallbacks2#onTrimMemory}.
+ */
+ void performTrimMemory(int level);
+}
diff --git a/src/com/android/tv/util/MultiLongSparseArray.java b/src/com/android/tv/util/MultiLongSparseArray.java
new file mode 100644
index 00000000..4c067892
--- /dev/null
+++ b/src/com/android/tv/util/MultiLongSparseArray.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.LongSparseArray;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Uses a {@link LongSparseArray} to hold sets of {@code T}.
+ *
+ * <p>This has the same memory and performance trade offs listed in {@link LongSparseArray}.
+ */
+public class MultiLongSparseArray<T> {
+ @VisibleForTesting
+ static final int DEFAULT_MAX_EMPTIES_KEPT = 4;
+ private final LongSparseArray<Set<T>> mSparseArray;
+ private final Set<T>[] mEmptySets;
+ private int mEmptyIndex = -1;
+
+ public MultiLongSparseArray() {
+ mSparseArray = new LongSparseArray<>();
+ mEmptySets = new Set[DEFAULT_MAX_EMPTIES_KEPT];
+ }
+
+ public MultiLongSparseArray(int initialCapacity, int emptyCacheSize) {
+ mSparseArray = new LongSparseArray<>(initialCapacity);
+ mEmptySets = new Set[emptyCacheSize];
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * replacing the previous mapping from the specified key if there
+ * was one.
+ */
+ public void put(long key, T value) {
+ Set<T> values = mSparseArray.get(key);
+ if (values == null) {
+ values = getEmptySet();
+ mSparseArray.put(key, values);
+ }
+ values.add(value);
+ }
+
+ /**
+ * Removes the value at the specified index.
+ */
+ public void remove(long key, T value) {
+ Set<T> values = mSparseArray.get(key);
+ if (values != null) {
+ values.remove(value);
+ if (values.isEmpty()) {
+ mSparseArray.remove(key);
+ cacheEmptySet(values);
+ }
+ }
+ }
+
+ /**
+ * Gets the set of Objects mapped from the specified key, or an empty set
+ * if no such mapping has been made.
+ */
+ public Iterable<T> get(long key) {
+ Set<T> values = mSparseArray.get(key);
+ return values == null ? Collections.EMPTY_SET : values;
+ }
+
+ /**
+ * Clears cached empty sets.
+ */
+ public void clearEmptyCache() {
+ while (mEmptyIndex >= 0) {
+ mEmptySets[mEmptyIndex--] = null;
+ }
+ }
+
+ @VisibleForTesting
+ int getEmptyCacheSize() {
+ return mEmptyIndex + 1;
+ }
+
+ private void cacheEmptySet(Set<T> emptySet) {
+ if (mEmptyIndex < DEFAULT_MAX_EMPTIES_KEPT - 1) {
+ mEmptySets[++mEmptyIndex] = emptySet;
+ }
+ }
+
+ private Set<T> getEmptySet() {
+ if (mEmptyIndex < 0) {
+ return CollectionUtils.createSmallSet();
+ }
+ Set<T> emptySet = mEmptySets[mEmptyIndex];
+ mEmptySets[mEmptyIndex--] = null;
+ return emptySet;
+ }
+
+ @Override
+ public String toString() {
+ return mSparseArray.toString() + "(emptyCacheSize=" + getEmptyCacheSize() + ")";
+ }
+}
diff --git a/src/com/android/tv/util/OnboardingUtils.java b/src/com/android/tv/util/OnboardingUtils.java
new file mode 100644
index 00000000..c693185e
--- /dev/null
+++ b/src/com/android/tv/util/OnboardingUtils.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.util;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.media.tv.TvContract.Channels;
+import android.preference.PreferenceManager;
+import android.support.annotation.UiThread;
+
+import com.android.tv.TvApplication;
+import com.android.tv.data.ChannelDataManager;
+
+/**
+ * A utility class related to onboarding experience.
+ */
+public final class OnboardingUtils {
+ private static final String PREF_KEY_IS_FIRST_BOOT = "pref_onbaording_is_first_boot";
+ private static final String PREF_KEY_IS_FIRST_RUN = "pref_onbaording_is_first_run";
+ private static final String PREF_KEY_ARE_CHANNELS_AVAILABLE =
+ "pref_onbaording_are_channels_available";
+
+ /**
+ * Checks if this is the first boot after the onboarding experience has been applied.
+ */
+ public static boolean isFirstBoot(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(PREF_KEY_IS_FIRST_BOOT, true);
+ }
+
+ /**
+ * Marks that the first boot has been completed.
+ */
+ public static void setFirstBootCompleted(Context context) {
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putBoolean(PREF_KEY_IS_FIRST_BOOT, false)
+ .apply();
+ }
+
+ /**
+ * Checks if this is the first run of {@link com.android.tv.MainActivity} after the
+ * onboarding experience has been applied.
+ */
+ public static boolean isFirstRun(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(PREF_KEY_IS_FIRST_RUN, true);
+ }
+
+ /**
+ * Marks that the first run of {@link com.android.tv.MainActivity} has been completed.
+ */
+ public static void setFirstRunCompleted(Context context) {
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putBoolean(PREF_KEY_IS_FIRST_RUN, false)
+ .apply();
+ }
+
+ /**
+ * Checks whether the onboarding screen should be shown or not.
+ */
+ public static boolean needToShowOnboarding(Context context) {
+ return isFirstRun(context) || !areChannelsAvailable(context);
+ }
+
+ /**
+ * Checks if there are any available tuner channels.
+ */
+ @UiThread
+ public static boolean areChannelsAvailable(Context context) {
+ ChannelDataManager manager = ((TvApplication) context.getApplicationContext())
+ .getChannelDataManager();
+ if (manager.isDbLoadFinished()) {
+ return manager.getChannelCount() != 0;
+ }
+ // This method should block the UI thread.
+ ContentResolver resolver = context.getContentResolver();
+ try (Cursor c = resolver.query(Channels.CONTENT_URI, new String[] {Channels._ID}, null,
+ null, null)) {
+ return c.getCount() != 0;
+ }
+ }
+}
diff --git a/src/com/android/tv/util/PermissionUtils.java b/src/com/android/tv/util/PermissionUtils.java
new file mode 100644
index 00000000..f39dba81
--- /dev/null
+++ b/src/com/android/tv/util/PermissionUtils.java
@@ -0,0 +1,70 @@
+package com.android.tv.util;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+
+/**
+ * Util class to handle permissions.
+ */
+public class PermissionUtils {
+ private static Boolean sHasAccessAllEpgPermission;
+ private static Boolean sHasAccessWatchedHistoryPermission;
+ private static Boolean sHasModifyParentalControlsPermission;
+
+ public static boolean hasAccessAllEpg(Context context) {
+ if (sHasAccessAllEpgPermission == null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ sHasAccessAllEpgPermission = context.checkSelfPermission(
+ "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA")
+ == PackageManager.PERMISSION_GRANTED;
+ } else {
+ sHasAccessAllEpgPermission = context.getPackageManager().checkPermission(
+ "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA",
+ context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ return sHasAccessAllEpgPermission;
+ }
+
+ public static boolean hasAccessWatchedHistory(Context context) {
+ if (sHasAccessWatchedHistoryPermission == null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ sHasAccessWatchedHistoryPermission = context.checkSelfPermission(
+ "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS")
+ == PackageManager.PERMISSION_GRANTED;
+ } else {
+ sHasAccessWatchedHistoryPermission = context.getPackageManager().checkPermission(
+ "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS",
+ context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ return sHasAccessWatchedHistoryPermission;
+ }
+
+ public static boolean hasModifyParentalControls(Context context) {
+ if (sHasModifyParentalControlsPermission == null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ sHasModifyParentalControlsPermission = context.checkSelfPermission(
+ "android.permission.MODIFY_PARENTAL_CONTROLS")
+ == PackageManager.PERMISSION_GRANTED;
+ } else {
+ sHasModifyParentalControlsPermission = context.getPackageManager().checkPermission(
+ "android.permission.MODIFY_PARENTAL_CONTROLS",
+ context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ return sHasModifyParentalControlsPermission;
+ }
+
+ public static boolean hasReadTvListings(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return context.checkSelfPermission("android.permission.READ_TV_LISTINGS")
+ == PackageManager.PERMISSION_GRANTED;
+ } else {
+ return context.getPackageManager().checkPermission(
+ "android.permission.MODIFY_PARENTAL_CONTROLS",
+ context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
+ }
+ }
+}
diff --git a/src/com/android/tv/util/PipInputManager.java b/src/com/android/tv/util/PipInputManager.java
index d5817907..3e4db654 100644
--- a/src/com/android/tv/util/PipInputManager.java
+++ b/src/com/android/tv/util/PipInputManager.java
@@ -30,7 +30,6 @@ 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;
@@ -50,7 +49,7 @@ public class PipInputManager {
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 Set<Listener> mListeners = CollectionUtils.createSmallSet();
private final TvInputCallback mTvInputCallback = new TvInputCallback() {
@Override
diff --git a/src/com/android/tv/util/RecurringRunner.java b/src/com/android/tv/util/RecurringRunner.java
index f51918e9..dcede666 100644
--- a/src/com/android/tv/util/RecurringRunner.java
+++ b/src/com/android/tv/util/RecurringRunner.java
@@ -52,8 +52,8 @@ public final class RecurringRunner {
}
public void start() {
+ SoftPreconditions.checkState(!mRunning, TAG, "start is called twice.");
if (mRunning) {
- Utils.engThrowElseWarn(TAG, "start is called twice.", new IllegalStateException());
return;
}
mRunning = true;
diff --git a/src/com/android/tv/util/SetupUtils.java b/src/com/android/tv/util/SetupUtils.java
index 683216a5..46299d3b 100644
--- a/src/com/android/tv/util/SetupUtils.java
+++ b/src/com/android/tv/util/SetupUtils.java
@@ -27,6 +27,10 @@ import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
+import com.android.tv.TvApplication;
+import com.android.tv.data.Channel;
+import com.android.tv.data.ChannelDataManager;
+
import java.util.HashSet;
import java.util.Set;
@@ -40,18 +44,20 @@ public class SetupUtils {
// 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.
+ // Set up inputs are inputs whose setup activity has been launched and finished successfully.
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 TvApplication mTvApplication;
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);
+ private SetupUtils(TvApplication tvApplication) {
+ mTvApplication = tvApplication;
+ mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(tvApplication);
mSetUpInputs = new HashSet<>(mSharedPreferences.getStringSet(PREF_KEY_SET_UP_INPUTS,
new HashSet<String>()));
mKnownInputs = new HashSet<>(mSharedPreferences.getStringSet(PREF_KEY_KNOWN_INPUTS,
@@ -66,10 +72,66 @@ public class SetupUtils {
if (sSetupUtils != null) {
return sSetupUtils;
}
- sSetupUtils = new SetupUtils(context.getApplicationContext());
+ sSetupUtils = new SetupUtils((TvApplication) context.getApplicationContext());
return sSetupUtils;
}
+ /**
+ * Additional work after the setup of TV input.
+ */
+ public void onTvInputSetupFinished(final String inputId, final Runnable postRunnable) {
+ // When TIS adds several channels, ChannelDataManager.Listener.onChannelList
+ // Updated() can be called several times. In this case, it is hard to detect
+ // which one is the last callback. To reduce error prune, we update channel
+ // list again and make all channels of {@code inputId} browsable.
+ onSetupDone(inputId);
+ final ChannelDataManager manager = mTvApplication.getChannelDataManager();
+ if (!manager.isDbLoadFinished()) {
+ manager.addListener(new ChannelDataManager.Listener() {
+ @Override
+ public void onLoadFinished() {
+ manager.removeListener(this);
+ updateChannelBrowsable(mTvApplication, inputId, postRunnable);
+ }
+
+ @Override
+ public void onChannelListUpdated() { }
+
+ @Override
+ public void onChannelBrowsableChanged() { }
+ });
+ } else {
+ updateChannelBrowsable(mTvApplication, inputId, postRunnable);
+ }
+ }
+
+ private static void updateChannelBrowsable(Context context, final String inputId,
+ final Runnable postRunnable) {
+ TvApplication tvApplication = (TvApplication) context.getApplicationContext();
+ final ChannelDataManager manager = tvApplication.getChannelDataManager();
+ manager.updateChannels(new Runnable() {
+ @Override
+ public void run() {
+ boolean browsableChanged = false;
+ for (Channel channel : manager.getChannelList()) {
+ if (channel.getInputId().equals(inputId)) {
+ if (!channel.isBrowsable()) {
+ manager.updateBrowsable(channel.getId(), true, true);
+ browsableChanged = true;
+ }
+ }
+ }
+ if (browsableChanged) {
+ manager.notifyChannelBrowsableChanged();
+ manager.applyUpdatedValuesToDb();
+ }
+ if (postRunnable != null) {
+ postRunnable.run();
+ }
+ }
+ });
+ }
+
public boolean isFirstTune() {
return mIsFirstTune;
}
@@ -91,14 +153,14 @@ public class SetupUtils {
}
/**
- * Returns true, if {@code inputId}'s setup activity has been launched.
+ * Returns {@code true}, if {@code inputId}'s setup has been done before.
*/
- public boolean hasSetupLaunched(String inputId) {
- boolean launched = mSetUpInputs.contains(inputId);
+ public boolean isSetupDone(String inputId) {
+ boolean done = mSetUpInputs.contains(inputId);
if (DEBUG) {
- Log.d(TAG, "hasSetupLaunched: (input=" + inputId + ", result= " + launched + ")");
+ Log.d(TAG, "isSetupDone: (input=" + inputId + ", result= " + done + ")");
}
- return launched;
+ return done;
}
/**
@@ -120,7 +182,7 @@ public class SetupUtils {
*/
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) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context);
Set<String> setUpInputs = new HashSet<>(sharedPreferences.getStringSet(
@@ -143,7 +205,7 @@ public class SetupUtils {
*/
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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (DEBUG) {
Log.d(TAG, "grantEpgPermission(context=" + context + ", packageName=" + packageName
+ ")");
@@ -155,7 +217,7 @@ public class SetupUtils {
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);
+ + " does not have permission.", e);
}
}
}
@@ -194,11 +256,15 @@ public class SetupUtils {
}
/**
- * Called when an setup activity is launched. Once it is called, {@link #hasSetupLaunched}
- * will return true for {@code inputId}.
+ * Called when an setup is done. Once it is called, {@link #isSetupDone} returns {@code true}
+ * for {@code inputId}.
*/
- public void onSetupLaunched(String inputId) {
- if (DEBUG) Log.d(TAG, "onSetupLaunched: input=" + inputId);
+ public void onSetupDone(String inputId) {
+ if (DEBUG) Log.d(TAG, "onSetupDone: input=" + inputId);
+ if (!mKnownInputs.contains(inputId)) {
+ Log.i(TAG, "An unknown input's setup has been done. inputId=" + inputId);
+ mKnownInputs.add(inputId);
+ }
mSetUpInputs.add(inputId);
mSharedPreferences.edit()
.putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs).apply();
diff --git a/src/com/android/tv/util/SoftPreconditions.java b/src/com/android/tv/util/SoftPreconditions.java
new file mode 100644
index 00000000..5c2a8170
--- /dev/null
+++ b/src/com/android/tv/util/SoftPreconditions.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.util;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.tv.BuildConfig;
+
+/**
+ * Simple static methods to be called at the start of your own methods to verify
+ * correct arguments and state.
+ *
+ * <p>{@code checkXXX} methods throw exceptions when {@link BuildConfig#ENG} is true, and
+ * logs a warning when it is false.
+ *
+ * <p>This is based on com.android.internal.util.Preconditions.
+ */
+public final class SoftPreconditions {
+ private static final String TAG = "SoftPreconditions";
+
+ /**
+ * Throws or logs if an expression involving the parameter of the calling
+ * method is not true.
+ *
+ * @param expression a boolean expression
+ * @param tag Used to identify the source of a log message. It usually
+ * identifies the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @throws IllegalArgumentException if {@code expression} is true
+ */
+ public static void checkArgument(final boolean expression, String tag, String msg) {
+ if (!expression) {
+ warn(tag, "Illegal argument", msg, new IllegalArgumentException(msg));
+ }
+ }
+
+ /**
+ * Throws or logs if an expression involving the parameter of the calling
+ * method is not true.
+ *
+ * @param expression a boolean expression
+ * @throws IllegalArgumentException if {@code expression} is true
+ */
+ public static void checkArgument(final boolean expression) {
+ checkArgument(expression, null, null);
+ }
+
+ /**
+ * Throws or logs if an and object is null.
+ *
+ * @param reference an object reference
+ * @param tag Used to identify the source of a log message. It usually
+ * identifies the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @return true if the object is null
+ * @throws NullPointerException if {@code reference} is null
+ */
+ public static <T> T checkNotNull(final T reference, String tag, String msg) {
+ if (reference == null) {
+ warn(tag, "Null Pointer", msg, new NullPointerException(msg));
+ }
+ return reference;
+ }
+
+ /**
+ * Throws or logs if an and object is null.
+ *
+ * @param reference an object reference
+ * @return true if the object is null
+ * @throws NullPointerException if {@code reference} is null
+ */
+ public static <T> T checkNotNull(final T reference) {
+ return checkNotNull(reference, null, null);
+ }
+
+ /**
+ * Throws or logs if an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method is not true.
+ *
+ * @param expression a boolean expression
+ * @param tag Used to identify the source of a log message. It usually
+ * identifies the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @throws IllegalStateException if {@code expression} is true
+ */
+ public static void checkState(final boolean expression, String tag, String msg) {
+ if (!expression) {
+ warn(tag, "Illegal State", msg, new IllegalStateException(msg));
+ }
+ }
+
+ /**
+ * Throws or logs if an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method is not true.
+ *
+ * @param expression a boolean expression
+ * @throws IllegalStateException if {@code expression} is true
+ */
+ public static void checkState(final boolean expression) {
+ checkState(expression, null, null);
+ }
+
+ /**
+ * Throws a {@link RuntimeException} if {@link BuildConfig#ENG} is true, else log a warning.
+ *
+ * @param tag Used to identify the source of a log message. It usually
+ * identifies the class or activity where the log call occurs.
+ * @param msg The message you would like logged
+ * @param e The exception to throw
+ */
+ public static void warn(String tag, String prefix, String msg, RuntimeException e)
+ throws RuntimeException{
+ if (BuildConfig.ENG) {
+ throw e;
+ } else {
+ if (TextUtils.isEmpty(tag)) {
+ tag = TAG;
+ }
+ String logMessage;
+ if (msg == null) {
+ logMessage = prefix;
+ } else {
+ logMessage = prefix + ": " + msg;
+ }
+ Log.w(tag, logMessage, e);
+ }
+ }
+
+ private SoftPreconditions() {
+ }
+}
diff --git a/src/com/android/tv/util/SystemProperties.java b/src/com/android/tv/util/SystemProperties.java
index 88266cad..235161b6 100644
--- a/src/com/android/tv/util/SystemProperties.java
+++ b/src/com/android/tv/util/SystemProperties.java
@@ -16,6 +16,8 @@
package com.android.tv.util;
+import com.android.tv.common.BooleanSystemProperty;
+
/**
* A convenience class for getting TV related system properties.
*/
@@ -51,12 +53,6 @@ public final class SystemProperties {
"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(
diff --git a/src/com/android/tv/util/TvInputManagerHelper.java b/src/com/android/tv/util/TvInputManagerHelper.java
index 66c0ba81..d11ecd82 100644
--- a/src/com/android/tv/util/TvInputManagerHelper.java
+++ b/src/com/android/tv/util/TvInputManagerHelper.java
@@ -29,8 +29,6 @@ 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.Collections;
import java.util.Comparator;
@@ -50,8 +48,7 @@ public class TvInputManagerHelper {
static {
BUNDLED_PACKAGE_SET.add("com.android.tv");
- BUNDLED_PACKAGE_SET.add("com.android.tv");
- BUNDLED_PACKAGE_SET.add("com.google.android.usbtuner");
+ BUNDLED_PACKAGE_SET.add("com.android.usbtuner");
}
private final Context mContext;
@@ -123,10 +120,6 @@ public class TvInputManagerHelper {
return;
}
mStarted = true;
- List<TvInputInfo> inputs = mTvInputManager.getTvInputList();
- if (inputs.size() < 1) {
- return;
- }
mTvInputManager.registerCallback(mInternalCallback, mHandler);
mInputMap.clear();
mInputStateMap.clear();
@@ -137,7 +130,8 @@ public class TvInputManagerHelper {
mInputStateMap.put(inputId, state);
mInputIdToPartnerInputMap.put(inputId, isPartnerInput(input));
}
- Assert.assertEquals(mInputStateMap.size(), mInputMap.size());
+ SoftPreconditions.checkState(mInputStateMap.size() == mInputMap.size(), TAG,
+ "mInputStateMap not the same size as mInputMap");
mContentRatingsManager.update();
}
@@ -215,7 +209,7 @@ public class TvInputManagerHelper {
}
/**
- * Loads label of {@param info}.
+ * Loads label of {@code info}.
*
* It's visible for comparator test to mock TvInputInfo.
* Package private is enough for this method, but public is necessary to workaround mockito
@@ -230,18 +224,18 @@ public class TvInputManagerHelper {
* Returns if TV input exists with the input id.
*/
public boolean hasTvInputInfo(String inputId) {
+ SoftPreconditions.checkState(mStarted, TAG,
+ "hasTvInputInfo() called before TvInputManagerHelper was started.");
if (!mStarted) {
- Utils.engThrowElseWarn(TAG,
- "hasTvInputInfo() called before TvInputManagerHelper was started.");
return false;
}
return !TextUtils.isEmpty(inputId) && mInputMap.get(inputId) != null;
}
public TvInputInfo getTvInputInfo(String inputId) {
+ SoftPreconditions.checkState(mStarted, TAG,
+ "getTvInputInfo() called before TvInputManagerHelper was started.");
if (!mStarted) {
- Utils.engThrowElseWarn(TAG,
- "getTvInputInfo() called before TvInputManagerHelper was started.");
return null;
}
if (inputId == null) {
@@ -270,8 +264,10 @@ public class TvInputManagerHelper {
}
public int getInputState(String inputId) {
+ SoftPreconditions.checkState(mStarted, TAG, "AvailabilityManager not started");
if (!mStarted) {
- throw new IllegalStateException("AvailabilityManager doesn't started");
+ return TvInputManager.INPUT_STATE_DISCONNECTED;
+
}
Integer state = mInputStateMap.get(inputId);
if (state == null) {
diff --git a/src/com/android/tv/util/Utils.java b/src/com/android/tv/util/Utils.java
index a0ed0924..81b469fe 100644
--- a/src/com/android/tv/util/Utils.java
+++ b/src/com/android/tv/util/Utils.java
@@ -17,11 +17,15 @@
package com.android.tv.util;
import android.annotation.SuppressLint;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
import android.database.Cursor;
import android.media.tv.TvContentRating;
import android.media.tv.TvContract;
@@ -29,6 +33,7 @@ import android.media.tv.TvContract.Channels;
import android.media.tv.TvInputInfo;
import android.media.tv.TvTrackInfo;
import android.net.Uri;
+import android.os.Build;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
@@ -38,14 +43,15 @@ import android.text.format.DateUtils;
import android.util.Log;
import android.view.View;
-import com.android.tv.BuildConfig;
+import com.android.tv.Features;
import com.android.tv.R;
+import com.android.tv.TvApplication;
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.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
@@ -386,6 +392,8 @@ public class Utils {
String language = context.getString(R.string.default_language);
if (track.getLanguage() != null) {
language = new Locale(track.getLanguage()).getDisplayName();
+ } else {
+ Log.d(TAG, "No language information found for the audio track: " + track);
}
StringBuilder metadata = new StringBuilder();
@@ -405,8 +413,13 @@ public class Utils {
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()));
+ if (track.getAudioChannelCount() > 0) {
+ metadata.append(context.getString(R.string.multi_audio_channel_suffix,
+ track.getAudioChannelCount()));
+ } else {
+ Log.d(TAG, "Invalid audio channel count (" + track.getAudioChannelCount()
+ + ") found for the audio track: " + track);
+ }
break;
}
if (showSampleRate) {
@@ -568,30 +581,103 @@ public class Utils {
}
/**
- * Throws a {@link RuntimeException} if {@link BuildConfig#ENG} is true, else log a warning.
+ * Check if the index is valid for the collection,
+ * @param collection the collection
+ * @param index the index position to test
+ * @return index >= 0 && index < collection.size().
+ */
+ public static boolean isIndexValid(@Nullable Collection<?> collection, int index) {
+ return collection == null ? false : index >= 0 && index < collection.size();
+ }
+
+ /**
+ * Returns a color integer associated with a particular resource ID.
*
- * @param tag Used to log message.
- * @param msg The message
+ * @see #getColor(android.content.res.Resources,int,Theme)
*/
- public static void engThrowElseWarn(String tag, String msg) {
- if (BuildConfig.ENG) {
- throw new RuntimeException(msg);
+ public static int getColor(Resources res, int id) {
+ return getColor(res, id, null);
+ }
+
+ /**
+ * Returns a color integer associated with a particular resource ID.
+ *
+ * <p>In M version, {@link android.content.res.Resources#getColor(int)} was deprecated and
+ * {@link android.content.res.Resources#getColor(int,Theme)} was newly added.
+ *
+ * @see android.content.res.Resources#getColor(int)
+ */
+ public static int getColor(Resources res, int id, @Nullable Theme theme) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return res.getColor(id, theme);
} else {
- Log.w(tag, msg);
+ return res.getColor(id);
}
}
/**
- * Throws a {@link RuntimeException} if {@link BuildConfig#ENG} is true, else log a warning.
+ * Returns a color state list associated with a particular resource ID.
+ *
+ * @see #getColorStateList(android.content.res.Resources,int,Theme)
+ */
+ public static ColorStateList getColorStateList(Resources res, int id) {
+ return getColorStateList(res, id, null);
+ }
+
+ /**
+ * Returns a color state list associated with a particular resource ID.
+ *
+ * <p>In M version, {@link android.content.res.Resources#getColorStateList(int)} was deprecated
+ * and {@link android.content.res.Resources#getColorStateList(int,Theme)} was newly added.
*
- * @param tag Used to log message.
- * @param msg The message
+ * @see android.content.res.Resources#getColorStateList(int)
*/
- public static void engThrowElseWarn(String tag, String msg, RuntimeException e) {
- if (BuildConfig.ENG) {
- throw e;
+ public static ColorStateList getColorStateList(Resources res, int id, @Nullable Theme theme) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return res.getColorStateList(id, theme);
} else {
- Log.w(tag, msg);
+ return res.getColorStateList(id);
+ }
+ }
+
+
+ private static final class SyncRunnable implements Runnable {
+ private final Runnable mTarget;
+ private boolean mComplete;
+
+ public SyncRunnable(Runnable target) {
+ mTarget = target;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mTarget.run();
+ } finally {
+ synchronized (this) {
+ mComplete = true;
+ notifyAll();
+ }
+ }
+ }
+
+ public void waitForComplete() {
+ boolean interrupted = false;
+ synchronized (this) {
+ try {
+ while (!mComplete) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ interrupted = true;
+ }
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
}
}
}
diff --git a/tests/common/Android.mk b/tests/common/Android.mk
index 62005890..aecd15bc 100644
--- a/tests/common/Android.mk
+++ b/tests/common/Android.mk
@@ -14,7 +14,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
# Link tv-common as shared library to avoid the problem of initialization of the constants
LOCAL_JAVA_LIBRARIES := tv-common
-LOCAL_INSTRUMENTATION_FOR := TV
+LOCAL_INSTRUMENTATION_FOR := LiveTv
LOCAL_MODULE := tv-test-common
LOCAL_MODULE_TAGS := optional
LOCAL_SDK_VERSION := system_current
diff --git a/tests/common/src/com/android/tv/testing/ChannelInfo.java b/tests/common/src/com/android/tv/testing/ChannelInfo.java
index 40a8bba8..28ba4a64 100644
--- a/tests/common/src/com/android/tv/testing/ChannelInfo.java
+++ b/tests/common/src/com/android/tv/testing/ChannelInfo.java
@@ -81,7 +81,7 @@ public final class ChannelInfo {
.setOriginalNetworkId(channelNumber);
if (context != null) {
// tests/input/tools/get_test_logos.sh only stores 1000 logos.
- int logo_num = (channelNumber % 1000) + 1;
+ int logo_num = (channelNumber % 1000);
builder.setLogoUrl(
"android.resource://com.android.tv.testinput/drawable/ch_" + logo_num
+ "_logo"
diff --git a/tests/common/src/com/android/tv/testing/ComparatorTester.java b/tests/common/src/com/android/tv/testing/ComparatorTester.java
index dc5cf00f..3774532f 100644
--- a/tests/common/src/com/android/tv/testing/ComparatorTester.java
+++ b/tests/common/src/com/android/tv/testing/ComparatorTester.java
@@ -125,4 +125,4 @@ public class ComparatorTester<T> {
}
}
}
-}
+} \ No newline at end of file
diff --git a/tests/common/src/com/android/tv/testing/Utils.java b/tests/common/src/com/android/tv/testing/Utils.java
index 33fe4c54..c1a7506f 100644
--- a/tests/common/src/com/android/tv/testing/Utils.java
+++ b/tests/common/src/com/android/tv/testing/Utils.java
@@ -25,11 +25,21 @@ import android.media.tv.TvContentRating;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.net.Uri;
+import android.os.Looper;
import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.tv.util.MainThreadExecutor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Random;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
/**
* An utility class for testing.
@@ -38,7 +48,11 @@ import java.io.OutputStream;
*
* @see com.android.tv.util.Utils#isRunningInTest
*/
-public class Utils {
+public final class Utils {
+ private static final String TAG ="Utils";
+
+ private static final long DEFAULT_RANDOM_SEED = getSeed();
+
public static String getUriStringForResource(Context context, int resId) {
if (resId == 0) {
return "";
@@ -105,5 +119,44 @@ public class Utils {
return ratings.toString();
}
+ /**
+ * 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);
+ }
+
+ /**
+ * Executes a call on the main thread, blocking until it is completed.
+ *
+ * <p>Useful for doing things that are not thread-safe, such as looking at or modifying the view
+ * hierarchy.
+ *
+ * @param runnable The code to run on the main thread.
+ */
+ public static void runOnMainSync(Runnable runnable) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ runnable.run();
+ } else {
+ Future<?> temp = MainThreadExecutor.getInstance().submit(runnable);
+ try {
+ temp.get();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private static long getSeed() {
+ // Set random seed as the date to track failed test data easily.
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.getDefault());
+ String today = dateFormat.format(new Date());
+ Log.d(TAG, "Today's random seed is " + today);
+ return Long.valueOf(today);
+ }
+
private Utils() {}
}
diff --git a/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.aidl b/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.aidl
index cdf43adb..8b3e7dcc 100644
--- a/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.aidl
+++ b/tests/common/src/com/android/tv/testing/testinput/ChannelStateData.aidl
@@ -1,3 +1,3 @@
package com.android.tv.testing.testinput;
-parcelable ChannelStateData;
+parcelable ChannelStateData; \ No newline at end of file
diff --git a/tests/common/src/com/android/tv/testing/testinput/ITestInputControl.aidl b/tests/common/src/com/android/tv/testing/testinput/ITestInputControl.aidl
index a82f378b..15039000 100644
--- a/tests/common/src/com/android/tv/testing/testinput/ITestInputControl.aidl
+++ b/tests/common/src/com/android/tv/testing/testinput/ITestInputControl.aidl
@@ -6,4 +6,4 @@ 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);
-}
+} \ No newline at end of file
diff --git a/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java
index 31204410..6757cf01 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/LiveChannelsUiDeviceHelper.java
@@ -48,4 +48,4 @@ public class LiveChannelsUiDeviceHelper extends BaseUiDeviceHelper {
mUiDevice.pressBack();
}
}
-}
+} \ No newline at end of file
diff --git a/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java b/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java
index cb403a61..3d992618 100644
--- a/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java
+++ b/tests/common/src/com/android/tv/testing/uihelper/MenuHelper.java
@@ -121,6 +121,10 @@ public class MenuHelper extends BaseUiDeviceHelper {
R.string.options_item_channel_sources);
}
+ public UiObject2 assertPressOptionsAbout() {
+ return assertPressMenuItem(R.string.menu_title_options,
+ R.string.options_item_about);
+ }
public UiObject2 assertPressOptionsClosedCaptions() {
return assertPressMenuItem(R.string.menu_title_options,
diff --git a/tests/func/Android.mk b/tests/func/Android.mk
index 53ed5e72..2fd68b08 100644
--- a/tests/func/Android.mk
+++ b/tests/func/Android.mk
@@ -14,7 +14,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
tv-test-common \
ub-uiautomator \
-LOCAL_INSTRUMENTATION_FOR := TV
+LOCAL_INSTRUMENTATION_FOR := LiveTv
LOCAL_SDK_VERSION := current
diff --git a/tests/func/src/com/android/tv/tests/ui/AboutFragmentTest.java b/tests/func/src/com/android/tv/tests/ui/AboutFragmentTest.java
new file mode 100644
index 00000000..cd7a5718
--- /dev/null
+++ b/tests/func/src/com/android/tv/tests/ui/AboutFragmentTest.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.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.Features;
+import com.android.tv.R;
+import com.android.tv.testing.uihelper.ByResource;
+import com.android.tv.testing.uihelper.SidePanelHelper;
+
+/**
+ * Test for the About menu.
+ */
+@LargeTest
+public class AboutFragmentTest extends LiveChannelsTestCase {
+ private SidePanelHelper mSidePanelHelper;
+ private BySelector mAboutSidePanel;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mSidePanelHelper = new SidePanelHelper(mDevice, mTargetResources);
+ mAboutSidePanel = mSidePanelHelper.bySidePanelTitled(R.string.side_panel_title_about);
+ }
+
+ public void testAllowAnalytics_present() {
+ mLiveChannelsHelper.assertAppStarted();
+ mMenuHelper.assertPressOptionsAbout();
+ assertWaitForCondition(mDevice, Until.hasObject(mAboutSidePanel));
+ BySelector improveSelector = ByResource
+ .text(mTargetResources, R.string.about_menu_improve_summary);
+ if (Features.ANALYTICS_OPT_OUT.isEnabled(getInstrumentation().getContext())) {
+ assertWaitForCondition(mDevice, Until.hasObject(improveSelector));
+ } else {
+ assertFalse(mDevice.hasObject(improveSelector));
+ }
+ mDevice.pressBack();
+ }
+}
diff --git a/tests/input/src/com/android/tv/testinput/TestTvInputService.java b/tests/input/src/com/android/tv/testinput/TestTvInputService.java
index 6f906786..a81977d6 100644
--- a/tests/input/src/com/android/tv/testinput/TestTvInputService.java
+++ b/tests/input/src/com/android/tv/testinput/TestTvInputService.java
@@ -342,4 +342,4 @@ public class TestTvInputService extends TvInputService {
}
}
}
-}
+} \ No newline at end of file
diff --git a/tests/jank/Android.mk b/tests/jank/Android.mk
index 28f8ddf0..3d0b3bfd 100644
--- a/tests/jank/Android.mk
+++ b/tests/jank/Android.mk
@@ -15,7 +15,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
ub-janktesthelper \
ub-uiautomator \
-LOCAL_INSTRUMENTATION_FOR := TV
+LOCAL_INSTRUMENTATION_FOR := LiveTv
LOCAL_SDK_VERSION := current
diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk
index 1cd4f153..55ee5276 100644
--- a/tests/unit/Android.mk
+++ b/tests/unit/Android.mk
@@ -14,7 +14,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
LOCAL_PACKAGE_NAME := TVUnitTests
-LOCAL_INSTRUMENTATION_FOR := TV
+LOCAL_INSTRUMENTATION_FOR := LiveTv
LOCAL_SDK_VERSION := current
diff --git a/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java b/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java
index 208085d6..e2f620ed 100644
--- a/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java
+++ b/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java
@@ -18,6 +18,7 @@ package com.android.tv;
import android.content.Context;
import android.os.SystemClock;
import android.test.ActivityInstrumentationTestCase2;
+import android.text.TextUtils;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
@@ -131,7 +132,7 @@ public abstract class BaseMainActivityTestCase
waitUntilChannelLoadingFinish();
List<Channel> channelList = mActivity.getChannelDataManager().getChannelList();
for (Channel c : channelList) {
- if (c.getDisplayName().equals(displayName)) {
+ if (TextUtils.equals(c.getDisplayName(), displayName)) {
return c;
}
}
diff --git a/tests/unit/src/com/android/tv/FeaturesTest.java b/tests/unit/src/com/android/tv/FeaturesTest.java
new file mode 100644
index 00000000..a0a6a5be
--- /dev/null
+++ b/tests/unit/src/com/android/tv/FeaturesTest.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;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+/**
+ * Test for features.
+ */
+@SmallTest
+public class FeaturesTest extends TestCase {
+
+ public void testPropertyFeatureKeyLength() {
+ // This forces the class to be loaded and verifies all PropertyFeature key lengths.
+ // If any keys are too long the test will fail to load.
+ assertEquals(false, Features.TEST_FEATURE.isEnabled(null));
+ }
+}
diff --git a/tests/unit/src/com/android/tv/MainActivityTest.java b/tests/unit/src/com/android/tv/MainActivityTest.java
index 04271a7b..a09111e8 100644
--- a/tests/unit/src/com/android/tv/MainActivityTest.java
+++ b/tests/unit/src/com/android/tv/MainActivityTest.java
@@ -58,7 +58,7 @@ public class MainActivityTest extends BaseMainActivityTestCase {
private void showProgramGuide() throws Throwable {
// Run on UI thread so views can be modified
- runTestOnUiThread(new Runnable() {
+ getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
mActivity.getOverlayManager().showProgramGuide();
diff --git a/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java b/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java
index b239d0ed..fa127369 100644
--- a/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java
+++ b/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java
@@ -25,9 +25,9 @@ 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.UiThreadTest;
import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
import android.test.mock.MockCursor;
@@ -39,6 +39,7 @@ import android.util.SparseArray;
import com.android.tv.analytics.StubTracker;
import com.android.tv.testing.ChannelInfo;
import com.android.tv.testing.Constants;
+import com.android.tv.testing.Utils;
import com.android.tv.util.TvInputManagerHelper;
import org.mockito.Matchers;
@@ -51,9 +52,10 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
- * Test for {@link com.android.tv.data.ChannelDataManager}
+ * Test for {@link ChannelDataManager}
*
* A test method may include tests for multiple methods to minimize the DB access.
+ * Note that all the methods of {@link ChannelDataManager} should be called from the UI thread.
*/
@SmallTest
public class ChannelDataManagerTest extends AndroidTestCase {
@@ -68,7 +70,6 @@ public class ChannelDataManagerTest extends AndroidTestCase {
private static final String COLUMN_LOCKED = "locked";
private ChannelDataManager mChannelDataManager;
- private HandlerThread mHandlerThread;
private TestChannelDataManagerListener mListener;
private FakeContentResolver mContentResolver;
private FakeContentProvider mContentProvider;
@@ -77,26 +78,32 @@ public class ChannelDataManagerTest extends AndroidTestCase {
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, new StubTracker(),
- mContentResolver, mHandlerThread.getLooper());
mListener = new TestChannelDataManagerListener();
- mChannelDataManager.addListener(mListener);
-
+ Utils.runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ TvInputManagerHelper mockHelper = Mockito.mock(TvInputManagerHelper.class);
+ Mockito.when(mockHelper.hasTvInputInfo(Matchers.anyString())).thenReturn(true);
+ mChannelDataManager = new ChannelDataManager(getContext(), mockHelper,
+ new StubTracker(), mContentResolver);
+ mChannelDataManager.addListener(mListener);
+ }
+ });
}
@Override
protected void tearDown() throws Exception {
+ Utils.runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mChannelDataManager.stop();
+ }
+ });
super.tearDown();
- mHandlerThread.quitSafely();
- mChannelDataManager.stop();
}
private void startAndWaitForComplete() throws Exception {
@@ -114,6 +121,7 @@ public class ChannelDataManagerTest extends AndroidTestCase {
startAndWaitForComplete();
}
+ @UiThreadTest
public void testIsDbLoadFinished() throws Exception {
startAndWaitForComplete();
assertTrue(mChannelDataManager.isDbLoadFinished());
@@ -125,6 +133,7 @@ public class ChannelDataManagerTest extends AndroidTestCase {
* - {@link ChannelDataManager#getChannelList}
* - {@link ChannelDataManager#getChannel}
*/
+ @UiThreadTest
public void testGetChannels() throws Exception {
startAndWaitForComplete();
@@ -159,6 +168,7 @@ public class ChannelDataManagerTest extends AndroidTestCase {
/**
* Test for {@link ChannelDataManager#getChannelCount} when no channel is available.
*/
+ @UiThreadTest
public void testGetChannels_noChannels() throws Exception {
mContentProvider.clear();
startAndWaitForComplete();
@@ -170,6 +180,7 @@ public class ChannelDataManagerTest extends AndroidTestCase {
* - {@link ChannelDataManager#updateBrowsable}
* - {@link ChannelDataManager#applyUpdatedValuesToDb}
*/
+ @UiThreadTest
public void testBrowsable() throws Exception {
startAndWaitForComplete();
@@ -208,6 +219,7 @@ public class ChannelDataManagerTest extends AndroidTestCase {
* - {@link ChannelDataManager#updateBrowsable}
* - {@link ChannelDataManager#applyUpdatedValuesToDb}
*/
+ @UiThreadTest
public void testBrowsable_skipNotification() throws Exception {
startAndWaitForComplete();
@@ -241,6 +253,7 @@ public class ChannelDataManagerTest extends AndroidTestCase {
* - {@link ChannelDataManager#updateLocked}
* - {@link ChannelDataManager#applyUpdatedValuesToDb}
*/
+ @UiThreadTest
public void testLocked() throws Exception {
startAndWaitForComplete();
@@ -269,6 +282,7 @@ public class ChannelDataManagerTest extends AndroidTestCase {
/**
* Test ChannelDataManager when channels in TvContract are updated, removed, or added.
*/
+ @UiThreadTest
public void testChannelListChanged() throws Exception {
startAndWaitForComplete();
@@ -336,8 +350,8 @@ public class ChannelDataManagerTest extends AndroidTestCase {
if (DEBUG) {
Log.d(TAG, "onChanged(uri=" + uri + ", observer=" + observer + ")");
}
- // Do not call {@link ContentObserver#onChange} directly
- // to run it on the {@link #mHandlerThread}.
+ // Do not call {@link ContentObserver#onChange} directly to run it on the correct
+ // thread.
if (observer != null) {
observer.dispatchChange(false, uri);
} else {
diff --git a/tests/unit/src/com/android/tv/data/ChannelNumberTest.java b/tests/unit/src/com/android/tv/data/ChannelNumberTest.java
index 1935b4d9..1dd18da0 100644
--- a/tests/unit/src/com/android/tv/data/ChannelNumberTest.java
+++ b/tests/unit/src/com/android/tv/data/ChannelNumberTest.java
@@ -43,6 +43,9 @@ public class ChannelNumberTest extends TestCase {
public void testParseChannelNumber() {
assertNull(parseChannelNumber(""));
assertNull(parseChannelNumber(" "));
+ assertNull(parseChannelNumber("abcd12"));
+ assertNull(parseChannelNumber("12abcd"));
+ assertNull(parseChannelNumber("-12"));
assertChannelEquals(parseChannelNumber("1"), "1", false, "");
assertChannelEquals(parseChannelNumber("1234 4321"), "1234", true, "4321");
assertChannelEquals(parseChannelNumber("3-4"), "3", true, "4");
@@ -76,6 +79,8 @@ public class ChannelNumberTest extends TestCase {
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(mal-formatted,1)<0", true, ChannelNumber.compare("abcd", "1") < 0);
+ assertEquals("compareTo(mal-formatted,1)<0", true, ChannelNumber.compare(".4", "1") < 0);
assertEquals("compareTo(1,null)>0", true, ChannelNumber.compare("1", null) > 0);
}
diff --git a/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java b/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java
index 60e3e210..78b3a930 100644
--- a/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java
+++ b/tests/unit/src/com/android/tv/data/ProgramDataManagerTest.java
@@ -27,7 +27,6 @@ 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;
@@ -80,6 +79,7 @@ public class ProgramDataManagerTest extends AndroidTestCase {
mHandlerThread.start();
mProgramDataManager = new ProgramDataManager(
mContentResolver, mClock, mHandlerThread.getLooper());
+ mProgramDataManager.setPrefetchEnabled(true);
mProgramDataManager.addListener(mListener);
}
@@ -95,14 +95,6 @@ public class ProgramDataManagerTest extends AndroidTestCase {
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}.
*/
@@ -150,8 +142,7 @@ public class ProgramDataManagerTest extends AndroidTestCase {
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));
+ assertProgramEquals(startTimeMs, programInfoAt, program);
index++;
}
// Case #2: Corner cases where there's a program that starts at the start of the range.
@@ -194,13 +185,12 @@ public class ProgramDataManagerTest extends AndroidTestCase {
TestProgramDataManagerOnCurrentProgramUpdatedListener listener =
new TestProgramDataManagerOnCurrentProgramUpdatedListener();
mProgramDataManager.addOnCurrentProgramUpdatedListener(testChannelId, listener);
- assertTrue(listener.currentProgramUpdatedLatch.await(WAIT_TIME_OUT_MS,
- TimeUnit.MILLISECONDS));
+ 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));
+ Program currentProgram = mProgramDataManager.getCurrentProgram(testChannelId);
+ assertProgramEquals(nextProgramStartTimeMs, nextProgramInfo, currentProgram);
+ assertEquals(listener.updatedProgram, currentProgram);
}
/**
@@ -220,8 +210,8 @@ public class ProgramDataManagerTest extends AndroidTestCase {
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());
+ assertTrue(
+ lastProgramEndTime < programList.get(programList.size() - 1).getEndTimeUtcMillis());
}
/**
@@ -239,6 +229,16 @@ public class ProgramDataManagerTest extends AndroidTestCase {
TimeUnit.MILLISECONDS));
}
+ public static void assertProgramEquals(long expectedStartTime, ProgramInfo expectedInfo,
+ Program actualProgram) {
+ assertEquals("title", expectedInfo.title, actualProgram.getTitle());
+ assertEquals("episode", expectedInfo.episode, actualProgram.getEpisodeTitle());
+ assertEquals("description", expectedInfo.description, actualProgram.getDescription());
+ assertEquals("startTime", expectedStartTime, actualProgram.getStartTimeUtcMillis());
+ assertEquals("endTime", expectedStartTime + expectedInfo.durationMs,
+ actualProgram.getEndTimeUtcMillis());
+ }
+
private class FakeContentResolver extends MockContentResolver {
@Override
public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
diff --git a/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java b/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java
new file mode 100644
index 00000000..e5da60eb
--- /dev/null
+++ b/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.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.data;
+
+import android.test.AndroidTestCase;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.tv.data.WatchedHistoryManager.WatchedRecord;
+import com.android.tv.testing.Utils;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test for {@link com.android.tv.data.WatchedHistoryManagerTest}
+ */
+@SmallTest
+public class WatchedHistoryManagerTest extends AndroidTestCase {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "WatchedHistoryManager";
+
+ // Wait time for expected success.
+ private static final long WAIT_TIME_OUT_MS = 1000L;
+ private static final int MAX_HISTORY_SIZE = 100;
+
+ private WatchedHistoryManager mWatchedHistoryManager;
+ private TestWatchedHistoryManagerListener mListener;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Utils.runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mWatchedHistoryManager = new WatchedHistoryManager(getContext(), MAX_HISTORY_SIZE);
+ mListener = new TestWatchedHistoryManagerListener();
+ mWatchedHistoryManager.setListener(mListener);
+ }
+ });
+ }
+
+ private void startAndWaitForComplete() throws Exception {
+ mWatchedHistoryManager.start();
+ try {
+ assertTrue(mListener.loadFinishedLatch.await(WAIT_TIME_OUT_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ throw e;
+ }
+ }
+
+ @UiThreadTest
+ public void testIsLoaded() throws Exception {
+ assertFalse(mWatchedHistoryManager.isLoaded());
+ startAndWaitForComplete();
+ assertTrue(mWatchedHistoryManager.isLoaded());
+ }
+
+ @UiThreadTest
+ public void testLogChannelViewStop() throws Exception {
+ startAndWaitForComplete();
+ long fakeId = 100000000;
+ long time = System.currentTimeMillis();
+ long duration = TimeUnit.MINUTES.toMillis(10);
+ Channel channel = new Channel.Builder().setId(fakeId).build();
+ mWatchedHistoryManager.logChannelViewStop(channel, time, duration);
+
+ WatchedRecord record = mWatchedHistoryManager.getRecord(0);
+ WatchedRecord recordFromSharedPreferences =
+ mWatchedHistoryManager.getRecordFromSharedPreferences(0);
+ assertEquals(record.channelId, fakeId);
+ assertEquals(record.watchedStartTime, time - duration);
+ assertEquals(record.duration, duration);
+ assertEquals(record, recordFromSharedPreferences);
+ }
+
+ @UiThreadTest
+ public void testCircularHistoryQueue() throws Exception {
+ startAndWaitForComplete();
+ final long startChannelId = 100000000;
+ long time = System.currentTimeMillis();
+ long duration = TimeUnit.MINUTES.toMillis(10);
+
+ int size = MAX_HISTORY_SIZE * 2;
+ for (int i = 0; i < size; ++i) {
+ Channel channel = new Channel.Builder().setId(startChannelId + i).build();
+ mWatchedHistoryManager.logChannelViewStop(channel, time + duration * i, duration);
+ }
+ for (int i = 0; i < MAX_HISTORY_SIZE; ++i) {
+ WatchedRecord record = mWatchedHistoryManager.getRecord(i);
+ WatchedRecord recordFromSharedPreferences =
+ mWatchedHistoryManager.getRecordFromSharedPreferences(i);
+ assertEquals(record, recordFromSharedPreferences);
+ assertEquals(record.channelId, startChannelId + size - 1 - i);
+ }
+ // Since the WatchedHistory is a circular queue, the value for 0 and maxHistorySize
+ // are same.
+ assertEquals(mWatchedHistoryManager.getRecordFromSharedPreferences(0),
+ mWatchedHistoryManager.getRecordFromSharedPreferences(MAX_HISTORY_SIZE));
+ }
+
+ @UiThreadTest
+ public void testWatchedRecordEquals() {
+ assertTrue(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 2, 3)));
+ assertFalse(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 2, 4)));
+ assertFalse(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(1, 4, 3)));
+ assertFalse(new WatchedRecord(1, 2, 3).equals(new WatchedRecord(4, 2, 3)));
+ }
+
+ @UiThreadTest
+ public void testEncodeDecodeWatchedRecord() throws Exception {
+ long fakeId = 100000000;
+ long time = System.currentTimeMillis();
+ long duration = TimeUnit.MINUTES.toMillis(10);
+ WatchedRecord record = new WatchedRecord(fakeId, time, duration);
+ WatchedRecord sameRecord = mWatchedHistoryManager.decode(
+ mWatchedHistoryManager.encode(record));
+ assertEquals(record, sameRecord);
+ }
+
+ private class TestWatchedHistoryManagerListener implements WatchedHistoryManager.Listener {
+ public CountDownLatch loadFinishedLatch = new CountDownLatch(1);
+
+ @Override
+ public void onLoadFinished() {
+ loadFinishedLatch.countDown();
+ }
+
+ @Override
+ public void onNewRecordAdded(WatchedRecord watchedRecord) { }
+ }
+}
diff --git a/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java b/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java
index 44026386..bf8fecde 100644
--- a/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java
+++ b/tests/unit/src/com/android/tv/recommendation/ChannelRecordTest.java
@@ -19,6 +19,8 @@ package com.android.tv.recommendation;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import com.android.tv.testing.Utils;
+
import java.util.Random;
import java.util.concurrent.TimeUnit;
@@ -38,7 +40,7 @@ public class ChannelRecordTest extends AndroidTestCase {
super.setUp();
mLatestWatchEndTimeMs = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
mChannelRecord = new ChannelRecord(getContext(), null, false);
- mRandom = RecommendationUtils.createTestRandom();
+ mRandom = Utils.createTestRandom();
}
public void testGetLastWatchEndTime_noHistory() {
diff --git a/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java b/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java
index febeeda1..0255947b 100644
--- a/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java
+++ b/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java
@@ -17,11 +17,11 @@
package com.android.tv.recommendation;
import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
import com.android.tv.data.Channel;
import com.android.tv.recommendation.RecommendationUtils.ChannelRecordSortedMapHelper;
import com.android.tv.recommendation.Recommender.Evaluator;
+import com.android.tv.testing.Utils;
import java.util.ArrayList;
import java.util.List;
@@ -47,7 +47,7 @@ public abstract class EvaluatorTestCase<T extends Evaluator> extends AndroidTest
mEvaluator = createEvaluator();
mEvaluator.setRecommender(mRecommender);
mChannelRecordSortedMap.setRecommender(mRecommender);
- mChannelRecordSortedMap.resetRandom(RecommendationUtils.createTestRandom());
+ mChannelRecordSortedMap.resetRandom(Utils.createTestRandom());
}
/**
diff --git a/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java b/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java
index 8e57a267..4a613995 100644
--- a/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java
+++ b/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java
@@ -17,21 +17,18 @@
package com.android.tv.recommendation;
import android.content.Context;
-import android.util.Log;
import com.android.tv.data.Channel;
+import com.android.tv.testing.Utils;
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;
@@ -39,23 +36,6 @@ 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}.
@@ -88,7 +68,7 @@ public class RecommendationUtils {
public static class ChannelRecordSortedMapHelper extends TreeMap<Long, ChannelRecord> {
private final Context mContext;
private Recommender mRecommender;
- private Random mRandom = createTestRandom();
+ private Random mRandom = Utils.createTestRandom();
public ChannelRecordSortedMapHelper(Context context) {
mContext = context;
diff --git a/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java b/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java
index 62c5b4ab..0ca67052 100644
--- a/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java
+++ b/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java
@@ -22,6 +22,7 @@ import android.test.suitebuilder.annotation.SmallTest;
import com.android.tv.data.Channel;
import com.android.tv.recommendation.RecommendationUtils.ChannelRecordSortedMapHelper;
+import com.android.tv.testing.Utils;
import java.util.ArrayList;
import java.util.Arrays;
@@ -78,7 +79,7 @@ public class RecommenderTest extends AndroidTestCase {
mChannelRecordSortedMap = new ChannelRecordSortedMapHelper(getContext());
mDataManager = RecommendationUtils
.createMockRecommendationDataManager(mChannelRecordSortedMap);
- mChannelRecordSortedMap.resetRandom(RecommendationUtils.createTestRandom());
+ mChannelRecordSortedMap.resetRandom(Utils.createTestRandom());
}
public void testRecommendChannels_includeRecommendedOnly_allChannelsHaveNoScore() {
diff --git a/tests/unit/src/com/android/tv/util/ImageCacheTest.java b/tests/unit/src/com/android/tv/util/ImageCacheTest.java
index 396372b2..74d5ef72 100644
--- a/tests/unit/src/com/android/tv/util/ImageCacheTest.java
+++ b/tests/unit/src/com/android/tv/util/ImageCacheTest.java
@@ -73,4 +73,4 @@ public class ImageCacheTest extends TestCase {
mImageCache.putIfNeeded( INFO_200);
assertSame("after", INFO_100, mImageCache.get(KEY));
}
-}
+} \ No newline at end of file
diff --git a/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java b/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.java
new file mode 100644
index 00000000..d44ffdef
--- /dev/null
+++ b/tests/unit/src/com/android/tv/util/MultiLongSparseArrayTest.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.test.MoreAsserts;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+import java.util.Collections;
+
+/**
+ * Tests for {@link MultiLongSparseArray}.
+ */
+@SmallTest
+public class MultiLongSparseArrayTest extends TestCase {
+
+ public void testEmpty() {
+ MultiLongSparseArray<String> sparseArray = new MultiLongSparseArray<>();
+ assertSame(Collections.EMPTY_SET, sparseArray.get(0));
+ }
+
+ public void testOneElement() {
+ MultiLongSparseArray<String> sparseArray = new MultiLongSparseArray<>();
+ sparseArray.put(0, "foo");
+ MoreAsserts.assertContentsInAnyOrder(sparseArray.get(0), "foo");
+ }
+
+ public void testTwoElements() {
+ MultiLongSparseArray<String> sparseArray = new MultiLongSparseArray<>();
+ sparseArray.put(0, "foo");
+ sparseArray.put(0, "bar");
+ MoreAsserts.assertContentsInAnyOrder(sparseArray.get(0), "foo", "bar");
+ }
+
+
+ public void testClearEmptyCache() {
+ MultiLongSparseArray<String> sparseArray = new MultiLongSparseArray<>();
+ sparseArray.clearEmptyCache();
+ assertEquals(0, sparseArray.getEmptyCacheSize());
+ sparseArray.put(0, "foo");
+ sparseArray.remove(0, "foo");
+ assertEquals(1, sparseArray.getEmptyCacheSize());
+ sparseArray.clearEmptyCache();
+ assertEquals(0, sparseArray.getEmptyCacheSize());
+ }
+
+ public void testMaxEmptyCacheSize() {
+ MultiLongSparseArray<String> sparseArray = new MultiLongSparseArray<>();
+ sparseArray.clearEmptyCache();
+ assertEquals(0, sparseArray.getEmptyCacheSize());
+ for (int i = 0; i <= MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT + 2; i++) {
+ sparseArray.put(i, "foo");
+ }
+ for (int i = 0; i <= MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT + 2; i++) {
+ sparseArray.remove(i, "foo");
+ }
+ assertEquals(MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT,
+ sparseArray.getEmptyCacheSize());
+ sparseArray.clearEmptyCache();
+ assertEquals(0, sparseArray.getEmptyCacheSize());
+ }
+
+ public void testReuseEmptySets() {
+ MultiLongSparseArray<String> sparseArray = new MultiLongSparseArray<>();
+ sparseArray.clearEmptyCache();
+ assertEquals(0, sparseArray.getEmptyCacheSize());
+ // create a bunch of sets
+ for (int i = 0; i <= MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT + 2; i++) {
+ sparseArray.put(i, "foo");
+ }
+ // remove them so they are all put in the cache.
+ for (int i = 0; i <= MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT + 2; i++) {
+ sparseArray.remove(i, "foo");
+ }
+ assertEquals(MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT,
+ sparseArray.getEmptyCacheSize());
+
+ // now create elements, that use the cached empty sets.
+ for (int i = 0; i < MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT; i++) {
+ sparseArray.put(10 + i, "bar");
+ assertEquals(MultiLongSparseArray.DEFAULT_MAX_EMPTIES_KEPT - i - 1,
+ sparseArray.getEmptyCacheSize());
+ }
+ }
+}
diff --git a/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java b/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.java
new file mode 100644
index 00000000..601ed661
--- /dev/null
+++ b/tests/unit/src/com/android/tv/util/UtilsTest_GetMultiAudioString.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.util;
+
+import android.content.Context;
+import android.media.tv.TvTrackInfo;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Tests for {@link com.android.tv.util.Utils#getMultiAudioString}.
+ */
+@SmallTest
+public class UtilsTest_GetMultiAudioString extends AndroidTestCase {
+ private static final String TRACK_ID = "test_track_id";
+ private static final int AUDIO_SAMPLE_RATE = 48000;
+
+ public void testAudioTrackLanguage() {
+ Context context = getContext();
+ assertEquals("Korean",
+ Utils.getMultiAudioString(context, createAudioTrackInfo("kor"), false));
+ assertEquals("English",
+ Utils.getMultiAudioString(context, createAudioTrackInfo("eng"), false));
+ assertEquals("Unknown language",
+ Utils.getMultiAudioString(context, createAudioTrackInfo(null), false));
+ // TODO: Check whether the following tests are expected.
+ assertEquals("", Utils.getMultiAudioString(context, createAudioTrackInfo(""), false));
+ assertEquals("abc", Utils.getMultiAudioString(context, createAudioTrackInfo("abc"), false));
+ }
+
+ public void testAudioTrackCount() {
+ Context context = getContext();
+ assertEquals("English",
+ Utils.getMultiAudioString(context, createAudioTrackInfo("eng", -1), false));
+ assertEquals("English",
+ Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 0), false));
+ assertEquals("English (mono)",
+ Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 1), false));
+ assertEquals("English (stereo)",
+ Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 2), false));
+ assertEquals("English (3 channels)",
+ Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 3), false));
+ assertEquals("English (4 channels)",
+ Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 4), false));
+ assertEquals("English (5 channels)",
+ Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 5), false));
+ assertEquals("English (5.1 surround)",
+ Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 6), false));
+ assertEquals("English (7 channels)",
+ Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 7), false));
+ assertEquals("English (7.1 surround)",
+ Utils.getMultiAudioString(context, createAudioTrackInfo("eng", 8), false));
+ }
+
+ public void testShowSampleRate() {
+ assertEquals("Korean (48kHz)",
+ Utils.getMultiAudioString(getContext(), createAudioTrackInfo("kor", 0), true));
+ assertEquals("Korean (7.1 surround, 48kHz)",
+ Utils.getMultiAudioString(getContext(), createAudioTrackInfo("kor", 8), true));
+ }
+
+ private static TvTrackInfo createAudioTrackInfo(String language) {
+ return createAudioTrackInfo(language, 0);
+ }
+
+ private static TvTrackInfo createAudioTrackInfo(String language, int channelCount) {
+ return new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, TRACK_ID)
+ .setLanguage(language).setAudioChannelCount(channelCount)
+ .setAudioSampleRate(AUDIO_SAMPLE_RATE).build();
+ }
+}
diff --git a/version.mk b/version.mk
index a59ac3c5..e4a0e140 100644
--- a/version.mk
+++ b/version.mk
@@ -48,19 +48,19 @@
base_version_major := 1
# Change this for each branch
-base_version_minor := 06
+base_version_minor := 07
# The date of the first commit checked in to the current branch
-base_version_since := 2015-07-22
+base_version_since := 2015-08-23
# 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)
+#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))))
# TODO http://b/22930520 version.mk sometimes goes backwards
-code_version_build=202
+code_version_build=007
#####################################################
#####################################################