[automerger skipped] Merge "Import translations. DO NOT MERGE ANYWHERE" into stage-aosp-master am: 529855066b -s ours
am skip reason: subject contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Car/libs/+/13164974
Change-Id: I15a58488a5386807bad4238b4e33d906f401c187
diff --git a/car-apps-common/res/drawable/control_bar_button_background.xml b/car-apps-common/res/drawable/control_bar_button_background.xml
index 2c6e1f2..09bd38a 100644
--- a/car-apps-common/res/drawable/control_bar_button_background.xml
+++ b/car-apps-common/res/drawable/control_bar_button_background.xml
@@ -17,6 +17,15 @@
~
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:state_pressed="true">
+ <shape android:shape="oval">
+ <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
+ <stroke android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
+ android:color="@color/car_ui_rotary_focus_pressed_stroke_color" />
+ <size android:width="@dimen/control_bar_button_background_radius"
+ android:height="@dimen/control_bar_button_background_radius"/>
+ </shape>
+ </item>
<item android:state_focused="true">
<shape android:shape="oval">
<solid android:color="@color/car_ui_rotary_focus_fill_color"/>
diff --git a/car-apps-common/res/drawable/hero_button_background.xml b/car-apps-common/res/drawable/hero_button_background.xml
index e036f4a..e5aeec5 100644
--- a/car-apps-common/res/drawable/hero_button_background.xml
+++ b/car-apps-common/res/drawable/hero_button_background.xml
@@ -14,6 +14,14 @@
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:state_pressed="true">
+ <shape android:shape="rectangle">
+ <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
+ <stroke android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
+ android:color="@color/car_ui_rotary_focus_pressed_stroke_color" />
+ <corners android:radius="@dimen/hero_button_corner_radius"/>
+ </shape>
+ </item>
<item android:state_focused="true">
<shape android:shape="rectangle">
<solid android:color="@color/car_ui_rotary_focus_fill_color"/>
diff --git a/car-apps-common/src/com/android/car/apps/common/ControlBar.java b/car-apps-common/src/com/android/car/apps/common/ControlBar.java
index 88d4dae..4181867 100644
--- a/car-apps-common/src/com/android/car/apps/common/ControlBar.java
+++ b/car-apps-common/src/com/android/car/apps/common/ControlBar.java
@@ -16,6 +16,8 @@
package com.android.car.apps.common;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -29,6 +31,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
@@ -90,6 +93,10 @@
private boolean mExpandEnabled;
// Callback for the expand/collapse button
private ExpandCollapseCallback mExpandCollapseCallback;
+ // The root of the transition animation.
+ private ViewGroup mTransitionRoot;
+ // Whether this control bar has focus.
+ private boolean mHasFocus;
// Default number of columns, if unspecified
private static final int DEFAULT_COLUMNS = 3;
@@ -160,6 +167,15 @@
mDefaultExpandCollapseView.setContentDescription(context.getString(
R.string.control_bar_expand_collapse_button));
mDefaultExpandCollapseView.setOnClickListener(v -> onExpandCollapse());
+
+ // Collapse the control bar when it is expanded and loses focus.
+ getViewTreeObserver().addOnGlobalFocusChangeListener((oldFocus, newFocus) -> {
+ boolean hasFocus = hasFocus();
+ if (mHasFocus && !hasFocus && mIsExpanded) {
+ onExpandCollapse();
+ }
+ mHasFocus = hasFocus;
+ });
}
private int getSlotIndex(@SlotPosition int slotPosition) {
@@ -310,12 +326,34 @@
.addTransition(new Fade())
.setDuration(animationDuration)
.setInterpolator(new FastOutSlowInInterpolator());
- TransitionManager.beginDelayedTransition(this, set);
+ maybeInitTransitionRoot();
+ TransitionManager.beginDelayedTransition(mTransitionRoot, set);
for (int i = 0; i < mNumExtraRowsInUse; i++) {
mRowsContainer.getChildAt(i).setVisibility(mIsExpanded ? View.VISIBLE : View.GONE);
}
}
+ private void maybeInitTransitionRoot() {
+ if (mTransitionRoot != null) {
+ return;
+ }
+ // During the control bar expanding/collapsing animation, the height of the control bar
+ // changes gradually. If the height of its ancestor is WRAP_CONTENT, the height of its
+ // ancestor will not change during the animation, causing janky animation. To fix it the
+ // animation should be played on the highest ancestor that wraps the control bar vertically.
+ mTransitionRoot = this;
+ ViewParent viewParent = getParent();
+ while (viewParent != null && viewParent instanceof ViewGroup) {
+ ViewGroup parent = (ViewGroup) viewParent;
+ if (parent.getLayoutParams().height == WRAP_CONTENT) {
+ mTransitionRoot = parent;
+ viewParent = parent.getParent();
+ } else {
+ break;
+ }
+ }
+ }
+
/**
* Returns the view assigned to the given row and column, after layout.
*
diff --git a/car-assist-client-lib/res/values-iw/strings.xml b/car-assist-client-lib/res/values-iw/strings.xml
index dc96640..510432d 100644
--- a/car-assist-client-lib/res/values-iw/strings.xml
+++ b/car-assist-client-lib/res/values-iw/strings.xml
@@ -16,6 +16,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="assist_action_failed_toast" msgid="3250146468076483714">"לא ניתן לבקש מ-Assistant לבצע פעולה!"</string>
+ <string name="assist_action_failed_toast" msgid="3250146468076483714">"לא ניתן לבקש מה-Assistant לבצע פעולה!"</string>
<string name="says" msgid="8575666015622916107">"רוצה להודיע כי"</string>
</resources>
diff --git a/car-broadcastradio-support/res/values-bs/strings.xml b/car-broadcastradio-support/res/values-bs/strings.xml
index e29e90e..42ff388 100644
--- a/car-broadcastradio-support/res/values-bs/strings.xml
+++ b/car-broadcastradio-support/res/values-bs/strings.xml
@@ -20,5 +20,5 @@
<string name="radio_fm_text" msgid="1973045042281933494">"FM"</string>
<string name="radio_dab_text" msgid="8456449462266648979">"DAB"</string>
<string name="program_list_text" msgid="4414150317304422313">"Stanice"</string>
- <string name="favorites_list_text" msgid="7829827713977109155">"Omiljeno"</string>
+ <string name="favorites_list_text" msgid="7829827713977109155">"Omiljeni"</string>
</resources>
diff --git a/car-broadcastradio-support/res/values-is/strings.xml b/car-broadcastradio-support/res/values-is/strings.xml
index b7e9135..1a2074a 100644
--- a/car-broadcastradio-support/res/values-is/strings.xml
+++ b/car-broadcastradio-support/res/values-is/strings.xml
@@ -20,5 +20,5 @@
<string name="radio_fm_text" msgid="1973045042281933494">"FM"</string>
<string name="radio_dab_text" msgid="8456449462266648979">"DAB"</string>
<string name="program_list_text" msgid="4414150317304422313">"Stöðvar"</string>
- <string name="favorites_list_text" msgid="7829827713977109155">"Uppáhald"</string>
+ <string name="favorites_list_text" msgid="7829827713977109155">"Eftirlæti"</string>
</resources>
diff --git a/car-broadcastradio-support/res/values-ky/strings.xml b/car-broadcastradio-support/res/values-ky/strings.xml
index ee67f46..4640491 100644
--- a/car-broadcastradio-support/res/values-ky/strings.xml
+++ b/car-broadcastradio-support/res/values-ky/strings.xml
@@ -20,5 +20,5 @@
<string name="radio_fm_text" msgid="1973045042281933494">"FM"</string>
<string name="radio_dab_text" msgid="8456449462266648979">"DAB"</string>
<string name="program_list_text" msgid="4414150317304422313">"Станциялар"</string>
- <string name="favorites_list_text" msgid="7829827713977109155">"Тандалмалар"</string>
+ <string name="favorites_list_text" msgid="7829827713977109155">"Сүйүктүүлөр"</string>
</resources>
diff --git a/car-broadcastradio-support/res/values-uz/strings.xml b/car-broadcastradio-support/res/values-uz/strings.xml
index bfb89d7..5ff7a60 100644
--- a/car-broadcastradio-support/res/values-uz/strings.xml
+++ b/car-broadcastradio-support/res/values-uz/strings.xml
@@ -20,5 +20,5 @@
<string name="radio_fm_text" msgid="1973045042281933494">"FM"</string>
<string name="radio_dab_text" msgid="8456449462266648979">"Raqamli radio"</string>
<string name="program_list_text" msgid="4414150317304422313">"Radiostansiyalar"</string>
- <string name="favorites_list_text" msgid="7829827713977109155">"Saralangan"</string>
+ <string name="favorites_list_text" msgid="7829827713977109155">"Saralanganlar"</string>
</resources>
diff --git a/car-media-common/res/drawable/fab_empty_foreground.xml b/car-media-common/res/drawable/fab_empty_foreground.xml
deleted file mode 100644
index d9fb901..0000000
--- a/car-media-common/res/drawable/fab_empty_foreground.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2018, The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<ripple
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:radius="0dp"
- android:color="@color/car_dark_blue_grey_700" />
diff --git a/car-media-common/res/layout/minimized_play_pause_stop_button_layout.xml b/car-media-common/res/layout/minimized_play_pause_stop_button_layout.xml
index 38389b0..1d70c72 100644
--- a/car-media-common/res/layout/minimized_play_pause_stop_button_layout.xml
+++ b/car-media-common/res/layout/minimized_play_pause_stop_button_layout.xml
@@ -20,11 +20,9 @@
android:focusable="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
- <!-- The invisible foreground ripple stops Android O from drawing an ugly square over the play button -->
<com.android.car.media.common.PlayPauseStopImageView
android:id="@+id/play_pause_stop"
style="@style/Widget.ActionButton"
- android:foreground="@drawable/fab_empty_foreground"
android:src="@drawable/ic_play_pause_stop_animated"/>
<ProgressBar
android:id="@+id/circular_progress_bar"
diff --git a/car-media-common/res/layout/play_pause_stop_button_layout.xml b/car-media-common/res/layout/play_pause_stop_button_layout.xml
index f7700fe..f61a821 100644
--- a/car-media-common/res/layout/play_pause_stop_button_layout.xml
+++ b/car-media-common/res/layout/play_pause_stop_button_layout.xml
@@ -20,11 +20,9 @@
android:focusable="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
- <!-- The invisible foreground ripple stops Android O from drawing an ugly square over the play button -->
<com.android.car.media.common.PlayPauseStopImageView
android:id="@+id/play_pause_stop"
style="@style/Widget.ActionButton"
- android:foreground="@drawable/fab_empty_foreground"
android:src="@drawable/ic_play_pause_stop_animated"/>
<ProgressBar
android:id="@+id/circular_progress_bar"
diff --git a/car-media-common/res/values-af/strings.xml b/car-media-common/res/values-af/strings.xml
index 81dc44c..c4efced 100644
--- a/car-media-common/res/values-af/strings.xml
+++ b/car-media-common/res/values-af/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Albumkunswerk"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Titelloos"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Iets is fout. Probeer later."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Kan dit nie op die oomblik doen nie"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Hierdie program kan dit nie doen nie"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Meld aan om hierdie program te gebruik"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premiumtoegang word vereis"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Luister tans op te veel toestelle"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Daardie inhoud word geblokkeer"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Kan nie daardie inhoud hier kry nie"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Speel reeds daardie inhoud"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Kan nie meer snitte oorslaan nie"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Kon nie voltooi word nie. Probeer weer."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Daar is niks anders op die waglys nie"</string>
</resources>
diff --git a/car-media-common/res/values-am/strings.xml b/car-media-common/res/values-am/strings.xml
index 1d89404..9888274 100644
--- a/car-media-common/res/values-am/strings.xml
+++ b/car-media-common/res/values-am/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"የአልበም ስነ ጥበብ"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"ርዕስ የለም"</string>
- <string name="default_error_message" msgid="4044331619453864482">"የሆነ ችግር አለ። በኋላ ይሞክሩ።"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"አሁን ይህን ማድረግ አይቻልም"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"ይህ መተግበሪያ ይህን ማድረግ አይችልም"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"ይህን መተግበሪያ ለመጠቀም በመለያ ይግቡ"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"ፕሪሚየም መዳረሻ ያስፈልጋል"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ከልክ በላይ ብዙ በሆኑ መሣሪያዎች ላይ በማዳመጥ ላይ"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ይዘቱ ታግዷል"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ይዘቱን እዚህ ማግኘት አልተቻለም"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"ይዘቱ አስቀድሞ በመጫወት ላይ"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ተጨማሪ ትራኮችን መዝለል አይቻልም"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"መጨረስ አልተቻለም። እንደገና ይሞክሩ።"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"ሌላ ምንም ነገር ወረፋ አልያዘም"</string>
</resources>
diff --git a/car-media-common/res/values-ar/strings.xml b/car-media-common/res/values-ar/strings.xml
index ca21255..609709e 100644
--- a/car-media-common/res/values-ar/strings.xml
+++ b/car-media-common/res/values-ar/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"صورة الألبوم"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"بلا عنوان"</string>
- <string name="default_error_message" msgid="4044331619453864482">"حدث خطأ. يُرجى المحاولة لاحقًا."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"يتعذّر على التطبيق تنفيذ هذا الإجراء في الوقت الحالي."</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"يتعذّر على التطبيق تنفيذ هذا الإجراء."</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"سجِّل دخولك لاستخدام هذا التطبيق."</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"مطلوب الحصول على إذن وصول بحساب مدفوع."</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"يتم الآن الاستماع على أجهزة أكثر من الحدّ المسموح به."</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"تم حظر هذا المحتوى."</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"لا يمكن الحصول على هذا المحتوى من هنا."</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"جارٍ تشغيل هذا المحتوى."</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"لا يمكن تخطّي المزيد من المقاطع الصوتية."</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"تعذَّر الإنهاء. يُرجى إعادة المحاولة."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"لم يتم وضع أي مقطع صوتي آخر في قائمة الانتظار."</string>
</resources>
diff --git a/car-media-common/res/values-as/strings.xml b/car-media-common/res/values-as/strings.xml
index 6bf03a7..1513a5e 100644
--- a/car-media-common/res/values-as/strings.xml
+++ b/car-media-common/res/values-as/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"এলবাম আৰ্ট"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"কোনো শিৰোনাম নাই"</string>
- <string name="default_error_message" msgid="4044331619453864482">"কিবা ভুল হ’ল। পাছত চেষ্টা কৰক।"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"সেইটো এই মুহূৰ্তত কৰিব নোৱাৰি"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"এই এপ্টোৱে সেইটো কৰিব নোৱাৰে"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"এই এপ্টো ব্যৱহাৰ কৰিবলৈ ছাইন ইন কৰক"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium এক্সেছৰ আৱশ্যক"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"বহুকেইটা ডিভাইচত শুনি আছে"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"সেই সমলটো অৱৰোধ কৰা আছে"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"সেই সমলটো ইয়াত পাব নোৱাৰি"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"সেই সমলটো ইতিমধ্যে প্লে’ হৈ আছে"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"আৰু ট্ৰেক এৰি যাব নোৱাৰি"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"সম্পূর্ণ কৰিব পৰা নগ’ল। পুনৰ চেষ্টা কৰক।"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"শাৰীত অন্য একো নাই"</string>
</resources>
diff --git a/car-media-common/res/values-az/strings.xml b/car-media-common/res/values-az/strings.xml
index 80509ae..9e8a836 100644
--- a/car-media-common/res/values-az/strings.xml
+++ b/car-media-common/res/values-az/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Albom təsviri"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Başlıq yoxdur"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Xəta baş verdi. Sonra cəhd edin."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Hazırda onu etmək olmur"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Bu tətbiq onu edə bilmir"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Bu tətbiqi istifadə etmək üçün daxil olun"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium giriş tələb olunur"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Çox cihazda dinləmə aşkarlanıb"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Bu məzmun bloklanıb"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Bu məzmunu əldə etmək mümkün deyil"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Hazırda bu məzmun oxudulur"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Başqa treki keçmək mümkün deyil"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Bitirmək mümkün olmadı. Yenidən cəhd edin."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Növbədə başqa heç nə yoxdur"</string>
</resources>
diff --git a/car-media-common/res/values-b+sr+Latn/strings.xml b/car-media-common/res/values-b+sr+Latn/strings.xml
index 26310be..60ea824 100644
--- a/car-media-common/res/values-b+sr+Latn/strings.xml
+++ b/car-media-common/res/values-b+sr+Latn/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Omot albuma"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Bez naslova"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Došlo je do greške. Probajte kasnije."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Trenutno ne može to da uradi"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Ova aplikacija ne može to da uradi"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Prijavite se da biste koristili ovu aplikaciju"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Potreban je premijum pristup"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Slušate na previše uređaja"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Taj sadržaj je blokiran"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Ne možete da dobijete taj sadržaj ovde"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Taj sadržaj se već reprodukuje"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Ne možete više da preskačete pesme"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Nismo uspeli da dovršimo radnju. Probajte opet."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ništa drugo nije stavljeno u redosled"</string>
</resources>
diff --git a/car-media-common/res/values-be/strings.xml b/car-media-common/res/values-be/strings.xml
index d782e53..4a98eab 100644
--- a/car-media-common/res/values-be/strings.xml
+++ b/car-media-common/res/values-be/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Вокладка альбома"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Без назвы"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Узнікла памылка. Паўтарыце спробу пазней."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Не ўдаецца выканаць гэты запыт"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Дзеянне недаступна ў гэтай праграме"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Каб выкарыстоўваць гэту праграму, увайдзіце"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Патрабуецца платны доступ"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Праслухоўванне адбываецца на занадта вялікай колькасці прылад"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Гэта змесціва заблакіравана"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"У вашым рэгіёне загрузіць гэта змесціва нельга"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Гэта змесціва ўжо прайграецца"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Прапускаць трэкі больш нельга"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Не ўдалося завяршыць. Паўтарыце спробу."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"У чарзе пуста"</string>
</resources>
diff --git a/car-media-common/res/values-bg/strings.xml b/car-media-common/res/values-bg/strings.xml
index 980e0fb..f595ed8 100644
--- a/car-media-common/res/values-bg/strings.xml
+++ b/car-media-common/res/values-bg/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Обложка на албума"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Няма заглавие"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Нещо не е наред. Опитайте по-късно."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Понастоящем тази заявка не може да се изпълни"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Приложението не може да изпълни тази заявка"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Влезте в профила си, за да използвате това приложение"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"За достъп се изисква платен профил"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Слуша се на твърде много устройства"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Това съдържание е блокирано"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Това съдържание не е налице за региона ви"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Това съдържание вече се възпроизвежда"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Не могат да се пропускат повече записи"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Не можа да завърши. Опитайте отново."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Няма нищо друго в опашката"</string>
</resources>
diff --git a/car-media-common/res/values-bn/strings.xml b/car-media-common/res/values-bn/strings.xml
index d02e247..c7366d7 100644
--- a/car-media-common/res/values-bn/strings.xml
+++ b/car-media-common/res/values-bn/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"অ্যালবাম আর্ট"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"কোনও শীর্ষক নেই"</string>
- <string name="default_error_message" msgid="4044331619453864482">"কোনও সমস্যা হয়েছে। পরে চেষ্টা করুন।"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"এই কাজটি এখন করা যাবে না"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"এই অ্যাপে এই কাজটি করা যাবে না"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"এই অ্যাপ ব্যবহার করতে সাইন-ইন করুন"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium অ্যাক্সেস থাকতে হবে"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"একাধিক ডিভাইসে শোনা হচ্ছে"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ওই কন্টেন্টটি ব্লক করা আছে"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ওই কন্টেন্টটি এখানে পাওয়া যাবে না"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"কন্টেন্টটি আগে থেকেই চলছে"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"আর কোনও ট্র্যাক এড়িয়ে যেতে পারবেন না"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"সম্পূর্ণ করা যায়নি। আবার চেষ্টা করুন।"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"সারিতে আর কিছু নেই"</string>
</resources>
diff --git a/car-media-common/res/values-bs/strings.xml b/car-media-common/res/values-bs/strings.xml
index 4f1df52..60ea824 100644
--- a/car-media-common/res/values-bs/strings.xml
+++ b/car-media-common/res/values-bs/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Omot albuma"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Bez naslova"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Nešto nije uredu. Pokušajte kasnije."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Taj zahtjev trenutno nije moguće izvršiti"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Ova aplikacija ne može izvršiti taj zahtjev"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Prijavite se da koristite ovu aplikaciju"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Potreban je premijum pristup"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Previše je uređaja na kojima se sluša"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Sadržaj je blokiran"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Nije moguće preuzeti taj sadržaj ovdje"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Reproduciranje tog sadržaja je već u toku"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Ne možete više preskakati numere"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Dovršavanje nije uspjelo. Pokušajte ponovo."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ništa više nije postavljeno u red čekanja"</string>
</resources>
diff --git a/car-media-common/res/values-ca/strings.xml b/car-media-common/res/values-ca/strings.xml
index 7a44b13..c672d9b 100644
--- a/car-media-common/res/values-ca/strings.xml
+++ b/car-media-common/res/values-ca/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Imatge de l\'àlbum"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Sense títol"</string>
- <string name="default_error_message" msgid="4044331619453864482">"S\'ha produït un error. Prova-ho més tard."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Ara mateix aquesta acció no es pot dur a terme"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"L\'aplicació no pot dur a terme aquesta acció"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Inicia la sessió per utilitzar aquesta aplicació"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Es requereix accés Premium"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"S\'està escoltant contingut en massa dispositius"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Aquest contingut està bloquejat"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"El contingut no està disponible en aquesta regió"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Aquest contingut ja s\'està reproduint"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"No es poden saltar més cançons"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"No s\'ha pogut acabar. Torna-ho a provar."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"No hi ha res més a la cua"</string>
</resources>
diff --git a/car-media-common/res/values-cs/strings.xml b/car-media-common/res/values-cs/strings.xml
index 0a45e6d..dfcffe0 100644
--- a/car-media-common/res/values-cs/strings.xml
+++ b/car-media-common/res/values-cs/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Obal alba"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Bez názvu"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Někde se stala chyba. Zkuste to později."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Tuto akci teď nelze provést"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Tuto akci aplikace nedokáže provést"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Chcete-li aplikaci použít, přihlaste se"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Je vyžadován prémiový přístup"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Poslech je aktivován v příliš mnoha zařízeních"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Obsah je blokován"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Tento obsah tu nelze načíst"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Tento obsah se už přehrává"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Další skladby nelze přeskočit"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Nelze dokončit. Zkuste to znovu."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ve frontě není nic dalšího"</string>
</resources>
diff --git a/car-media-common/res/values-da/strings.xml b/car-media-common/res/values-da/strings.xml
index a875184..1640516 100644
--- a/car-media-common/res/values-da/strings.xml
+++ b/car-media-common/res/values-da/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Albumgrafik"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Ingen titel"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Der er noget galt. Prøv senere."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Det er ikke muligt lige nu"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Det kan denne app ikke gøre"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Log ind for at bruge denne app"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Dette kræver en Premium-konto"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Du lytter på for mange enheder"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Indholdet er blokeret"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Der er ikke adgang til indholdet her"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Indholdet afspilles allerede"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Du kan ikke springe flere numre over"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Handlingen kunne ikke afsluttes. Prøv igen."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Der er ikke mere i køen"</string>
</resources>
diff --git a/car-media-common/res/values-de/strings.xml b/car-media-common/res/values-de/strings.xml
index b5894cb..7bafae6 100644
--- a/car-media-common/res/values-de/strings.xml
+++ b/car-media-common/res/values-de/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Albumcover"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Kein Titel"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Ein Fehler ist aufgetreten. Versuch es später noch mal."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Das ist gerade nicht möglich"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Diese App kann das nicht"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Damit du diese App verwenden kannst, musst du dich zuerst anmelden"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premiumzugriff erforderlich"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Es wird auf zu vielen Geräten gleichzeitig gestreamt"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Der Inhalt ist gesperrt"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Der Inhalt kann hier nicht abgerufen werden"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Der Inhalt wird bereits wiedergegeben"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Du kannst keine weiteren Titel überspringen"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Die Aktion konnte nicht abgeschlossen werden. Versuch es noch einmal."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Es ist sonst nichts in der Warteschlange"</string>
</resources>
diff --git a/car-media-common/res/values-el/strings.xml b/car-media-common/res/values-el/strings.xml
index 9bc13cd..0b5de6d 100644
--- a/car-media-common/res/values-el/strings.xml
+++ b/car-media-common/res/values-el/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Εξώφυλλο άλμπουμ"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Χωρίς τίτλο"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Παρουσιάστηκε κάποιο πρόβλημα. Δοκιμάστε αργότερα."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Δεν είναι δυνατή η εκτέλεση του αιτήματος αυτήν τη στιγμή."</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Δεν είναι δυνατή η εκτέλεση του αιτήματος από αυτήν την εφαρμογή."</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Συνδεθείτε, για να χρησιμοποιήσετε αυτήν την εφαρμογή."</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Απαιτείται premium πρόσβαση."</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Ακρόαση πάρα πολλών συσκευών"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Αυτό το περιεχόμενο είναι αποκλεισμένο."</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Δεν είναι δυνατή η λήψη αυτού του περιεχομένου εδώ."</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Γίνεται ήδη αναπαραγωγή αυτού του περιεχομένου."</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Δεν είναι δυνατή η παράβλεψη περισσότερων κομματιών."</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Δεν ήταν δυνατή η ολοκλήρωση. Δοκιμάστε ξανά."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Δεν υπάρχει κάτι άλλο στην ουρά."</string>
</resources>
diff --git a/car-media-common/res/values-en-rAU/strings.xml b/car-media-common/res/values-en-rAU/strings.xml
index b9c3337..98e3148 100644
--- a/car-media-common/res/values-en-rAU/strings.xml
+++ b/car-media-common/res/values-en-rAU/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Album Art"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"No title"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Something’s wrong. Try later."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Can’t do that at the moment"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"This app can’t do that"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Sign in to use this app"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium access required"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Listening on too many devices"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"That content is blocked"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Can’t get that content here"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Already playing that content"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Can’t skip any more tracks"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Couldn’t finish. Try again."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nothing else is queued up"</string>
</resources>
diff --git a/car-media-common/res/values-en-rCA/strings.xml b/car-media-common/res/values-en-rCA/strings.xml
index b9c3337..98e3148 100644
--- a/car-media-common/res/values-en-rCA/strings.xml
+++ b/car-media-common/res/values-en-rCA/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Album Art"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"No title"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Something’s wrong. Try later."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Can’t do that at the moment"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"This app can’t do that"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Sign in to use this app"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium access required"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Listening on too many devices"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"That content is blocked"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Can’t get that content here"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Already playing that content"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Can’t skip any more tracks"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Couldn’t finish. Try again."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nothing else is queued up"</string>
</resources>
diff --git a/car-media-common/res/values-en-rGB/strings.xml b/car-media-common/res/values-en-rGB/strings.xml
index b9c3337..98e3148 100644
--- a/car-media-common/res/values-en-rGB/strings.xml
+++ b/car-media-common/res/values-en-rGB/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Album Art"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"No title"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Something’s wrong. Try later."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Can’t do that at the moment"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"This app can’t do that"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Sign in to use this app"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium access required"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Listening on too many devices"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"That content is blocked"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Can’t get that content here"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Already playing that content"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Can’t skip any more tracks"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Couldn’t finish. Try again."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nothing else is queued up"</string>
</resources>
diff --git a/car-media-common/res/values-en-rIN/strings.xml b/car-media-common/res/values-en-rIN/strings.xml
index b9c3337..98e3148 100644
--- a/car-media-common/res/values-en-rIN/strings.xml
+++ b/car-media-common/res/values-en-rIN/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Album Art"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"No title"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Something’s wrong. Try later."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Can’t do that at the moment"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"This app can’t do that"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Sign in to use this app"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium access required"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Listening on too many devices"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"That content is blocked"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Can’t get that content here"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Already playing that content"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Can’t skip any more tracks"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Couldn’t finish. Try again."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nothing else is queued up"</string>
</resources>
diff --git a/car-media-common/res/values-en-rXC/strings.xml b/car-media-common/res/values-en-rXC/strings.xml
index 326c0ff..ecdf930 100644
--- a/car-media-common/res/values-en-rXC/strings.xml
+++ b/car-media-common/res/values-en-rXC/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Album Art"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"No Title"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Something’s wrong. Try later."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Can’t do that right now"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"This app can’t do that"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Sign in to use this app"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium access required"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Listening on too many devices"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"That content is blocked"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Can’t get that content here"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Already playing that content"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Can’t skip any more tracks"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Couldn’t finish. Try again."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nothing else is queued up"</string>
</resources>
diff --git a/car-media-common/res/values-es-rUS/strings.xml b/car-media-common/res/values-es-rUS/strings.xml
index 48d6213..18ba657 100644
--- a/car-media-common/res/values-es-rUS/strings.xml
+++ b/car-media-common/res/values-es-rUS/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Imagen del álbum"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Sin título"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Se produjo un error. Vuelve a intentarlo más tarde."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"No se puede realizar esa acción en este momento"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Esta app no puede realizar esa acción"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Accede para usar esta app"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Se requiere acceso Premium"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Se está escuchando contenido en demasiados dispositivos"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ese contenido está bloqueado"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"No se puede acceder a ese contenido en esta región"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ya se está reproduciendo ese contenido"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"No se pueden omitir más pistas"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"No se pudo completar la acción. Vuelve a intentarlo."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"No hay más contenido en la cola"</string>
</resources>
diff --git a/car-media-common/res/values-es/strings.xml b/car-media-common/res/values-es/strings.xml
index f70760a..18ba657 100644
--- a/car-media-common/res/values-es/strings.xml
+++ b/car-media-common/res/values-es/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Imagen del álbum"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Sin título"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Se ha producido un error. Inténtalo más tarde."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"No se puede hacer en estos momentos"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"No se puede hacer con esta aplicación"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Inicia sesión para utilizar esta aplicación"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Se necesita acceso premium"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Se está escuchando en demasiados dispositivos"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ese contenido está bloqueado"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Aquí no se puede reproducir ese contenido"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ya se está reproduciendo ese contenido"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"No se pueden saltar más pistas"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"No se ha podido finalizar. Inténtalo de nuevo."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"No hay nada más en la cola"</string>
</resources>
diff --git a/car-media-common/res/values-et/strings.xml b/car-media-common/res/values-et/strings.xml
index a5d7fc3..8ca4cf2 100644
--- a/car-media-common/res/values-et/strings.xml
+++ b/car-media-common/res/values-et/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Albumi kujundus"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Pealkiri puudub"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Midagi on valesti. Proovige hiljem."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Seda ei saa praegu teha"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"See rakendus ei saa seda teha"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Rakenduse kasutamiseks logige sisse"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Vaja on Premium-tasemel juurdepääsu"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Kuulatakse liiga paljudes seadmetes"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"See sisu on blokeeritud"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Seda sisu ei saa siin esitada"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Seda sisu juba esitatakse"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Rohkem lugusid ei saa vahele jätta"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Ei saanud lõpetada. Proovige uuesti."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Midagi muud pole järjekorras"</string>
</resources>
diff --git a/car-media-common/res/values-eu/strings.xml b/car-media-common/res/values-eu/strings.xml
index c989683..896aa2c 100644
--- a/car-media-common/res/values-eu/strings.xml
+++ b/car-media-common/res/values-eu/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Albumaren azala"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Izenik gabea"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Arazoren bat izan da. Saiatu geroago."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Ezin da egin halakorik une honetan"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Aplikazio honek ezin du egin halakorik"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Aplikazioa erabiltzeko, hasi saioa"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium-eko sarbidea behar da"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Gailu gehiegitatik jasotzen ari da soinua"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Eduki hori blokeatuta dago"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Ezin da eskuratu eduki hori lurralde honetan"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Dagoeneko ari da erreproduzitzen eduki hori"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Ezin da saltatu pista gehiagorik"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Ezin izan da amaitu ekintza. Saiatu berriro."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ez dago beste ezer ilaran"</string>
</resources>
diff --git a/car-media-common/res/values-fa/strings.xml b/car-media-common/res/values-fa/strings.xml
index b3168a0..24bac8e 100644
--- a/car-media-common/res/values-fa/strings.xml
+++ b/car-media-common/res/values-fa/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"عکس روی جلد آلبوم"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"بدون عنوان"</string>
- <string name="default_error_message" msgid="4044331619453864482">"مشکلی رخ داد. بعداً امتحان کنید."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"درحالحاضر انجام نمیشود"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"این برنامه نمیتواند این کار را انجام دهد"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"برای استفاده از این برنامه، به سیستم وارد شوید"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"دسترسی ممتاز لازم است"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"درحال گوش کردن به تعداد زیادی دستگاه"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"این محتوا مسدود شده است"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"نمیتوان این محتوا را در اینجا دریافت کرد"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"این محتوا از قبل درحال پخش است"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"از هیچ آهنگ دیگری نمیتوان رد شد"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"تکمیل نشد. دوباره امتحان کنید."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"هیچ مورد دیگری در صف پخش نیست"</string>
</resources>
diff --git a/car-media-common/res/values-fi/strings.xml b/car-media-common/res/values-fi/strings.xml
index c558794..0fcaaf5 100644
--- a/car-media-common/res/values-fi/strings.xml
+++ b/car-media-common/res/values-fi/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Albumin kansitaide"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Ei nimeä"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Jotain meni pieleen. Yritä myöhemmin."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Tämä ei juuri nyt onnistu"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Sovellus ei tue pyyntöä"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Kirjaudu sisään käyttääksesi tätä sovellusta"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Edellyttää premium-tilausta"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Kuuntelu käynnissä liian monella laitteella"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Tämä sisältö on estetty"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Sisältö ei ole saatavilla täällä"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Sisältöä toistetaan jo"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Enimmäismäärä kappaleita ohitettu"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Ei onnistunut. Yritä uudelleen."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ei muuta jonossa"</string>
</resources>
diff --git a/car-media-common/res/values-fr-rCA/strings.xml b/car-media-common/res/values-fr-rCA/strings.xml
index eb784b4..66e06e1 100644
--- a/car-media-common/res/values-fr-rCA/strings.xml
+++ b/car-media-common/res/values-fr-rCA/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Image de l\'album"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Aucun titre"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Une erreur s\'est produite. Réessayez plus tard."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Impossible d\'effectuer cette action pour le moment"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Cette application ne peut pas effectuer cette action"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Connectez-vous pour utiliser cette application"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Un accès payant est requis"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Écoute en cours sur trop d\'appareils"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ce contenu est bloqué"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Impossible d\'obtenir ce contenu ici"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ce contenu est déjà en cours de lecture"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Impossible de passer à d\'autres chansons"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Impossible de terminer l\'action. Réessayez."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Rien d\'autre n\'est dans la file d\'attente"</string>
</resources>
diff --git a/car-media-common/res/values-fr/strings.xml b/car-media-common/res/values-fr/strings.xml
index 8a86806..dfbc082 100644
--- a/car-media-common/res/values-fr/strings.xml
+++ b/car-media-common/res/values-fr/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Image de l\'album"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Sans titre"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Une erreur s\'est produite. Réessayez plus tard."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Impossible d\'effectuer cette opération pour le moment"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Cette application ne peut pas effectuer cette opération"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Connectez-vous pour utiliser cette application"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Accès Premium requis"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Écoute en cours sur trop d\'appareils"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ce contenu est bloqué"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Impossible d\'accéder à ce contenu ici"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ce contenu est déjà en cours de lecture"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Impossible de passer d\'autres titres"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Impossible de terminer l\'opération. Veuillez réessayer."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Aucun autre titre dans la file d\'attente"</string>
</resources>
diff --git a/car-media-common/res/values-gl/strings.xml b/car-media-common/res/values-gl/strings.xml
index a1a8fc5..6971016 100644
--- a/car-media-common/res/values-gl/strings.xml
+++ b/car-media-common/res/values-gl/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Portada de álbum"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Sen título"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Produciuse un problema. Téntao máis tarde."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Nestes momentos non se pode realizar a acción"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Esta aplicación non pode realizar a acción"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Inicia sesión para utilizar esta aplicación"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Requírese acceso premium"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Estase escoitando contido en demasiados dispositivos"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ese contido está bloqueado"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Aquí non se pode acceder a ese contido"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Xa se está reproducindo ese contido"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Non se poden saltar máis pistas"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Non se puido completar a acción. Téntao de novo."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Non hai nada máis na cola"</string>
</resources>
diff --git a/car-media-common/res/values-gu/strings.xml b/car-media-common/res/values-gu/strings.xml
index b59fa2a..569394f 100644
--- a/car-media-common/res/values-gu/strings.xml
+++ b/car-media-common/res/values-gu/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"આલ્બમ આર્ટ"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"કોઈ શીર્ષક નથી"</string>
- <string name="default_error_message" msgid="4044331619453864482">"કંઈક ખોટું થયું. થોડા સમય પછી પ્રયાસ કરો."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"તે અત્યારે કરી શકતા નથી"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"આ ઍપ તે કરી શકતી નથી"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"આ ઍપનો ઉપયોગ કરવા માટે સાઇન ઇન કરો"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"પ્રીમિયમ ઍક્સેસ જરૂરી છે"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ઘણા બધા ડિવાઇસ પર સાંભળી રહ્યાં છે"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"તે કન્ટેન્ટ બ્લૉક કર્યું છે"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"તે કન્ટેન્ટ અહીં મેળવી શકાતું નથી"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"તે કન્ટેન્ટ પહેલાંથી ચલાવી રહ્યાં છે"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"કોઈ વધુ ટ્રૅક છોડી શકાતા નથી"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"સમાપ્ત કરી શક્યાં નથી. ફરી પ્રયાસ કરો."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"બીજું કંઈ કતારમાં નથી"</string>
</resources>
diff --git a/car-media-common/res/values-hi/strings.xml b/car-media-common/res/values-hi/strings.xml
index 58fed76..f13088f 100644
--- a/car-media-common/res/values-hi/strings.xml
+++ b/car-media-common/res/values-hi/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"एल्बम आर्ट"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"कोई शीर्षक नहीं"</string>
- <string name="default_error_message" msgid="4044331619453864482">"कोई गड़बड़ी हुई. बाद में कोशिश करें."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"अभी नहीं किया जा सकता"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"इस ऐप्लिकेशन पर यह काम नहीं किया जा सकता"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"इस ऐप्लिकेशन का इस्तेमाल करने के लिए साइन इन करें"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"इसके लिए Premium का ऐक्सेस ज़रूरी है"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"आप इसे बहुत सारे डिवाइस पर चला रहे/रही हैं"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"कॉन्टेंट पर रोक लगा दी गई है"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"यह कॉन्टेंट यहां नहीं चलाया जा सकता"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"यह कॉन्टेंट पहले से ही चल रहा है"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"इससे ज़्यादा गाने नहीं छोड़े जा सकते"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"कार्रवाई पूरी नहीं हो सकी. फिर से कोशिश करें."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"सूची में और कुछ नहीं है"</string>
</resources>
diff --git a/car-media-common/res/values-hr/strings.xml b/car-media-common/res/values-hr/strings.xml
index 007109e..00e19d1 100644
--- a/car-media-common/res/values-hr/strings.xml
+++ b/car-media-common/res/values-hr/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Slika naslovnice albuma"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Bez naslova"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Nešto nije u redu. Pokušajte kasnije."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Trenutačno to ne možemo učiniti"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Ova aplikacija nema tu mogućnost"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Prijavite se za upotrebu te aplikacije."</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Potreban je pristup uz dodatnu naplatu"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Slušate na previše uređaja"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Taj je sadržaj blokiran"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Taj sadržaj nije dostupan ovdje"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Taj se sadržaj već reproducira"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Ne možete više preskakati pjesme"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Završavanje nije uspjelo. Pokušajte ponovo."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nema više ničeg u redu čekanja"</string>
</resources>
diff --git a/car-media-common/res/values-hu/strings.xml b/car-media-common/res/values-hu/strings.xml
index a56ebba..8631465 100644
--- a/car-media-common/res/values-hu/strings.xml
+++ b/car-media-common/res/values-hu/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Lemezborító"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Nincs cím"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Hiba történt. Próbálja újra később."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Jelenleg nem lehetséges"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Ez az alkalmazás nem képes erre"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Az alkalmazás használatához jelentkezzen be"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Prémium hozzáférés szükséges"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Túl sok eszköz van használatban"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"A tartalom le van tiltva"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Nem lehet betölteni a tartalmat"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"A tartalom lejátszása már folyamatban van"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Nem lehet több számot átugrani"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Nem sikerült befejezni. Próbálkozzon újra."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Semmi más nincs a sorban"</string>
</resources>
diff --git a/car-media-common/res/values-hy/strings.xml b/car-media-common/res/values-hy/strings.xml
index 5fd71be..197a067 100644
--- a/car-media-common/res/values-hy/strings.xml
+++ b/car-media-common/res/values-hy/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Ալբոմի շապիկ"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Անանուն"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Սխալ առաջացավ։ Փորձեք ավելի ուշ։"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Այս պահին հնարավոր չէ անել դա"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Հավելվածը չի աջակցում այդ գործողությունը"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Մուտք գործեք՝ հավելվածն օգտագործելու համար"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Անհրաժեշտ է պրեմիում հաշիվ"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Չափից շատ սարքերում է բովանդակություն նվագարկվում"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Բովանդակությունն արգելափակված է"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Բովանդակությունն անհասանելի է այս տարածաշրջանում"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Բովանդակությունն արդեն նվագարկվում է"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Այլևս հնարավոր չէ կատարումներ բաց թողնել"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Չհաջողվեց ավարտել։ Նորից փորձեք։"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Հերթացանկը դատարկ է"</string>
</resources>
diff --git a/car-media-common/res/values-in/strings.xml b/car-media-common/res/values-in/strings.xml
index 3c287f2..e331c0a 100644
--- a/car-media-common/res/values-in/strings.xml
+++ b/car-media-common/res/values-in/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Sampul Album"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Tanpa Judul"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Terjadi masalah. Coba nanti."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Tidak dapat melakukannya saat ini"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Aplikasi ini tidak dapat melakukannya"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Login untuk menggunakan aplikasi ini"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Perlu akses premium"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Terlalu banyak perangkat digunakan untuk mendengarkan"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Konten tersebut diblokir"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Tidak dapat memuat konten tersebut di sini"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Sedang memutar konten tersebut"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Tidak dapat melewati lagu lagi"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Tidak dapat diselesaikan. Coba lagi."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Tidak ada antrean lagi"</string>
</resources>
diff --git a/car-media-common/res/values-is/strings.xml b/car-media-common/res/values-is/strings.xml
index 34102e5..b26c4c6 100644
--- a/car-media-common/res/values-is/strings.xml
+++ b/car-media-common/res/values-is/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Plötuumslag"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Enginn titill"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Eitthvað er ekki í lagi. Reyndu síðar."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Ekki er hægt að framkvæma þetta eins og er"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Forritið getur ekki framkvæmt þetta"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Skráðu þig inn til að nota þetta forrit"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium-aðgangur er nauðsynlegur"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Hlustað í of mörgum tækjum"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Þetta efni er á bannlista"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Efnið er ekki tiltækt hér"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Þegar að spila þetta efni"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Ekki er hægt að sleppa fleiri lögum"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Ekki tókst að ljúka aðgerð. Reyndu aftur."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ekkert annað er í röð"</string>
</resources>
diff --git a/car-media-common/res/values-it/strings.xml b/car-media-common/res/values-it/strings.xml
index d0ef413..bab7f0a 100644
--- a/car-media-common/res/values-it/strings.xml
+++ b/car-media-common/res/values-it/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Copertina dell\'album"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Nessun titolo"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Si è verificato un problema. Prova più tardi."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Al momento non è possibile svolgere l\'operazione"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Questa app non supporta l\'azione richiesta"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Accedi per usare questa app"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"È necessario l\'accesso Premium"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Ascolto attivo su troppi dispositivi"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Contenuti bloccati"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Qui non è possibile scaricare questi contenuti"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Contenuti già in riproduzione"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Impossibile saltare altre tracce"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Impossibile completare. Riprova."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nient\'altro in coda"</string>
</resources>
diff --git a/car-media-common/res/values-iw/strings.xml b/car-media-common/res/values-iw/strings.xml
index a0118e1..9e12f85 100644
--- a/car-media-common/res/values-iw/strings.xml
+++ b/car-media-common/res/values-iw/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"עטיפת אלבום"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"ללא שם"</string>
- <string name="default_error_message" msgid="4044331619453864482">"משהו השתבש. יש לנסות מאוחר יותר."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"לא ניתן לבצע את הפעולה הזו כרגע"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"אי אפשר לבצע פעולה זו באפליקציה הזו"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"יש להיכנס לחשבון כדי להשתמש באפליקציה הזו"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"נדרשת גישה ל-Premium"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"מתבצעת האזנה ביותר מדי מכשירים"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"התוכן הזה חסום"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"לא ניתן לקבל את התוכן הזה כאן"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"התוכן הזה כבר פועל"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"לא ניתן לדלג יותר על טראקים"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"לא ניתן היה לסיים. יש לנסות שוב."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"אין עוד שירים ברשימת השירים"</string>
</resources>
diff --git a/car-media-common/res/values-ja/strings.xml b/car-media-common/res/values-ja/strings.xml
index b1a0da3..8d46866 100644
--- a/car-media-common/res/values-ja/strings.xml
+++ b/car-media-common/res/values-ja/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"アルバムアート"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"タイトルなし"</string>
- <string name="default_error_message" msgid="4044331619453864482">"エラーが発生しました。しばらくしてからお試しください。"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"現在、利用できません"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"このアプリではサポートされていません"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"このアプリを使用するにはログインしてください"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"プレミアム アカウントが必要です"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"再生しているデバイスが多すぎます"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"このコンテンツはブロックされています"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"このコンテンツはこの地域では利用できません"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"このコンテンツはすでに再生中です"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"これ以上トラックをスキップできません"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"完了できません。もう一度お試しください。"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"キューが一杯で追加できません"</string>
</resources>
diff --git a/car-media-common/res/values-ka/strings.xml b/car-media-common/res/values-ka/strings.xml
index 39b2549..1e467fe 100644
--- a/car-media-common/res/values-ka/strings.xml
+++ b/car-media-common/res/values-ka/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"ალბომის გარეკანი"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"უსათაურო"</string>
- <string name="default_error_message" msgid="4044331619453864482">"წარმოიქმნა შეფერხება. ცადეთ მოგვიანებით."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"ამჟამად ამის გაკეთება შეუძლებელია"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"ეს აპი ამას ვერ გააკეთებს"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"ამ აპით სარგებლობისთვის შედით სისტემაში"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"საჭიროა პრემიუმ ტიპის წვდომა"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"მოსმენა მიმდინარეობს მეტისმეტად ბევრ მოწყობილობაზე"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ეს კონტენტი დაბლოკილია"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ამ კონტენტს აქ ვერ მიიღებთ"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"ეს კონტენტი უკვე იკვრება"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"მეტ ჩანაწერს ვერ გამოტოვებთ"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"დასრულება ვერ მოხერხდა. ცადეთ ხელახლა."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"რიგში აღარაფერია"</string>
</resources>
diff --git a/car-media-common/res/values-kk/strings.xml b/car-media-common/res/values-kk/strings.xml
index 0225852..ec84f1a 100644
--- a/car-media-common/res/values-kk/strings.xml
+++ b/car-media-common/res/values-kk/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Альбом мұқабасы"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Атауы жоқ"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Бірдеңе дұрыс емес. Кейінірек қайталап көріңіз."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Мұны дәл қазір істеу мүмкін емес."</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Бұл қолданба мұны істей алмайды."</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Бұл қолданбаны пайдалану үшін есептік жазбаға кіріңіз."</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Премиум рұқсат қажет."</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Тым көп құрылғыларда тыңдалып жатыр."</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Бұл мазмұнға тыйым салынған."</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Мазмұнды алу мүмкін емес."</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Әлдеқашан ойнатылып жатыр."</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Басқа тректерді өткізіп жіберу мүмкін емес."</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Аяқталмады. Қайталап көріңіз."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Кезекке басқа ештеңе қойылмаған."</string>
</resources>
diff --git a/car-media-common/res/values-km/strings.xml b/car-media-common/res/values-km/strings.xml
index 95399ff..a9320b1 100644
--- a/car-media-common/res/values-km/strings.xml
+++ b/car-media-common/res/values-km/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"ក្របអាល់ប៊ុម"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"គ្មានចំណងជើងទេ"</string>
- <string name="default_error_message" msgid="4044331619453864482">"មានអ្វីមួយខុសប្រក្រតី។ សូមព្យាយាមនៅពេលក្រោយ។"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"មិនអាចធ្វើតាមសំណើនោះឥឡូវនេះបានទេ"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"កម្មវិធីនេះមិនអាចធ្វើតាមសំណើនោះបានទេ"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"ចូលគណនី ដើម្បីប្រើកម្មវិធីនេះ"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"តម្រូវឱ្យមានការចូលប្រើលំដាប់ខ្ពស់"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"កំពុងស្ដាប់នៅលើឧបករណ៍ច្រើនពេក"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ខ្លឹមសារនោះត្រូវបានទប់ស្កាត់"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"មិនអាចយកខ្លឹមសារនោះនៅទីនេះបានទេ"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"កំពុងចាក់ខ្លឹមសារនោះស្រាប់ហើយ"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"មិនអាចរំលងចម្រៀងបានទៀតទេ"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"មិនអាចបញ្ចប់បានទេ។ សូមព្យាយាមម្ដងទៀត។"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"មិនមានអ្វីផ្សេងទៀតនៅក្នុងជួរទេ"</string>
</resources>
diff --git a/car-media-common/res/values-kn/strings.xml b/car-media-common/res/values-kn/strings.xml
index e690790..4a02cfd 100644
--- a/car-media-common/res/values-kn/strings.xml
+++ b/car-media-common/res/values-kn/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"ಆಲ್ಬಮ್ ಕಲೆ"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"ಯಾವುದೇ ಶೀರ್ಷಿಕೆಯಿಲ್ಲ"</string>
- <string name="default_error_message" msgid="4044331619453864482">"ಏನೋ ತಪ್ಪಾಗಿದೆ. ನಂತರ ಪ್ರಯತ್ನಿಸಿ."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"ಸದ್ಯಕ್ಕೆ ಅದನ್ನು ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"ಈ ಆ್ಯಪ್ನಿಂದ ಅದನ್ನು ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"ಈ ಆ್ಯಪ್ ಬಳಸಲು ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"ಪ್ರೀಮಿಯಂ ಪ್ರವೇಶದ ಅಗತ್ಯವಿದೆ"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ಹಲವಾರು ಸಾಧನಗಳಲ್ಲಿ ಆಲಿಸಲಾಗುತ್ತಿದೆ"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ವಿಷಯವನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ಆ ವಿಷಯವನ್ನು ಇಲ್ಲಿ ಪಡೆದುಕೊಳ್ಳಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"ಈಗಾಗಲೇ ಆ ವಿಷಯವನ್ನು ಪ್ಲೇ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ಇನ್ನು ಮುಂದೆ ಟ್ರ್ಯಾಕ್ಗಳನ್ನು ಸ್ಕಿಪ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"ಪೂರ್ಣಗೊಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"ಸರದಿಯಲ್ಲಿ ಯಾವುದು ಬಾಕಿ ಉಳಿದಿಲ್ಲ"</string>
</resources>
diff --git a/car-media-common/res/values-ko/strings.xml b/car-media-common/res/values-ko/strings.xml
index d50fd0f..568f6de 100644
--- a/car-media-common/res/values-ko/strings.xml
+++ b/car-media-common/res/values-ko/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"앨범아트"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"제목 없음"</string>
- <string name="default_error_message" msgid="4044331619453864482">"문제가 발생했습니다. 나중에 다시 시도해 주세요."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"지금은 요청한 작업을 할 수 없습니다."</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"요청한 작업이 이 앱에서 지원되지 않습니다."</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"이 앱을 사용하려면 로그인하세요."</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"프리미엄 액세스 권한이 필요합니다."</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"너무 많은 기기에서 스트리밍하고 있습니다."</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"차단된 콘텐츠입니다."</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"이 지역에서 재생할 수 없는 콘텐츠입니다."</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"요청한 콘텐츠가 이미 재생 중입니다."</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"트랙을 더 이상 건너뛸 수 없습니다."</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"완료할 수 없습니다. 다시 시도해 주세요."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"현재 재생목록이 비어있습니다."</string>
</resources>
diff --git a/car-media-common/res/values-ky/strings.xml b/car-media-common/res/values-ky/strings.xml
index 8ca5ae0..5dd1ccc 100644
--- a/car-media-common/res/values-ky/strings.xml
+++ b/car-media-common/res/values-ky/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Альбом мукабасы"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Аталышы жок"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Бир жерден ката кетти. Бир аздан кийин кайталап көрүңүз."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Аны азыр аткаруу мүмкүн эмес"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Бул колдонмо аны аткара албайт"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Бул колдонмону пайдалануу үчүн аккаунтуңузга кириңиз"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Артыкчылыктуу кирүү мүмкүнчүлүгү талап кылынат"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Өтө көп түзмөк угулууда"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ал мазмун бөгөттөлгөн"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Ал мазмунду алуу мүмкүн эмес"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ал мазмун ойнотулууда"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Эми тректерди өткөрүп жиберүүгө болбойт"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Аягына чыккан жок. Кайталап көрүңүз."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Кезекте эч нерсе жок"</string>
</resources>
diff --git a/car-media-common/res/values-lo/strings.xml b/car-media-common/res/values-lo/strings.xml
index 7e92949..aee4892 100644
--- a/car-media-common/res/values-lo/strings.xml
+++ b/car-media-common/res/values-lo/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"ໜ້າປົກອະລະບ້ຳ"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"ບໍ່ມີຊື່"</string>
- <string name="default_error_message" msgid="4044331619453864482">"ມີບ່າງຢ່າງຜິດພາດ. ລອງໃໝ່ພາຍຫຼັງ."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"ບໍ່ສາມາດເຮັດໄດ້ຕອນນີ້"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"ແອັບນີ້ບໍ່ສາມາດເຮັດສິ່ງນັ້ນໄດ້"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"ເຂົ້າສູ່ລະບົບເພື່ອໃຊ້ແອັບນີ້"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"ຕ້ອງມີສິດເຂົ້າເຖິງລະດັບພຣີມຽມ"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ກຳລັງຟັງຢູ່ໃນຫຼາຍອຸປະກອນເກີນໄປ"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ເນື້ອຫານັ້ນຖືກບລັອກໄວ້"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ບໍ່ສາມາດຮັບເນື້ອຫານັ້ນຢູ່ບ່ອນນີ້"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"ຫຼິ້ນເນື້ອຫານັ້ນແລ້ວ"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ຂ້າມເພງບໍ່ໄດ້ອີກແລ້ວ"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"ບໍ່ສາມາດສຳເລັດໄດ້. ລອງໃໝ່."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"ບໍ່ມີລາຍການອື່ນໃນຄິວ"</string>
</resources>
diff --git a/car-media-common/res/values-lt/strings.xml b/car-media-common/res/values-lt/strings.xml
index be85d02..b4f1926 100644
--- a/car-media-common/res/values-lt/strings.xml
+++ b/car-media-common/res/values-lt/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Albumo viršelis"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Nėra pavadinimo"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Kažkas nepavyko. Bandykite vėliau."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Dabar negalima atlikti to veiksmo"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Ši programa negali atlikti to veiksmo"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Prisijunkite, kad galėtumėte naudoti šią programą"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Būtina mokama prieiga"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Klausoma naudojant per daug įrenginių"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Tas turinys užblokuotas"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Negalima gauti to turinio čia"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Tas turinys jau leidžiamas"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Daugiau takelių praleisti nebegalima"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Nepavyko užbaigti. Bandykite dar kartą."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Eilėje nieko nebėra"</string>
</resources>
diff --git a/car-media-common/res/values-lv/strings.xml b/car-media-common/res/values-lv/strings.xml
index 754a59d..d4b6044 100644
--- a/car-media-common/res/values-lv/strings.xml
+++ b/car-media-common/res/values-lv/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Albuma noformējums"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Bez nosaukuma"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Radās problēma. Mēģiniet vēlāk."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Pašlaik nevar veikt šo darbību."</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Šo darbību nevar veikt šajā lietotnē."</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Pierakstieties, lai izmantotu šo lietotni."</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Nepieciešama maksas piekļuve."</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Klausīšanās notiek pārāk daudz ierīcēs."</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Šis saturs ir bloķēts."</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Nevar šeit parādīt šo saturu."</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Šis saturs jau tiek atskaņots."</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Vairs nevar izlaist nevienu ierakstu."</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Nevarēja pabeigt. Mēģiniet vēlreiz."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Rindā vairs nav nekā cita."</string>
</resources>
diff --git a/car-media-common/res/values-mk/strings.xml b/car-media-common/res/values-mk/strings.xml
index 5e71b5a..d46c551 100644
--- a/car-media-common/res/values-mk/strings.xml
+++ b/car-media-common/res/values-mk/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Корица на албум"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Без наслов"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Нешто не е во ред. Обидете се подоцна."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Не може да се направи тоа во моментов"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Апликацијава не може да го направи тоа"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Најавете се за да ја користите апликацијава"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Потребен е пристап со Premium"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Се слуша на премногу уреди"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Таа содржина е блокирана"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Не може да се добие таа содржина тука"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Веќе е пуштена таа содржина"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Не може да прескокне повеќе песни"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Не може да заврши. Обидете се повторно."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Веќе ништо не чека на ред"</string>
</resources>
diff --git a/car-media-common/res/values-ml/strings.xml b/car-media-common/res/values-ml/strings.xml
index edcd0e1..cd1743b 100644
--- a/car-media-common/res/values-ml/strings.xml
+++ b/car-media-common/res/values-ml/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"ആൽബം ആർട്ട്"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"പേരില്ല"</string>
- <string name="default_error_message" msgid="4044331619453864482">"എന്തോ കുഴപ്പമുണ്ട്. പിന്നീട് ശ്രമിക്കുക."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"അത് ഇപ്പോൾ ചെയ്യാനാകില്ല"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"ഈ ആപ്പിന് അത് ചെയ്യാനാകില്ല"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"ഈ ആപ്പ് ഉപയോഗിക്കാൻ സെെൻ ഇൻ ചെയ്യുക"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"പ്രീമിയം ആക്സസ് ആവശ്യമാണ്"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"നിരവധി ഉപകരണങ്ങളിൽ കേൾക്കുന്നു"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ആ ഉള്ളടക്കം ബ്ലോക്ക് ചെയ്തിരിക്കുന്നു"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ആ ഉള്ളടക്കം ഇവിടെ ലഭ്യമല്ല"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"ആ ഉള്ളടക്കം നിലവിൽ പ്ലേ ചെയ്യുകയാണ്"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ഇനിയും ട്രാക്കുകൾ ഒഴിവാക്കാനാകില്ല"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"പൂർത്തിയാക്കാനായില്ല. വീണ്ടും ശ്രമിക്കുക."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"മറ്റൊന്നും ക്യൂവിലില്ല"</string>
</resources>
diff --git a/car-media-common/res/values-mn/strings.xml b/car-media-common/res/values-mn/strings.xml
index b9cb37c..0cbec6a 100644
--- a/car-media-common/res/values-mn/strings.xml
+++ b/car-media-common/res/values-mn/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Цомгийн зураг"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Гарчиг алга"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Алдаа гарлаа. Дараа оролдоно уу."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Яг одоо үүнийг хийх боломжгүй"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Энэ апп үүнийг хийх боломжгүй"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Энэ аппыг ашиглахын тулд нэвтэрнэ үү"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium хандалт шаардлагатай"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Хэт олон төхөөрөмж дээр сонсож байна"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Энэ контентыг блоклосон байна"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Энэ контентыг энд авах боломжгүй"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Энэ контентыг аль хэдийн тоглуулж байна"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Бичлэг дахин алгасах боломжгүй"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Дуусгаж чадсангүй. Дахин оролдоно уу."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Дараалалд өөр юу ч алга"</string>
</resources>
diff --git a/car-media-common/res/values-mr/strings.xml b/car-media-common/res/values-mr/strings.xml
index 27547ba..450fddd 100644
--- a/car-media-common/res/values-mr/strings.xml
+++ b/car-media-common/res/values-mr/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"अल्बम कला"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"शीर्षक नाही"</string>
- <string name="default_error_message" msgid="4044331619453864482">"काहीतरी चूक झाली. नंतर प्रयत्न करा."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"ते आता करू शकत नाही"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"हे ॲप ते करू शकत नाही"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"हे ॲप वापरण्यासाठी साइन इन करा"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"प्रीमियम ॲक्सेस आवश्यक आहे"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"खूप जास्त डिव्हाइसवर ऐकले जात आहे"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"तो आशय ब्लॉक केलेला आहे"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"तो आशय येथे मिळवू शकत नाही"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"तो आशय आधीपासून प्ले करत आहे"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"आणखी ट्रॅक वगळू शकत नाही"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"पूर्ण करता आले नाही. पुन्हा प्रयत्न करा."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"इतर काहीही क्यू केलेले नाही"</string>
</resources>
diff --git a/car-media-common/res/values-ms/strings.xml b/car-media-common/res/values-ms/strings.xml
index 3a16397..f5660bf 100644
--- a/car-media-common/res/values-ms/strings.xml
+++ b/car-media-common/res/values-ms/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Seni Album"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Tiada Tajuk"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Ada yang tidak kena. Cuba nanti."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Tidak dapat berbuat demikian sekarang"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Apl ini tidak dapat berbuat demikian"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Log masuk untuk menggunakan apl ini"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Akses premium diperlukan"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Mendengar pada terlalu banyak peranti"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Kandungan itu disekat"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Tidak boleh mendapatkan kandungan itu di sini"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Sudah pun memainkan kandungan itu"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Tidak boleh melangkau apa-apa lagu lagi"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Tidak dapat diselesaikan. Cuba lagi."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Tiada lagu lain dalam baris gilir"</string>
</resources>
diff --git a/car-media-common/res/values-my/strings.xml b/car-media-common/res/values-my/strings.xml
index f4723b0..e717f45 100644
--- a/car-media-common/res/values-my/strings.xml
+++ b/car-media-common/res/values-my/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"အယ်လ်ဘမ်ပုံ"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"ခေါင်းစဉ် မရှိပါ"</string>
- <string name="default_error_message" msgid="4044331619453864482">"တစ်ခုခု မှားနေသည်။ နောက်မှ စမ်းကြည့်ပါ။"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"၎င်းကို ယခု မပြုလုပ်နိုင်ပါ"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"ဤအက်ပ်က ၎င်းကို မပြုလုပ်နိုင်ပါ"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"ဤအက်ပ်အသုံးပြုရန် လက်မှတ်ထိုးဝင်ပါ"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"ပရီမီယံ အသုံးပြုခွင့် လိုအပ်သည်"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"စက်ပစ္စည်းအများအပြားတွင် နားထောင်နေသည်"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ထိုအကြောင်းအရာကို ပိတ်ထားသည်"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ထိုအကြောင်းအရာကို ဤနေရာတွင် မရရှိနိုင်ပါ"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"ထိုအကြောင်းအရာကို ဖွင့်နေပြီဖြစ်သည်"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"နောက်ထပ်သီချင်းပုဒ်များ ကျော်၍မရတော့ပါ"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"အပြီးသတ်၍ မရပါ။ ထပ်စမ်းကြည့်ပါ။"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"အခြားစီထားသည်များ မရှိပါ"</string>
</resources>
diff --git a/car-media-common/res/values-nb/strings.xml b/car-media-common/res/values-nb/strings.xml
index 878beb2..b01654e 100644
--- a/car-media-common/res/values-nb/strings.xml
+++ b/car-media-common/res/values-nb/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Albumgrafikk"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Ingen tittel"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Noe gikk galt. Prøv senere."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Kan ikke gjøre det akkurat nå"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Denne appen kan ikke gjøre det"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Logg på for å bruke denne appen"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premiumtilgang kreves"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Du lytter på for mange enheter"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Innholdet er blokkert"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Innholdet er ikke tilgjengelig her"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Du spiller allerede av innholdet"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Kan ikke hoppe over flere spor"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Kunne ikke fullføre. Prøv på nytt."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ingenting annet står i køen"</string>
</resources>
diff --git a/car-media-common/res/values-ne/strings.xml b/car-media-common/res/values-ne/strings.xml
index 5eed37d..8e2bf79 100644
--- a/car-media-common/res/values-ne/strings.xml
+++ b/car-media-common/res/values-ne/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"एल्बम आर्ट"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"शीर्षक छैन"</string>
- <string name="default_error_message" msgid="4044331619453864482">"केही चिज गडबड छ। पछि प्रयास गर्नुहोस्।"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"त्यो कार्य अहिले नै गर्न सकिँदैन"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"यस अनुप्रयोगले उक्त कार्य गर्न सक्दैन"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"यो एप प्रयोग गर्न साइन इन गर्नुहोस्"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"यसका लागि प्रिमियम खातामाथिको पहुँच आवश्यक हुन्छ"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"अत्यधिक यन्त्रहरूबाट सुनिँदै छ"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"उक्त सामग्री ब्लक गरिएको छ"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"उक्त सामग्री यहाँ प्राप्त गर्न सकिँदैन"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"उक्त सामग्री प्ले भइरहेको छ"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"योभन्दा धेरै गीतहरू स्किप गर्न सकिँदैन"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"कार्य पूरा गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"सूचीमा अरू केही पनि छैन"</string>
</resources>
diff --git a/car-media-common/res/values-nl/strings.xml b/car-media-common/res/values-nl/strings.xml
index e8acdbc..8259676 100644
--- a/car-media-common/res/values-nl/strings.xml
+++ b/car-media-common/res/values-nl/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Albumhoes"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Geen titel"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Er is iets misgegaan. Probeer het later opnieuw."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Kan dat nu niet doen"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Deze app kan dat niet doen"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Log in om deze app te gebruiken"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium toegang vereist"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Er wordt op te veel apparaten geluisterd"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Die content is geblokkeerd"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Kan die content hier niet krijgen"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Die content wordt al afgespeeld"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Kan geen nummers meer overslaan"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Kan niet voltooien. Probeer het opnieuw."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Er staat niets anders in de wachtrij"</string>
</resources>
diff --git a/car-media-common/res/values-or/strings.xml b/car-media-common/res/values-or/strings.xml
index e503cd8..a714293 100644
--- a/car-media-common/res/values-or/strings.xml
+++ b/car-media-common/res/values-or/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"ଆଲବମ୍ ଆର୍ଟ"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"କୌଣସି ଟାଇଟେଲ୍ ନାହିଁ"</string>
- <string name="default_error_message" msgid="4044331619453864482">"କିଛି ସମସ୍ୟା ଅଛି। ପରେ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"ବର୍ତ୍ତମାନ ତାହା କରାଯାଇପାରିବ ନାହିଁ"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"ଏହି ଆପ୍ ତାହା କରିପାରିବ ନାହିଁ"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"ଏହି ଆପ୍ ବ୍ୟବହାର କରିବାକୁ ସାଇନ୍ ଇନ୍ କରନ୍ତୁ"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"ପ୍ରିମିୟମ୍ ଆକ୍ସେସ୍ ଆବଶ୍ୟକ"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ଅନେକ ଡିଭାଇସରେ ଶୁଣାଯାଉଛି"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ସେହି ବିଷୟବସ୍ତୁକୁ ବ୍ଲକ୍ କରାଯାଇଛି"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ଏଠାରେ ସେହି ବିଷୟବସ୍ତୁ ପାଇପାରିବେ ନାହିଁ"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"ପୂର୍ବରୁ ସେହି ବିଷୟବସ୍ତୁ ଚଲାଯାଉଛି"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ଆଉ ଅଧିକ ଟ୍ରାକକୁ ବାଦ୍ ଦିଆଯାଇପାରିବ ନାହିଁ"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"ସମାପ୍ତ କରାଯାଇପାରିଲା ନାହିଁ। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"ଧାଡ଼ିରେ ଆଉ କିଛି ନାହିଁ"</string>
</resources>
diff --git a/car-media-common/res/values-pa/strings.xml b/car-media-common/res/values-pa/strings.xml
index 5d02f20..e3a607b 100644
--- a/car-media-common/res/values-pa/strings.xml
+++ b/car-media-common/res/values-pa/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"ਐਲਬਮ ਕਲਾ"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"ਕੋਈ ਸਿਰਲੇਖ ਨਹੀਂ"</string>
- <string name="default_error_message" msgid="4044331619453864482">"ਕੋਈ ਗੜਬੜ ਹੈ। ਬਾਅਦ ਵਿੱਚ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"ਫਿਲਹਾਲ ਇਹ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"ਇਹ ਐਪ ਇਹ ਨਹੀਂ ਕਰ ਸਕਦੀ"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"ਇਸ ਐਪ ਨੂੰ ਵਰਤਣ ਲਈ ਸਾਈਨ-ਇਨ ਕਰੋ"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"ਪ੍ਰੀਮੀਅਮ ਪਹੁੰਚ ਦੀ ਲੋੜ ਹੈ"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ਬਹੁਤ ਜ਼ਿਆਦਾ ਡੀਵਾਈਸਾਂ \'ਤੇ ਸੁਣਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ਇਸ ਸਮੱਗਰੀ ਨੂੰ ਬਲਾਕ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ਇੱਥੇ ਇਹ ਸਮੱਗਰੀ ਨਹੀਂ ਮਿਲ ਸਕਦੀ"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"ਇਹ ਸਮੱਗਰੀ ਪਹਿਲਾਂ ਹੀ ਚੱਲ ਰਹੀ ਹੈ"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ਕਿਸੇ ਹੋਰ ਟਰੈਕ ਨੂੰ ਛੱਡਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"ਪੂਰਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"ਹੋਰ ਕੁਝ ਵੀ ਕਤਾਰਬੱਧ ਨਹੀਂ ਹੈ"</string>
</resources>
diff --git a/car-media-common/res/values-pl/strings.xml b/car-media-common/res/values-pl/strings.xml
index 1bdcf9a..fd00ae5 100644
--- a/car-media-common/res/values-pl/strings.xml
+++ b/car-media-common/res/values-pl/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Okładka albumu"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Bez tytułu"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Coś poszło nie tak. Spróbuj później."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Teraz nie można tego zrobić"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Ta aplikacja nie ma takiej funkcji"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Zaloguj się, by używać tej aplikacji"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Wymagany jest dostęp na poziomie premium"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Słuchasz na zbyt wielu urządzeniach"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Te materiały są zablokowane"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Te materiały są niedostępne w tym regionie"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Te materiały są już odtwarzane"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Nie można pominąć większej liczby utworów"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Nie udało się dokończyć. Spróbuj ponownie."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nie ma już nic w kolejce"</string>
</resources>
diff --git a/car-media-common/res/values-pt-rPT/strings.xml b/car-media-common/res/values-pt-rPT/strings.xml
index ab6e0ff..2ae65bf 100644
--- a/car-media-common/res/values-pt-rPT/strings.xml
+++ b/car-media-common/res/values-pt-rPT/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Imagem do álbum"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Sem título"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Ocorreu um problema. Tente mais tarde."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Não é possível responder a esse pedido neste momento."</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Esta app não consegue responder a esse pedido."</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Inicie sessão para utilizar esta app."</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Acesso premium necessário."</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"A ouvir em demasiados dispositivos."</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Esse conteúdo está bloqueado."</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Não é possível obter esse conteúdo aqui."</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Esse conteúdo já está em reprodução."</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Não é possível ignorar mais faixas."</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Não foi possível terminar. Tente novamente."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Não existem mais elementos em fila."</string>
</resources>
diff --git a/car-media-common/res/values-pt/strings.xml b/car-media-common/res/values-pt/strings.xml
index 2a295f8..1b39a7b 100644
--- a/car-media-common/res/values-pt/strings.xml
+++ b/car-media-common/res/values-pt/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Arte do álbum"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Sem título"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Algo deu errado. Tente mais tarde."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Não é possível fazer isso no momento"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Não é possível fazer isso com este app"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Faça login para usar este app"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"É necessário ter acesso Premium"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Ouvindo em muitos dispositivos"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Esse conteúdo está bloqueado"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Não é possível usar esse conteúdo aqui"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Esse conteúdo já está tocando"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Não é possível pular mais faixas"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Não foi possível concluir esta ação. Tente novamente."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Não há mais nada na fila"</string>
</resources>
diff --git a/car-media-common/res/values-ro/strings.xml b/car-media-common/res/values-ro/strings.xml
index 3bcb08d..38c53cc 100644
--- a/car-media-common/res/values-ro/strings.xml
+++ b/car-media-common/res/values-ro/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Grafica albumului"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Fără titlu"</string>
- <string name="default_error_message" msgid="4044331619453864482">"A apărut o problemă. Încercați mai târziu."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Acțiunea nu poate fi realizată momentan"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Aplicația nu poate realiza această acțiune"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Conectați-vă pentru a folosi aplicația"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Este necesar accesul premium"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Se ascultă pe prea multe dispozitive"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Conținutul este blocat"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Conținutul nu poate fi descărcat aici"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Conținutul se redă deja"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Nu mai pot fi omise melodii"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Nu s-a putut finaliza. Încercați din nou."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Nu mai există elemente în coadă"</string>
</resources>
diff --git a/car-media-common/res/values-ru/strings.xml b/car-media-common/res/values-ru/strings.xml
index 6f4a6ba..990650f 100644
--- a/car-media-common/res/values-ru/strings.xml
+++ b/car-media-common/res/values-ru/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Обложка альбома"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Без названия"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Ошибка. Повторите попытку позже."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Сейчас это действие недоступно."</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Действие недоступно в этом приложении."</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Чтобы использовать это приложение, войдите в аккаунт."</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Требуется премиум-доступ."</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Слишком много устройств, на которых воспроизводится аудио."</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Контент заблокирован."</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Контент недоступен в вашем регионе."</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Этот контент уже воспроизводится."</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Пропускать треки больше нельзя."</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Произошла ошибка. Повторите попытку."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"В очереди воспроизведения нет других файлов."</string>
</resources>
diff --git a/car-media-common/res/values-si/strings.xml b/car-media-common/res/values-si/strings.xml
index f748730..7da552b 100644
--- a/car-media-common/res/values-si/strings.xml
+++ b/car-media-common/res/values-si/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"ඇල්බම කලාව"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"මාතෘකාවක් නැත"</string>
- <string name="default_error_message" msgid="4044331619453864482">"යම් දෙයක් වැරදියි. පසුව උත්සාහ කරන්න."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"මේ දැන් එය කළ නොහැක"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"මෙම යෙදුමට එය කළ නොහැක"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"මෙම යෙදුම භාවිත කිරීමට පුරන්න"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"පාරිතෝෂික ප්රවේශය අවශ්යයි"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"ඕනෑවට වඩා උපාංග මත සවන් දීම"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"එම අන්තර්ගතය අවහිර කර ඇත"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"එම අන්තර්ගතය මෙතැනින් ලබා ගත නොහැක"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"දැනටමත් එම අන්තර්ගතය වාදනය කරයි"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"තවත් ගීත මඟ හැරිය නොහැක"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"අවසන් කිරීමට නොහැකි විය. නැවත උත්සාහ කරන්න."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"වෙන කිසිවක් පෝලිමේ නැත"</string>
</resources>
diff --git a/car-media-common/res/values-sk/strings.xml b/car-media-common/res/values-sk/strings.xml
index 7a12c96..d94bac4 100644
--- a/car-media-common/res/values-sk/strings.xml
+++ b/car-media-common/res/values-sk/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Obrázok albumu"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Bez názvu"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Vyskytol sa problém. Skúste to znova."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Teraz to nie je možné"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Táto aplikácia to nedokáže"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Ak chcete použiť túto aplikáciu, prihláste sa"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Vyžaduje sa prémiový prístup"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Počúva sa na príliš veľa zariadeniach"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Daný obsah je blokovaný"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Daný obsah tu nie je k dispozícii"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Tento obsah sa už prehráva"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Nie je možné preskočiť žiadne ďalšie skladby"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Akciu sa nepodarilo dokončiť. Skúste to znova."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Poradie je prázdne"</string>
</resources>
diff --git a/car-media-common/res/values-sl/strings.xml b/car-media-common/res/values-sl/strings.xml
index aec70f5..683507c 100644
--- a/car-media-common/res/values-sl/strings.xml
+++ b/car-media-common/res/values-sl/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Slika albuma"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Brez naslova"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Prišlo je do težave. Poskusite pozneje."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Trenutno to ni izvedljivo"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Ta aplikacija ne more izvesti tega"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Prijavite se, če želite uporabljati to aplikacijo"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Potrebujete plačljiv dostop"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Poslušanje v preveč napravah"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Ta vsebina je blokirana"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Te vsebine tukaj ni mogoče pridobiti"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ta vsebina se že predvaja"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Dosegli ste omejitev preskakovanja skladb"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Ni bilo mogoče dokončati. Poskusite znova."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Čakalna vrsta je prazna"</string>
</resources>
diff --git a/car-media-common/res/values-sq/strings.xml b/car-media-common/res/values-sq/strings.xml
index d4d02e1..354b5ed 100644
--- a/car-media-common/res/values-sq/strings.xml
+++ b/car-media-common/res/values-sq/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Kopertina e albumit"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Pa titull"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Ndodhi një gabim. Provo më vonë."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Këtë nuk mund ta bësh në këtë moment"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Ky aplikacion nuk mund ta bëjë këtë"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Identifikohu për të përdorur këtë aplikacion"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Kërkohet qasje Premium"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Po dëgjon në shumë pajisje"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Kjo përmbajtje është bllokuar"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Ajo përmbajtje nuk mund të merret këtu"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Ajo përmbajtje po luhet tashmë"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Nuk mund të kapërcejë këngë të tjera"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Nuk mundi të përfundojë. Provo sërish."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Asgjë tjetër nuk është në radhë"</string>
</resources>
diff --git a/car-media-common/res/values-sr/strings.xml b/car-media-common/res/values-sr/strings.xml
index 69c898d..e6173ba 100644
--- a/car-media-common/res/values-sr/strings.xml
+++ b/car-media-common/res/values-sr/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Омот албума"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Без наслова"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Дошло је до грешке. Пробајте касније."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Тренутно не може то да уради"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Ова апликација не може то да уради"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Пријавите се да бисте користили ову апликацију"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Потребан је премијум приступ"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Слушате на превише уређаја"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Тај садржај је блокиран"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Не можете да добијете тај садржај овде"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Тај садржај се већ репродукује"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Не можете више да прескачете песме"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Нисмо успели да довршимо радњу. Пробајте опет."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ништа друго није стављено у редослед"</string>
</resources>
diff --git a/car-media-common/res/values-sv/strings.xml b/car-media-common/res/values-sv/strings.xml
index 05a0d28..ce52ecf 100644
--- a/car-media-common/res/values-sv/strings.xml
+++ b/car-media-common/res/values-sv/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Skivomslag"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Ingen titel"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Något är fel. Försök igen senare."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Det går inte just nu"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Det går inte med den här appen"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Logga in om du vill använda appen"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium-konto krävs"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"För många enheter streamas"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Det innehållet har blockerats"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Det innehållet är inte tillgängligt här"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Det innehållet spelas redan"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Det går inte att hoppa över fler låtar"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Det gick inte att slutföra. Försök igen."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Inget annat har lagts i uppspelningskön"</string>
</resources>
diff --git a/car-media-common/res/values-sw/strings.xml b/car-media-common/res/values-sw/strings.xml
index 30937ac..282555c 100644
--- a/car-media-common/res/values-sw/strings.xml
+++ b/car-media-common/res/values-sw/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Sanaa ya Albamu"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Hakuna Jina"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Hitilafu fulani imetokea. Jaribu baadaye."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Imeshindwa kutekeleza ombi kwa sasa"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Programu hii imeshindwa kutekeleza ombi lako"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Ingia katika akaunti ili utumie programu hii"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Unahitaji kutumia akaunti ya kulipia"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Unasikiliza kwenye vifaa vingi mno"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Maudhui hayo yamezuiwa"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Imeshindwa kupata maudhui haya hapa"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Tayari inacheza wimbo huo"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Imeshindwa kuruka nyimbo zaidi"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Imeshindwa kumaliza. Jaribu tena."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Hakuna wimbo mwingine kwenye foleni"</string>
</resources>
diff --git a/car-media-common/res/values-ta/strings.xml b/car-media-common/res/values-ta/strings.xml
index 6fc14ba..dd522b8 100644
--- a/car-media-common/res/values-ta/strings.xml
+++ b/car-media-common/res/values-ta/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"ஆல்பம் ஆர்ட்"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"தலைப்பு இல்லை"</string>
- <string name="default_error_message" msgid="4044331619453864482">"ஏதோ தவறாகிவிட்டது. பிறகு முயலவும்."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"தற்சமயம் இதைச் செய்ய இயலாது"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"இந்த ஆப்ஸால் அதைச் செய்ய இயலாது"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"இந்த ஆப்ஸை உபயோகிக்க உள்நுழைய வேண்டும்"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium அணுகல் தேவை"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"நிறைய சாதனங்களைக் கவனிக்கிறது"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"அந்த உள்ளடக்கம் தடுக்கப்பட்டுள்ளது"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"அந்த உள்ளடக்கத்தை இங்கே பெற முடியவில்லை"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"அந்த உள்ளடக்கம் ஏற்கெனவே பிளே செய்யப்பட்டுக் கொண்டிருக்கிறது"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"இதற்கு மேல் டிராக்குகளைத் தவிர்க்க முடியாது"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"நிறைவடையவில்லை. மீண்டும் முயலவும்."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"வேறு எதுவும் வரிசையில் இல்லை"</string>
</resources>
diff --git a/car-media-common/res/values-te/strings.xml b/car-media-common/res/values-te/strings.xml
index f3dec11..62a7464 100644
--- a/car-media-common/res/values-te/strings.xml
+++ b/car-media-common/res/values-te/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"ఆల్బమ్ ఆర్ట్"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"శీర్షిక లేదు"</string>
- <string name="default_error_message" msgid="4044331619453864482">"ఏదో తప్పు జరిగింది. తర్వాత ట్రై చేయండి."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"దానిని ఇప్పుడు చేయడం సాధ్యపడదు"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"ఈ యాప్ దానిని చేయలేదు"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"ఈ యాప్ను ఉపయోగించడానికి సైన్ ఇన్ చేయండి"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"ప్రీమియం యాక్సెస్ అవసరం"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"అనేక పరికరాలలో వింటున్నారు"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"ఆ కంటెంట్ బ్లాక్ చేయబడింది"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"ఆ కంటెంట్ను ఇక్కడ పొందడం సాధ్యపడదు"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"ఇప్పటికే ఆ కంటెంట్ ప్లే అవుతోంది"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ఇంక ఏ ట్రాక్లనూ స్కిప్ చేయడం సాధ్యపడదు"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"పూర్తి చేయడం సాధ్యపడలేదు. మళ్లీ ట్రై చేయండి."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"క్రమ వరుసలో ఏమీ లేదు"</string>
</resources>
diff --git a/car-media-common/res/values-th/strings.xml b/car-media-common/res/values-th/strings.xml
index 3e3aa59..f642719 100644
--- a/car-media-common/res/values-th/strings.xml
+++ b/car-media-common/res/values-th/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"ปกอัลบั้ม"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"ไม่มีชื่อ"</string>
- <string name="default_error_message" msgid="4044331619453864482">"มีข้อผิดพลาดเกิดขึ้น ลองอีกครั้งในภายหลัง"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"ดำเนินการดังกล่าวไม่ได้ในขณะนี้"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"แอปนี้ดำเนินการดังกล่าวไม่ได้"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"ลงชื่อเข้าใช้เพื่อใช้แอปนี้"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"ต้องมีสิทธิ์เข้าถึงระดับพรีเมียม"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"กำลังเปิดฟังจากหลายอุปกรณ์เกินไป"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"เนื้อหาดังกล่าวถูกบล็อก"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"เปิดเนื้อหาดังกล่าวไม่ได้ที่นี่"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"กำลังเล่นเนื้อหาดังกล่าวอยู่แล้ว"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"ข้ามแทร็กอื่นอีกไม่ได้แล้ว"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"ดำเนินการไม่สำเร็จ ลองใหม่"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"ไม่มีรายการอื่นในคิว"</string>
</resources>
diff --git a/car-media-common/res/values-tl/strings.xml b/car-media-common/res/values-tl/strings.xml
index 39bc081..a2da896 100644
--- a/car-media-common/res/values-tl/strings.xml
+++ b/car-media-common/res/values-tl/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Album Art"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Walang Pamagat"</string>
- <string name="default_error_message" msgid="4044331619453864482">"May problema. Subukan sa ibang pagkakataon."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Hindi iyon magagawa ngayon"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Hindi iyon magagawa ng app na ito"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Mag-sign in para gamitin ang app na ito"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Kailangan ng premium na access"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Nakikinig sa masyadong maraming device"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Naka-block ang content na iyon"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Hindi makukuha ang content na iyon dito"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Pine-play na ang content na iyon"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Hindi na makakalaktaw pa ng track"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Hindi matapos. Subukan ulit."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Wala nang naka-queue"</string>
</resources>
diff --git a/car-media-common/res/values-tr/strings.xml b/car-media-common/res/values-tr/strings.xml
index 7fc645e..6f717ac 100644
--- a/car-media-common/res/values-tr/strings.xml
+++ b/car-media-common/res/values-tr/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Albüm Kapağı"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Başlıksız"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Bir şeyler ters gitti. Daha sonra tekrar deneyin."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"İşlem şu anda gerçekleştirilemiyor"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Uygulama bu işlemi gerçekleştiremiyor"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Bu uygulamayı kullanmak için oturum açın"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Premium erişim gerekli"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Çok fazla cihazda dinleme yapılıyor"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Bu içerik engellendi"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"İçeriğe buradan erişilemiyor"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Bu içerik zaten oynatılıyor"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Daha fazla parça atlanamıyor"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Tamamlanamadı. Tekrar deneyin."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Sıraya eklenmiş başka bir şey yok"</string>
</resources>
diff --git a/car-media-common/res/values-uk/strings.xml b/car-media-common/res/values-uk/strings.xml
index e377c98..0661490 100644
--- a/car-media-common/res/values-uk/strings.xml
+++ b/car-media-common/res/values-uk/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Обкладинка альбому"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Без назви"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Сталася помилка. Повторіть спробу пізніше."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Наразі ця дія недоступна"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Додаток не підтримує цю дію"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Увійдіть, щоб використовувати цей додаток"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Потрібен преміум-доступ"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Забагато пристроїв, на яких відтворюється контент"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Цей контент заблоковано"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Цей контент недоступний у вашому регіоні"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Цей контент уже відтворюється"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Більше не можна пропускати композиції"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Не вдалося закінчити. Повторіть спробу."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Черга порожня"</string>
</resources>
diff --git a/car-media-common/res/values-ur/strings.xml b/car-media-common/res/values-ur/strings.xml
index 19dfce7..ccf505c 100644
--- a/car-media-common/res/values-ur/strings.xml
+++ b/car-media-common/res/values-ur/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"البم آرٹ"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"کوئی عنوان نہیں ہے"</string>
- <string name="default_error_message" msgid="4044331619453864482">"کچھ غلط ہو گیا۔ بعد میں کوشش کریں۔"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"ابھی وہ ایسا نہیں کر سکتا"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"یہ ایپ ایسا نہیں کر سکتی"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"اس ایپ کا استعمال کرنے کے لیے سائن ان کریں"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"پریمیم رسائی درکار ہے"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"بہت سے آلات پر سنا جا رہا ہے"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"وہ مواد مسدود ہے"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"یہاں وہ مواد حاصل نہیں کر سکتے"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"وہ مواد پہلے سے ہی چلایا جا رہا ہے"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"مزید کسی اور ٹریکس کو نظر انداز نہیں کر سکتے"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"پورا نہیں ہو سکا۔ پھر آزمائيں۔"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"مزید کچھ اور کی قطار نہیں ہے"</string>
</resources>
diff --git a/car-media-common/res/values-uz/strings.xml b/car-media-common/res/values-uz/strings.xml
index b2bfd1e..f3e8826 100644
--- a/car-media-common/res/values-uz/strings.xml
+++ b/car-media-common/res/values-uz/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Albom muqovasi"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Nomsiz"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Nimadir xato ketdi Keyinroq qayta urining."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Buni hozir amalga oshira olmaydi"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Bu ilova bu amalni bajara olmaydi"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Bu ilovadan foydalanish uchun hisobingizga kiring"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Ishonchli ruxsat kerak"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Koʻplab qurilmalar tinglanmoqda"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Bu kontent bloklangan"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Bu kontentni bu yerga olish imkonsiz"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Bu kontent allaqachon ijro etilmoqda"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Boshqa treklarni qoldirib ketish imkonsiz"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Tugallanmadi. Qayta urining."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Ijro navbatida boshqa fayllar mavjud emas"</string>
</resources>
diff --git a/car-media-common/res/values-vi/strings.xml b/car-media-common/res/values-vi/strings.xml
index f75fe5a..2a99840 100644
--- a/car-media-common/res/values-vi/strings.xml
+++ b/car-media-common/res/values-vi/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Ảnh bìa album"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Không có tiêu đề"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Đã xảy ra lỗi. Hãy thử lại sau."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Không thể thực hiện yêu cầu đó ngay bây giờ"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Ứng dụng này không thể thực hiện yêu cầu đó"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Hãy đăng nhập để dùng ứng dụng này"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Yêu cầu quyền truy cập đặc biệt"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Đang nghe trên quá nhiều thiết bị"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Nội dung đó đã bị chặn"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Không thể xem nội dung đó tại đây"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Đang phát nội dung đó rồi"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Không thể bỏ qua bản nhạc nào nữa"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Không thể hoàn tất. Hãy thử lại."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Không có nội dung nào khác trong hàng đợi"</string>
</resources>
diff --git a/car-media-common/res/values-zh-rCN/strings.xml b/car-media-common/res/values-zh-rCN/strings.xml
index 9688f0e..b3ddef1 100644
--- a/car-media-common/res/values-zh-rCN/strings.xml
+++ b/car-media-common/res/values-zh-rCN/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"专辑封面"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"无标题"</string>
- <string name="default_error_message" msgid="4044331619453864482">"出了点问题。请稍后重试。"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"目前无法执行该操作"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"此应用无法执行该操作"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"登录才能使用此应用"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"需要有付费帐号才能访问"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"同时使用太多设备聆听"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"该内容已遭到屏蔽"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"所在地区不提供这项内容"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"系统已在播放这项内容"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"无法再跳过更多曲目"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"无法完成操作。请重试。"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"队列中没有任何其他内容"</string>
</resources>
diff --git a/car-media-common/res/values-zh-rHK/strings.xml b/car-media-common/res/values-zh-rHK/strings.xml
index 1e84ca7..6ce371a 100644
--- a/car-media-common/res/values-zh-rHK/strings.xml
+++ b/car-media-common/res/values-zh-rHK/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"專輯封面"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"無標題"</string>
- <string name="default_error_message" msgid="4044331619453864482">"發生錯誤,請稍後再試。"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"目前無法執行此操作"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"應用程式不支援此操作"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"登入帳戶才能使用此應用程式"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"必須付費才能存取"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"同時使用太多裝置聆聽音樂"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"內容已被封鎖"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"所在地區不提供該內容"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"系統已開始播放該內容"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"無法再略過曲目"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"無法完成此操作,請再試一次。"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"序列上沒有其他項目"</string>
</resources>
diff --git a/car-media-common/res/values-zh-rTW/strings.xml b/car-media-common/res/values-zh-rTW/strings.xml
index 1b50b13..6ce371a 100644
--- a/car-media-common/res/values-zh-rTW/strings.xml
+++ b/car-media-common/res/values-zh-rTW/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"專輯封面"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"無標題"</string>
- <string name="default_error_message" msgid="4044331619453864482">"發生錯誤,請稍後再試。"</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"目前無法執行這項操作"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"應用程式不支援這項操作"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"必須登入帳戶,才能使用這個應用程式"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"必須付費才能存取"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"同時使用太多裝置聆聽音樂"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"內容已遭到封鎖"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"所在地區不提供該內容"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"系統已開始播放該內容"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"無法再略過曲目"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"無法完成這項操作,請再試一次。"</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"待播清單上沒有其他項目"</string>
</resources>
diff --git a/car-media-common/res/values-zu/strings.xml b/car-media-common/res/values-zu/strings.xml
index 6f06f6d..cde3b48 100644
--- a/car-media-common/res/values-zu/strings.xml
+++ b/car-media-common/res/values-zu/strings.xml
@@ -19,16 +19,4 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="album_art" msgid="3392647029019061691">"Ubuciko be-albhamu"</string>
<string name="metadata_default_title" msgid="5902775732281325081">"Asikho isihloko"</string>
- <string name="default_error_message" msgid="4044331619453864482">"Kukhona okungalungile. Zama emuva kwesikhathi."</string>
- <string name="error_code_app_error" msgid="3608680401453743688">"Ayikwazi ukwenza lokho khona manje"</string>
- <string name="error_code_not_supported" msgid="8004310657548193089">"Lolu hlelo lokusebenza alukwazi ukwenza lokho"</string>
- <string name="error_code_authentication_expired" msgid="1727285213286610186">"Ngena ngemvume ukuze usebenzise lolu hlelo lokusebenza"</string>
- <string name="error_code_premium_account_required" msgid="2328664287270814966">"Ukufinyelela kwe-premium kuyadingeka"</string>
- <string name="error_code_concurrent_stream_limit" msgid="493048763425570552">"Ilalele kumadivayisi amaningi kakhulu"</string>
- <string name="error_code_parental_control_restricted" msgid="325145513462419399">"Lokho okuqukethwe kuvinjelwe"</string>
- <string name="error_code_not_available_in_region" msgid="5840935836875073145">"Ayikwazi ukuletha lokho okuqukethwe lapha"</string>
- <string name="error_code_content_already_playing" msgid="1306236349553004461">"Isivele idlala lokho okuqukethwe"</string>
- <string name="error_code_skip_limit_reached" msgid="4203743406433151146">"Ayikwazi ukweqa amanye amathrekhi amaningi"</string>
- <string name="error_code_action_aborted" msgid="8611777981356536501">"Ayikwazanga ukuqeda. Zama futhi."</string>
- <string name="error_code_end_of_queue" msgid="6935022448319288887">"Akukho okunye okufakwe kulayini"</string>
</resources>
diff --git a/car-media-common/src/com/android/car/media/common/ControlBarHelper.java b/car-media-common/src/com/android/car/media/common/ControlBarHelper.java
index 0f011fd..4e3b7b9 100644
--- a/car-media-common/src/com/android/car/media/common/ControlBarHelper.java
+++ b/car-media-common/src/com/android/car/media/common/ControlBarHelper.java
@@ -64,8 +64,8 @@
model.getProgress().observe(owner,
progress -> {
- progressBar.setProgress((int) progress.getProgress());
progressBar.setMax((int) progress.getMaxProgress());
+ progressBar.setProgress((int) progress.getProgress());
});
}
}
diff --git a/car-messenger-common/src/com/android/car/messenger/common/Utils.java b/car-messenger-common/src/com/android/car/messenger/common/Utils.java
index 68ed3aa..c52cc56 100644
--- a/car-messenger-common/src/com/android/car/messenger/common/Utils.java
+++ b/car-messenger-common/src/com/android/car/messenger/common/Utils.java
@@ -373,9 +373,9 @@
public static String constructGroupConversationHeader(String senderName, String groupName,
String delimiter, BidiFormatter bidiFormatter) {
String formattedSenderName = bidiFormatter.unicodeWrap(senderName,
- TextDirectionHeuristics.FIRSTSTRONG_LTR);
+ TextDirectionHeuristics.FIRSTSTRONG_LTR, /* isolate= */ true);
String formattedGroupName = bidiFormatter.unicodeWrap(groupName,
- TextDirectionHeuristics.LOCALE);
+ TextDirectionHeuristics.FIRSTSTRONG_LTR, /* isolate= */ true);
String title = String.join(delimiter, formattedSenderName, formattedGroupName);
return bidiFormatter.unicodeWrap(title, TextDirectionHeuristics.LOCALE);
}
diff --git a/car-messenger-common/tests/unit/src/com.android.car.messenger.common/UtilsTest.java b/car-messenger-common/tests/unit/src/com.android.car.messenger.common/UtilsTest.java
index acdffbd..c64703a 100644
--- a/car-messenger-common/tests/unit/src/com.android.car.messenger.common/UtilsTest.java
+++ b/car-messenger-common/tests/unit/src/com.android.car.messenger.common/UtilsTest.java
@@ -200,4 +200,17 @@
assertThat(actual).isEqualTo(expected);
}
+
+ @Test
+ public void testTitleConstructorRtl_withTrailingPunctuation() {
+ String actual = Utils.constructGroupConversationHeader("Christopher",
+ "Abcd!!!", TITLE_DELIMITER, /* isRtl */
+ RTL_FORMATTER).trim();
+
+ String expected =
+ "\u200F\u202A\u200F\u202AChristopher\u202C\u200F : \u200F\u202AAbcd!!!"
+ + "\u202C\u200F\u202C\u200F";
+
+ assertThat(actual).isEqualTo(expected);
+ }
}
diff --git a/car-telephony-common/res/values-af/strings.xml b/car-telephony-common/res/values-af/strings.xml
index 56aebcf..7e19555 100644
--- a/car-telephony-common/res/values-af/strings.xml
+++ b/car-telephony-common/res/values-af/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Koppel tans …"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Bel tans …"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Hou aan"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Oproep beëindig"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Hou aan"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Oproep beëindig"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Gekoppel"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Lui tans …"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Ontkoppel tans …"</string>
diff --git a/car-telephony-common/res/values-am/strings.xml b/car-telephony-common/res/values-am/strings.xml
index 2714928..6d0315e 100644
--- a/car-telephony-common/res/values-am/strings.xml
+++ b/car-telephony-common/res/values-am/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"በመገናኘት ላይ…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"በመደወል ላይ"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"ያዝና ቆይ"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"ጥሪ አብቅቷል"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"ያዝናቆይ"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"ጥሪ አብቅቷል"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"ተገናኝቷል"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"በመጥራት ላይ…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"ግንኝነትን በማቋረጥ ላይ…"</string>
diff --git a/car-telephony-common/res/values-ar/strings.xml b/car-telephony-common/res/values-ar/strings.xml
index aa5ceca..fcc72fb 100644
--- a/car-telephony-common/res/values-ar/strings.xml
+++ b/car-telephony-common/res/values-ar/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"جارٍ الاتصال…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"جارٍ طلب الرقم…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"قيد الانتظار"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"تم إنهاء المكالمة"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"قيد الانتظار"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"انتهت المكالمة"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"متّصل"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"جارٍ إطلاق الرنين…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"جارٍ قطع الاتصال…"</string>
diff --git a/car-telephony-common/res/values-as/strings.xml b/car-telephony-common/res/values-as/strings.xml
index c2d94c4..a4c90ae 100644
--- a/car-telephony-common/res/values-as/strings.xml
+++ b/car-telephony-common/res/values-as/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"সংযোগ কৰি থকা হৈছে…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"ডায়েল কৰি থকা হৈছে…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"হ’ল্ডত আছে"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"কলটো সমাপ্ত হ’ল"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"হ’ল্ডত আছে"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"কল শেষ হৈছে"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"সংযোগ কৰা হ’ল"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"ৰিং কৰি আছে…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"সংযোগ ছেদ হৈ আছে…"</string>
diff --git a/car-telephony-common/res/values-az/strings.xml b/car-telephony-common/res/values-az/strings.xml
index 6882db1..d29aeab 100644
--- a/car-telephony-common/res/values-az/strings.xml
+++ b/car-telephony-common/res/values-az/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Qoşulur…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Nömrə yığılır…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Xətdə"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Zəng bitdi"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Gözləmədə"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Zəng sona çatdı"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Qoşuldu"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Zəng çalır…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Bağlantı kəsilir…"</string>
diff --git a/car-telephony-common/res/values-b+sr+Latn/strings.xml b/car-telephony-common/res/values-b+sr+Latn/strings.xml
index 04dcbbe..d05ef72 100644
--- a/car-telephony-common/res/values-b+sr+Latn/strings.xml
+++ b/car-telephony-common/res/values-b+sr+Latn/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Povezuje se…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Poziva se…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Na čekanju"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Poziv je završen"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Na čekanju"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Poziv je završen"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Povezan"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Zvoni…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Prekida se veza…"</string>
diff --git a/car-telephony-common/res/values-be/strings.xml b/car-telephony-common/res/values-be/strings.xml
index 49e9f44..1e1bcdf 100644
--- a/car-telephony-common/res/values-be/strings.xml
+++ b/car-telephony-common/res/values-be/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Ідзе падключэнне…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Ідзе набор нумара…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"На ўтрыманні"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Выклік завершаны"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"На ўтрыманні"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Выклік скончаны"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Падключана"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Ідзе празвон…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Ідзе адключэнне…"</string>
diff --git a/car-telephony-common/res/values-bg/strings.xml b/car-telephony-common/res/values-bg/strings.xml
index 99d16bd..1f38d61 100644
--- a/car-telephony-common/res/values-bg/strings.xml
+++ b/car-telephony-common/res/values-bg/strings.xml
@@ -20,9 +20,9 @@
<string name="voicemail" msgid="2125552157407909509">"Гласова поща"</string>
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Свързва се…"</string>
- <string name="call_state_dialing" msgid="1534599871716648114">"Набиране…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Задържане на обаждането"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Обаждането завърши"</string>
+ <string name="call_state_dialing" msgid="1534599871716648114">"Набира се…"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Задържано"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Обаждането завърши"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Установена е връзка"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Звъни се…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Връзката се прекр…"</string>
diff --git a/car-telephony-common/res/values-bn/strings.xml b/car-telephony-common/res/values-bn/strings.xml
index 65f38ef..4855b6e 100644
--- a/car-telephony-common/res/values-bn/strings.xml
+++ b/car-telephony-common/res/values-bn/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"কানেক্ট হচ্ছে…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"ডায়াল করা হচ্ছে…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"হোল্ডে রয়েছে"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"কল শেষ হয়েছে"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"হোল্ডে আছে"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"কল শেষ হয়েছে"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"কানেক্ট করা হয়েছে"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"রিং হচ্ছে…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"ডিসকানেক্ট করা হচ্ছে…"</string>
diff --git a/car-telephony-common/res/values-bs/strings.xml b/car-telephony-common/res/values-bs/strings.xml
index fbfb012..1d6a8b8 100644
--- a/car-telephony-common/res/values-bs/strings.xml
+++ b/car-telephony-common/res/values-bs/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Povezivanje…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Biranje…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Na čekanju"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Poziv je završen"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Na čekanju"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Poziv je završen"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Povezan"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Zvoni…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Prekidanje veze…"</string>
diff --git a/car-telephony-common/res/values-ca/strings.xml b/car-telephony-common/res/values-ca/strings.xml
index 1ff8957..356f31c 100644
--- a/car-telephony-common/res/values-ca/strings.xml
+++ b/car-telephony-common/res/values-ca/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"S\'està connectant…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"S\'està marcant…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"En espera"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Trucada finalitzada"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"En espera"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Trucada finalitzada"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Connectat"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Està sonant…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"S\'està desconnectant…"</string>
diff --git a/car-telephony-common/res/values-cs/strings.xml b/car-telephony-common/res/values-cs/strings.xml
index 7b6e1d7..41a29b5 100644
--- a/car-telephony-common/res/values-cs/strings.xml
+++ b/car-telephony-common/res/values-cs/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Připojování…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Vytáčení…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Přidržený hovor"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Hovor byl ukončen"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Podrženo"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Hovor byl ukončen"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Připojeno"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Vyzvánění…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Odpojování…"</string>
diff --git a/car-telephony-common/res/values-da/strings.xml b/car-telephony-common/res/values-da/strings.xml
index c1507fe..dcd52a0 100644
--- a/car-telephony-common/res/values-da/strings.xml
+++ b/car-telephony-common/res/values-da/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Tilslutter…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Ringer op…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"På hold"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Opkaldet er slut"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Afventer"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Opkaldet er slut"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Der er forbindelse"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Ringer…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Afbryder…"</string>
diff --git a/car-telephony-common/res/values-de/strings.xml b/car-telephony-common/res/values-de/strings.xml
index 131fc08..a5257d3 100644
--- a/car-telephony-common/res/values-de/strings.xml
+++ b/car-telephony-common/res/values-de/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Verbinden…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Rufaufbau…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Anruf wird gehalten"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Anruf beendet"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Warten"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Anruf beendet"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Verbunden"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Klingelt…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Verbindung wird getrennt…"</string>
diff --git a/car-telephony-common/res/values-el/strings.xml b/car-telephony-common/res/values-el/strings.xml
index 6392460..40d1716 100644
--- a/car-telephony-common/res/values-el/strings.xml
+++ b/car-telephony-common/res/values-el/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Σύνδεση…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Κλήση…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Σε αναμονή"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Η κλήση τερματίστηκε"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Σε αναμονή"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Η κλήση τερματίστηκε"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Συνδέθηκε"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Κλήση…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Αποσύνδεση…"</string>
diff --git a/car-telephony-common/res/values-en-rAU/strings.xml b/car-telephony-common/res/values-en-rAU/strings.xml
index cc8a536..06d1973 100644
--- a/car-telephony-common/res/values-en-rAU/strings.xml
+++ b/car-telephony-common/res/values-en-rAU/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Connecting…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Dialling…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"On hold"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Call ended"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"On Hold"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Call Ended"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Connected"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Ringing…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Disconnecting…"</string>
diff --git a/car-telephony-common/res/values-en-rCA/strings.xml b/car-telephony-common/res/values-en-rCA/strings.xml
index cc8a536..06d1973 100644
--- a/car-telephony-common/res/values-en-rCA/strings.xml
+++ b/car-telephony-common/res/values-en-rCA/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Connecting…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Dialling…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"On hold"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Call ended"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"On Hold"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Call Ended"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Connected"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Ringing…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Disconnecting…"</string>
diff --git a/car-telephony-common/res/values-en-rGB/strings.xml b/car-telephony-common/res/values-en-rGB/strings.xml
index cc8a536..06d1973 100644
--- a/car-telephony-common/res/values-en-rGB/strings.xml
+++ b/car-telephony-common/res/values-en-rGB/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Connecting…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Dialling…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"On hold"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Call ended"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"On Hold"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Call Ended"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Connected"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Ringing…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Disconnecting…"</string>
diff --git a/car-telephony-common/res/values-en-rIN/strings.xml b/car-telephony-common/res/values-en-rIN/strings.xml
index cc8a536..06d1973 100644
--- a/car-telephony-common/res/values-en-rIN/strings.xml
+++ b/car-telephony-common/res/values-en-rIN/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Connecting…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Dialling…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"On hold"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Call ended"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"On Hold"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Call Ended"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Connected"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Ringing…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Disconnecting…"</string>
diff --git a/car-telephony-common/res/values-en-rXC/strings.xml b/car-telephony-common/res/values-en-rXC/strings.xml
index 43e60e9..b80f8fa 100644
--- a/car-telephony-common/res/values-en-rXC/strings.xml
+++ b/car-telephony-common/res/values-en-rXC/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Connecting…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Dialing…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"On hold"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Call ended"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"On Hold"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Call Ended"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Connected"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Ringing…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Disconnecting…"</string>
diff --git a/car-telephony-common/res/values-es-rUS/strings.xml b/car-telephony-common/res/values-es-rUS/strings.xml
index 12fb629..2e71140 100644
--- a/car-telephony-common/res/values-es-rUS/strings.xml
+++ b/car-telephony-common/res/values-es-rUS/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Conectando…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Marcando…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"En espera"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Llamada finalizada"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"En espera"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Llamada finalizada"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Conectado"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Sonando…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Desconectando…"</string>
diff --git a/car-telephony-common/res/values-es/strings.xml b/car-telephony-common/res/values-es/strings.xml
index 6cff2e2..390354f 100644
--- a/car-telephony-common/res/values-es/strings.xml
+++ b/car-telephony-common/res/values-es/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Conectando…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Marcando…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"En espera"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Llamada finalizada"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"En espera"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Llamada finalizada"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Conectado"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Sonando…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Desconectando…"</string>
diff --git a/car-telephony-common/res/values-et/strings.xml b/car-telephony-common/res/values-et/strings.xml
index 9926364..ee65909 100644
--- a/car-telephony-common/res/values-et/strings.xml
+++ b/car-telephony-common/res/values-et/strings.xml
@@ -21,9 +21,9 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Ühendamine …"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Valimine …"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Ootel"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Kõne lõpetati"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Ootel"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Kõne lõpetati"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Ühendatud"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Heliseb …"</string>
- <string name="call_state_call_ending" msgid="5037498349965472247">"Ühenduse katkestamine…"</string>
+ <string name="call_state_call_ending" msgid="5037498349965472247">"Ühenduse katkest. …"</string>
</resources>
diff --git a/car-telephony-common/res/values-eu/strings.xml b/car-telephony-common/res/values-eu/strings.xml
index ff34106..ddaec85 100644
--- a/car-telephony-common/res/values-eu/strings.xml
+++ b/car-telephony-common/res/values-eu/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Konektatzen…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Markatzen…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Zain"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Amaitu da deia"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Zain"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Deia amaituta"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Konektatuta"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Deitzen…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Deskonektatzen…"</string>
diff --git a/car-telephony-common/res/values-fa/strings.xml b/car-telephony-common/res/values-fa/strings.xml
index 0418d6e..092b944 100644
--- a/car-telephony-common/res/values-fa/strings.xml
+++ b/car-telephony-common/res/values-fa/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"درحال اتصال…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"درحال شمارهگیری"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"درانتظار"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"تماس پایان یافت"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"در انتظار"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"تماس پایان یافت"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"متصل"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"درحال زنگ زدن…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"درحال قطع اتصال…"</string>
diff --git a/car-telephony-common/res/values-fi/strings.xml b/car-telephony-common/res/values-fi/strings.xml
index bf46e0b..ce6ff53 100644
--- a/car-telephony-common/res/values-fi/strings.xml
+++ b/car-telephony-common/res/values-fi/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Yhdistetään…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Soitetaan…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Pidossa"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Puhelu lopetettu"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Pidossa"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Puhelu päättyi"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Yhdistetty"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Soi…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Katkaistaan…"</string>
diff --git a/car-telephony-common/res/values-fr-rCA/strings.xml b/car-telephony-common/res/values-fr-rCA/strings.xml
index bed5d9d..cef9278 100644
--- a/car-telephony-common/res/values-fr-rCA/strings.xml
+++ b/car-telephony-common/res/values-fr-rCA/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> : <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Connexion en cours…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Composition…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"En attente"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Appel terminé"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"En attente"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Appel terminé"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Connecté"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Sonnerie en cours…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Déconnexion…"</string>
diff --git a/car-telephony-common/res/values-fr/strings.xml b/car-telephony-common/res/values-fr/strings.xml
index 35833e0..164f23d 100644
--- a/car-telephony-common/res/values-fr/strings.xml
+++ b/car-telephony-common/res/values-fr/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Connexion…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Composition numéro…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"En attente"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Appel terminé"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"En attente"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Appel terminé"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Appel en cours"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Sonnerie…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Déconnexion…"</string>
diff --git a/car-telephony-common/res/values-gl/strings.xml b/car-telephony-common/res/values-gl/strings.xml
index 2058cad..2251c16 100644
--- a/car-telephony-common/res/values-gl/strings.xml
+++ b/car-telephony-common/res/values-gl/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Conectando…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Marcando…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"En espera"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Chamada finalizada"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"En espera"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Chamada finalizada"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Conectado"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Facendo soar…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Desconectando…"</string>
diff --git a/car-telephony-common/res/values-gu/strings.xml b/car-telephony-common/res/values-gu/strings.xml
index 7040087..04cf443 100644
--- a/car-telephony-common/res/values-gu/strings.xml
+++ b/car-telephony-common/res/values-gu/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"કનેક્ટ થઈ રહ્યું છે…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"ડાયલ કરી રહ્યાં છે…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"હોલ્ડ પર"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"કૉલ સમાપ્ત થયો"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"હોલ્ડ પર"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"કૉલ સમાપ્ત થયો"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"કનેક્ટેડ"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"રિંગ વાગી રહી છે…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"ડિસ્કનેક્ટ થાય છે…"</string>
diff --git a/car-telephony-common/res/values-hi/strings.xml b/car-telephony-common/res/values-hi/strings.xml
index 418e776..5fdb213 100644
--- a/car-telephony-common/res/values-hi/strings.xml
+++ b/car-telephony-common/res/values-hi/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"कनेक्ट हो रहा है…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"डायल किया जा रहा है…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"होल्ड पर है"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"कॉल खत्म हुआ"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"कॉल होल्ड पर है"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"कॉल खत्म हो गया"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"जुड़ गया है"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"घंटी बज रही है…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"डिसकनेक्ट हो रहा है…"</string>
diff --git a/car-telephony-common/res/values-hr/strings.xml b/car-telephony-common/res/values-hr/strings.xml
index 6a35140..5e0f4d7 100644
--- a/car-telephony-common/res/values-hr/strings.xml
+++ b/car-telephony-common/res/values-hr/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Povezivanje…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Biranje broja…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Na čekanju"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Poziv je završio"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Na čekanju"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Poziv je završio"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Povezano"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Zvonjenje…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Isključivanje…"</string>
diff --git a/car-telephony-common/res/values-hu/strings.xml b/car-telephony-common/res/values-hu/strings.xml
index 26e0165..fa95e1a 100644
--- a/car-telephony-common/res/values-hu/strings.xml
+++ b/car-telephony-common/res/values-hu/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Csatlakozás…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Tárcsázás…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Várakoztatva"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"A hívás befejeződött"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Várakoztatva"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"A hívás befejeződött"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Csatlakozva"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Csörgés…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Kapcsolat bontása…"</string>
diff --git a/car-telephony-common/res/values-hy/strings.xml b/car-telephony-common/res/values-hy/strings.xml
index 9a0cdeb..c8d7ea9 100644
--- a/car-telephony-common/res/values-hy/strings.xml
+++ b/car-telephony-common/res/values-hy/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Միացում…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Համարհավաքում…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Զանգը սպասման մեջ է"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Զանգն ավարտվեց"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Սպասում"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Զանգն ավարտվեց"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Միացած է"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Զանգ…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Անջատվում է…"</string>
diff --git a/car-telephony-common/res/values-in/strings.xml b/car-telephony-common/res/values-in/strings.xml
index 7450656..53a7831 100644
--- a/car-telephony-common/res/values-in/strings.xml
+++ b/car-telephony-common/res/values-in/strings.xml
@@ -21,9 +21,9 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Menghubungkan…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Memanggil…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Ditangguhkan"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Panggilan diakhiri"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Harap Tunggu"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Panggilan Diakhiri"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Terhubung"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Berdering…"</string>
- <string name="call_state_call_ending" msgid="5037498349965472247">"Memutus sambungan..."</string>
+ <string name="call_state_call_ending" msgid="5037498349965472247">"Memutus hubungan..."</string>
</resources>
diff --git a/car-telephony-common/res/values-is/strings.xml b/car-telephony-common/res/values-is/strings.xml
index e330acb..b91a805 100644
--- a/car-telephony-common/res/values-is/strings.xml
+++ b/car-telephony-common/res/values-is/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Tengist…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Hringir…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Í bið"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Lagt á"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Í bið"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Símtali lokið"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Tengt"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Hringir…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Aftengist…"</string>
diff --git a/car-telephony-common/res/values-it/strings.xml b/car-telephony-common/res/values-it/strings.xml
index 47b659c..cb4bc61 100644
--- a/car-telephony-common/res/values-it/strings.xml
+++ b/car-telephony-common/res/values-it/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Connessione…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Chiamata…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"In attesa"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Chiamata terminata"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"In attesa"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Chiamata terminata"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Connesso"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Sta squillando…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Disconnessione…"</string>
diff --git a/car-telephony-common/res/values-iw/strings.xml b/car-telephony-common/res/values-iw/strings.xml
index 67620e7..0d1cd68 100644
--- a/car-telephony-common/res/values-iw/strings.xml
+++ b/car-telephony-common/res/values-iw/strings.xml
@@ -20,9 +20,9 @@
<string name="voicemail" msgid="2125552157407909509">"דואר קולי"</string>
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"מתחבר…"</string>
- <string name="call_state_dialing" msgid="1534599871716648114">"החיוג מתבצע…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"בהמתנה"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"השיחה הסתיימה"</string>
+ <string name="call_state_dialing" msgid="1534599871716648114">"מחייג…"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"בהמתנה"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"השיחה הסתיימה"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"מתבצעת שיחה"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"מצלצל…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"מתנתק…"</string>
diff --git a/car-telephony-common/res/values-ja/strings.xml b/car-telephony-common/res/values-ja/strings.xml
index a231047..4c0a6c1 100644
--- a/car-telephony-common/res/values-ja/strings.xml
+++ b/car-telephony-common/res/values-ja/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"接続中…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"発信中…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"保留中"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"通話終了"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"保留中"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"通話終了"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"接続済み"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"着信中…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"切断中…"</string>
diff --git a/car-telephony-common/res/values-ka/strings.xml b/car-telephony-common/res/values-ka/strings.xml
index 6db5a44..858ffa3 100644
--- a/car-telephony-common/res/values-ka/strings.xml
+++ b/car-telephony-common/res/values-ka/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"დაკავშირება…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"მიმდინარეობს აკრეფა…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"შეყოვნების რეჟიმში"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"ზარი დასრულდა"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"შეყოვნების რეჟიმში"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"ზარი დასრულდა"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"დაკავშირებულია"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"ირეკება…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"კავშირი წყდება…"</string>
diff --git a/car-telephony-common/res/values-kk/strings.xml b/car-telephony-common/res/values-kk/strings.xml
index 0e767dd..48eba3d 100644
--- a/car-telephony-common/res/values-kk/strings.xml
+++ b/car-telephony-common/res/values-kk/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Жалғануда…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Терілуде…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Күтуде"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Қоңырау аяқталды"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Күтуде"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Қоңырау аяқталды"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Жалғанды"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Шылдырлату…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Ажыратылуда…"</string>
diff --git a/car-telephony-common/res/values-km/strings.xml b/car-telephony-common/res/values-km/strings.xml
index 44b7870..a483de3 100644
--- a/car-telephony-common/res/values-km/strings.xml
+++ b/car-telephony-common/res/values-km/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"កំពុងភ្ជាប់…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"កំពុងហៅ…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"កំពុងរង់ចាំ"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"បានបញ្ចប់ការហៅទូរសព្ទ"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"រង់ចាំ"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"បានបញ្ចប់ការហៅ"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"បានភ្ជាប់"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"កំពុងរោទ៍…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"កំពុងផ្ដាច់…"</string>
diff --git a/car-telephony-common/res/values-kn/strings.xml b/car-telephony-common/res/values-kn/strings.xml
index 2f04e28..7b57804 100644
--- a/car-telephony-common/res/values-kn/strings.xml
+++ b/car-telephony-common/res/values-kn/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"ಸಂಪರ್ಕಿಸಲಾಗುತ್ತಿದೆ…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"ಡಯಲ್ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"ಹೋಲ್ಡ್ ಮಾಡಲಾಗಿದೆ"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"ಕರೆ ಅಂತ್ಯಗೊಂಡಿದೆ"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"ಹೋಲ್ಡ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"ಕರೆ ಅಂತ್ಯಗೊಂಡಿದೆ"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"ರಿಂಗ್ ಆಗುತ್ತಿದೆ…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾ…"</string>
diff --git a/car-telephony-common/res/values-ko/strings.xml b/car-telephony-common/res/values-ko/strings.xml
index b210c69..1171186 100644
--- a/car-telephony-common/res/values-ko/strings.xml
+++ b/car-telephony-common/res/values-ko/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"연결 중…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"전화 거는 중..."</string>
- <string name="call_state_hold" msgid="8063542005458186874">"대기 중"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"통화 종료됨"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"대기 중"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"통화 종료됨"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"연결됨"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"벨소리 울리는 중…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"연결 해제 중..."</string>
diff --git a/car-telephony-common/res/values-ky/strings.xml b/car-telephony-common/res/values-ky/strings.xml
index 599ca63..b76e748 100644
--- a/car-telephony-common/res/values-ky/strings.xml
+++ b/car-telephony-common/res/values-ky/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Туташууда…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Терилүүдө…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Күтүү режиминде"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Чалуу аяктады"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Күтүү режиминде"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Чалуу аяктады"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Туташты"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Шыңгырап жатат…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Ажыратылууда…"</string>
diff --git a/car-telephony-common/res/values-lo/strings.xml b/car-telephony-common/res/values-lo/strings.xml
index ec95f01..b6cc9df 100644
--- a/car-telephony-common/res/values-lo/strings.xml
+++ b/car-telephony-common/res/values-lo/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"ກຳລັງເຊື່ອມຕໍ່…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"ກຳລັງໂທ…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"ພັກສາຍ"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"ວາງສາຍແລ້ວ"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"ຖືສາຍລໍຖ້າ"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"ວາງສາຍແລ້ວ"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"ເຊື່ອມຕໍ່ແລ້ວ"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"ກຳລັງດັງ…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"ກຳລັງຕັດການເຊື່ອມຕໍ່…"</string>
diff --git a/car-telephony-common/res/values-lt/strings.xml b/car-telephony-common/res/values-lt/strings.xml
index 550d875..ea0508a 100644
--- a/car-telephony-common/res/values-lt/strings.xml
+++ b/car-telephony-common/res/values-lt/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Prisijungiama…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Renkamas numeris…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Sulaikyta"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Skambutis baigtas"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Sulaikyta"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Skambutis baigtas"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Sujungta"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Skambinama…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Atsijungiama…"</string>
diff --git a/car-telephony-common/res/values-lv/strings.xml b/car-telephony-common/res/values-lv/strings.xml
index 184c937..28e49f5 100644
--- a/car-telephony-common/res/values-lv/strings.xml
+++ b/car-telephony-common/res/values-lv/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Veido savienojumu…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Sastāda numuru…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Aizturēts"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Zvans pabeigts"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Aizturēts"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Zvans ir pabeigts"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Savienots"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Zvana…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Notiek atvienošana…"</string>
diff --git a/car-telephony-common/res/values-mk/strings.xml b/car-telephony-common/res/values-mk/strings.xml
index c3ef6eb..b6053e3 100644
--- a/car-telephony-common/res/values-mk/strings.xml
+++ b/car-telephony-common/res/values-mk/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Се поврзува…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Бирање…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"На чекање"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Повикот заврши"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"На чекање"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Повикот заврши"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Поврзан"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Ѕвони…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Се исклучува…"</string>
diff --git a/car-telephony-common/res/values-ml/strings.xml b/car-telephony-common/res/values-ml/strings.xml
index 85d886c..e940e9b 100644
--- a/car-telephony-common/res/values-ml/strings.xml
+++ b/car-telephony-common/res/values-ml/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"കണക്റ്റ് ചെയ്യുന്നു…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"ഡയൽ ചെയ്യുന്നു…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"ഹോൾഡിലാണ്"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"കോൾ അവസാനിച്ചു"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"ഹോള്ഡിലാണ്"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"കോൾ അവസാനിച്ചു"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"കണക്റ്റ് ചെയ്തു"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"റിംഗ് ചെയ്യുന്നു…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"വിച്ഛേദിക്കുന്നു…"</string>
diff --git a/car-telephony-common/res/values-mn/strings.xml b/car-telephony-common/res/values-mn/strings.xml
index 74956f3..5fdc776 100644
--- a/car-telephony-common/res/values-mn/strings.xml
+++ b/car-telephony-common/res/values-mn/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Холбогдож байна…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Залгаж байна…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Хүлээлгэд байгаа"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Дуудлага дууссан"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Хүлээлгэнд байна"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Дуудлагыг тасалсан"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Холбогдсон"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Дуугарч байна…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Салгаж байна…"</string>
diff --git a/car-telephony-common/res/values-mr/strings.xml b/car-telephony-common/res/values-mr/strings.xml
index d3036e1..9c7f085 100644
--- a/car-telephony-common/res/values-mr/strings.xml
+++ b/car-telephony-common/res/values-mr/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"कनेक्ट करत आहे…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"डायल करत आहे…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"होल्डवर आहे"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"कॉल संपला आहे"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"होल्ड वर"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"कॉल संपला"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"कनेक्ट केले"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"रिंग होत आहे…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"डिस्कनेक्ट करत आहे…"</string>
diff --git a/car-telephony-common/res/values-ms/strings.xml b/car-telephony-common/res/values-ms/strings.xml
index 038ac0c..1c1d5de 100644
--- a/car-telephony-common/res/values-ms/strings.xml
+++ b/car-telephony-common/res/values-ms/strings.xml
@@ -21,9 +21,9 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Menyambung…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Mendail…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Ditunda"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Panggilan tamat"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Ditunda"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Panggilan Tamat"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Disambungkan"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Berdering…"</string>
- <string name="call_state_call_ending" msgid="5037498349965472247">"Memutuskan sambungan…"</string>
+ <string name="call_state_call_ending" msgid="5037498349965472247">"Memutuskan sambungn…"</string>
</resources>
diff --git a/car-telephony-common/res/values-my/strings.xml b/car-telephony-common/res/values-my/strings.xml
index e2d5565..130935a 100644
--- a/car-telephony-common/res/values-my/strings.xml
+++ b/car-telephony-common/res/values-my/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"ချိတ်ဆက်နေသည်…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"ခေါ်ဆိုနေသည်…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"ဖုန်းကိုင်ထားသည်"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"ခေါ်ဆိုမှု ပြီးသွားပါပြီ"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"ဖုန်းကိုင်ထားသည်"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"ခေါ်ဆိုမှု ပြီးပါပြီ"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"ချိတ်ဆက်ထားသည်"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"အသံမြည်နေသည်…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"အဆက်အသွယ် ဖြတ်နေသည်…"</string>
diff --git a/car-telephony-common/res/values-nb/strings.xml b/car-telephony-common/res/values-nb/strings.xml
index e6e90b3..119f352 100644
--- a/car-telephony-common/res/values-nb/strings.xml
+++ b/car-telephony-common/res/values-nb/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Kobler til …"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Slår nummeret …"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"På vent"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Anropet er avsluttet"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"På vent"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Anropet er avsluttet"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Tilkoblet"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Ringer …"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Kobler fra …"</string>
diff --git a/car-telephony-common/res/values-ne/strings.xml b/car-telephony-common/res/values-ne/strings.xml
index 2283a98..b021f11 100644
--- a/car-telephony-common/res/values-ne/strings.xml
+++ b/car-telephony-common/res/values-ne/strings.xml
@@ -21,9 +21,9 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"जोड्दै…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"डायल गर्दै…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"होल्डमा छ"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"कल समाप्त भयो"</string>
- <string name="call_state_call_active" msgid="2769644783657864202">"कनेक्ट गरिएको छ"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"होल्डमा छ"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"कल समाप्त भयो"</string>
+ <string name="call_state_call_active" msgid="2769644783657864202">"जडान गरिएको छ"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"घन्टी बज्दै छ…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"विच्छेद गर्दै…"</string>
</resources>
diff --git a/car-telephony-common/res/values-nl/strings.xml b/car-telephony-common/res/values-nl/strings.xml
index c1f4c00..7d00163 100644
--- a/car-telephony-common/res/values-nl/strings.xml
+++ b/car-telephony-common/res/values-nl/strings.xml
@@ -21,9 +21,9 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Verbinden…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Kiezen…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"In de wacht"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Gesprek beëindigd"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"In de wacht"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Gesprek beëindigd"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Verbonden"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Gaat over…"</string>
- <string name="call_state_call_ending" msgid="5037498349965472247">"Verbreken…"</string>
+ <string name="call_state_call_ending" msgid="5037498349965472247">"Verb. verbreken…"</string>
</resources>
diff --git a/car-telephony-common/res/values-or/strings.xml b/car-telephony-common/res/values-or/strings.xml
index c95fa40..775e33c 100644
--- a/car-telephony-common/res/values-or/strings.xml
+++ b/car-telephony-common/res/values-or/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"ସଂଯୋଗ ହେଉଛି…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"ଡାଏଲ୍ କରାଯାଉଛି…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"ହୋଲ୍ଡରେ ଅଛି"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"କଲ୍ ଶେଷ ହୋଇଛି"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"ହୋଲ୍ଡରେ ଅଛି"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"କଲ୍ ସମାପ୍ତ ହେଲା"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"ସଂଯୋଗ ହୋଇଛି"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"ରିଙ୍ଗ ହେଉଛି…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"ବିଚ୍ଛିନ୍ନ ହେଉଛି…"</string>
diff --git a/car-telephony-common/res/values-pa/strings.xml b/car-telephony-common/res/values-pa/strings.xml
index dcad72f..129276b 100644
--- a/car-telephony-common/res/values-pa/strings.xml
+++ b/car-telephony-common/res/values-pa/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"ਕਨੈਕਟ ਹੋ ਰਿਹਾ ਹੈ…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"ਡਾਇਲ ਹੋ ਰਿਹਾ ਹੈ…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"ਰੋਕ ਕੇ ਰੱਖੀ ਗਈ"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"ਕਾਲ ਸਮਾਪਤ ਹੋਈ"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"ਰੋਕ ਕੇ ਰੱਖੀ ਗਈ"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"ਕਾਲ ਸਮਾਪਤ ਹੋਈ"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"ਕਨੈਕਟ ਹੈ"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"ਘੰਟੀ ਵੱਜ ਰਹੀ ਹੈ…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"ਡਿਸਕਨੈਕਟ ਹੋ ਰਿਹਾ ਹੈ…"</string>
diff --git a/car-telephony-common/res/values-pl/strings.xml b/car-telephony-common/res/values-pl/strings.xml
index 7b87eef..1212857 100644
--- a/car-telephony-common/res/values-pl/strings.xml
+++ b/car-telephony-common/res/values-pl/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Łączę…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Wybieram numer…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Oczekujące"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Połączenie zakończone"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Oczekujące"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Koniec połączenia"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Połączono"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Dzwonię…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Rozłączam…"</string>
diff --git a/car-telephony-common/res/values-pt-rPT/strings.xml b/car-telephony-common/res/values-pt-rPT/strings.xml
index b0577d0..3b518e6 100644
--- a/car-telephony-common/res/values-pt-rPT/strings.xml
+++ b/car-telephony-common/res/values-pt-rPT/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"A ligar…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"A marcar…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Em espera"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Chamada terminada"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Em espera"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Chamada terminada"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Ligado"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"A tocar…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"A desligar…"</string>
diff --git a/car-telephony-common/res/values-pt/strings.xml b/car-telephony-common/res/values-pt/strings.xml
index f56f6ad..7531adb 100644
--- a/car-telephony-common/res/values-pt/strings.xml
+++ b/car-telephony-common/res/values-pt/strings.xml
@@ -20,9 +20,9 @@
<string name="voicemail" msgid="2125552157407909509">"Correio de voz"</string>
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Conectando…"</string>
- <string name="call_state_dialing" msgid="1534599871716648114">"Chamando...…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Em espera"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Chamada encerrada"</string>
+ <string name="call_state_dialing" msgid="1534599871716648114">"Discando…"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Em espera"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Chamada encerrada"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Conectado"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Tocando…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Desconectando…"</string>
diff --git a/car-telephony-common/res/values-ro/strings.xml b/car-telephony-common/res/values-ro/strings.xml
index 7d78d46..a9883de 100644
--- a/car-telephony-common/res/values-ro/strings.xml
+++ b/car-telephony-common/res/values-ro/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Se conectează…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Se apelează…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"În așteptare"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Apel încheiat"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"În așteptare"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Apel încheiat"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Conectat"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Sună…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Se deconectează…"</string>
diff --git a/car-telephony-common/res/values-ru/strings.xml b/car-telephony-common/res/values-ru/strings.xml
index 6731414..2e37211 100644
--- a/car-telephony-common/res/values-ru/strings.xml
+++ b/car-telephony-common/res/values-ru/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Подключение…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Набор номера…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Вызов на удержании"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Вызов завершен"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"На удержании"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Вызов завершен"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Подключено"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Вызов…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Отключение…"</string>
diff --git a/car-telephony-common/res/values-si/strings.xml b/car-telephony-common/res/values-si/strings.xml
index b0ac2e8..399c8d8 100644
--- a/car-telephony-common/res/values-si/strings.xml
+++ b/car-telephony-common/res/values-si/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"සබැඳෙමින්…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"අමතමින්…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"රඳවා ගනිමින්"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"ඇමතුම අවසන් විය"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"රඳවා ඇත"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"ඇමතුම නිමා විය"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"සම්බන්ධයි"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"නාද වෙමින්…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"විසන්ධි වෙමින්…"</string>
diff --git a/car-telephony-common/res/values-sk/strings.xml b/car-telephony-common/res/values-sk/strings.xml
index 0aeef23..83721d3 100644
--- a/car-telephony-common/res/values-sk/strings.xml
+++ b/car-telephony-common/res/values-sk/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Pripája sa…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Vytáča sa…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Podržaný hovor"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Hovor bol ukončený"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Podržané"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Hovor bol ukončený"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Pripojené"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Prezváňa sa…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Odpája sa…"</string>
diff --git a/car-telephony-common/res/values-sl/strings.xml b/car-telephony-common/res/values-sl/strings.xml
index ce84056..03a7063 100644
--- a/car-telephony-common/res/values-sl/strings.xml
+++ b/car-telephony-common/res/values-sl/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Povezovanje …"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Klicanje …"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Na čakanju"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Klic je končan"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Zadržan"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Klic je bil končan"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Povezano"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Zvonjenje …"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Prekin. povezave …"</string>
diff --git a/car-telephony-common/res/values-sq/strings.xml b/car-telephony-common/res/values-sq/strings.xml
index 87af7f4..f6439eb 100644
--- a/car-telephony-common/res/values-sq/strings.xml
+++ b/car-telephony-common/res/values-sq/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Po lidhet…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Po formon numrin…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Në pritje"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Telefonata përfundoi"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Në pritje"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Telefonata përfundoi"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Lidhur"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Po bie zilja…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Po shkëputet…"</string>
diff --git a/car-telephony-common/res/values-sr/strings.xml b/car-telephony-common/res/values-sr/strings.xml
index 4b48d1a..1f3837f 100644
--- a/car-telephony-common/res/values-sr/strings.xml
+++ b/car-telephony-common/res/values-sr/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Повезује се…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Позива се…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"На чекању"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Позив је завршен"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"На чекању"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Позив је завршен"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Повезан"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Звони…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Прекида се веза…"</string>
diff --git a/car-telephony-common/res/values-sv/strings.xml b/car-telephony-common/res/values-sv/strings.xml
index 2aff4e7..f4b9b1a 100644
--- a/car-telephony-common/res/values-sv/strings.xml
+++ b/car-telephony-common/res/values-sv/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Ansluter …"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Ringer upp …"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Parkerat"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Samtal avslutat"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Parkerat"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Samtal avslutat"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Ansluten"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Ringer …"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Kopplar från …"</string>
diff --git a/car-telephony-common/res/values-sw/strings.xml b/car-telephony-common/res/values-sw/strings.xml
index 28a496b..f6adbc1 100644
--- a/car-telephony-common/res/values-sw/strings.xml
+++ b/car-telephony-common/res/values-sw/strings.xml
@@ -20,10 +20,10 @@
<string name="voicemail" msgid="2125552157407909509">"Ujumbe wa sauti"</string>
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Inaunganisha…"</string>
- <string name="call_state_dialing" msgid="1534599871716648114">"Inapiga…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Imesitishwa"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Simu imekamilika"</string>
+ <string name="call_state_dialing" msgid="1534599871716648114">"Inapigia…"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Inangoja"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Simu Imekamilika"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Imeunganisha"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Inalia…"</string>
- <string name="call_state_call_ending" msgid="5037498349965472247">"Inakata…"</string>
+ <string name="call_state_call_ending" msgid="5037498349965472247">"Inaondoa…"</string>
</resources>
diff --git a/car-telephony-common/res/values-ta/strings.xml b/car-telephony-common/res/values-ta/strings.xml
index d6bb4c9..d835fab 100644
--- a/car-telephony-common/res/values-ta/strings.xml
+++ b/car-telephony-common/res/values-ta/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"இணைக்கிறது…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"டயல் செய்கிறது…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"ஹோல்டில் உள்ளது"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"அழைப்பு முடிந்தது"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"காத்திருப்பில்"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"அழைப்பு முடிந்தது"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"இணைக்கப்பட்டது"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"அழைக்கிறது…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"துண்டிக்கிறது…"</string>
diff --git a/car-telephony-common/res/values-te/strings.xml b/car-telephony-common/res/values-te/strings.xml
index 39975e4..2ebcb08 100644
--- a/car-telephony-common/res/values-te/strings.xml
+++ b/car-telephony-common/res/values-te/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"కనెక్ట్ అవుతోంది…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"డయల్ చేస్తోంది…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"హోల్డ్లో ఉంది"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"కాల్ ముగిసింది"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"హోల్డ్లో ఉంది"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"కాల్ ముగిసింది"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"కనెక్ట్ చేయబడింది"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"రింగ్ అవుతోంది…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"డిస్కనెక్టవుతోంది…"</string>
diff --git a/car-telephony-common/res/values-th/strings.xml b/car-telephony-common/res/values-th/strings.xml
index 6b5a368..7af5876 100644
--- a/car-telephony-common/res/values-th/strings.xml
+++ b/car-telephony-common/res/values-th/strings.xml
@@ -21,9 +21,9 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"กำลังเชื่อมต่อ…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"กำลังโทรออก…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"พักสาย"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"วางสายแล้ว"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"พักสาย"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"วางสายแล้ว"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"เชื่อมต่อแล้ว"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"กำลังส่งเสียง…"</string>
- <string name="call_state_call_ending" msgid="5037498349965472247">"ยกเลิกการโทรออก…"</string>
+ <string name="call_state_call_ending" msgid="5037498349965472247">"ยกเลิกการเชื่อมต่อ…"</string>
</resources>
diff --git a/car-telephony-common/res/values-tl/strings.xml b/car-telephony-common/res/values-tl/strings.xml
index 2bdda5c..f5f02ef 100644
--- a/car-telephony-common/res/values-tl/strings.xml
+++ b/car-telephony-common/res/values-tl/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Kumokonekta…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Dina-dial…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Naka-hold"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Tinapos ang tawag"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Naka-hold"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Natapos ang Tawag"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Nakakonekta"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Nagri-ring…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Dinidiskonekta…"</string>
diff --git a/car-telephony-common/res/values-tr/strings.xml b/car-telephony-common/res/values-tr/strings.xml
index dc05ebf..8da8d62 100644
--- a/car-telephony-common/res/values-tr/strings.xml
+++ b/car-telephony-common/res/values-tr/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Bağlanıyor…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Numara çevriliyor…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Beklemede"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Çağrı sonlandırıldı"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Beklemede"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Çağrı Sonlandırıldı"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Bağlandı"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Zil çaldırılıyor…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Bağlantı kesiliyor…"</string>
diff --git a/car-telephony-common/res/values-uk/strings.xml b/car-telephony-common/res/values-uk/strings.xml
index f72fa83..6c05db2 100644
--- a/car-telephony-common/res/values-uk/strings.xml
+++ b/car-telephony-common/res/values-uk/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"З’єднання…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Набір номера…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Утримується"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Виклик завершено"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Утримується"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Виклик завершено"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Під’єднано"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Дзвінок…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Від’єднання…"</string>
diff --git a/car-telephony-common/res/values-ur/strings.xml b/car-telephony-common/res/values-ur/strings.xml
index 3deac0b..92dfe09 100644
--- a/car-telephony-common/res/values-ur/strings.xml
+++ b/car-telephony-common/res/values-ur/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"منسلک ہو رہا ہے…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"ڈائل کر رہا ہے…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"ہولڈ پر ہے"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"کال ختم ہو گئی"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"ہولڈ پر ہے"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"کال ختم ہوگئی"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"منسلک ہے"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"گھنٹی بج رہی ہے…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"غیر منسلک ہو رہا ہے…"</string>
diff --git a/car-telephony-common/res/values-uz/strings.xml b/car-telephony-common/res/values-uz/strings.xml
index a818836..2683304 100644
--- a/car-telephony-common/res/values-uz/strings.xml
+++ b/car-telephony-common/res/values-uz/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Ulanmoqda…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Raqam terilmoqda…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Pauzada"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Chaqiruv tugadi"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Kutish holatida"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Chaqiruv yakunlandi"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Ulandi"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Jiringlamoqda…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Uzilmoqda…"</string>
diff --git a/car-telephony-common/res/values-vi/strings.xml b/car-telephony-common/res/values-vi/strings.xml
index d0dc77b..e52194a 100644
--- a/car-telephony-common/res/values-vi/strings.xml
+++ b/car-telephony-common/res/values-vi/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Đang kết nối…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Đang quay số..."</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Đang giữ máy"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Cuộc gọi đã kết thúc"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Đang chờ"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Cuộc gọi đã kết thúc"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Đã kết nối"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Đang đổ chuông..."</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Đang ngắt kết nối…"</string>
diff --git a/car-telephony-common/res/values-zh-rCN/strings.xml b/car-telephony-common/res/values-zh-rCN/strings.xml
index 0c2fc09..1a6a652 100644
--- a/car-telephony-common/res/values-zh-rCN/strings.xml
+++ b/car-telephony-common/res/values-zh-rCN/strings.xml
@@ -17,12 +17,12 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="unknown" msgid="3237922751873109097">"未知"</string>
- <string name="voicemail" msgid="2125552157407909509">"语音信息"</string>
+ <string name="voicemail" msgid="2125552157407909509">"语音邮件"</string>
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"正在连接…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"正在拨号…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"保持"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"通话已结束"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"呼叫等待"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"通话已结束"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"已连接"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"正在响铃…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"正在断开连接…"</string>
diff --git a/car-telephony-common/res/values-zh-rHK/strings.xml b/car-telephony-common/res/values-zh-rHK/strings.xml
index 6872577..c92766e 100644
--- a/car-telephony-common/res/values-zh-rHK/strings.xml
+++ b/car-telephony-common/res/values-zh-rHK/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"正在連接…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"正在撥號…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"保留通話"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"通話已結束"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"保留通話"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"通話已結束"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"已連接"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"正在發出鈴聲…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"正在解除連接…"</string>
diff --git a/car-telephony-common/res/values-zh-rTW/strings.xml b/car-telephony-common/res/values-zh-rTW/strings.xml
index 41928bb..decc1db 100644
--- a/car-telephony-common/res/values-zh-rTW/strings.xml
+++ b/car-telephony-common/res/values-zh-rTW/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"連線中…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"撥號中…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"保留中"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"通話已結束"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"保留中"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"通話結束"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"已連線"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"鈴響中…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"正在中斷連線…"</string>
diff --git a/car-telephony-common/res/values-zu/strings.xml b/car-telephony-common/res/values-zu/strings.xml
index fa90708..0978e00 100644
--- a/car-telephony-common/res/values-zu/strings.xml
+++ b/car-telephony-common/res/values-zu/strings.xml
@@ -21,8 +21,8 @@
<string name="phone_label_with_info" msgid="4652109530699808645">"<xliff:g id="LABEL">%1$s</xliff:g> · <xliff:g id="DURATION">%2$s</xliff:g>"</string>
<string name="call_state_connecting" msgid="5930724746375294866">"Iyaxhuma…"</string>
<string name="call_state_dialing" msgid="1534599871716648114">"Iyadayela…"</string>
- <string name="call_state_hold" msgid="8063542005458186874">"Ibambile"</string>
- <string name="call_state_call_ended" msgid="1432127342949555464">"Ikholi iqediwe"</string>
+ <string name="call_state_hold" msgid="6834028102796624100">"Ibanjiwe"</string>
+ <string name="call_state_call_ended" msgid="4159349597599886429">"Ikholi iqediwe"</string>
<string name="call_state_call_active" msgid="2769644783657864202">"Ixhunyiwe"</string>
<string name="call_state_call_ringing" msgid="4618803402954375017">"Iyakhala…"</string>
<string name="call_state_call_ending" msgid="5037498349965472247">"Iyanqamula…"</string>
diff --git a/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java b/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
index dac0aa3..8ed7e73 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
@@ -25,8 +25,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
+import androidx.lifecycle.Transformations;
import com.android.car.apps.common.log.L;
@@ -59,10 +59,9 @@
private final Map<String, Map<String, Contact>> mLookupKeyContactMap = new HashMap<>();
/**
- * A map which divides contacts LiveData by account.
+ * A map which divides contacts by account.
*/
- private final Map<String, MutableLiveData<List<Contact>>> mAccountContactsLiveDataMap =
- new ArrayMap<>();
+ private final Map<String, List<Contact>> mAccountContactsMap = new ArrayMap<>();
private boolean mIsLoaded = false;
/**
@@ -160,10 +159,8 @@
* Bluetooth address.
*/
public LiveData<List<Contact>> getContactsLiveDataByAccount(String accountName) {
- if (!mAccountContactsLiveDataMap.containsKey(accountName)) {
- mAccountContactsLiveDataMap.put(accountName, new MutableLiveData<>());
- }
- return mAccountContactsLiveDataMap.get(accountName);
+ return Transformations.map(mContactListAsyncQueryLiveData,
+ contacts -> contacts == null ? null : mAccountContactsMap.get(accountName));
}
/**
@@ -252,12 +249,13 @@
subMap.put(lookupKey, Contact.fromCursor(mContext, cursor, subMap.get(lookupKey)));
}
+ mAccountContactsMap.clear();
for (String accountName : contactMap.keySet()) {
Map<String, Contact> subMap = contactMap.get(accountName);
contactList.addAll(subMap.values());
- MutableLiveData<List<Contact>> accountContactsLiveData =
- (MutableLiveData<List<Contact>>) getContactsLiveDataByAccount(accountName);
- accountContactsLiveData.postValue(new ArrayList<>(subMap.values()));
+ List<Contact> accountContacts = new ArrayList<>();
+ accountContacts.addAll(subMap.values());
+ mAccountContactsMap.put(accountName, accountContacts);
}
mLookupKeyContactMap.clear();
diff --git a/car-telephony-common/src/com/android/car/telephony/common/ObservableAsyncQuery.java b/car-telephony-common/src/com/android/car/telephony/common/ObservableAsyncQuery.java
index b3c1fb1..394b6d4 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/ObservableAsyncQuery.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/ObservableAsyncQuery.java
@@ -50,9 +50,9 @@
private AsyncQueryHandler mAsyncQueryHandler;
private QueryParam.Provider mQueryParamProvider;
- private Cursor mCurrentCursor;
private OnQueryFinishedListener mOnQueryFinishedListener;
private ContentObserver mContentObserver;
+ private ContentResolver mContentResolver;
private boolean mIsActive = false;
private int mToken;
@@ -65,6 +65,7 @@
@NonNull ContentResolver cr,
@NonNull OnQueryFinishedListener listener) {
mAsyncQueryHandler = new AsyncQueryHandlerImpl(this, cr);
+ mContentResolver = cr;
mContentObserver = new ContentObserver(mAsyncQueryHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -83,6 +84,7 @@
public void startQuery() {
L.d(TAG, "startQuery");
mAsyncQueryHandler.cancelOperation(mToken); // Cancel the query task.
+ mContentResolver.unregisterContentObserver(mContentObserver);
mToken++;
QueryParam queryParam = mQueryParamProvider.getQueryParam();
@@ -95,6 +97,7 @@
queryParam.mSelection,
queryParam.mSelectionArgs,
queryParam.mOrderBy);
+ mContentResolver.registerContentObserver(queryParam.mUri, false, mContentObserver);
} else {
mOnQueryFinishedListener.onQueryFinished(null);
}
@@ -109,7 +112,7 @@
public void stopQuery() {
L.d(TAG, "stopQuery");
mIsActive = false;
- cleanupCursorIfNecessary();
+ mContentResolver.unregisterContentObserver(mContentObserver);
mAsyncQueryHandler.cancelOperation(mToken); // Cancel the query task.
}
@@ -118,23 +121,11 @@
return;
}
L.d(TAG, "onQueryComplete");
- cleanupCursorIfNecessary();
- if (cursor != null) {
- cursor.registerContentObserver(mContentObserver);
- mCurrentCursor = cursor;
- }
if (mOnQueryFinishedListener != null) {
mOnQueryFinishedListener.onQueryFinished(cursor);
}
}
- protected void cleanupCursorIfNecessary() {
- if (mCurrentCursor != null) {
- mCurrentCursor.unregisterContentObserver(mContentObserver);
- }
- mCurrentCursor = null;
- }
-
private static class AsyncQueryHandlerImpl extends AsyncQueryHandler {
private ObservableAsyncQuery mQuery;
diff --git a/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java b/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java
index a790318..40d93a7 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java
@@ -129,11 +129,10 @@
}
String countryIso = getCurrentCountryIsoFromLocale(context);
- L.d(TAG, "PhoneNumberUtils.formatNumberToE16, number: "
- + piiLog(number) + ", country: " + countryIso);
+ L.d(TAG, "PhoneNumberUtils.formatNumber, number: " + piiLog(number)
+ + ", country: " + countryIso);
- String e164 = PhoneNumberUtils.formatNumberToE164(number, countryIso);
- String formattedNumber = PhoneNumberUtils.formatNumber(number, e164, countryIso);
+ String formattedNumber = PhoneNumberUtils.formatNumber(number, countryIso);
formattedNumber = TextUtils.isEmpty(formattedNumber) ? number : formattedNumber;
L.d(TAG, "getFormattedNumber, result: " + piiLog(formattedNumber));
@@ -509,6 +508,24 @@
* valid, it will mark all new missed call log as read.
*/
public static void markCallLogAsRead(Context context, String phoneNumberString) {
+ markCallLogAsRead(context, CallLog.Calls.NUMBER, phoneNumberString);
+ }
+
+ /**
+ * Mark missed call log matching given call log id as read. If phone number string is not
+ * valid, it will mark all new missed call log as read.
+ */
+ public static void markCallLogAsRead(Context context, long callLogId) {
+ markCallLogAsRead(context, CallLog.Calls._ID,
+ callLogId < 0 ? null : String.valueOf(callLogId));
+ }
+
+ /**
+ * Mark missed call log matching given column name and selection argument as read. If the column
+ * name or the selection argument is not valid, mark all new missed call log as read.
+ */
+ private static void markCallLogAsRead(Context context, String columnName,
+ String selectionArg) {
if (context.checkSelfPermission(Manifest.permission.WRITE_CALL_LOG)
!= PackageManager.PERMISSION_GRANTED) {
L.w(TAG, "Missing WRITE_CALL_LOG permission; not marking missed calls as read.");
@@ -525,21 +542,22 @@
where.append(CallLog.Calls.TYPE);
where.append(" = ?");
selectionArgs.add(Integer.toString(CallLog.Calls.MISSED_TYPE));
- if (!TextUtils.isEmpty(phoneNumberString)) {
+ if (!TextUtils.isEmpty(columnName) && !TextUtils.isEmpty(selectionArg)) {
where.append(" AND ");
- where.append(CallLog.Calls.NUMBER);
+ where.append(columnName);
where.append(" = ?");
- selectionArgs.add(phoneNumberString);
+ selectionArgs.add(selectionArg);
}
String[] selectionArgsArray = new String[0];
try {
- context
- .getContentResolver()
- .update(
- CallLog.Calls.CONTENT_URI,
- contentValues,
- where.toString(),
- selectionArgs.toArray(selectionArgsArray));
+ ContentResolver contentResolver = context.getContentResolver();
+ contentResolver.update(
+ CallLog.Calls.CONTENT_URI,
+ contentValues,
+ where.toString(),
+ selectionArgs.toArray(selectionArgsArray));
+ // #update doesn't notify change any more. Notify change to rerun query from database.
+ contentResolver.notifyChange(CallLog.Calls.CONTENT_URI, null);
} catch (IllegalArgumentException e) {
L.e(TAG, "markCallLogAsRead failed", e);
}
diff --git a/car-ui-lib/.gitignore b/car-ui-lib/.gitignore
index a6be7ed..57df0a4 100644
--- a/car-ui-lib/.gitignore
+++ b/car-ui-lib/.gitignore
@@ -16,3 +16,6 @@
# Android studio's layout inspector captures
captures/
+
+# A file created when launching android emulators
+read-snapshot.txt
diff --git a/car-ui-lib/build.gradle b/car-ui-lib/build.gradle
index 9744622..28633d9 100644
--- a/car-ui-lib/build.gradle
+++ b/car-ui-lib/build.gradle
@@ -23,7 +23,7 @@
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.6.1'
+ classpath 'com.android.tools.build:gradle:4.0.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/car-ui-lib/car-ui-lib/build.gradle b/car-ui-lib/car-ui-lib/build.gradle
index 5c6ce32..201a8d6 100644
--- a/car-ui-lib/car-ui-lib/build.gradle
+++ b/car-ui-lib/car-ui-lib/build.gradle
@@ -60,20 +60,20 @@
api 'androidx.core:core:1.2.0'
implementation 'com.android.support:support-annotations:28.0.0'
- testImplementation "androidx.test.ext:junit:1.1.1"
- testImplementation 'org.robolectric:robolectric:4.4'
+ testImplementation "androidx.test.ext:junit:1.1.2"
+ testImplementation 'org.robolectric:robolectric:4.3.1'
testImplementation "org.mockito:mockito-core:2.19.0"
testImplementation "com.google.truth:truth:0.29"
testImplementation "org.testng:testng:6.9.9"
androidTestImplementation 'org.hamcrest:hamcrest-library:1.3'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
- androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0'
androidTestImplementation "com.google.truth:truth:0.29"
- androidTestImplementation "androidx.test.ext:junit:1.1.1"
+ androidTestImplementation "androidx.test.ext:junit:1.1.2"
androidTestImplementation "org.mockito:mockito-core:2.19.0"
- androidTestImplementation 'androidx.test:runner:1.1.0'
- androidTestImplementation 'androidx.test:rules:1.1.0'
+ androidTestImplementation 'androidx.test:runner:1.3.0'
+ androidTestImplementation 'androidx.test:rules:1.3.0'
// This is needed to be able to spy certain classes with Mockito
// It's major/minor version must match Mockito's.
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito-inline:2.19.0'
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/AndroidManifest.xml b/car-ui-lib/car-ui-lib/src/androidTest/AndroidManifest.xml
index 81df306..79b51bb 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/AndroidManifest.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/AndroidManifest.xml
@@ -16,14 +16,19 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.car.ui.test">
- <application android:debuggable="true" android:theme="@style/Theme.CarUi">
+ <application android:debuggable="true" android:theme="@style/Theme.CarUi.NoToolbar">
<uses-library android:name="android.test.runner" />
<activity android:name="com.android.car.ui.TestActivity" />
<activity android:name="com.android.car.ui.recyclerview.CarUiRecyclerViewTestActivity" />
+ <activity android:name="com.android.car.ui.imewidescreen.CarUiImeWideScreenTestActivity" />
<activity android:name="com.android.car.ui.FocusAreaTestActivity" />
<activity android:name="com.android.car.ui.FocusParkingViewTestActivity" />
<activity android:name="com.android.car.ui.preference.PreferenceTestActivity" />
<activity
+ android:name="com.android.car.ui.preference.NonFullscreenPreferenceFragmentTest$MyActivity"
+ android:theme="@style/Theme.CarUi.WithToolbar"/>
+ <activity android:name="com.android.car.ui.utils.ViewUtilsTestActivity" />
+ <activity
android:name="com.android.car.ui.toolbar.ToolbarTestActivity"
android:theme="@style/Theme.CarUi.WithToolbar"/>
</application>
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/AlertDialogBuilderTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/AlertDialogBuilderTest.java
new file mode 100644
index 0000000..0aaa250
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/AlertDialogBuilderTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import android.app.AlertDialog;
+import android.database.Cursor;
+import android.view.View;
+
+import androidx.test.espresso.Root;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.recyclerview.CarUiContentListItem;
+import com.android.car.ui.recyclerview.CarUiListItemAdapter;
+import com.android.car.ui.recyclerview.CarUiRadioButtonListItem;
+import com.android.car.ui.recyclerview.CarUiRadioButtonListItemAdapter;
+import com.android.car.ui.test.R;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class AlertDialogBuilderTest {
+
+ @Rule
+ public ActivityTestRule<TestActivity> mActivityRule =
+ new ActivityTestRule<>(TestActivity.class);
+
+ @Test
+ public void test_AlertDialogBuilder_works() throws Throwable {
+ String title = "Test message from AlertDialogBuilder";
+ String subtitle = "Subtitle from AlertDialogBuilder";
+ mActivityRule.runOnUiThread(() ->
+ new AlertDialogBuilder(mActivityRule.getActivity())
+ .setMessage(title)
+ .setSubtitle(subtitle)
+ .show());
+
+ AlertDialog dialog = checkDefaultButtonExists(true,
+ new AlertDialogBuilder(mActivityRule.getActivity())
+ .setMessage(title)
+ .setSubtitle(subtitle));
+ onView(withText(title))
+ .inRoot(new RootWithDecorMatcher(dialog.getWindow().getDecorView()))
+ .check(matches(isDisplayed()));
+ onView(withText(subtitle))
+ .inRoot(new RootWithDecorMatcher(dialog.getWindow().getDecorView()))
+ .check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void test_showSingleListChoiceItem_StringArray_hidesDefaultButton() throws Throwable {
+ AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+ .setAllowDismissButton(false)
+ .setSingleChoiceItems(new CharSequence[]{"Item 1", "Item 2"}, 0,
+ ((dialog, which) -> {
+ }));
+
+ checkDefaultButtonExists(false, builder);
+ }
+
+ @Test
+ public void test_showSingleListChoiceItem_StringArrayResource_hidesDefaultButton()
+ throws Throwable {
+ AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+ .setAllowDismissButton(false)
+ .setSingleChoiceItems(R.array.test_string_array, 0, ((dialog, which) -> {
+ }));
+
+ checkDefaultButtonExists(false, builder);
+ }
+
+ @Test
+ public void test_showSingleListChoiceItem_CarUiRadioButtonListItemAdapter_forcesDefaultButton()
+ throws Throwable {
+ CarUiRadioButtonListItem item1 = new CarUiRadioButtonListItem();
+ item1.setTitle("Item 1");
+ CarUiRadioButtonListItem item2 = new CarUiRadioButtonListItem();
+ item2.setTitle("Item 2");
+ CarUiRadioButtonListItem item3 = new CarUiRadioButtonListItem();
+ item3.setTitle("Item 3");
+
+ CarUiRadioButtonListItemAdapter adapter = new CarUiRadioButtonListItemAdapter(
+ Arrays.asList(item1, item2, item3));
+ AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+ .setAllowDismissButton(false)
+ .setSingleChoiceItems(adapter);
+
+ checkDefaultButtonExists(true, builder);
+ }
+
+ @Test
+ public void test_showSingleListChoiceItem_cursor_hidesDefaultButton() throws Throwable {
+ Cursor cursor = new FakeCursor(Arrays.asList("Item 1", "Item 2"), "ColumnName");
+ AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+ .setTitle("Title")
+ .setAllowDismissButton(false)
+ .setSingleChoiceItems(cursor, 0, "ColumnName", ((dialog, which) -> {
+ }));
+
+ checkDefaultButtonExists(false, builder);
+ }
+
+ @Test
+ public void test_setItems_StringArrayResource_hidesDefaultButton() throws Throwable {
+ AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+ .setAllowDismissButton(false)
+ .setItems(R.array.test_string_array, ((dialog, which) -> {
+ }));
+
+ checkDefaultButtonExists(false, builder);
+ }
+
+ @Test
+ public void test_setItems_StringArray_hidesDefaultButton() throws Throwable {
+ AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+ .setAllowDismissButton(false)
+ .setItems(new CharSequence[]{"Item 1", "Item 2"}, ((dialog, which) -> {
+ }));
+
+ checkDefaultButtonExists(false, builder);
+ }
+
+ @Test
+ public void test_setAdapter_hidesDefaultButton()
+ throws Throwable {
+ CarUiContentListItem item1 = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+ item1.setTitle("Item 1");
+ CarUiContentListItem item2 = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+ item2.setTitle("Item 2");
+ CarUiContentListItem item3 = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+ item3.setTitle("Item 3");
+
+ CarUiListItemAdapter adapter = new CarUiListItemAdapter(
+ Arrays.asList(item1, item2, item3));
+ AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+ .setAllowDismissButton(false)
+ .setAdapter(adapter);
+
+ checkDefaultButtonExists(false, builder);
+ }
+
+ @Test
+ public void test_multichoiceItems_StringArrayResource_forcesDefaultButton()
+ throws Throwable {
+ AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+ .setAllowDismissButton(false)
+ .setMultiChoiceItems(R.array.test_string_array, null,
+ ((dialog, which, isChecked) -> {
+ }));
+
+ checkDefaultButtonExists(true, builder);
+ }
+
+ @Test
+ public void test_multichoiceItems_StringArray_forcesDefaultButton()
+ throws Throwable {
+ AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+ .setAllowDismissButton(false)
+ .setMultiChoiceItems(new CharSequence[]{"Test 1", "Test 2"}, null,
+ ((dialog, which, isChecked) -> {
+ }));
+
+ checkDefaultButtonExists(true, builder);
+ }
+
+ @Test
+ public void test_multichoiceItems_Cursor_forcesDefaultButton()
+ throws Throwable {
+ Cursor cursor = new FakeCursor(Arrays.asList("Item 1", "Item 2"), "Label");
+ AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+ .setAllowDismissButton(false)
+ .setMultiChoiceItems(cursor, "isChecked", "Label",
+ ((dialog, which, isChecked) -> {
+ }));
+
+ checkDefaultButtonExists(true, builder);
+ }
+
+ private AlertDialog checkDefaultButtonExists(boolean shouldExist, AlertDialogBuilder builder)
+ throws Throwable {
+ AtomicBoolean done = new AtomicBoolean(false);
+ AlertDialog[] result = new AlertDialog[1];
+ mActivityRule.runOnUiThread(() -> {
+ try {
+ result[0] = builder.create();
+ result[0].show();
+ } catch (RuntimeException e) {
+ assert e.getMessage() != null;
+ assert e.getMessage().contains(
+ "must have at least one button to disable the dismiss button");
+
+ assert shouldExist;
+ done.set(true);
+ }
+ });
+
+ if (done.get()) {
+ return result[0];
+ }
+
+ if (shouldExist) {
+ onView(withText(R.string.car_ui_alert_dialog_default_button))
+ .inRoot(new RootWithDecorMatcher(result[0].getWindow().getDecorView()))
+ .check(matches(isDisplayed()));
+ } else {
+ onView(withText(R.string.car_ui_alert_dialog_default_button))
+ .inRoot(new RootWithDecorMatcher(result[0].getWindow().getDecorView()))
+ .check(doesNotExist());
+ }
+
+ return result[0];
+ }
+
+ private static class RootWithDecorMatcher extends TypeSafeMatcher<Root> {
+
+ private View mView;
+
+ RootWithDecorMatcher(View view) {
+ mView = view;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is a root with a certain decor");
+ }
+
+ @Override
+ protected boolean matchesSafely(Root item) {
+ return item.getDecorView() == mView;
+ }
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FakeCursor.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FakeCursor.java
new file mode 100644
index 0000000..d6bc4e1
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FakeCursor.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui;
+
+import android.database.AbstractCursor;
+
+import java.util.List;
+
+public class FakeCursor extends AbstractCursor {
+
+ private List<String> mRows;
+ private String mColumnName;
+
+ public FakeCursor(List<String> rows, String columnName) {
+ mRows = rows;
+ mColumnName = columnName;
+ }
+
+ @Override
+ public int getCount() {
+ return mRows.size();
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ return new String[] { mColumnName };
+ }
+
+ @Override
+ public String getString(int column) {
+ return mRows.get(getPosition());
+ }
+
+ @Override
+ public short getShort(int column) {
+ return 0;
+ }
+
+ @Override
+ public int getInt(int column) {
+ return 0;
+ }
+
+ @Override
+ public long getLong(int column) {
+ return 0;
+ }
+
+ @Override
+ public float getFloat(int column) {
+ return 0;
+ }
+
+ @Override
+ public double getDouble(int column) {
+ return 0;
+ }
+
+ @Override
+ public boolean isNull(int column) {
+ return mRows.get(getPosition()) == null;
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTest.java
index 9c1ce88..b0e7c88 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTest.java
@@ -16,24 +16,36 @@
package com.android.car.ui;
+import static android.view.View.FOCUS_DOWN;
+import static android.view.View.FOCUS_LEFT;
+import static android.view.View.FOCUS_RIGHT;
+import static android.view.View.FOCUS_UP;
import static android.view.View.LAYOUT_DIRECTION_LTR;
import static android.view.View.LAYOUT_DIRECTION_RTL;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+import static com.android.car.ui.RotaryCache.CACHE_TYPE_DISABLED;
+import static com.android.car.ui.RotaryCache.CACHE_TYPE_NEVER_EXPIRE;
+import static com.android.car.ui.utils.RotaryConstants.ACTION_NUDGE_SHORTCUT;
+import static com.android.car.ui.utils.RotaryConstants.ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA;
import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_BOTTOM_BOUND_OFFSET;
import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_LEFT_BOUND_OFFSET;
import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_RIGHT_BOUND_OFFSET;
import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_TOP_BOUND_OFFSET;
+import static com.android.car.ui.utils.RotaryConstants.NUDGE_DIRECTION;
import static com.google.common.truth.Truth.assertThat;
import android.os.Bundle;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.test.rule.ActivityTestRule;
+import com.android.car.ui.test.R;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -41,7 +53,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-/** Unit tests for {@link FocusArea}. */
+/** Unit tests for {@link FocusArea} not in touch mode. */
public class FocusAreaTest {
private static final long WAIT_TIME_MS = 3000;
@@ -50,82 +62,365 @@
new ActivityTestRule<>(FocusAreaTestActivity.class);
private FocusAreaTestActivity mActivity;
- private TestFocusArea mFocusArea;
+ private TestFocusArea mFocusArea1;
private TestFocusArea mFocusArea2;
- private View mChild;
- private View mDefaultFocus;
- private View mNonChild;
- private View mChild1;
- private View mChild2;
+ private TestFocusArea mFocusArea3;
+ private TestFocusArea mFocusArea4;
+ private FocusParkingView mFpv;
+ private View mView1;
+ private Button mButton1;
+ private View mView2;
+ private View mDefaultFocus2;
+ private View mView3;
+ private View mNudgeShortcut3;
+ private View mView4;
@Before
public void setUp() {
mActivity = mActivityRule.getActivity();
- mFocusArea = mActivity.findViewById(R.id.focus_area);
- mFocusArea.enableForegroundHighlight();
+ mFocusArea1 = mActivity.findViewById(R.id.focus_area1);
mFocusArea2 = mActivity.findViewById(R.id.focus_area2);
- mChild = mActivity.findViewById(R.id.child);
- mDefaultFocus = mActivity.findViewById(R.id.default_focus);
- mNonChild = mActivity.findViewById(R.id.non_child);
- mChild1 = mActivity.findViewById(R.id.child1);
- mChild2 = mActivity.findViewById(R.id.child2);
+ mFocusArea3 = mActivity.findViewById(R.id.focus_area3);
+ mFocusArea4 = mActivity.findViewById(R.id.focus_area4);
+ mFpv = mActivity.findViewById(R.id.fpv);
+ mView1 = mActivity.findViewById(R.id.view1);
+ mButton1 = mActivity.findViewById(R.id.button1);
+ mView2 = mActivity.findViewById(R.id.view2);
+ mDefaultFocus2 = mActivity.findViewById(R.id.default_focus2);
+ mView3 = mActivity.findViewById(R.id.view3);
+ mNudgeShortcut3 = mActivity.findViewById(R.id.nudge_shortcut3);
+ mView4 = mActivity.findViewById(R.id.view4);
}
@Test
- public void testLoseFocus() throws Exception {
- mChild.post(() -> {
- mChild.requestFocus();
- });
- mFocusArea.setOnDrawCalled(false);
- mFocusArea.setDrawCalled(false);
-
- // FocusArea lost focus.
+ public void testDrawMethodsCalled() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- mNonChild.post(() -> {
- mNonChild.requestFocus();
- mNonChild.post(() -> {
- latch.countDown();
- });
+ mView1.post(() -> {
+ mView1.requestFocus();
+ mFocusArea1.enableForegroundHighlight();
+ mFocusArea2.enableForegroundHighlight();
+ mFocusArea1.setOnDrawCalled(false);
+ mFocusArea1.setDrawCalled(false);
+ mFocusArea2.setOnDrawCalled(false);
+ mFocusArea2.setDrawCalled(false);
+
+ mView2.requestFocus();
+ mView2.post(() -> latch.countDown());
});
- assertDrawMethodsCalled(latch);
+
+ // The methods should be called when a FocusArea gains or loses focus.
+ assertDrawMethodsCalled(mFocusArea1, latch);
+ assertDrawMethodsCalled(mFocusArea2, latch);
}
@Test
- public void testGetFocus() throws Exception {
- mNonChild.post(() -> {
- mNonChild.requestFocus();
- });
- mFocusArea.setOnDrawCalled(false);
- mFocusArea.setDrawCalled(false);
+ public void testPerformAccessibilityAction_actionNudgeShortcut() {
+ mFocusArea1.post(() -> {
+ // Nudge to the nudgeShortcut view.
+ mView3.requestFocus();
+ assertThat(mView3.isFocused()).isTrue();
+ Bundle arguments = new Bundle();
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_RIGHT);
+ mFocusArea3.performAccessibilityAction(ACTION_NUDGE_SHORTCUT, arguments);
+ assertThat(mNudgeShortcut3.isFocused()).isTrue();
- // FocusArea got focus.
- CountDownLatch latch = new CountDownLatch(1);
- mChild.post(() -> {
- mChild.requestFocus();
- mChild.post(() -> {
- latch.countDown();
- });
+ // nudgeShortcutDirection doesn't match. The focus should stay the same.
+ mView3.requestFocus();
+ assertThat(mView3.isFocused()).isTrue();
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+ mFocusArea3.performAccessibilityAction(ACTION_NUDGE_SHORTCUT, arguments);
+ assertThat(mView3.isFocused()).isTrue();
+
+ // No nudgeShortcut view in the current FocusArea. The focus should stay the same.
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_RIGHT);
+ mFocusArea1.performAccessibilityAction(ACTION_NUDGE_SHORTCUT, arguments);
+ assertThat(mView1.isFocused()).isTrue();
});
- assertDrawMethodsCalled(latch);
+ }
+
+
+ @Test
+ public void testPerformAccessibilityAction_actionFocus() {
+ mFocusArea1.post(() -> {
+ mFocusArea1.performAccessibilityAction(ACTION_FOCUS, null);
+ assertThat(mView1.isFocused()).isTrue();
+
+ // It should focus on the default or the first view in the FocusArea.
+ mFocusArea2.performAccessibilityAction(ACTION_FOCUS, null);
+ assertThat(mDefaultFocus2.isFocused()).isTrue();
+ });
}
@Test
- public void testFocusOnDefaultFocus() throws Exception {
- assertThat(mDefaultFocus.isFocused()).isFalse();
+ public void testPerformAccessibilityAction_actionFocus_enabledFocusCache() {
+ mFocusArea1.post(() -> {
+ RotaryCache cache =
+ new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+ mFocusArea1.setRotaryCache(cache);
- Bundle bundle = new Bundle();
- CountDownLatch latch = new CountDownLatch(1);
- mFocusArea.post(() -> {
- mFocusArea.performAccessibilityAction(ACTION_FOCUS, bundle);
- latch.countDown();
+ mButton1.requestFocus();
+ assertThat(mButton1.isFocused()).isTrue();
+ mView2.requestFocus();
+ assertThat(mView2.isFocused()).isTrue();
+
+ // With cache, it should focus on the lastly focused view in the FocusArea.
+ mFocusArea1.performAccessibilityAction(ACTION_FOCUS, null);
+ assertThat(mButton1.isFocused()).isTrue();
});
- latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
- assertThat(mDefaultFocus.isFocused()).isTrue();
+ }
+
+ @Test
+ public void testPerformAccessibilityAction_actionFocus_disabledFocusCache() {
+ mFocusArea1.post(() -> {
+ RotaryCache cache = new RotaryCache(CACHE_TYPE_DISABLED, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+ mFocusArea1.setRotaryCache(cache);
+
+ mButton1.requestFocus();
+ assertThat(mButton1.isFocused()).isTrue();
+ mView2.requestFocus();
+ assertThat(mView2.isFocused()).isTrue();
+
+ // Without cache, it should focus on the default or the first view in the FocusArea.
+ mFocusArea1.performAccessibilityAction(ACTION_FOCUS, null);
+ assertThat(mView1.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testPerformAccessibilityAction_actionFocus_lastFocusedViewRemoved() {
+ mFocusArea1.post(() -> {
+ // Focus on mDefaultFocus2 in mFocusArea2, then mView1 in mFocusArea21.
+ mDefaultFocus2.requestFocus();
+ assertThat(mDefaultFocus2.isFocused()).isTrue();
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+
+ // Remove mDefaultFocus2, then Perform ACTION_FOCUS on mFocusArea2.
+ mFocusArea2.removeView(mDefaultFocus2);
+ mFocusArea2.performAccessibilityAction(ACTION_FOCUS, null);
+
+ // mView2 in mFocusArea2 should get focused.
+ assertThat(mView2.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testPerformAccessibilityAction_actionNudgeToAnotherFocusArea_enabledCache() {
+ mFocusArea1.post(() -> {
+ RotaryCache cache1 =
+ new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+ mFocusArea1.setRotaryCache(cache1);
+ RotaryCache cache2 =
+ new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+ mFocusArea2.setRotaryCache(cache2);
+
+ // Focus on the second view in mFocusArea1, then nudge to mFocusArea2.
+ mButton1.requestFocus();
+ assertThat(mButton1.isFocused()).isTrue();
+ Bundle arguments = new Bundle();
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+ mFocusArea2.performAccessibilityAction(ACTION_FOCUS, arguments);
+ assertThat(mDefaultFocus2.isFocused()).isTrue();
+
+ // Nudge back. It should focus on the cached view (mButton1) in the cached
+ // FocusArea (mFocusArea1).
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+ mFocusArea2.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+ assertThat(mButton1.isFocused()).isTrue();
+
+ // Nudge back. It should fail and the focus should stay the same because of one-way
+ // nudge history.
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+ mFocusArea1.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+ assertThat(mButton1.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testPerformAccessibilityAction_actionNudgeToAnotherFocusArea_mixedCache() {
+ mFocusArea1.post(() -> {
+ // Disabled FocusCache but enabled FocusAreaCache.
+ RotaryCache cache1 =
+ new RotaryCache(CACHE_TYPE_DISABLED, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+ mFocusArea1.setRotaryCache(cache1);
+ RotaryCache cache2 =
+ new RotaryCache(CACHE_TYPE_DISABLED, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+ mFocusArea2.setRotaryCache(cache2);
+
+ // Focus on the second view in mFocusArea1, then nudge to mFocusArea2.
+ mButton1.requestFocus();
+ assertThat(mButton1.isFocused()).isTrue();
+ Bundle arguments = new Bundle();
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+ mFocusArea2.performAccessibilityAction(ACTION_FOCUS, arguments);
+ assertThat(mDefaultFocus2.isFocused()).isTrue();
+
+ // Nudge back. Since FocusCache is disabled, it should focus on the default or the first
+ // view (mView1) in the cached FocusArea (mFocusArea1).
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+ mFocusArea2.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+ assertThat(mView1.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testPerformAccessibilityAction_actionNudgeToAnotherFocusArea_mixedCache2() {
+ mFocusArea1.post(() -> {
+ // Enabled FocusCache but disabled FocusAreaCache.
+ RotaryCache cache1 =
+ new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_DISABLED, 0);
+ mFocusArea1.setRotaryCache(cache1);
+ RotaryCache cache2 =
+ new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_DISABLED, 0);
+ mFocusArea2.setRotaryCache(cache2);
+
+ // Focus on the second view in mFocusArea1, then nudge to mFocusArea2.
+ mButton1.requestFocus();
+ assertThat(mButton1.isFocused()).isTrue();
+ Bundle arguments = new Bundle();
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+ mFocusArea2.performAccessibilityAction(ACTION_FOCUS, arguments);
+ assertThat(mDefaultFocus2.isFocused()).isTrue();
+
+ // Nudge back. Since FocusAreaCache is disabled, nudge should fail and the focus should
+ // stay the same.
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+ mFocusArea2.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+ assertThat(mDefaultFocus2.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testPerformAccessibilityAction_actionNudgeToAnotherFocusArea_specifiedTarget() {
+ mFocusArea1.post(() -> {
+ // Nudge to specified FocusArea.
+ mView4.requestFocus();
+ assertThat(mView4.isFocused()).isTrue();
+ Bundle arguments = new Bundle();
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_LEFT);
+ mFocusArea4.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+ assertThat(mDefaultFocus2.isFocused()).isTrue();
+
+ // Direction doesn't match specified FocusArea. The focus should stay the same.
+ mView4.requestFocus();
+ assertThat(mView4.isFocused()).isTrue();
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+ mFocusArea4.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+ assertThat(mView4.isFocused()).isTrue();
+
+ // The FocusArea doesn't specify a target FocusArea. The focus should stay the same.
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_LEFT);
+ mFocusArea1.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+ assertThat(mView1.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testDefaultFocusOverridesHistory_override() {
+ mFocusArea1.post(() -> {
+ RotaryCache cache =
+ new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+ mFocusArea2.setRotaryCache(cache);
+ mFocusArea2.setDefaultFocusOverridesHistory(true);
+
+ mView2.requestFocus();
+ assertThat(mView2.isFocused()).isTrue();
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+
+ // The focused view should be the default focus view rather than the cached view.
+ mFocusArea2.performAccessibilityAction(ACTION_FOCUS, null);
+ assertThat(mDefaultFocus2.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testDefaultFocusOverridesHistory_notOverride() {
+ mFocusArea1.post(() -> {
+ RotaryCache cache =
+ new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+ mFocusArea2.setRotaryCache(cache);
+ mFocusArea2.setDefaultFocusOverridesHistory(false);
+
+ mView2.requestFocus();
+ assertThat(mView2.isFocused()).isTrue();
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+
+ // The focused view should be the cached view rather than the default focus view.
+ mFocusArea2.performAccessibilityAction(ACTION_FOCUS, null);
+ assertThat(mView2.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testClearFocusAreaHistoryWhenRotating_clear() {
+ mFocusArea1.post(() -> {
+ RotaryCache cache1 =
+ new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+ mFocusArea1.setRotaryCache(cache1);
+ mFocusArea1.setClearFocusAreaHistoryWhenRotating(true);
+ RotaryCache cache2 =
+ new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+ mFocusArea2.setRotaryCache(cache2);
+ mFocusArea2.setClearFocusAreaHistoryWhenRotating(true);
+
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+
+ // Nudging down from mFocusArea1 to mFocusArea2.
+ Bundle arguments = new Bundle();
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+ mFocusArea2.performAccessibilityAction(ACTION_FOCUS, arguments);
+ assertThat(mDefaultFocus2.isFocused()).isTrue();
+ // Rotate.
+ mView2.requestFocus();
+ assertThat(mView2.isFocused()).isTrue();
+ // Since nudge history is cleared, nudging up should fail and the focus should stay
+ // the same.
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+ mFocusArea2.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+ assertThat(mView2.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testClearFocusAreaHistoryWhenRotating_notClear() {
+ mFocusArea1.post(() -> {
+ RotaryCache cache1 =
+ new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+ mFocusArea1.setRotaryCache(cache1);
+ mFocusArea1.setClearFocusAreaHistoryWhenRotating(false);
+ RotaryCache cache2 =
+ new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
+ mFocusArea2.setRotaryCache(cache2);
+ mFocusArea2.setClearFocusAreaHistoryWhenRotating(false);
+
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+
+ // Nudging down from mFocusArea1 to mFocusArea2.
+ Bundle arguments = new Bundle();
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+ mFocusArea2.performAccessibilityAction(ACTION_FOCUS, arguments);
+ assertThat(mDefaultFocus2.isFocused()).isTrue();
+ // Rotate.
+ mView2.requestFocus();
+ assertThat(mView2.isFocused()).isTrue();
+ // Nudging up should move focus to mFocusArea1 according to nudge history.
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+ mFocusArea2.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+ assertThat(mView1.isFocused()).isTrue();
+ });
}
@Test
public void testBoundsOffset() {
- assertThat(mFocusArea.getLayoutDirection()).isEqualTo(LAYOUT_DIRECTION_LTR);
+ assertThat(mFocusArea1.getLayoutDirection()).isEqualTo(LAYOUT_DIRECTION_LTR);
// FocusArea's bounds offset specified in layout file:
// 10dp(start), 20dp(end), 30dp(top), 40dp(bottom).
@@ -133,36 +428,33 @@
int right = dp2Px(20);
int top = dp2Px(30);
int bottom = dp2Px(40);
- AccessibilityNodeInfo node = mFocusArea.createAccessibilityNodeInfo();
+ AccessibilityNodeInfo node = mFocusArea1.createAccessibilityNodeInfo();
assertBoundsOffset(node, left, top, right, bottom);
node.recycle();
}
@Test
- public void testBoundsOffsetWithRtl() throws Exception {
- CountDownLatch latch = new CountDownLatch(1);
- mFocusArea.post(() -> {
- mFocusArea.setLayoutDirection(LAYOUT_DIRECTION_RTL);
- latch.countDown();
- });
- latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
- assertThat(mFocusArea.getLayoutDirection()).isEqualTo(LAYOUT_DIRECTION_RTL);
+ public void testBoundsOffsetWithRtl() {
+ mFocusArea1.post(() -> {
+ mFocusArea1.setLayoutDirection(LAYOUT_DIRECTION_RTL);
+ assertThat(mFocusArea1.getLayoutDirection()).isEqualTo(LAYOUT_DIRECTION_RTL);
- // FocusArea highlight padding specified in layout file:
- // 10dp(start), 20dp(end), 30dp(top), 40dp(bottom).
- int left = dp2Px(20);
- int right = dp2Px(10);
- int top = dp2Px(30);
- int bottom = dp2Px(40);
- AccessibilityNodeInfo node = mFocusArea.createAccessibilityNodeInfo();
- assertBoundsOffset(node, left, top, right, bottom);
- node.recycle();
+ // FocusArea highlight padding specified in layout file:
+ // 10dp(start), 20dp(end), 30dp(top), 40dp(bottom).
+ int left = dp2Px(20);
+ int right = dp2Px(10);
+ int top = dp2Px(30);
+ int bottom = dp2Px(40);
+ AccessibilityNodeInfo node = mFocusArea1.createAccessibilityNodeInfo();
+ assertBoundsOffset(node, left, top, right, bottom);
+ node.recycle();
+ });
}
@Test
public void testSetBoundsOffset() {
- mFocusArea.setBoundsOffset(50, 60, 70, 80);
- AccessibilityNodeInfo node = mFocusArea.createAccessibilityNodeInfo();
+ mFocusArea1.setBoundsOffset(50, 60, 70, 80);
+ AccessibilityNodeInfo node = mFocusArea1.createAccessibilityNodeInfo();
assertBoundsOffset(node, 50, 60, 70, 80);
node.recycle();
}
@@ -181,20 +473,37 @@
}
@Test
- public void testLastFocusedViewRemoved() {
- mChild1.post(() -> {
- // Focus on mChild1 in mFocusArea2, then mChild in mFocusArea .
- mChild1.requestFocus();
- assertThat(mChild1.isFocused()).isTrue();
- mChild.requestFocus();
- assertThat(mChild.isFocused()).isTrue();
+ public void testBug170423337() {
+ mFocusArea1.post(() -> {
+ // Focus on app bar (assume mFocusArea1 is app bar).
+ mView1.requestFocus();
- // Remove mChild1 in mFocusArea2, then Perform ACTION_FOCUS on mFocusArea2.
- mFocusArea2.removeView(mChild1);
- mFocusArea2.performAccessibilityAction(ACTION_FOCUS, null);
+ // Nudge down to browse list (assume mFocusArea2 is browse list).
+ Bundle arguments = new Bundle();
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_DOWN);
+ mFocusArea2.performAccessibilityAction(ACTION_FOCUS, arguments);
+ assertThat(mDefaultFocus2.isFocused()).isTrue();
- // mChild2 in mFocusArea2 should get focused.
- assertThat(mChild2.isFocused()).isTrue();
+ // Nudge down to playback control bar (assume mFocusArea3 is playback control bar).
+ mFocusArea3.performAccessibilityAction(ACTION_FOCUS, arguments);
+ assertThat(mView3.isFocused()).isTrue();
+
+ // Nudge down to navigation bar (navigation bar is in system window without FocusAreas).
+ mFpv.performAccessibilityAction(ACTION_FOCUS, null);
+
+ // Nudge up to playback control bar.
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+ mFocusArea3.performAccessibilityAction(ACTION_FOCUS, arguments);
+ assertThat(mView3.isFocused()).isTrue();
+
+ // Nudge up to browse list.
+ arguments.putInt(NUDGE_DIRECTION, FOCUS_UP);
+ mFocusArea3.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+ assertThat(mDefaultFocus2.isFocused()).isTrue();
+
+ // Nudge up, and it should focus on app bar.
+ mFocusArea2.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+ assertThat(mView1.isFocused()).isTrue();
});
}
@@ -212,9 +521,10 @@
return (int) (dp * mActivity.getResources().getDisplayMetrics().density + 0.5f);
}
- private void assertDrawMethodsCalled(CountDownLatch latch) throws Exception {
+ private void assertDrawMethodsCalled(@NonNull TestFocusArea focusArea, CountDownLatch latch)
+ throws Exception {
latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
- assertThat(mFocusArea.onDrawCalled()).isTrue();
- assertThat(mFocusArea.drawCalled()).isTrue();
+ assertThat(focusArea.onDrawCalled()).isTrue();
+ assertThat(focusArea.drawCalled()).isTrue();
}
}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTouchModeTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTouchModeTest.java
new file mode 100644
index 0000000..5c06f84
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusAreaTouchModeTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui;
+
+import static android.view.View.FOCUS_RIGHT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.graphics.Rect;
+import android.view.View;
+
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.test.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/** Unit tests for {@link FocusArea} in touch mode. */
+public class FocusAreaTouchModeTest {
+ @Rule
+ public ActivityTestRule<FocusAreaTestActivity> mActivityRule =
+ new ActivityTestRule<>(FocusAreaTestActivity.class, /* initialTouchMode= */ true);
+
+ private FocusAreaTestActivity mActivity;
+ private TestFocusArea mFocusArea2;
+ private View mView1;
+
+ @Before
+ public void setUp() {
+ mActivity = mActivityRule.getActivity();
+ mFocusArea2 = mActivity.findViewById(R.id.focus_area2);
+ mView1 = mActivity.findViewById(R.id.view1);
+ }
+
+ @Test
+ public void testOnRequestFocusInDescendants_doesNothing() {
+ mFocusArea2.post(() -> {
+ Rect previouslyFocusedRect = new Rect();
+ previouslyFocusedRect.left = mView1.getLeft();
+ previouslyFocusedRect.top = mView1.getTop();
+ previouslyFocusedRect.right = previouslyFocusedRect.left + mView1.getWidth();
+ previouslyFocusedRect.bottom = previouslyFocusedRect.top + mView1.getHeight();
+ boolean focusTaken =
+ mFocusArea2.onRequestFocusInDescendants(FOCUS_RIGHT, previouslyFocusedRect);
+
+ assertWithMessage("onRequestFocusInDescendants returned").that(focusTaken).isFalse();
+ assertWithMessage("No view should be focused")
+ .that(mFocusArea2.getRootView().findFocus()).isNull();
+ });
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java
index 887ac8e..e706ae1 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java
@@ -16,64 +16,242 @@
package com.android.car.ui;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+
+import static com.android.car.ui.utils.RotaryConstants.ACTION_RESTORE_DEFAULT_FOCUS;
+
import static com.google.common.truth.Truth.assertThat;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
import androidx.test.rule.ActivityTestRule;
+import com.android.car.ui.recyclerview.TestContentLimitingAdapter;
import com.android.car.ui.test.R;
+import com.android.car.ui.utils.CarUiUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/** Unit test for {@link FocusParkingView}. */
+/** Unit test for {@link FocusParkingView} not in touch mode. */
public class FocusParkingViewTest {
- private static final long WAIT_TIME_MS = 3000;
+ private static final int NUM_ITEMS = 40;
@Rule
public ActivityTestRule<FocusParkingViewTestActivity> mActivityRule =
new ActivityTestRule<>(FocusParkingViewTestActivity.class);
private FocusParkingViewTestActivity mActivity;
+ private FocusParkingView mFpv;
+ private ViewGroup mParent1;
+ private View mView1;
+ private View mFocusedByDefault;
+ private RecyclerView mList;
@Before
public void setUp() {
mActivity = mActivityRule.getActivity();
+ mFpv = mActivity.findViewById(R.id.fpv);
+ mParent1 = mActivity.findViewById(R.id.parent1);
+ mView1 = mActivity.findViewById(R.id.view1);
+ mFocusedByDefault = mActivity.findViewById(R.id.focused_by_default);
+ mList = mActivity.findViewById(R.id.list);
+
+ mList.post(() -> {
+ mList.setLayoutManager(new LinearLayoutManager(mActivity));
+ mList.setAdapter(new TestContentLimitingAdapter(NUM_ITEMS));
+ CarUiUtils.setRotaryScrollEnabled(mList, /* isVertical= */ true);
+ });
}
@Test
- public void testFocusParkingViewCanTakeFocus() throws Exception {
- FocusParkingView focusParkingView = mActivity.findViewById(R.id.focus_parking);
-
- CountDownLatch latch = new CountDownLatch(1);
- focusParkingView.post(() -> {
- focusParkingView.requestFocus();
- focusParkingView.post(() -> {
- latch.countDown();
- });
- });
- latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-
- assertThat(focusParkingView.isFocused()).isTrue();
+ public void testGetWidthAndHeight() {
+ assertThat(mFpv.getWidth()).isEqualTo(1);
+ assertThat(mFpv.getHeight()).isEqualTo(1);
}
+
@Test
- public void testFocusParkingViewFocusedWhenWindowLostFocus() throws Exception {
- FocusParkingView focusParkingView = mActivity.findViewById(R.id.focus_parking);
- assertThat(focusParkingView.isFocused()).isFalse();
+ public void testRequestFocus_focusOnDefaultFocus() {
+ mFpv.post(() -> {
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
- CountDownLatch latch = new CountDownLatch(1);
- focusParkingView.post(() -> {
- focusParkingView.onWindowFocusChanged(false);
- focusParkingView.post(() -> {
- latch.countDown();
- });
+ mFpv.requestFocus();
+ assertThat(mFocusedByDefault.isFocused()).isTrue();
});
- latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+ }
- assertThat(focusParkingView.isFocused()).isTrue();
+ @Test
+ public void testRestoreDefaultFocus_focusOnDefaultFocus() {
+ mFpv.post(() -> {
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+
+ mFpv.restoreDefaultFocus();
+ assertThat(mFocusedByDefault.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testOnWindowFocusChanged_loseFocus() {
+ mFpv.post(() -> {
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+
+ mFpv.onWindowFocusChanged(false);
+ assertThat(mFpv.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testOnWindowFocusChanged_focusOnDefaultFocus() {
+ mFpv.post(() -> {
+ mFpv.performAccessibilityAction(ACTION_FOCUS, null);
+ assertThat(mFpv.isFocused()).isTrue();
+
+ mFpv.onWindowFocusChanged(true);
+ assertThat(mFocusedByDefault.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testPerformAccessibilityAction_actionRestoreDefaultFocus() {
+ mFpv.post(() -> {
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+
+ mFpv.performAccessibilityAction(ACTION_RESTORE_DEFAULT_FOCUS, null);
+ assertThat(mFocusedByDefault.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testPerformAccessibilityAction_actionFocus() {
+ mFpv.post(() -> {
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+
+ mFpv.performAccessibilityAction(ACTION_FOCUS, null);
+ assertThat(mFpv.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testRestoreFocusInRoot_recyclerViewItemRemoved() {
+ mList.post(() -> mList.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mList.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ View firstItem = mList.getLayoutManager().findViewByPosition(0);
+ firstItem.requestFocus();
+ assertThat(firstItem.isFocused()).isTrue();
+
+ ViewGroup parent = (ViewGroup) firstItem.getParent();
+ parent.removeView(firstItem);
+ assertThat(mFocusedByDefault.isFocused()).isTrue();
+ }
+ })
+ );
+ }
+
+ @Test
+ public void testRestoreFocusInRoot_recyclerViewItemScrolledOffScreen() {
+ mList.post(() -> mList.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mList.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ View firstItem = mList.getLayoutManager().findViewByPosition(0);
+ firstItem.requestFocus();
+ assertThat(firstItem.isFocused()).isTrue();
+
+ mList.scrollToPosition(NUM_ITEMS - 1);
+ mList.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mList.getViewTreeObserver()
+ .removeOnGlobalLayoutListener(this);
+ assertThat(mList.isFocused()).isTrue();
+ }
+ });
+ }
+ }));
+ }
+
+ @Test
+ public void testRestoreFocusInRoot_focusedViewRemoved() {
+ mFpv.post(() -> {
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+
+ ViewGroup parent = (ViewGroup) mView1.getParent();
+ parent.removeView(mView1);
+ assertThat(mFocusedByDefault.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testRestoreFocusInRoot_focusedViewDisabled() {
+ mFpv.post(() -> {
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+
+ mView1.setEnabled(false);
+ assertThat(mFocusedByDefault.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testRestoreFocusInRoot_focusedViewBecomesInvisible() {
+ mFpv.post(() -> {
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+
+ mView1.setVisibility(View.INVISIBLE);
+ assertThat(mFocusedByDefault.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testRestoreFocusInRoot_focusedViewParentBecomesInvisible() {
+ mFpv.post(() -> {
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+
+ mParent1.setVisibility(View.INVISIBLE);
+ assertThat(mFocusedByDefault.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testRequestFocus_focusesFpvWhenShouldRestoreFocusIsFalse() {
+ mFpv.post(() -> {
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+ mFpv.setShouldRestoreFocus(false);
+
+ mFpv.requestFocus();
+ assertThat(mFpv.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testRestoreDefaultFocus_focusesFpvWhenShouldRestoreFocusIsFalse() {
+ mFpv.post(() -> {
+ mView1.requestFocus();
+ assertThat(mView1.isFocused()).isTrue();
+ mFpv.setShouldRestoreFocus(false);
+
+ mFpv.restoreDefaultFocus();
+ assertThat(mFpv.isFocused()).isTrue();
+ });
}
}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTouchModeTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTouchModeTest.java
new file mode 100644
index 0000000..d872bb6
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTouchModeTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui;
+
+import static com.android.car.ui.utils.RotaryConstants.ACTION_RESTORE_DEFAULT_FOCUS;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.view.View;
+
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.test.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/** Unit test for {@link FocusParkingView} in touch mode. */
+public class FocusParkingViewTouchModeTest {
+
+ @Rule
+ public ActivityTestRule<FocusParkingViewTestActivity> mActivityRule =
+ new ActivityTestRule<>(FocusParkingViewTestActivity.class,
+ /* initialTouchMode= */ true);
+
+ private FocusParkingView mFpv;
+
+ @Before
+ public void setUp() {
+ FocusParkingViewTestActivity activity = mActivityRule.getActivity();
+ mFpv = activity.findViewById(R.id.fpv);
+ }
+
+ @Test
+ public void testRestoreDefaultFocus_doesNothing() {
+ mFpv.post(() -> {
+ assertThat(mFpv.getRootView().findFocus()).isNull();
+
+ boolean result = mFpv.restoreDefaultFocus();
+
+ assertWithMessage("restoreDefaultFocus returned").that(result).isFalse();
+ assertWithMessage("No view should be focused")
+ .that(mFpv.getRootView().findFocus()).isNull();
+ });
+ }
+
+ @Test
+ public void testRequestFocus_doesNothing() {
+ mFpv.post(() -> {
+ assertThat(mFpv.getRootView().findFocus()).isNull();
+
+ boolean result = mFpv.requestFocus(View.FOCUS_DOWN, /* previouslyFocusedRect= */ null);
+
+ assertWithMessage("requestFocus returned").that(result).isFalse();
+ assertWithMessage("No view should be focused")
+ .that(mFpv.getRootView().findFocus()).isNull();
+ });
+ }
+
+ @Test
+ public void testPerformActionRestoreDefaultFocus_exitsTouchMode() {
+ mFpv.post(() -> {
+ assertThat(mFpv.getRootView().findFocus()).isNull();
+
+ boolean result = mFpv.performAccessibilityAction(
+ ACTION_RESTORE_DEFAULT_FOCUS, /* arguments= */ null);
+
+ assertWithMessage("performAccessibilityAction returned").that(result).isTrue();
+ assertWithMessage("A view should be focused")
+ .that(mFpv.getRootView().findFocus()).isNotNull();
+ });
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/RotaryCacheTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/RotaryCacheTest.java
new file mode 100644
index 0000000..b1b9b59
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/RotaryCacheTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui;
+
+import static com.android.car.ui.RotaryCache.CACHE_TYPE_EXPIRED_AFTER_SOME_TIME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link RotaryCache}. */
+@RunWith(AndroidJUnit4.class)
+public class RotaryCacheTest {
+ private static final int CACHE_TIME_OUT_MS = 10000;
+
+ private RotaryCache mRotaryCache;
+ private long mValidTime;
+ private long mExpiredTime;
+ private Context mContext;
+ private FocusArea mFocusArea;
+ private View mFocusedView;
+
+ @Before
+ public void setUp() {
+ mRotaryCache = new RotaryCache(CACHE_TYPE_EXPIRED_AFTER_SOME_TIME, CACHE_TIME_OUT_MS,
+ CACHE_TYPE_EXPIRED_AFTER_SOME_TIME, CACHE_TIME_OUT_MS);
+ mValidTime = CACHE_TIME_OUT_MS - 1;
+ mExpiredTime = CACHE_TIME_OUT_MS + 1;
+ mContext = ApplicationProvider.getApplicationContext();
+ mFocusArea = new FocusArea(mContext);
+ mFocusedView = new View(mContext);
+ }
+
+ @Test
+ public void testGetFocusedView_inTheCache() {
+ mRotaryCache.saveFocusedView(mFocusedView, 0);
+ View view = mRotaryCache.getFocusedView(mValidTime);
+ assertThat(view).isEqualTo(mFocusedView);
+ }
+
+ @Test
+ public void testGetFocusedView_notInTheCache() {
+ View view = mRotaryCache.getFocusedView(mValidTime);
+ assertThat(view).isNull();
+ }
+
+ @Test
+ public void testGetFocusedView_expiredCache() {
+ mRotaryCache.saveFocusedView(mFocusedView, 0);
+ View view = mRotaryCache.getFocusedView(mExpiredTime);
+ assertThat(view).isNull();
+ }
+
+ @Test
+ public void testGetCachedFocusArea_inTheCache() {
+ int direction = View.FOCUS_LEFT;
+ mRotaryCache.saveFocusArea(direction, mFocusArea, 0);
+ FocusArea focusArea = mRotaryCache.getCachedFocusArea(direction, mValidTime);
+ assertThat(focusArea).isEqualTo(mFocusArea);
+ }
+
+ @Test
+ public void testGetCachedFocusArea_notInTheCache() {
+ int direction = View.FOCUS_LEFT;
+ mRotaryCache.saveFocusArea(direction, mFocusArea, 0);
+
+ FocusArea focusArea = mRotaryCache.getCachedFocusArea(View.FOCUS_RIGHT, mValidTime);
+ assertThat(focusArea).isNull();
+ focusArea = mRotaryCache.getCachedFocusArea(View.FOCUS_UP, mValidTime);
+ assertThat(focusArea).isNull();
+ }
+
+ @Test
+ public void testGetCachedFocusArea_expiredCache() {
+ int direction = View.FOCUS_LEFT;
+ mRotaryCache.saveFocusArea(direction, mFocusArea, 0);
+ FocusArea focusArea = mRotaryCache.getCachedFocusArea(direction, mExpiredTime);
+ assertThat(focusArea).isNull();
+ }
+
+ @Test
+ public void testClearFocusAreaHistory() {
+ mRotaryCache.saveFocusArea(View.FOCUS_UP, mFocusArea, 0);
+ assertThat(mRotaryCache.getCachedFocusArea(View.FOCUS_UP, mValidTime)).isEqualTo(
+ mFocusArea);
+
+ mRotaryCache.clearFocusAreaHistory();
+ assertThat(mRotaryCache.getCachedFocusArea(View.FOCUS_UP, mValidTime)).isNull();
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenControllerTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenControllerTest.java
new file mode 100644
index 0000000..11d33f4
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenControllerTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.imewidescreen;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.REQUEST_RENDER_CONTENT_AREA;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_ACTION;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.inputmethodservice.ExtractEditText;
+import android.inputmethodservice.InputMethodService;
+import android.inputmethodservice.InputMethodService.Insets;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.widget.FrameLayout;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.test.R;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link CarUiImeWideScreenController}.
+ */
+public class CarUiImeWideScreenControllerTest {
+
+ private Context mContext = ApplicationProvider.getApplicationContext();
+
+ @Mock
+ Context mMockContext;
+
+ @Mock
+ InputMethodService mInputMethodService;
+
+ @Mock
+ Dialog mDialog;
+
+ @Mock
+ Window mWindow;
+
+ private CarUiImeWideScreenTestActivity mActivity;
+
+ @Rule
+ public ActivityTestRule<CarUiImeWideScreenTestActivity> mActivityRule =
+ new ActivityTestRule<>(CarUiImeWideScreenTestActivity.class);
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mActivity = mActivityRule.getActivity();
+ }
+
+ @After
+ public void destroy() {
+ mActivity.finish();
+ }
+
+ @Test
+ public void createWideScreenImeView_shouldWrapTheViewInTemplate() {
+ // make sure view is wrapped in the template.
+ assertNotNull(mActivity.findViewById(R.id.test_ime_input_view_id));
+
+ // check all views in template default visibility.
+ onView(withId(R.id.car_ui_wideScreenDescriptionTitle)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.car_ui_wideScreenDescription)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.car_ui_inputExtractActionAutomotive)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.car_ui_wideScreenSearchResultList)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.car_ui_wideScreenErrorMessage)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.car_ui_wideScreenError)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.car_ui_contentAreaAutomotive)).check(matches(not(isDisplayed())));
+
+ onView(withId(R.id.car_ui_wideScreenExtractedTextIcon)).check(matches(isDisplayed()));
+ onView(withId(R.id.car_ui_wideScreenClearData)).check(matches(isDisplayed()));
+ onView(withId(R.id.car_ui_fullscreenArea)).check(matches(isDisplayed()));
+ onView(withId(R.id.car_ui_inputExtractEditTextContainer)).check(matches(isDisplayed()));
+
+ // check if the click listener is installed on the image to clear data.
+ View clearDataIcon = mActivity.findViewById(R.id.car_ui_wideScreenClearData);
+ assertTrue(clearDataIcon.hasOnClickListeners());
+ }
+
+ @Test
+ public void onComputeInsets_showContentArea_shouldUpdateEntireAreaAsTouchable() {
+ when(mInputMethodService.getWindow()).thenReturn(mDialog);
+ when(mDialog.getWindow()).thenReturn(mWindow);
+ View view = new FrameLayout(mContext);
+ view.setTop(0);
+ view.setBottom(200);
+ when(mWindow.getDecorView()).thenReturn(view);
+
+ InputMethodService.Insets outInsets = new Insets();
+ CarUiImeWideScreenController carUiImeWideScreenController = getController();
+ carUiImeWideScreenController.onComputeInsets(outInsets);
+
+ assertThat(outInsets.touchableInsets, is(InputMethodService.Insets.TOUCHABLE_INSETS_FRAME));
+ assertThat(outInsets.contentTopInsets, is(200));
+ assertThat(outInsets.visibleTopInsets, is(200));
+ }
+
+ @Test
+ public void onComputeInsets_hideContentArea_shouldUpdateRegionAsTouchable() {
+ when(mInputMethodService.getWindow()).thenReturn(mDialog);
+ when(mDialog.getWindow()).thenReturn(mWindow);
+ View view = new FrameLayout(mContext);
+ view.setTop(0);
+ view.setBottom(200);
+ when(mWindow.getDecorView()).thenReturn(view);
+
+ View imeInputView = LayoutInflater.from(mContext)
+ .inflate(R.layout.test_ime_input_view, null, false);
+ CarUiImeWideScreenController carUiImeWideScreenController = getController();
+ carUiImeWideScreenController.setExtractEditText(new ExtractEditText(mContext));
+ carUiImeWideScreenController.createWideScreenImeView(imeInputView);
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(REQUEST_RENDER_CONTENT_AREA, false);
+ carUiImeWideScreenController.onAppPrivateCommand(WIDE_SCREEN_ACTION, bundle);
+
+ InputMethodService.Insets outInsets = new Insets();
+ carUiImeWideScreenController.onComputeInsets(outInsets);
+
+ assertThat(outInsets.touchableInsets,
+ is(InputMethodService.Insets.TOUCHABLE_INSETS_REGION));
+ assertThat(outInsets.contentTopInsets, is(200));
+ assertThat(outInsets.visibleTopInsets, is(200));
+ }
+
+ private CarUiImeWideScreenController getController() {
+ return new CarUiImeWideScreenController(mContext, mInputMethodService) {
+ @Override
+ public boolean isWideScreenMode() {
+ return true;
+ }
+ };
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenTestActivity.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenTestActivity.java
new file mode 100644
index 0000000..8ffd5a2
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenTestActivity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.imewidescreen;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.car.ui.test.R;
+
+/**
+ * An {@link Activity} that mimics a wide screen IME and displays the template for testing.
+ */
+public class CarUiImeWideScreenTestActivity extends Activity {
+ public static CarUiImeWideScreenController sCarUiImeWideScreenController;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.car_ui_ime_wide_screen_test_activity);
+
+ FrameLayout root = findViewById(R.id.test_activity);
+
+ sCarUiImeWideScreenController = new CarUiImeWideScreenController(this, null) {
+ @Override
+ public boolean isWideScreenMode() {
+ return true;
+ }
+ };
+
+ View imeInputView = LayoutInflater.from(this)
+ .inflate(R.layout.test_ime_input_view, null, false);
+
+ View templateView = sCarUiImeWideScreenController.createWideScreenImeView(imeInputView);
+
+ root.addView(templateView);
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/PaddingMatcher.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/PaddingMatcher.java
new file mode 100644
index 0000000..5ab1a86
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/PaddingMatcher.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.matchers;
+
+import android.view.View;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+
+public class PaddingMatcher extends TypeSafeMatcher<View> {
+
+ public enum Side {
+ TOP,
+ BOTTOM,
+ LEFT,
+ RIGHT,
+ START,
+ END
+ }
+
+ private Side mSide;
+ private int mMin;
+ private int mMax;
+
+ public PaddingMatcher(Side side, int min, int max) {
+ mSide = side;
+ mMin = min;
+ mMax = max;
+ }
+
+ @Override
+ protected boolean matchesSafely(View item) {
+ int padding = 0;
+ switch (mSide) {
+ case TOP:
+ padding = item.getPaddingTop();
+ break;
+ case BOTTOM:
+ padding = item.getPaddingBottom();
+ break;
+ case LEFT:
+ padding = item.getPaddingLeft();
+ break;
+ case RIGHT:
+ padding = item.getPaddingRight();
+ break;
+ case START:
+ padding = item.getPaddingStart();
+ break;
+ case END:
+ padding = item.getPaddingEnd();
+ break;
+ }
+
+ if (mMin >= 0 && padding < mMin) {
+ return false;
+ }
+
+ return mMax < 0 || padding <= mMax;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description
+ .appendText("with " + mSide.toString() + " padding between " + mMin + " and " + mMax);
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java
index 74ce4fa..cdef653 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java
@@ -18,6 +18,8 @@
import android.view.View;
+import com.android.car.ui.matchers.PaddingMatcher.Side;
+
import org.hamcrest.Matcher;
public class ViewMatchers {
@@ -32,4 +34,12 @@
public static Matcher<View> withIndex(Matcher<View> matcher, int index) {
return new IndexMatcher(matcher, index);
}
+
+ public static Matcher<View> withPadding(Side side, int exactly) {
+ return new PaddingMatcher(side, exactly, exactly);
+ }
+
+ public static Matcher<View> withPaddingAtLeast(Side side, int min) {
+ return new PaddingMatcher(side, min, -1);
+ }
}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/NonFullscreenPreferenceFragmentTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/NonFullscreenPreferenceFragmentTest.java
new file mode 100644
index 0000000..33aaa18
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/NonFullscreenPreferenceFragmentTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.preference;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.Espresso.pressBack;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static com.android.car.ui.matchers.ViewMatchers.withPadding;
+import static com.android.car.ui.matchers.ViewMatchers.withPaddingAtLeast;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.ListPreference;
+import androidx.preference.MultiSelectListPreference;
+import androidx.preference.PreferenceScreen;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.ui.baselayout.Insets;
+import com.android.car.ui.baselayout.InsetsChangedListener;
+import com.android.car.ui.core.CarUi;
+import com.android.car.ui.matchers.PaddingMatcher.Side;
+import com.android.car.ui.toolbar.ToolbarController;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+public class NonFullscreenPreferenceFragmentTest {
+
+ private static final String EXTRA_FULLSCREEN = "fullscreen";
+ private static final String TOOLBAR_DEFAULT_TEXT = "Test!";
+ private static final String PREFERENCE_SCREEN_TITLE = "PreferenceScreen Title";
+ private static final String LIST_PREFERENCE_TITLE = "List Preference";
+ private static final String MULTI_SELECT_LIST_PREFERENCE_TITLE = "MultiSelect List Preference";
+ private static final String BACK_CONTENT_DESCRIPTION = "Back";
+ private static final String[] ITEMS = { "Item 1", "Item 2", "Item 3" };
+
+ @Rule
+ public ActivityScenarioRule<PreferenceTestActivity> mActivityRule =
+ new ActivityScenarioRule<>(PreferenceTestActivity.class);
+
+ @Test
+ public void test_fullscreen_changesTitle() {
+ try (ActivityScenario<MyActivity> scenario =
+ ActivityScenario.launch(MyActivity.newIntent(true))) {
+
+ onView(withText(TOOLBAR_DEFAULT_TEXT)).check(doesNotExist());
+ onView(withText(PREFERENCE_SCREEN_TITLE)).check(matches(isDisplayed()));
+ onView(isAssignableFrom(RecyclerView.class)).check(
+ matches(withPaddingAtLeast(Side.TOP, 1)));
+
+ onView(withText(MULTI_SELECT_LIST_PREFERENCE_TITLE)).perform(click());
+ onView(withText(MULTI_SELECT_LIST_PREFERENCE_TITLE)).check(matches(isDisplayed()));
+ onView(withText(ITEMS[0])).check(matches(isDisplayed()));
+ onView(isAssignableFrom(RecyclerView.class)).check(
+ matches(withPaddingAtLeast(Side.TOP, 1)));
+ onView(withContentDescription(BACK_CONTENT_DESCRIPTION)).perform(click());
+
+ onView(withText(LIST_PREFERENCE_TITLE)).perform(click());
+ onView(withText(LIST_PREFERENCE_TITLE)).check(matches(isDisplayed()));
+ onView(withText(ITEMS[0])).check(matches(isDisplayed()));
+ onView(isAssignableFrom(RecyclerView.class)).check(
+ matches(withPaddingAtLeast(Side.TOP, 1)));
+ onView(withContentDescription(BACK_CONTENT_DESCRIPTION)).perform(click());
+ }
+ }
+
+ @Test
+ public void test_nonFullscreen_doesntChangeTitle() {
+ try (ActivityScenario<MyActivity> scenario =
+ ActivityScenario.launch(MyActivity.newIntent(false))) {
+
+ onView(withText(TOOLBAR_DEFAULT_TEXT)).check(matches(isDisplayed()));
+ onView(withText(PREFERENCE_SCREEN_TITLE)).check(doesNotExist());
+ onView(isAssignableFrom(RecyclerView.class)).check(matches(withPadding(Side.TOP, 0)));
+
+ onView(withText(MULTI_SELECT_LIST_PREFERENCE_TITLE)).perform(click());
+ onView(withText(MULTI_SELECT_LIST_PREFERENCE_TITLE)).check(doesNotExist());
+ onView(withText(TOOLBAR_DEFAULT_TEXT)).check(matches(isDisplayed()));
+ onView(withText(ITEMS[0])).check(matches(isDisplayed()));
+ onView(isAssignableFrom(RecyclerView.class)).check(matches(withPadding(Side.TOP, 0)));
+ onView(withContentDescription(BACK_CONTENT_DESCRIPTION)).check(doesNotExist());
+ pressBack();
+
+ onView(withText(LIST_PREFERENCE_TITLE)).perform(click());
+ onView(withText(LIST_PREFERENCE_TITLE)).check(doesNotExist());
+ onView(withText(TOOLBAR_DEFAULT_TEXT)).check(matches(isDisplayed()));
+ onView(withText(ITEMS[0])).check(matches(isDisplayed()));
+ onView(isAssignableFrom(RecyclerView.class)).check(matches(withPadding(Side.TOP, 0)));
+ onView(withContentDescription(BACK_CONTENT_DESCRIPTION)).check(doesNotExist());
+ pressBack();
+ }
+ }
+
+
+ public static class MyActivity extends AppCompatActivity implements InsetsChangedListener {
+
+ private boolean mIsFullScreen = false;
+
+ public static Intent newIntent(boolean isFullScreen) {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ Intent intent = new Intent(context, MyActivity.class);
+ intent.putExtra(EXTRA_FULLSCREEN, isFullScreen);
+ return intent;
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ToolbarController toolbar = CarUi.requireToolbar(this);
+ toolbar.setTitle(TOOLBAR_DEFAULT_TEXT);
+
+ mIsFullScreen = getIntent().getBooleanExtra(EXTRA_FULLSCREEN, true);
+ if (savedInstanceState == null) {
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(android.R.id.content, new MyPreferenceFragment(mIsFullScreen))
+ .commitNow();
+ }
+ }
+
+ @Override
+ public void onCarUiInsetsChanged(@NonNull Insets insets) {
+ if (!mIsFullScreen) {
+ requireViewById(android.R.id.content).setPadding(insets.getLeft(), insets.getTop(),
+ insets.getRight(), insets.getBottom());
+ } else {
+ // No-op marker for the preference fragment to handle it
+ }
+ }
+ }
+
+ public static class MyPreferenceFragment extends PreferenceFragment {
+
+ private final boolean mIsFullScreen;
+
+ public MyPreferenceFragment(boolean isFullScreen) {
+ mIsFullScreen = isFullScreen;
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ PreferenceScreen screen = getPreferenceManager()
+ .createPreferenceScreen(requireContext());
+
+ ListPreference listPreference = new CarUiListPreference(getContext());
+ listPreference.setTitle(LIST_PREFERENCE_TITLE);
+ listPreference.setKey(LIST_PREFERENCE_TITLE);
+ listPreference.setEntries(ITEMS);
+ listPreference.setEntryValues(new CharSequence[]{"1", "2", "3"});
+
+ MultiSelectListPreference multiSelectListPreference =
+ new CarUiMultiSelectListPreference(getContext());
+ multiSelectListPreference.setTitle(MULTI_SELECT_LIST_PREFERENCE_TITLE);
+ multiSelectListPreference.setKey(MULTI_SELECT_LIST_PREFERENCE_TITLE);
+ multiSelectListPreference.setEntries(ITEMS);
+ multiSelectListPreference.setEntryValues(new CharSequence[]{"1", "2", "3"});
+
+ screen.addPreference(listPreference);
+ screen.addPreference(multiSelectListPreference);
+
+ screen.setTitle(PREFERENCE_SCREEN_TITLE);
+ setPreferenceScreen(screen);
+ }
+
+ @Override
+ protected boolean isFullScreenFragment() {
+ return mIsFullScreen;
+ }
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiListItemTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiListItemTest.java
index bd21173..da446e1 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiListItemTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiListItemTest.java
@@ -29,13 +29,10 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.view.View;
-
import androidx.test.rule.ActivityTestRule;
import com.android.car.ui.R;
@@ -43,7 +40,6 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.List;
@@ -242,7 +238,8 @@
CarUiContentListItem.OnClickListener clickListener = mock(
CarUiContentListItem.OnClickListener.class);
- View.OnClickListener supplementalIconClickListener = mock(View.OnClickListener.class);
+ CarUiContentListItem.OnClickListener supplementalIconClickListener = mock(
+ CarUiContentListItem.OnClickListener.class);
CarUiContentListItem item = new CarUiContentListItem(
CarUiContentListItem.Action.ICON);
@@ -262,18 +259,14 @@
// listener.
onView(withId(R.id.title)).perform(click());
verify(clickListener, times(1)).onClick(item);
- verify(supplementalIconClickListener, times(0)).onClick(any());
+ verify(supplementalIconClickListener, times(0)).onClick(item);
- ArgumentCaptor<View> iconCaptor = ArgumentCaptor.forClass(View.class);
onView(withId(R.id.supplemental_icon)).perform(click());
// Check that icon is argument for single call to click listener.
- verify(supplementalIconClickListener, times(1)).onClick(iconCaptor.capture());
+ verify(supplementalIconClickListener, times(1)).onClick(item);
// Verify that the standard click listener wasn't also fired.
verify(clickListener, times(1)).onClick(item);
-
- View icon = mCarUiRecyclerView.findViewById(R.id.supplemental_icon);
- assertEquals(icon, iconCaptor.getValue());
}
@Test
@@ -282,7 +275,8 @@
CarUiContentListItem.OnClickListener mockedItemOnClickListener = mock(
CarUiContentListItem.OnClickListener.class);
- View.OnClickListener mockedIconListener = mock(View.OnClickListener.class);
+ CarUiContentListItem.OnClickListener mockedIconListener = mock(
+ CarUiContentListItem.OnClickListener.class);
CarUiContentListItem item = new CarUiContentListItem(
CarUiContentListItem.Action.ICON);
@@ -306,7 +300,7 @@
// Clicks anywhere on the icon should invoke both listeners.
onView(withId(R.id.action_container)).perform(click());
verify(mockedItemOnClickListener, times(1)).onClick(item);
- verify(mockedIconListener, times(1)).onClick(any(View.class));
+ verify(mockedIconListener, times(1)).onClick(item);
}
@Test
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
index 79a26f1..ea4a8c3 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
@@ -41,8 +41,10 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -159,6 +161,9 @@
when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_numOfColumns), anyInt()))
.thenReturn(3);
+ // Ensure the CarUiRecyclerViewLayout constant matches the styleable attribute enum value
+ assertEquals(CarUiRecyclerView.CarUiRecyclerViewLayout.GRID, 1);
+
CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerView(mTestableContext);
ViewGroup container = mActivity.findViewById(R.id.test_container);
TestAdapter adapter = new TestAdapter(4);
@@ -203,6 +208,9 @@
when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_layoutStyle), anyInt()))
.thenReturn(CarUiRecyclerView.CarUiRecyclerViewLayout.LINEAR);
+ // Ensure the CarUiRecyclerViewLayout constant matches the styleable attribute enum value
+ assertEquals(CarUiRecyclerView.CarUiRecyclerViewLayout.LINEAR, 0);
+
CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerView(mTestableContext);
ViewGroup container = mActivity.findViewById(R.id.test_container);
TestAdapter adapter = new TestAdapter(4);
@@ -328,7 +336,14 @@
onView(withId(R.id.list)).check(matches(isDisplayed()));
CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
- FixedSizeTestAdapter adapter = new FixedSizeTestAdapter(50, carUiRecyclerView.getHeight());
+
+ // Can't use OrientationHelper here, because it returns 0 when calling getTotalSpace methods
+ // until LayoutManager's onLayoutComplete is called. In this case waiting until the first
+ // item of the list is displayed guarantees that OrientationHelper is initialized properly.
+ int totalSpace = carUiRecyclerView.getHeight()
+ - carUiRecyclerView.getPaddingTop()
+ - carUiRecyclerView.getPaddingBottom();
+ PerfectFitTestAdapter adapter = new PerfectFitTestAdapter(5, totalSpace);
mActivity.runOnUiThread(() -> carUiRecyclerView.setAdapter(adapter));
IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
@@ -337,6 +352,9 @@
LinearLayoutManager layoutManager =
(LinearLayoutManager) carUiRecyclerView.getLayoutManager();
+ OrientationHelper orientationHelper = OrientationHelper.createVerticalHelper(layoutManager);
+ assertEquals(totalSpace, orientationHelper.getTotalSpace());
+
// Move down one page so there will be sufficient pages for up and downs.
onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
@@ -507,46 +525,48 @@
});
IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+ onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
OrientationHelper orientationHelper =
OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
-
- int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
+ int screenHeight = orientationHelper.getTotalSpace();
// Scroll to a position where long item is partially visible.
// Scrolling from top, scrollToPosition() aligns the pos-1 item to bottom.
onView(withId(R.id.list)).perform(scrollToPosition(longItemPosition - 1));
// Scroll by half the height of the screen so the long item is partially visible.
mActivity.runOnUiThread(() -> carUiRecyclerView.scrollBy(0, screenHeight / 2));
-
- onView(withText(adapter.getItemText(longItemPosition))).check(matches(isDisplayed()));
+ // This is needed to make sure scroll is finished before looking for the long item.
+ onView(withText(adapter.getItemText(longItemPosition - 1))).check(matches(isDisplayed()));
// Verify long item is partially shown.
View longItem = getLongItem(carUiRecyclerView);
assertThat(
orientationHelper.getDecoratedStart(longItem),
- is(greaterThan(carUiRecyclerView.getTop())));
+ is(greaterThan(orientationHelper.getStartAfterPadding())));
onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
// Verify long item is snapped to top.
- assertThat(orientationHelper.getDecoratedStart(longItem), is(equalTo(0)));
+ assertThat(orientationHelper.getDecoratedStart(longItem),
+ is(equalTo(orientationHelper.getStartAfterPadding())));
assertThat(orientationHelper.getDecoratedEnd(longItem),
- is(greaterThan(carUiRecyclerView.getBottom())));
+ is(greaterThan(orientationHelper.getEndAfterPadding())));
// Set a limit to avoid test stuck in non-moving state.
- while (orientationHelper.getDecoratedEnd(longItem) > carUiRecyclerView.getBottom()) {
+ while (orientationHelper.getDecoratedEnd(longItem)
+ > orientationHelper.getEndAfterPadding()) {
onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
}
// Verify long item end is aligned to bottom.
assertThat(orientationHelper.getDecoratedEnd(longItem),
- is(equalTo(carUiRecyclerView.getHeight())));
+ is(equalTo(orientationHelper.getEndAfterPadding())));
onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
// Verify that the long item is no longer visible; Should be on the next child
assertThat(
orientationHelper.getDecoratedStart(longItem),
- is(lessThan(carUiRecyclerView.getTop())));
+ is(lessThan(orientationHelper.getStartAfterPadding())));
}
@Test
@@ -575,6 +595,7 @@
});
IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+ onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
OrientationHelper orientationHelper =
OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
@@ -589,9 +610,8 @@
View longItem = getLongItem(carUiRecyclerView);
// Making sure we've reached end of the recyclerview, after
// adding bottom padding
- assertThat(orientationHelper.getDecoratedEnd(longItem)
- + carUiRecyclerView.getPaddingBottom(),
- is(equalTo(carUiRecyclerView.getHeight())));
+ assertThat(orientationHelper.getDecoratedEnd(longItem),
+ is(equalTo(orientationHelper.getEndAfterPadding())));
}
@Test
@@ -615,6 +635,7 @@
});
IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+ onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
OrientationHelper orientationHelper =
OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
@@ -624,27 +645,25 @@
// Verify long item is off-screen.
View longItem = getLongItem(carUiRecyclerView);
+
assertThat(
orientationHelper.getDecoratedEnd(longItem),
- is(greaterThan(carUiRecyclerView.getTop())));
+ is(lessThanOrEqualTo(orientationHelper.getEndAfterPadding())));
- onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click());
-
- // Verify long item is snapped to bottom.
- assertThat(orientationHelper.getDecoratedEnd(longItem),
- is(equalTo(carUiRecyclerView.getHeight())));
- assertThat(orientationHelper.getDecoratedStart(longItem), is(lessThan(0)));
-
-
- int decoratedStart = orientationHelper.getDecoratedStart(longItem);
-
- while (decoratedStart < 0) {
+ if (orientationHelper.getStartAfterPadding() - orientationHelper.getDecoratedStart(longItem)
+ < orientationHelper.getTotalSpace()) {
onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click());
- decoratedStart = orientationHelper.getDecoratedStart(longItem);
- }
+ assertThat(orientationHelper.getDecoratedStart(longItem),
+ is(greaterThanOrEqualTo(orientationHelper.getStartAfterPadding())));
+ } else {
+ int topBeforeClick = orientationHelper.getDecoratedStart(longItem);
- // Verify long item top is aligned to top.
- assertThat(orientationHelper.getDecoratedStart(longItem), is(equalTo(0)));
+ onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click());
+
+ // Verify we scrolled 1 screen
+ assertThat(orientationHelper.getStartAfterPadding() - topBeforeClick,
+ is(equalTo(orientationHelper.getTotalSpace())));
+ }
}
@Test
@@ -668,6 +687,7 @@
});
IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+ onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
OrientationHelper orientationHelper =
OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
@@ -685,20 +705,21 @@
View longItem = getLongItem(carUiRecyclerView);
assertThat(
orientationHelper.getDecoratedStart(longItem),
- is(greaterThan(carUiRecyclerView.getTop())));
+ is(greaterThan(orientationHelper.getStartAfterPadding())));
onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
// Verify long item is snapped to top.
- assertThat(orientationHelper.getDecoratedStart(longItem), is(equalTo(0)));
+ assertThat(orientationHelper.getDecoratedStart(longItem),
+ is(equalTo(orientationHelper.getStartAfterPadding())));
assertThat(orientationHelper.getDecoratedEnd(longItem),
- is(greaterThan(carUiRecyclerView.getBottom())));
+ is(greaterThan(orientationHelper.getEndAfterPadding())));
onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
// Verify long item does not snap to bottom.
assertThat(orientationHelper.getDecoratedEnd(longItem),
- not(equalTo(carUiRecyclerView.getHeight())));
+ not(equalTo(orientationHelper.getEndAfterPadding())));
}
@Test
@@ -727,6 +748,7 @@
});
IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+ onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
OrientationHelper orientationHelper =
OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
@@ -741,12 +763,10 @@
View longItem = getLongItem(carUiRecyclerView);
// Making sure we've reached end of the recyclerview, after
// adding bottom padding
- assertThat(orientationHelper.getDecoratedEnd(longItem)
- + carUiRecyclerView.getPaddingBottom(),
- is(equalTo(carUiRecyclerView.getHeight())));
+ assertThat(orientationHelper.getDecoratedEnd(longItem),
+ is(equalTo(orientationHelper.getEndAfterPadding())));
}
-
@Test
public void testPageDownMaintainsMinimumScrollThumbTrackHeight() {
mActivity.runOnUiThread(
@@ -849,6 +869,58 @@
assertThat(mCarUiRecyclerView.getPaddingEnd(), is(equalTo(10)));
}
+ @Test
+ public void testSetAlphaToRecyclerViewWithoutScrollbar() {
+ doReturn(false).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+
+ CarUiRecyclerView mCarUiRecyclerView = new CarUiRecyclerView(mTestableContext);
+
+ assertThat(mCarUiRecyclerView.getAlpha(), is(equalTo(1.0f)));
+
+ mCarUiRecyclerView.setAlpha(0.5f);
+
+ assertThat(mCarUiRecyclerView.getAlpha(), is(equalTo(0.5f)));
+ }
+
+ @Test
+ public void testSetAlphaToRecyclerViewWithScrollbar() {
+ mActivity.runOnUiThread(
+ () -> mActivity.setContentView(
+ R.layout.car_ui_recycler_view_test_activity));
+
+ onView(withId(R.id.list)).check(matches(isDisplayed()));
+
+ CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
+
+ ViewGroup container = (ViewGroup) carUiRecyclerView.getParent().getParent();
+
+ assertThat(carUiRecyclerView.getAlpha(), is(equalTo(1.0f)));
+ assertThat(container.getAlpha(), is(equalTo(1.0f)));
+
+ carUiRecyclerView.setAlpha(0.5f);
+
+ assertThat(carUiRecyclerView.getAlpha(), is(equalTo(1.0f)));
+ assertThat(container.getAlpha(), is(equalTo(0.5f)));
+ }
+
+ @Test
+ public void testCallAnimateOnRecyclerViewWithScrollbar() {
+ doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+ CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerView(mTestableContext);
+
+ ViewGroup container = mActivity.findViewById(R.id.test_container);
+ container.post(() -> {
+ container.addView(carUiRecyclerView);
+ carUiRecyclerView.setAdapter(new TestAdapter(100));
+ });
+
+ onView(withId(R.id.car_ui_scroll_bar)).check(matches(isDisplayed()));
+
+ ViewGroup recyclerViewContainer = (ViewGroup) carUiRecyclerView.getParent().getParent();
+
+ assertThat(carUiRecyclerView.animate(), is(equalTo(recyclerViewContainer.animate())));
+ }
+
/**
* Returns an item in the current list view whose height is taller than that of
* the CarUiRecyclerView. If that item exists, then it is returned; otherwise an {@link
@@ -857,10 +929,12 @@
* @return An item that is taller than the CarUiRecyclerView.
*/
private View getLongItem(CarUiRecyclerView recyclerView) {
- for (int i = 0; i < recyclerView.getChildCount(); i++) {
- View item = recyclerView.getChildAt(i);
+ OrientationHelper orientationHelper =
+ OrientationHelper.createVerticalHelper(recyclerView.getLayoutManager());
+ for (int i = 0; i < recyclerView.getLayoutManager().getChildCount(); i++) {
+ View item = recyclerView.getLayoutManager().getChildAt(i);
- if (item.getHeight() > recyclerView.getHeight()) {
+ if (item.getHeight() > orientationHelper.getTotalSpace()) {
return item;
}
}
@@ -941,16 +1015,29 @@
}
}
- private static class FixedSizeTestAdapter extends RecyclerView.Adapter<TestViewHolder> {
+ private static class PerfectFitTestAdapter extends RecyclerView.Adapter<TestViewHolder> {
- private static final int ITEMS_PER_PAGE = 5;
+ private static final int MIN_HEIGHT = 30;
private final List<String> mData;
private final int mItemHeight;
- FixedSizeTestAdapter(int itemCount, int recyclerViewHeight) {
- mData = new ArrayList<>(itemCount);
- mItemHeight = recyclerViewHeight / ITEMS_PER_PAGE;
+ private int getMinHeightPerItemToFitScreen(int screenHeight) {
+ // When the height is a prime number, there can only be 1 item per page
+ int minHeight = screenHeight;
+ for (int i = screenHeight; i >= 1; i--) {
+ if (screenHeight % i == 0 && screenHeight / i >= MIN_HEIGHT) {
+ minHeight = screenHeight / i;
+ break;
+ }
+ }
+ return minHeight;
+ }
+ PerfectFitTestAdapter(int numOfPages, int recyclerViewHeight) {
+ mItemHeight = getMinHeightPerItemToFitScreen(recyclerViewHeight);
+ int itemsPerPage = recyclerViewHeight / mItemHeight;
+ int itemCount = itemsPerPage * numOfPages;
+ mData = new ArrayList<>(itemCount);
for (int i = 0; i < itemCount; i++) {
mData.add(getItemText(i));
}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestContentLimitingAdapter.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestContentLimitingAdapter.java
index a65be43..1af9a70 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestContentLimitingAdapter.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestContentLimitingAdapter.java
@@ -31,7 +31,7 @@
private final List<String> mItems;
- TestContentLimitingAdapter(int numItems) {
+ public TestContentLimitingAdapter(int numItems) {
mItems = new ArrayList<>();
for (int i = 0; i < numItems; i++) {
mItems.add("Item " + i);
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTest.java
new file mode 100644
index 0000000..3cc9b7e
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTest.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.utils;
+
+import static android.view.View.GONE;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
+import static com.android.car.ui.utils.ViewUtils.DEFAULT_FOCUS;
+import static com.android.car.ui.utils.ViewUtils.FOCUSED_BY_DEFAULT;
+import static com.android.car.ui.utils.ViewUtils.IMPLICIT_DEFAULT_FOCUS;
+import static com.android.car.ui.utils.ViewUtils.NO_FOCUS;
+import static com.android.car.ui.utils.ViewUtils.REGULAR_FOCUS;
+import static com.android.car.ui.utils.ViewUtils.SCROLLABLE_CONTAINER_FOCUS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.FocusArea;
+import com.android.car.ui.FocusParkingView;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+import com.android.car.ui.recyclerview.TestContentLimitingAdapter;
+import com.android.car.ui.test.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/** Unit tests for {@link ViewUtils}. */
+public class ViewUtilsTest {
+
+ @Rule
+ public ActivityTestRule<ViewUtilsTestActivity> mActivityRule =
+ new ActivityTestRule<>(ViewUtilsTestActivity.class);
+
+ private ViewUtilsTestActivity mActivity;
+ private FocusArea mFocusArea1;
+ private FocusArea mFocusArea2;
+ private FocusArea mFocusArea3;
+ private FocusArea mFocusArea4;
+ private FocusArea mFocusArea5;
+ private FocusParkingView mFpv;
+ private View mView2;
+ private View mFocusedByDefault3;
+ private View mView4;
+ private View mDefaultFocus4;
+ private CarUiRecyclerView mList5;
+ private View mRoot;
+
+ @Before
+ public void setUp() {
+ mActivity = mActivityRule.getActivity();
+ mFocusArea1 = mActivity.findViewById(R.id.focus_area1);
+ mFocusArea2 = mActivity.findViewById(R.id.focus_area2);
+ mFocusArea3 = mActivity.findViewById(R.id.focus_area3);
+ mFocusArea4 = mActivity.findViewById(R.id.focus_area4);
+ mFocusArea5 = mActivity.findViewById(R.id.focus_area5);
+ mFpv = mActivity.findViewById(R.id.fpv);
+ mView2 = mActivity.findViewById(R.id.view2);
+ mFocusedByDefault3 = mActivity.findViewById(R.id.focused_by_default3);
+ mView4 = mActivity.findViewById(R.id.view4);
+ mDefaultFocus4 = mActivity.findViewById(R.id.default_focus4);
+ mList5 = mActivity.findViewById(R.id.list5);
+ mRoot = mFocusArea1.getRootView();
+
+ mRoot.post(() -> {
+ mList5.setLayoutManager(new LinearLayoutManager(mActivity));
+ mList5.setAdapter(new TestContentLimitingAdapter(/* numItems= */ 2));
+ CarUiUtils.setRotaryScrollEnabled(mList5, /* isVertical= */ true);
+ });
+ }
+
+ @Test
+ public void testRootVisible() {
+ mRoot.post(() -> assertThat(mRoot.getVisibility()).isEqualTo(VISIBLE));
+ }
+
+ @Test
+ public void testGetAncestorFocusArea() {
+ mRoot.post(() -> assertThat(ViewUtils.getAncestorFocusArea(mView2)).isEqualTo(mFocusArea2));
+ }
+
+ @Test
+ public void testGetAncestorFocusArea_doesNotReturnItself() {
+ mRoot.post(() -> assertThat(ViewUtils.getAncestorFocusArea(mFocusArea2)).isNull());
+ }
+
+ @Test
+ public void testGetAncestorFocusArea_outsideFocusArea() {
+ mRoot.post(() -> assertThat(ViewUtils.getAncestorFocusArea(mFpv)).isNull());
+ }
+
+ @Test
+ public void testGetAncestorScrollableContainer() {
+ mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+ assertThat(ViewUtils.getAncestorScrollableContainer(firstItem))
+ .isEqualTo(mList5);
+ }
+ }));
+ }
+
+ @Test
+ public void testGetAncestorScrollableContainer_returnNull() {
+ mRoot.post(() -> assertThat(ViewUtils.getAncestorScrollableContainer(mView2)).isNull());
+ }
+
+ @Test
+ public void testFindFocusedByDefaultView() {
+ mRoot.post(() -> {
+ View focusedByDefault = ViewUtils.findFocusedByDefaultView(mRoot);
+ assertThat(focusedByDefault).isEqualTo(mFocusedByDefault3);
+ });
+ }
+
+ @Test
+ public void testFindFocusedByDefaultView_skipNotFocusable() {
+ mRoot.post(() -> {
+ mFocusedByDefault3.setFocusable(false);
+ View focusedByDefault = ViewUtils.findFocusedByDefaultView(mRoot);
+ assertThat(focusedByDefault).isNull();
+ });
+ }
+
+ @Test
+ public void testFindFocusedByDefaultView_skipInvisibleView() {
+ mRoot.post(() -> {
+ mFocusArea3.setVisibility(INVISIBLE);
+ assertThat(mFocusArea3.getVisibility()).isEqualTo(INVISIBLE);
+ View focusedByDefault = ViewUtils.findFocusedByDefaultView(mRoot);
+ assertThat(focusedByDefault).isNull();
+ });
+ }
+
+ @Test
+ public void testFindFocusedByDefaultView_skipInvisibleAncestor() {
+ mRoot.post(() -> {
+ mRoot.setVisibility(INVISIBLE);
+ View focusedByDefault = ViewUtils.findFocusedByDefaultView(mFocusArea3);
+ assertThat(focusedByDefault).isNull();
+ });
+ }
+
+ @Test
+ public void testFindImplicitDefaultFocusView_inRoot() {
+ mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+ View implicitDefaultFocus = ViewUtils.findImplicitDefaultFocusView(mRoot);
+ assertThat(implicitDefaultFocus).isEqualTo(firstItem);
+ }
+ }));
+ }
+
+ @Test
+ public void testFindImplicitDefaultFocusView_inFocusArea() {
+ mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+ View implicitDefaultFocus =
+ ViewUtils.findImplicitDefaultFocusView(mFocusArea5);
+ assertThat(implicitDefaultFocus).isEqualTo(firstItem);
+ }
+ }));
+ }
+
+ @Test
+ public void testFindImplicitDefaultFocusView_skipInvisibleAncestor() {
+ mRoot.post(() -> {
+ mRoot.setVisibility(INVISIBLE);
+ View implicitDefaultFocus = ViewUtils.findImplicitDefaultFocusView(mFocusArea5);
+ assertThat(implicitDefaultFocus).isNull();
+ });
+ }
+
+ @Test
+ public void testFindFirstFocusableDescendant() {
+ mRoot.post(() -> {
+ mFocusArea2.setFocusable(true);
+ View firstFocusable = ViewUtils.findFirstFocusableDescendant(mRoot);
+ assertThat(firstFocusable).isEqualTo(mFocusArea2);
+ });
+ }
+
+ @Test
+ public void testFindFirstFocusableDescendant_skipItself() {
+ mRoot.post(() -> {
+ mFocusArea2.setFocusable(true);
+ View firstFocusable = ViewUtils.findFirstFocusableDescendant(mFocusArea2);
+ assertThat(firstFocusable).isEqualTo(mView2);
+ });
+ }
+
+ @Test
+ public void testFindFirstFocusableDescendant_skipInvisibleAndGoneView() {
+ mRoot.post(() -> {
+ mFocusArea2.setVisibility(INVISIBLE);
+ mFocusArea3.setVisibility(GONE);
+ View firstFocusable = ViewUtils.findFirstFocusableDescendant(mRoot);
+ assertThat(firstFocusable).isEqualTo(mView4);
+ });
+ }
+
+ @Test
+ public void testFindFirstFocusableDescendant_skipInvisibleAncestor() {
+ mRoot.post(() -> {
+ mRoot.setVisibility(INVISIBLE);
+ View firstFocusable = ViewUtils.findFirstFocusableDescendant(mFocusArea2);
+ assertThat(firstFocusable).isNull();
+ });
+ }
+
+ @Test
+ public void testIsImplicitDefaultFocusView_firstItem() {
+ mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+ assertThat(ViewUtils.isImplicitDefaultFocusView(firstItem)).isTrue();
+ }
+ }));
+ }
+
+ @Test
+ public void testIsImplicitDefaultFocusView_secondItem() {
+ mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ View secondItem = mList5.getLayoutManager().findViewByPosition(1);
+ assertThat(ViewUtils.isImplicitDefaultFocusView(secondItem)).isFalse();
+ }
+ }));
+ }
+
+ @Test
+ public void testIsImplicitDefaultFocusView_normalView() {
+ mRoot.post(() -> assertThat(ViewUtils.isImplicitDefaultFocusView(mView2)).isFalse());
+ }
+
+ @Test
+ public void testIsImplicitDefaultFocusView_skipInvisibleAncestor() {
+ mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mFocusArea5.setVisibility(INVISIBLE);
+ View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+ assertThat(ViewUtils.isImplicitDefaultFocusView(firstItem)).isFalse();
+ }
+ }));
+ }
+
+ @Test
+ public void testRequestFocus() {
+ mRoot.post(() -> assertRequestFocus(mView2, true));
+ }
+
+ @Test
+ public void testRequestFocus_nullView() {
+ mRoot.post(() -> assertRequestFocus(null, false));
+ }
+
+ @Test
+ public void testRequestFocus_alreadyFocused() {
+ mRoot.post(() -> {
+ assertRequestFocus(mView2, true);
+ // mView2 is already focused before requesting focus.
+ assertRequestFocus(mView2, true);
+ });
+ }
+
+ @Test
+ public void testRequestFocus_notFocusable() {
+ mRoot.post(() -> {
+ mView2.setFocusable(false);
+ assertRequestFocus(mView2, false);
+ });
+ }
+
+ @Test
+ public void testRequestFocus_disabled() {
+ mRoot.post(() -> {
+ mView2.setEnabled(false);
+ assertRequestFocus(mView2, false);
+ });
+ }
+
+ @Test
+ public void testRequestFocus_notVisible() {
+ mRoot.post(() -> {
+ mView2.setVisibility(View.INVISIBLE);
+ assertRequestFocus(mView2, false);
+ });
+ }
+
+ @Test
+ public void testRequestFocus_skipInvisibleAncestor() {
+ mRoot.post(() -> {
+ mFocusArea2.setVisibility(View.INVISIBLE);
+ assertRequestFocus(mView2, false);
+ });
+ }
+
+ @Test
+ public void testRequestFocus_zeroWidth() {
+ mRoot.post(() -> {
+ mView2.setRight(mView2.getLeft());
+ assertThat(mView2.getWidth()).isEqualTo(0);
+ assertRequestFocus(mView2, false);
+ });
+ }
+
+ @Test
+ public void testRequestFocus_detachedFromWindow() {
+ mRoot.post(() -> {
+ mFocusArea2.removeView(mView2);
+ assertRequestFocus(mView2, false);
+ });
+ }
+
+ @Test
+ public void testRequestFocus_FocusParkingView() {
+ mRoot.post(() -> {
+ assertRequestFocus(mView2, true);
+ assertRequestFocus(mFpv, false);
+ });
+ }
+
+ @Test
+ public void testRequestFocus_rotaryContainer() {
+ mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ assertRequestFocus(mList5, false);
+ }
+ }));
+ }
+
+ @Test
+ public void testRequestFocus_scrollableContainer() {
+ mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ assertRequestFocus(mList5, false);
+ }
+ }));
+ }
+
+ @Test
+ public void testAdjustFocus_inRoot() {
+ mRoot.post(() -> {
+ assertRequestFocus(mView2, true);
+ ViewUtils.adjustFocus(mRoot, null);
+ assertThat(mFocusedByDefault3.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testAdjustFocus_inFocusAreaWithDefaultFocus() {
+ mRoot.post(() -> {
+ assertRequestFocus(mView2, true);
+ ViewUtils.adjustFocus(mFocusArea3, null);
+ assertThat(mFocusedByDefault3.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testAdjustFocus_inFocusAreaWithoutDefaultFocus() {
+ mRoot.post(() -> {
+ assertRequestFocus(mView4, true);
+ ViewUtils.adjustFocus(mFocusArea2, null);
+ assertThat(mView2.isFocused()).isTrue();
+ });
+ }
+
+ @Test
+ public void testAdjustFocus_inFocusAreaWithoutFocusableDescendant() {
+ mRoot.post(() -> {
+ assertRequestFocus(mView2, true);
+ boolean success = ViewUtils.adjustFocus(mFocusArea1, null);
+ assertThat(mFocusArea1.hasFocus()).isFalse();
+ assertThat(success).isFalse();
+ });
+ }
+
+ @Test
+ public void testAdjustFocus_differentFocusLevels() {
+ mRoot.post(() -> {
+ assertThat(ViewUtils.adjustFocus(mFocusArea2, SCROLLABLE_CONTAINER_FOCUS)).isTrue();
+ assertThat(ViewUtils.adjustFocus(mFocusArea2, REGULAR_FOCUS)).isFalse();
+
+ assertThat(ViewUtils.adjustFocus(mFocusArea5, REGULAR_FOCUS)).isTrue();
+ assertThat(ViewUtils.adjustFocus(mFocusArea5, IMPLICIT_DEFAULT_FOCUS)).isFalse();
+
+ assertThat(ViewUtils.adjustFocus(mFocusArea4, IMPLICIT_DEFAULT_FOCUS)).isTrue();
+ assertThat(ViewUtils.adjustFocus(mFocusArea4, DEFAULT_FOCUS)).isFalse();
+
+ assertThat(ViewUtils.adjustFocus(mFocusArea3, DEFAULT_FOCUS)).isTrue();
+ assertThat(ViewUtils.adjustFocus(mFocusArea3, FOCUSED_BY_DEFAULT)).isFalse();
+
+ View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+ firstItem.setFocusable(false);
+ View secondItem = mList5.getLayoutManager().findViewByPosition(1);
+ secondItem.setFocusable(false);
+ assertThat(ViewUtils.adjustFocus(mFocusArea5, NO_FOCUS)).isTrue();
+ assertThat(ViewUtils.adjustFocus(mFocusArea5, SCROLLABLE_CONTAINER_FOCUS)).isFalse();
+ });
+ }
+
+ @Test
+ public void testGetFocusLevel() {
+ mRoot.post(() -> {
+ assertThat(ViewUtils.getFocusLevel(null)).isEqualTo(NO_FOCUS);
+ assertThat(ViewUtils.getFocusLevel(mFpv)).isEqualTo(NO_FOCUS);
+ mFocusArea2.setVisibility(INVISIBLE);
+ assertThat(ViewUtils.getFocusLevel(mView2)).isEqualTo(NO_FOCUS);
+
+ assertThat(ViewUtils.getFocusLevel(mList5)).isEqualTo(SCROLLABLE_CONTAINER_FOCUS);
+
+ assertThat(ViewUtils.getFocusLevel(mView4)).isEqualTo(REGULAR_FOCUS);
+
+ mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+ assertThat(ViewUtils.getFocusLevel(firstItem))
+ .isEqualTo(IMPLICIT_DEFAULT_FOCUS);
+ }
+ }));
+
+ assertThat(ViewUtils.getFocusLevel(mDefaultFocus4)).isEqualTo(DEFAULT_FOCUS);
+
+ assertThat(ViewUtils.getFocusLevel(mFocusedByDefault3)).isEqualTo(FOCUSED_BY_DEFAULT);
+ });
+ }
+
+ private static void assertRequestFocus(@Nullable View view, boolean focused) {
+ boolean result = ViewUtils.requestFocus(view);
+ assertThat(result).isEqualTo(focused);
+ if (view != null) {
+ assertThat(view.isFocused()).isEqualTo(focused);
+ }
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTestActivity.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTestActivity.java
new file mode 100644
index 0000000..87434d4
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTestActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.utils;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.car.ui.test.R;
+
+/** An activity used for testing {@link ViewUtils}. */
+public class ViewUtilsTestActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.view_utils_test_activity);
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/car_ui_ime_wide_screen_test_activity.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/car_ui_ime_wide_screen_test_activity.xml
new file mode 100644
index 0000000..e260300
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/car_ui_ime_wide_screen_test_activity.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/test_activity"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+</FrameLayout>
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_area_test_activity.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_area_test_activity.xml
index 524de8d..da1255d 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_area_test_activity.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_area_test_activity.xml
@@ -18,52 +18,79 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="match_parent">
- <View
- android:focusable="true"
- android:layout_width="100dp"
- android:layout_height="100dp"/>
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <com.android.car.ui.FocusParkingView
+ android:id="@+id/fpv"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
<com.android.car.ui.TestFocusArea
- android:id="@+id/focus_area"
+ android:id="@+id/focus_area1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- app:defaultFocus="@+id/default_focus"
app:startBoundOffset="10dp"
app:endBoundOffset="20dp"
app:topBoundOffset="30dp"
app:bottomBoundOffset="40dp">
<View
- android:id="@+id/child"
+ android:id="@+id/view1"
android:focusable="true"
android:layout_width="100dp"
android:layout_height="100dp"/>
- <View
- android:id="@+id/default_focus"
- android:focusable="true"
+ <Button
+ android:id="@+id/button1"
android:layout_width="100dp"
android:layout_height="100dp"/>
</com.android.car.ui.TestFocusArea>
- <View
- android:id="@+id/non_child"
- android:focusable="true"
- android:layout_width="100dp"
- android:layout_height="100dp"/>
<com.android.car.ui.TestFocusArea
android:id="@+id/focus_area2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ app:defaultFocus="@+id/default_focus2"
app:highlightPaddingHorizontal="10dp"
app:highlightPaddingVertical="20dp"
app:highlightPaddingStart="30dp"
app:highlightPaddingTop="40dp"
app:startBoundOffset="50dp">
<View
- android:id="@+id/child1"
+ android:id="@+id/view2"
android:focusable="true"
android:layout_width="100dp"
android:layout_height="100dp"/>
<View
- android:id="@+id/child2"
+ android:id="@+id/default_focus2"
+ android:focusable="true"
+ android:layout_width="100dp"
+ android:layout_height="100dp"/>
+ </com.android.car.ui.TestFocusArea>
+ <com.android.car.ui.TestFocusArea
+ android:id="@+id/focus_area3"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:nudgeShortcut="@+id/nudge_shortcut3"
+ app:nudgeShortcutDirection="right">
+ <View
+ android:id="@+id/view3"
+ android:focusable="true"
+ android:layout_width="100dp"
+ android:layout_height="100dp"/>
+ <View
+ android:focusable="true"
+ android:layout_width="100dp"
+ android:layout_height="100dp"/>
+ <View
+ android:id="@+id/nudge_shortcut3"
+ android:focusable="true"
+ android:layout_width="100dp"
+ android:layout_height="100dp"/>
+ </com.android.car.ui.TestFocusArea>
+ <com.android.car.ui.TestFocusArea
+ android:id="@+id/focus_area4"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:nudgeLeft="@+id/focus_area2">
+ <View
+ android:id="@+id/view4"
android:focusable="true"
android:layout_width="100dp"
android:layout_height="100dp"/>
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml
index f3f623d..02d7326 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml
@@ -14,18 +14,32 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<FrameLayout
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <!-- In some cases Android will focus the first focusable view automatically. To prevent the
- FocusParkingView getting focused unintentionally, we put a focusable Button above the
- FocusParkingView. -->
- <Button
- android:layout_width="100dp"
- android:layout_height="40dp"/>
<com.android.car.ui.FocusParkingView
- android:id="@+id/focus_parking"
+ android:id="@+id/fpv"
+ android:layout_width="10dp"
+ android:layout_height="10dp"/>
+ <LinearLayout
+ android:id="@+id/parent1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <View
+ android:id="@+id/view1"
+ android:layout_width="100dp"
+ android:layout_height="40dp"
+ android:focusable="true"/>
+ </LinearLayout>
+ <View
+ android:id="@+id/focused_by_default"
+ android:layout_width="100dp"
+ android:layout_height="40dp"
+ android:focusable="true"
+ android:focusedByDefault="true"/>
+ <com.android.car.ui.recyclerview.CarUiRecyclerView
+ android:id="@+id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
-</FrameLayout>
+</LinearLayout>
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_car_ui_recycler_view_list_item.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_car_ui_recycler_view_list_item.xml
index 7e8717e..8297f6b 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_car_ui_recycler_view_list_item.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_car_ui_recycler_view_list_item.xml
@@ -18,7 +18,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ android:focusable="true">
<TextView
android:layout_width="wrap_content"
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_ime_input_view.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_ime_input_view.xml
new file mode 100644
index 0000000..6b1fcb8
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_ime_input_view.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/test_ime_input_view_id"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+</FrameLayout>
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_list_item.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_list_item.xml
index e789145..1fab70e 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_list_item.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/test_list_item.xml
@@ -18,7 +18,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ android:focusable="true">
<TextView
android:layout_width="wrap_content"
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/view_utils_test_activity.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/view_utils_test_activity.xml
new file mode 100644
index 0000000..72c2366
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/view_utils_test_activity.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <com.android.car.ui.FocusParkingView
+ android:id="@+id/fpv"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <com.android.car.ui.FocusArea
+ android:id="@+id/focus_area1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <View
+ android:layout_width="100dp"
+ android:layout_height="100dp"/>
+ </com.android.car.ui.FocusArea>
+ <com.android.car.ui.FocusArea
+ android:id="@+id/focus_area2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <View
+ android:layout_width="100dp"
+ android:layout_height="100dp"/>
+ <View
+ android:id="@+id/view2"
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:focusable="true"/>
+ </com.android.car.ui.FocusArea>
+ <com.android.car.ui.FocusArea
+ android:id="@+id/focus_area3"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <View
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:focusable="true"/>
+ <View
+ android:id="@+id/focused_by_default3"
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:focusable="true"
+ android:focusedByDefault="true"/>
+ </com.android.car.ui.FocusArea>
+ <com.android.car.ui.FocusArea
+ android:id="@+id/focus_area4"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:defaultFocus="@+id/default_focus4">
+ <View
+ android:id="@+id/view4"
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:focusable="true"/>
+ <View
+ android:id="@+id/default_focus4"
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:focusable="true"/>
+ </com.android.car.ui.FocusArea>
+ <com.android.car.ui.FocusArea
+ android:id="@+id/focus_area5"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <com.android.car.ui.recyclerview.CarUiRecyclerView
+ android:id="@+id/list5"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </com.android.car.ui.FocusArea>
+</LinearLayout>
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/values/strings.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/values/strings.xml
index 21f4ee4..7926d9c 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/res/values/strings.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/values/strings.xml
@@ -31,4 +31,10 @@
<string name="title_dropdown_preference">Dropdown preference</string>
<string name="title_twoaction_preference">TwoAction preference</string>
<string name="summary_twoaction_preference">A widget should be visible on the right</string>
+
+ <string-array name="test_string_array">
+ <item>Item 1</item>
+ <item>Item 2</item>
+ <item>Item 3</item>
+ </string-array>
</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/AlertDialogBuilder.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/AlertDialogBuilder.java
index a025e9e..ed508d3 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/AlertDialogBuilder.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/AlertDialogBuilder.java
@@ -15,12 +15,20 @@
*/
package com.android.car.ui;
+import static android.view.WindowInsets.Type.ime;
+
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.ADD_DESC_TITLE_TO_CONTENT_AREA;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.ADD_DESC_TO_CONTENT_AREA;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_ACTION;
+
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
import android.text.InputFilter;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -28,6 +36,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ImageView;
@@ -60,6 +69,41 @@
private CharSequence mSubtitle;
private Drawable mIcon;
private boolean mIconTinted;
+ private boolean mAllowDismissButton = true;
+ private boolean mHasSingleChoiceBodyButton = false;
+ private EditText mCarUiEditText;
+ private InputMethodManager mInputMethodManager;
+ private String mWideScreenTitle;
+ private String mWideScreenTitleDesc;
+ private ViewGroup mRoot;
+
+ // Whenever the IME is closed and opened again, the title and desc information needs to be
+ // passed to the IME to be rendered. If the information is not passed to the IME the content
+ // area of the IME will render nothing into the content area.
+ private final View.OnApplyWindowInsetsListener mOnApplyWindowInsetsListener = (v, insets) -> {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ // WindowInsets.isVisible() is only available on R or above
+ return v.onApplyWindowInsets(insets);
+ }
+
+ if (insets.isVisible(ime())) {
+ Bundle bundle = new Bundle();
+ String title = mWideScreenTitle != null ? mWideScreenTitle : mTitle.toString();
+ bundle.putString(ADD_DESC_TITLE_TO_CONTENT_AREA, title);
+ if (mWideScreenTitleDesc != null) {
+ bundle.putString(ADD_DESC_TO_CONTENT_AREA, mWideScreenTitleDesc);
+ }
+ mInputMethodManager.sendAppPrivateCommand(mCarUiEditText, WIDE_SCREEN_ACTION,
+ bundle);
+ }
+ return v.onApplyWindowInsets(insets);
+ };
+
+ private final AlertDialog.OnDismissListener mOnDismissListener = dialog -> {
+ if (mRoot != null) {
+ mRoot.setOnApplyWindowInsetsListener(null);
+ }
+ };
public AlertDialogBuilder(Context context) {
// Resource id specified as 0 uses the parent contexts resolved value for alertDialogTheme.
@@ -68,6 +112,8 @@
public AlertDialogBuilder(Context context, int themeResId) {
mBuilder = new AlertDialog.Builder(context, themeResId);
+ mInputMethodManager = (InputMethodManager)
+ context.getSystemService(Context.INPUT_METHOD_SERVICE);
mContext = context;
}
@@ -329,6 +375,7 @@
public AlertDialogBuilder setItems(@ArrayRes int itemsId,
final DialogInterface.OnClickListener listener) {
mBuilder.setItems(itemsId, listener);
+ mHasSingleChoiceBodyButton = true;
return this;
}
@@ -341,6 +388,7 @@
public AlertDialogBuilder setItems(CharSequence[] items,
final DialogInterface.OnClickListener listener) {
mBuilder.setItems(items, listener);
+ mHasSingleChoiceBodyButton = true;
return this;
}
@@ -353,6 +401,7 @@
public AlertDialogBuilder setAdapter(final ListAdapter adapter,
final DialogInterface.OnClickListener listener) {
mBuilder.setAdapter(adapter, listener);
+ mHasSingleChoiceBodyButton = true;
return this;
}
@@ -363,6 +412,7 @@
*/
public AlertDialogBuilder setAdapter(final CarUiListItemAdapter adapter) {
setCustomList(adapter);
+ mHasSingleChoiceBodyButton = true;
return this;
}
@@ -390,6 +440,7 @@
final DialogInterface.OnClickListener listener,
String labelColumn) {
mBuilder.setCursor(cursor, listener, labelColumn);
+ mHasSingleChoiceBodyButton = true;
return this;
}
@@ -413,6 +464,7 @@
public AlertDialogBuilder setMultiChoiceItems(@ArrayRes int itemsId, boolean[] checkedItems,
final DialogInterface.OnMultiChoiceClickListener listener) {
mBuilder.setMultiChoiceItems(itemsId, checkedItems, listener);
+ mHasSingleChoiceBodyButton = false;
return this;
}
@@ -435,6 +487,7 @@
public AlertDialogBuilder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems,
final DialogInterface.OnMultiChoiceClickListener listener) {
mBuilder.setMultiChoiceItems(items, checkedItems, listener);
+ mHasSingleChoiceBodyButton = false;
return this;
}
@@ -460,6 +513,7 @@
String labelColumn,
final DialogInterface.OnMultiChoiceClickListener listener) {
mBuilder.setMultiChoiceItems(cursor, isCheckedColumn, labelColumn, listener);
+ mHasSingleChoiceBodyButton = true;
return this;
}
@@ -480,6 +534,7 @@
public AlertDialogBuilder setSingleChoiceItems(@ArrayRes int itemsId, int checkedItem,
final DialogInterface.OnClickListener listener) {
mBuilder.setSingleChoiceItems(itemsId, checkedItem, listener);
+ mHasSingleChoiceBodyButton = true;
return this;
}
@@ -502,6 +557,7 @@
String labelColumn,
final DialogInterface.OnClickListener listener) {
mBuilder.setSingleChoiceItems(cursor, checkedItem, labelColumn, listener);
+ mHasSingleChoiceBodyButton = true;
return this;
}
@@ -521,6 +577,7 @@
public AlertDialogBuilder setSingleChoiceItems(CharSequence[] items, int checkedItem,
final DialogInterface.OnClickListener listener) {
mBuilder.setSingleChoiceItems(items, checkedItem, listener);
+ mHasSingleChoiceBodyButton = true;
return this;
}
@@ -534,6 +591,7 @@
public AlertDialogBuilder setSingleChoiceItems(ListAdapter adapter, int checkedItem,
final DialogInterface.OnClickListener listener) {
mBuilder.setSingleChoiceItems(adapter, checkedItem, listener);
+ mHasSingleChoiceBodyButton = true;
return this;
}
@@ -548,13 +606,13 @@
* dismissed when an item is clicked. It will only be dismissed if clicked on a
* button, if no buttons are supplied it's up to the user to dismiss the dialog.
* @return This Builder object to allow for chaining of calls to set methods
- *
* @deprecated Use {@link #setSingleChoiceItems(CarUiRadioButtonListItemAdapter)} instead.
*/
@Deprecated
public AlertDialogBuilder setSingleChoiceItems(CarUiRadioButtonListItemAdapter adapter,
final DialogInterface.OnClickListener listener) {
setCustomList(adapter);
+ mHasSingleChoiceBodyButton = false;
return this;
}
@@ -570,6 +628,7 @@
*/
public AlertDialogBuilder setSingleChoiceItems(CarUiRadioButtonListItemAdapter adapter) {
setCustomList(adapter);
+ mHasSingleChoiceBodyButton = false;
return this;
}
@@ -583,6 +642,7 @@
public AlertDialogBuilder setOnItemSelectedListener(
final AdapterView.OnItemSelectedListener listener) {
mBuilder.setOnItemSelectedListener(listener);
+ mHasSingleChoiceBodyButton = true;
return this;
}
@@ -602,19 +662,19 @@
View contentView = LayoutInflater.from(mContext).inflate(
R.layout.car_ui_alert_dialog_edit_text, null);
- EditText editText = CarUiUtils.requireViewByRefId(contentView, R.id.textbox);
- editText.setText(prompt);
+ mCarUiEditText = CarUiUtils.requireViewByRefId(contentView, R.id.textbox);
+ mCarUiEditText.setText(prompt);
if (textChangedListener != null) {
- editText.addTextChangedListener(textChangedListener);
+ mCarUiEditText.addTextChangedListener(textChangedListener);
}
if (inputFilters != null) {
- editText.setFilters(inputFilters);
+ mCarUiEditText.setFilters(inputFilters);
}
if (inputType != 0) {
- editText.setInputType(inputType);
+ mCarUiEditText.setInputType(inputType);
}
mBuilder.setView(contentView);
@@ -635,6 +695,35 @@
return setEditBox(prompt, textChangedListener, inputFilters, 0);
}
+ /**
+ * By default, the AlertDialogBuilder may add a "Dismiss" button if you don't provide
+ * a positive/negative/neutral button. This is so that the dialog is still dismissible
+ * using the rotary controller. If however, you add buttons that can close the dialog via
+ * {@link #setAdapter(CarUiListItemAdapter)} or a similar method, then you may wish to
+ * suppress the addition of the dismiss button, which this method allows for.
+ *
+ * @param allowDismissButton If true, a "Dismiss" button may be added to the dialog.
+ * If false, it will never be added.
+ * @return this Builder object to allow for chaining of calls to set methods
+ */
+ public AlertDialogBuilder setAllowDismissButton(boolean allowDismissButton) {
+ mAllowDismissButton = allowDismissButton;
+ return this;
+ }
+
+ /**
+ * Sets the title and desc related to the dialog within the IMS templates.
+ *
+ * @param title title to be set.
+ * @param desc description related to the dialog.
+ * @return this Builder object to allow for chaining of calls to set methods
+ */
+ public AlertDialogBuilder setEditTextTitleAndDescForWideScreen(String title, String desc) {
+ mWideScreenTitle = title;
+ mWideScreenTitleDesc = desc;
+
+ return this;
+ }
/** Final steps common to both {@link #create()} and {@link #show()} */
private void prepareDialog() {
@@ -659,7 +748,14 @@
}
mBuilder.setCustomTitle(customTitle);
- if (!mNeutralButtonSet && !mNegativeButtonSet && !mPositiveButtonSet) {
+ if (!mAllowDismissButton && !mHasSingleChoiceBodyButton
+ && !mNeutralButtonSet && !mNegativeButtonSet && !mPositiveButtonSet) {
+ throw new RuntimeException(
+ "The dialog must have at least one button to disable the dismiss button");
+ }
+ if (mContext.getResources().getBoolean(R.bool.car_ui_alert_dialog_force_dismiss_button)
+ && !mNeutralButtonSet && !mNegativeButtonSet && !mPositiveButtonSet
+ && mAllowDismissButton) {
String mDefaultButtonText = mContext.getString(
R.string.car_ui_alert_dialog_default_button);
mBuilder.setNegativeButton(mDefaultButtonText, (dialog, which) -> {
@@ -683,9 +779,13 @@
// wrap-around. Android will focus on the first view automatically when the dialog is shown,
// and we want it to focus on the title instead of the FocusParkingView, so we put the
// FocusParkingView at the end of dialog window.
- ViewGroup root = (ViewGroup) alertDialog.getWindow().getDecorView().getRootView();
+ mRoot = (ViewGroup) alertDialog.getWindow().getDecorView().getRootView();
FocusParkingView fpv = new FocusParkingView(mContext);
- root.addView(fpv);
+ mRoot.addView(fpv);
+
+ // apply window insets listener to know when IME is visible so we can set title and desc.
+ mRoot.setOnApplyWindowInsetsListener(mOnApplyWindowInsetsListener);
+ setOnDismissListener(mOnDismissListener);
return alertDialog;
}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusArea.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusArea.java
index f5c6c1d..0488d28 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusArea.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusArea.java
@@ -25,11 +25,14 @@
import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_RIGHT_BOUND_OFFSET;
import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_TOP_BOUND_OFFSET;
import static com.android.car.ui.utils.RotaryConstants.NUDGE_DIRECTION;
+import static com.android.car.ui.utils.ViewUtils.NO_FOCUS;
+import static com.android.car.ui.utils.ViewUtils.REGULAR_FOCUS;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.SystemClock;
@@ -38,7 +41,6 @@
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewParent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.LinearLayout;
@@ -282,13 +284,14 @@
/**
* Updates {@link #mPreviousFocusArea} when the focus has moved from another FocusArea to this
- * FocusArea.
+ * FocusArea, and sets it to {@code null} in any other cases.
*/
private void maybeUpdatePreviousFocusArea(boolean hasFocus, View oldFocus) {
- if (mHasFocus || !hasFocus || oldFocus == null) {
+ if (mHasFocus || !hasFocus || oldFocus == null || oldFocus instanceof FocusParkingView) {
+ mPreviousFocusArea = null;
return;
}
- mPreviousFocusArea = getAncestorFocusArea(oldFocus);
+ mPreviousFocusArea = ViewUtils.getAncestorFocusArea(oldFocus);
if (mPreviousFocusArea == null) {
Log.w(TAG, "No parent FocusArea for " + oldFocus);
}
@@ -305,7 +308,7 @@
if (!hasFocus || oldFocus == null) {
return;
}
- FocusArea oldFocusArea = getAncestorFocusArea(oldFocus);
+ FocusArea oldFocusArea = ViewUtils.getAncestorFocusArea(oldFocus);
if (oldFocusArea != this) {
return;
}
@@ -456,6 +459,28 @@
}
@Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ // To ensure the focus is initialized properly in rotary mode when there is a window focus
+ // change, this FocusArea will grab the focus from the currently focused view if one of this
+ // FocusArea's descendants is a better focus candidate than the currently focused view.
+ if (hasWindowFocus && !isInTouchMode()) {
+ maybeAdjustFocus();
+ }
+ super.onWindowFocusChanged(hasWindowFocus);
+ }
+
+ /**
+ * Focuses on another view in this FocusArea if the view is a better focus candidate than the
+ * currently focused view.
+ */
+ private boolean maybeAdjustFocus() {
+ View root = getRootView();
+ View focus = root.findFocus();
+ return ViewUtils.adjustFocus(root, focus);
+ }
+
+
+ @Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
switch (action) {
case ACTION_FOCUS:
@@ -480,12 +505,6 @@
}
private boolean focusOnDescendant() {
- if (focusOnFocusedByDefaultView()) {
- return true;
- }
- if (focusOnPrimaryFocusView()) {
- return true;
- }
if (mDefaultFocusOverridesHistory) {
// Check mDefaultFocus before last focused view.
if (focusDefaultFocusView() || focusOnLastFocusedView()) {
@@ -500,28 +519,26 @@
return focusOnFirstFocusableView();
}
- private boolean focusOnFocusedByDefaultView() {
- View focusedByDefaultView = ViewUtils.findFocusedByDefaultView(this);
- return requestFocus(focusedByDefaultView);
- }
-
- private boolean focusOnPrimaryFocusView() {
- View primaryFocus = ViewUtils.findPrimaryFocusView(this);
- return requestFocus(primaryFocus);
- }
-
private boolean focusDefaultFocusView() {
- return requestFocus(mDefaultFocusView);
+ return ViewUtils.adjustFocus(this, /* currentLevel= */ REGULAR_FOCUS);
+ }
+
+ /**
+ * Gets the {@code app:defaultFocus} view.
+ *
+ * @hidden
+ */
+ public View getDefaultFocusView() {
+ return mDefaultFocusView;
}
private boolean focusOnLastFocusedView() {
View lastFocusedView = mRotaryCache.getFocusedView(SystemClock.uptimeMillis());
- return requestFocus(lastFocusedView);
+ return ViewUtils.requestFocus(lastFocusedView);
}
private boolean focusOnFirstFocusableView() {
- View firstFocusableView = ViewUtils.findFocusableDescendant(this);
- return requestFocus(firstFocusableView);
+ return ViewUtils.adjustFocus(this, /* currentLevel= */ NO_FOCUS);
}
private boolean nudgeToShortcutView(Bundle arguments) {
@@ -540,7 +557,7 @@
// nudge to another FocusArea.
return false;
}
- return requestFocus(mNudgeShortcutView);
+ return ViewUtils.requestFocus(mNudgeShortcutView);
}
private boolean nudgeToAnotherFocusArea(Bundle arguments) {
@@ -557,9 +574,6 @@
success = targetFocusArea != null && targetFocusArea.focusOnDescendant();
}
- if (success) {
- saveFocusAreaHistory(direction, this, targetFocusArea, elapsedRealtime);
- }
return success;
}
@@ -569,14 +583,15 @@
: arguments.getInt(NUDGE_DIRECTION, INVALID_DIRECTION);
}
- /** Saves bidirectional FocusArea nudge history. */
private void saveFocusAreaHistory(int direction, @NonNull FocusArea sourceFocusArea,
@NonNull FocusArea targetFocusArea, long elapsedRealtime) {
- sourceFocusArea.mRotaryCache.saveFocusArea(direction, targetFocusArea, elapsedRealtime);
-
- int oppositeDirection = getOppositeDirection(direction);
- targetFocusArea.mRotaryCache.saveFocusArea(oppositeDirection, sourceFocusArea,
- elapsedRealtime);
+ // Save one-way rather than two-way nudge history to avoid infinite nudge loop.
+ if (sourceFocusArea.mRotaryCache.getCachedFocusArea(direction, elapsedRealtime) == null) {
+ // Save reversed nudge history so that the users can nudge back to where they were.
+ int oppositeDirection = getOppositeDirection(direction);
+ targetFocusArea.mRotaryCache.saveFocusArea(oppositeDirection, sourceFocusArea,
+ elapsedRealtime);
+ }
}
/** Returns the direction opposite the given {@code direction} */
@@ -596,17 +611,6 @@
+ "FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, or FOCUS_RIGHT.");
}
- private static FocusArea getAncestorFocusArea(@NonNull View view) {
- ViewParent parent = view.getParent();
- while (parent != null) {
- if (parent instanceof FocusArea) {
- return (FocusArea) parent;
- }
- parent = parent.getParent();
- }
- return null;
- }
-
@Nullable
private FocusArea getSpecifiedFocusArea(int direction) {
maybeInitializeSpecifiedFocusAreas();
@@ -660,6 +664,19 @@
bundle.putInt(FOCUS_AREA_BOTTOM_BOUND_OFFSET, mBottomOffset);
}
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ if (isInTouchMode()) {
+ return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+ }
+ return maybeAdjustFocus();
+ }
+
+ @Override
+ public boolean restoreDefaultFocus() {
+ return maybeAdjustFocus();
+ }
+
private void maybeInitializeSpecifiedFocusAreas() {
if (mSpecifiedNudgeFocusAreaMap != null) {
return;
@@ -672,16 +689,11 @@
}
}
- private boolean requestFocus(@Nullable View view) {
- if (view == null || !view.isAttachedToWindow()) {
- return false;
- }
- // Exit touch mode and focus the view. The view may not be focusable in touch mode, so we
- // need to exit touch mode before focusing it.
- return view.performAccessibilityAction(ACTION_FOCUS, null);
- }
-
- /** Sets the padding (in pixels) of the FocusArea highlight. */
+ /**
+ * Sets the padding (in pixels) of the FocusArea highlight.
+ * <p>
+ * It doesn't affect other values, such as the paddings on its child views.
+ */
public void setHighlightPadding(int left, int top, int right, int bottom) {
if (mPaddingLeft == left && mPaddingTop == top && mPaddingRight == right
&& mPaddingBottom == bottom) {
@@ -694,7 +706,13 @@
invalidate();
}
- /** Sets the offset (in pixels) of the FocusArea's bounds. */
+ /**
+ * Sets the offset (in pixels) of the FocusArea's bounds.
+ * <p>
+ * It only affects the perceived bounds for the purposes of finding the nudge target. It doesn't
+ * affect the FocusArea's view bounds or highlight bounds. The offset should only be used when
+ * FocusAreas are overlapping and nudge interaction is ambiguous.
+ */
public void setBoundsOffset(int left, int top, int right, int bottom) {
mLeftOffset = left;
mTopOffset = top;
@@ -702,8 +720,28 @@
mBottomOffset = bottom;
}
+ /** Sets the default focus view in this FocusArea. */
+ public void setDefaultFocus(@NonNull View defaultFocus) {
+ mDefaultFocusView = defaultFocus;
+ }
+
@VisibleForTesting
void enableForegroundHighlight() {
mEnableForegroundHighlight = true;
}
+
+ @VisibleForTesting
+ void setDefaultFocusOverridesHistory(boolean override) {
+ mDefaultFocusOverridesHistory = override;
+ }
+
+ @VisibleForTesting
+ void setRotaryCache(@NonNull RotaryCache rotaryCache) {
+ mRotaryCache = rotaryCache;
+ }
+
+ @VisibleForTesting
+ void setClearFocusAreaHistoryWhenRotating(boolean clear) {
+ mClearFocusAreaHistoryWhenRotating = clear;
+ }
}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingView.java
index 8a42cea..9d5cfeb 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingView.java
@@ -15,28 +15,34 @@
*/
package com.android.car.ui;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
import static com.android.car.ui.utils.RotaryConstants.ACTION_HIDE_IME;
import static com.android.car.ui.utils.RotaryConstants.ACTION_RESTORE_DEFAULT_FOCUS;
import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
import android.os.Bundle;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
import com.android.car.ui.utils.ViewUtils;
/**
* A transparent {@link View} that can take focus. It's used by {@link
- * com.android.car.rotary.RotaryService} to support rotary controller navigation. Each {@link
- * android.view.Window} should have one FocusParkingView as the first focusable view in the view
- * tree, and outside of all {@link FocusArea}s. If multiple FocusParkingView are added in the
- * window, only the first one will be focusable.
+ * com.android.car.rotary.RotaryService} to support rotary controller navigation. It's also used to
+ * initialize the focus when in rotary mode.
+ * <p>
+ * To support the rotary controller, each {@link android.view.Window} must have a FocusParkingView
+ * as the first focusable view in the view tree, and outside of all {@link FocusArea}s.
* <p>
* Android doesn't clear focus automatically when focus is set in another window. If we try to clear
* focus in the previous window, Android will re-focus a view in that window, resulting in two
@@ -45,41 +51,67 @@
* matter whether it's focused or not. It can take focus so that RotaryService can "park" the focus
* on it to remove the focus highlight.
* <p>
- * If the focused view is scrolled off the screen, Android will refocus the first focusable view in
- * the window. The FocusParkingView should be the first view so that it gets focus. The
- * RotaryService detects this and moves focus to the scrolling container.
- * <p>
* If there is only one focus area in the current window, rotating the controller within the focus
* area will cause RotaryService to move the focus around from the view on the right to the view on
* the left or vice versa. Adding this view to each window can fix this issue. When RotaryService
* finds out the focus target is a FocusParkingView, it will know a wrap-around is going to happen.
* Then it will avoid the wrap-around by not moving focus.
+ * <p>
+ * To ensure the focus is initialized properly when there is a window change, the FocusParkingView
+ * will not get focused when the framework wants to focus on it. Instead, it will try to find a
+ * better focus target in the window and focus on the target. That said, the FocusParkingView can
+ * still be focused in order to clear focus highlight in the window, such as when RotaryService
+ * performs {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_FOCUS} on the
+ * FocusParkingView, or the window has lost focus.
*/
public class FocusParkingView extends View {
- private static final String TAG = "FocusParkingView";
+
+ /**
+ * The focused view in the window containing this FocusParkingView. It's null if no view is
+ * focused, or the focused view is a FocusParkingView.
+ */
+ @Nullable
+ private View mFocusedView;
+
+ /** The scrollable container that contains the {@link #mFocusedView}, if any. */
+ @Nullable
+ ViewGroup mScrollableContainer;
+
+ /**
+ * Whether to restore focus when the frameworks wants to focus this view. When false, this view
+ * allows itself to be focused instead. This should be false for the {@code FocusParkingView} in
+ * an {@code ActivityView}. The default value is true.
+ */
+ private boolean mShouldRestoreFocus;
public FocusParkingView(Context context) {
super(context);
- init();
+ init(context, /* attrs= */ null);
}
public FocusParkingView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
- init();
+ init(context, attrs);
}
public FocusParkingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- init();
+ init(context, attrs);
}
public FocusParkingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- init();
+ init(context, attrs);
}
- private void init() {
+ private void init(Context context, @Nullable AttributeSet attrs) {
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FocusParkingView);
+ mShouldRestoreFocus = a.getBoolean(R.styleable.FocusParkingView_shouldRestoreFocus,
+ /* defValue= */ true);
+ }
+
// This view is focusable, visible and enabled so it can take focus.
setFocusable(View.FOCUSABLE);
setVisibility(VISIBLE);
@@ -94,6 +126,12 @@
// Prevent Android from drawing the default focus highlight for this view when it's focused.
setDefaultFocusHighlightEnabled(false);
+
+ // Keep track of the focused view so that we can recover focus when it's removed.
+ getViewTreeObserver().addOnGlobalFocusChangeListener((oldFocus, newFocus) -> {
+ mFocusedView = newFocus instanceof FocusParkingView ? null : newFocus;
+ mScrollableContainer = ViewUtils.getAncestorScrollableContainer(mFocusedView);
+ });
}
@Override
@@ -107,12 +145,21 @@
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
if (!hasWindowFocus) {
- // We need to clear the focus (by parking the focus on the FocusParkingView) once the
- // current window goes to background. This can't be done by RotaryService because
- // RotaryService sees the window as removed, thus can't perform any action (such as
- // focus, clear focus) on the nodes in the window. So FocusParkingView has to grab the
- // focus proactively.
- requestFocus();
+ // We need to clear the focus highlight(by parking the focus on the FocusParkingView)
+ // once the current window goes to background. This can't be done by RotaryService
+ // because RotaryService sees the window as removed, thus can't perform any action
+ // (such as focus, clear focus) on the nodes in the window. So FocusParkingView has to
+ // grab the focus proactively.
+ super.requestFocus(FOCUS_DOWN, null);
+
+ // OnGlobalFocusChangeListener won't be triggered when the window lost focus, so reset
+ // the focused view here.
+ mFocusedView = null;
+ mScrollableContainer = null;
+ } else if (isFocused()) {
+ // When FocusParkingView is focused and the window just gets focused, transfer the view
+ // focus to a non-FocusParkingView in the window.
+ restoreFocusInRoot(/* checkForTouchMode= */ true);
}
super.onWindowFocusChanged(hasWindowFocus);
}
@@ -126,21 +173,7 @@
public boolean performAccessibilityAction(int action, Bundle arguments) {
switch (action) {
case ACTION_RESTORE_DEFAULT_FOCUS:
- View root = getRootView();
-
- // If there is a view focused by default and it can take focus, move focus to it.
- View defaultFocus = ViewUtils.findFocusedByDefaultView(root);
- if (defaultFocus != null) {
- return defaultFocus.requestFocus();
- }
-
- // If there is a primary focus view, move focus to it.
- View primaryFocus = ViewUtils.findPrimaryFocusView(root);
- if (primaryFocus != null) {
- return primaryFocus.requestFocus();
- }
-
- return false;
+ return restoreFocusInRoot(/* checkForTouchMode= */ false);
case ACTION_HIDE_IME:
InputMethodManager inputMethodManager =
getContext().getSystemService(InputMethodManager.class);
@@ -149,33 +182,93 @@
case ACTION_FOCUS:
// Don't leave this to View to handle as it will exit touch mode.
if (!hasFocus()) {
- return requestFocus();
+ return super.requestFocus(FOCUS_DOWN, null);
}
- break;
+ return false;
}
return super.performAccessibilityAction(action, arguments);
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
+ public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+ if (!mShouldRestoreFocus) {
+ return super.requestFocus(direction, previouslyFocusedRect);
+ }
+ // Find a better target to focus instead of focusing this FocusParkingView when the
+ // framework wants to focus it.
+ return restoreFocusInRoot(/* checkForTouchMode= */ true);
+ }
- // If there is a FocusParkingView already, make the one after in the view tree
- // non-focusable.
- boolean []isBefore = new boolean[1];
- View anotherFpv = ViewUtils.depthFirstSearch(getRootView(), v -> {
- if (this == v) {
- isBefore[0] = true;
- }
- return v != this && v instanceof FocusParkingView && v.isFocusable();
- });
- if (anotherFpv != null) {
- Log.w(TAG, "There should be only one FocusParkingView in the window");
- if (isBefore[0]) {
- anotherFpv.setFocusable(false);
- } else {
- setFocusable(false);
+ @Override
+ public boolean restoreDefaultFocus() {
+ if (!mShouldRestoreFocus) {
+ return super.restoreDefaultFocus();
+ }
+ // Find a better target to focus instead of focusing this FocusParkingView when the
+ // framework wants to focus it.
+ return restoreFocusInRoot(/* checkForTouchMode= */ true);
+ }
+
+ /**
+ * Sets whether this view should restore focus when the framework wants to focus this view. When
+ * set to false, this view allows itself to be focused instead. This should be set to false for
+ * the {@code FocusParkingView} in an {@code ActivityView}. The default value is true.
+ */
+ public void setShouldRestoreFocus(boolean shouldRestoreFocus) {
+ mShouldRestoreFocus = shouldRestoreFocus;
+ }
+
+ private boolean restoreFocusInRoot(boolean checkForTouchMode) {
+ // Don't do anything in touch mode if checkForTouchMode is true.
+ if (checkForTouchMode && isInTouchMode()) {
+ return false;
+ }
+ // The focused view was in a scrollable container and the Framework unfocused it because it
+ // was scrolled off the screen. In this case focus on the scrollable container so that the
+ // rotary controller can scroll the scrollable container.
+ if (maybeFocusOnScrollableContainer()) {
+ return true;
+ }
+ // Otherwise try to find the best target view to focus.
+ if (ViewUtils.adjustFocus(getRootView(), /* currentFocus= */ null)) {
+ return true;
+ }
+ // It failed to find a target view (e.g., all the views are not shown), so focus on this
+ // FocusParkingView as fallback.
+ return super.requestFocus(FOCUS_DOWN, /* previouslyFocusedRect= */ null);
+ }
+
+ private boolean maybeFocusOnScrollableContainer() {
+ // If the focused view was in a scrollable container and it was scrolled off the screen,
+ // focus on the scrollable container. When a view is scrolled off the screen, it is no
+ // longer attached to window and its parent is not null. When a view is removed, its parent
+ // is null. There is no need to focus on the scrollable container when its focused element
+ // is removed.
+ if (mFocusedView != null && !mFocusedView.isAttachedToWindow()
+ && mFocusedView.getParent() != null && mScrollableContainer != null
+ && mScrollableContainer.isAttachedToWindow() && mScrollableContainer.isShown()) {
+ RecyclerView recyclerView = mScrollableContainer instanceof RecyclerView
+ ? (RecyclerView) mScrollableContainer
+ : null;
+ if (mScrollableContainer.requestFocus()) {
+ if (recyclerView != null && recyclerView.isComputingLayout()) {
+ // When a RecyclerView gains focus, it won't dispatch AccessibilityEvent if its
+ // layout is not ready. So wait until its layout is ready then dispatch the
+ // event.
+ getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ // At this point the layout is complete and the dimensions of
+ // recyclerView and any child views are known.
+ recyclerView.sendAccessibilityEvent(TYPE_VIEW_FOCUSED);
+ getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ }
+ });
+ }
+ return true;
}
}
+ return false;
}
}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/BaseLayoutController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/BaseLayoutController.java
index 1a8360e..1e5a46c 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/BaseLayoutController.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/BaseLayoutController.java
@@ -23,7 +23,6 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import androidx.annotation.LayoutRes;
@@ -47,7 +46,7 @@
* It also exposes a {@link ToolbarController} to access the toolbar. This may be null if
* used with a base layout without a Toolbar.
*/
-public class BaseLayoutController {
+final class BaseLayoutController {
private static final Map<Activity, BaseLayoutController> sBaseLayoutMap = new WeakHashMap<>();
@@ -180,7 +179,6 @@
}
InsetsUpdater insetsUpdater = new InsetsUpdater(activity, baseLayout, contentView);
- insetsUpdater.installListeners();
return Pair.create(toolbarController, insetsUpdater);
}
@@ -208,7 +206,7 @@
* none of the Activity/Fragments implement {@link InsetsChangedListener}, it will set
* padding on the content view equal to the insets.
*/
- public static final class InsetsUpdater implements ViewTreeObserver.OnGlobalLayoutListener {
+ static final class InsetsUpdater {
// These tags mark views that should overlay the content view in the base layout.
// OEMs should add them to views in their base layout, ie: android:tag="car_ui_left_inset"
// Apps will then be able to draw under these views, but will be encouraged to not put
@@ -228,7 +226,6 @@
private final View mBottomInsetView;
private InsetsChangedListener mInsetsChangedListenerDelegate;
- private boolean mInsetsDirty = true;
@NonNull
private Insets mInsets = new Insets();
@@ -240,7 +237,7 @@
* @param baseLayout The root view of the base layout
* @param contentView The android.R.id.content View
*/
- public InsetsUpdater(
+ InsetsUpdater(
@Nullable Activity activity,
@NonNull View baseLayout,
@NonNull View contentView) {
@@ -259,7 +256,7 @@
int oldLeft, int oldTop, int oldRight, int oldBottom) -> {
if (left != oldLeft || top != oldTop
|| right != oldRight || bottom != oldBottom) {
- mInsetsDirty = true;
+ recalcInsets();
}
};
@@ -279,17 +276,6 @@
mContentViewContainer.addOnLayoutChangeListener(layoutChangeListener);
}
- /**
- * Install a global layout listener, during which the insets will be recalculated and
- * dispatched.
- */
- public void installListeners() {
- // The global layout listener will run after all the individual layout change listeners
- // so that we only updateInsets once per layout, even if multiple inset views changed
- mContentView.getRootView().getViewTreeObserver()
- .addOnGlobalLayoutListener(this);
- }
-
@NonNull
Insets getInsets() {
return mInsets;
@@ -300,13 +286,9 @@
}
/**
- * onGlobalLayout() should recalculate the amount of insets we need, and then dispatch them.
+ * Recalculate the amount of insets we need, and then dispatch them.
*/
- @Override
- public void onGlobalLayout() {
- if (!mInsetsDirty) {
- return;
- }
+ public void recalcInsets() {
// Calculate how much each inset view overlays the content view
@@ -339,7 +321,6 @@
}
Insets insets = new Insets(left, top, right, bottom);
- mInsetsDirty = false;
if (!insets.equals(mInsets)) {
mInsets = insets;
dispatchNewInsets(insets);
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeSearchListItem.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeSearchListItem.java
new file mode 100644
index 0000000..8476869
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeSearchListItem.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.imewidescreen;
+
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.Nullable;
+
+import com.android.car.ui.recyclerview.CarUiContentListItem;
+import com.android.car.ui.recyclerview.CarUiListItemAdapter;
+
+/**
+ * Definition of list items that can be inserted into {@link CarUiListItemAdapter}. This class is
+ * used to display the search items in the template for wide screen mode.
+ *
+ * The class is used to pass application icon resources ids to the IME for rendering in its
+ * process. Applications can also pass a unique id for each item and supplemental icon that will be
+ * used by the IME to notify the application when a click action is taken on them.
+ */
+public class CarUiImeSearchListItem extends CarUiContentListItem {
+
+ private int mIconResId;
+ private int mSupplementalIconResId;
+
+ public CarUiImeSearchListItem(Action action) {
+ super(action);
+ }
+
+ /**
+ * Sets the icon of the item. Icon must be a BitmapDrawable.
+ *
+ * @param icon the icon to display.
+ */
+ @Override
+ public void setIcon(@Nullable Drawable icon) {
+ if (icon instanceof BitmapDrawable || icon == null) {
+ super.setIcon(icon);
+ return;
+ }
+ throw new RuntimeException("icon should be of type BitmapDrawable");
+ }
+
+ /**
+ * Sets supplemental icon to be displayed in a list item. Icon must be a BitmapDrawable.
+ *
+ * @param icon the Drawable to set as the icon, or null to clear the content.
+ * @param listener the callback that is invoked when the icon is clicked.
+ */
+ @Override
+ public void setSupplementalIcon(@Nullable Drawable icon,
+ @Nullable OnClickListener listener) {
+ if (icon instanceof BitmapDrawable || icon == null) {
+ super.setSupplementalIcon(icon, listener);
+ return;
+ }
+ throw new RuntimeException("icon should be of type BitmapDrawable");
+ }
+
+
+ /**
+ * Returns the icons resource of the item.
+ */
+ public int getIconResId() {
+ return mIconResId;
+ }
+
+ /**
+ * Sets the icons resource of the item.
+ */
+ public void setIconResId(int iconResId) {
+ mIconResId = iconResId;
+ }
+
+ /**
+ * Returns the supplemental icon resource id of the item.
+ */
+ public int getSupplementalIconResId() {
+ return mSupplementalIconResId;
+ }
+
+ /**
+ * Sets supplemental icon resource id.
+ */
+ public void setSupplementalIconResId(int supplementalIconResId) {
+ mSupplementalIconResId = supplementalIconResId;
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenController.java
new file mode 100644
index 0000000..bf7d2bf
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenController.java
@@ -0,0 +1,806 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.imewidescreen;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.inputmethodservice.ExtractEditText;
+import android.inputmethodservice.InputMethodService;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.SurfaceControlViewHost.SurfacePackage;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.R;
+import com.android.car.ui.recyclerview.CarUiContentListItem;
+import com.android.car.ui.recyclerview.CarUiListItemAdapter;
+import com.android.car.ui.utils.CarUiUtils;
+
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class to build an IME that support widescreen mode.
+ *
+ * <p> This class provides helper methods that should be invoked during the lifecycle of an IME.
+ * Usage of these methods are listed below.
+ * <ul>
+ * <li>create an instance of {@link CarUiImeWideScreenController} in
+ * {@link InputMethodService#onCreate()}</li>
+ * <li>return {@link #onEvaluateFullscreenMode(boolean)} from
+ * {@link InputMethodService#onEvaluateFullscreenMode()}</li>
+ * <li>return the view created by
+ * {@link #createWideScreenImeView(View)}
+ * from {@link InputMethodService#onCreateInputView()}</li>
+ * <li>{@link #onComputeInsets(InputMethodService.Insets) should be called from
+ * {@link InputMethodService#onComputeInsets(InputMethodService.Insets)}</li>
+ * <li>{@link #onAppPrivateCommand(String, Bundle) should be called from {
+ * @link InputMethodService#onAppPrivateCommand(String, Bundle)}}</li>
+ * <li>{@link #setExtractViewShown(boolean)} should be called from
+ * {@link InputMethodService#setExtractViewShown(boolean)}</li>
+ * <li>{@link #onStartInputView(EditorInfo, InputConnection, CharSequence)} should be called
+ * from {@link InputMethodService#onStartInputView(EditorInfo, boolean)}</li>
+ * <li>{@link #onFinishInputView()} should be called from
+ * {@link InputMethodService#onFinishInputView(boolean)}</li>
+ * </ul>
+ *
+ * <p> All the methods in this class are guarded with a check {@link #isWideScreenMode()}. If
+ * wide screen mode is disabled all the method would return without doing anything. Also, IME
+ * should check for {@link #isWideScreenMode()} in
+ * {@link InputMethodService#setExtractViewShown(boolean)} and return the original value instead
+ * of false. for more info see {@link #setExtractViewShown(boolean)}
+ */
+public class CarUiImeWideScreenController {
+
+ private static final String TAG = "ImeWideScreenController";
+ private static final String NOT_ASTERISK_OR_CAPTURED_ASTERISK = "[^*]+|(\\*)";
+
+ // Automotive wide screen mode bundle keys.
+
+ // Action name of the action to support wide screen mode templates data.
+ public static final String WIDE_SCREEN_ACTION = "automotive_wide_screen";
+ // Action name of action that will be used by IMS to notify the application to clear the data
+ // in the EditText.
+ public static final String WIDE_SCREEN_CLEAR_DATA_ACTION = "automotive_wide_screen_clear_data";
+ // Key to provide the resource id for the icon that will be displayed in the input area. If
+ // this is not provided applications icon will be used. Value format is int.
+ public static final String WIDE_SCREEN_EXTRACTED_TEXT_ICON_RES_ID =
+ "extracted_text_icon_res_id";
+ // Key to determine if IME should display the content area or not. Content area is referred to
+ // the area used by IME to display search results, description title and description
+ // provided by the application. By default it will be shown but this value could be ignored
+ // if bool/car_ui_ime_wide_screen_allow_app_hide_content_area is set to false. Value format
+ // is boolean.
+ public static final String REQUEST_RENDER_CONTENT_AREA = "request_render_content_area";
+ // Key used to provide the description title to be rendered in the content area. Value format
+ // is String.
+ public static final String ADD_DESC_TITLE_TO_CONTENT_AREA = "add_desc_title_to_content_area";
+ // Key used to provide the description to be rendered in the content area. Value format is
+ // String.
+ public static final String ADD_DESC_TO_CONTENT_AREA = "add_desc_to_content_area";
+ // Key used to provide the error description to be rendered in the input area. Value format
+ // is String.
+ public static final String ADD_ERROR_DESC_TO_INPUT_AREA = "add_error_desc_to_input_area";
+
+ // wide screen search item keys. Each search item contains a title, sub-title, primary image
+ // and an secondary image. Click actions can be performed on item and secondary image.
+ // Application will be notified with the Ids of item clicked.
+
+ // Each key below represents a list. Search results will be displayed in the same order as
+ // the list provided by the application. For example, to create the search item at index 0
+ // controller will get the information from each lists index 0.
+
+ // Key used to provide list of unique id for each item. This same id will be sent back to
+ // the application when the item is clicked. Value format is ArrayList<String>
+ public static final String SEARCH_RESULT_ITEM_ID_LIST = "search_result_item_id_list";
+ // Key used to provide the list of titles for each search item. Value format is
+ // ArrayList<String>
+ public static final String SEARCH_RESULT_TITLE_LIST = "search_result_title_list";
+ // Key used to provide the list of sub titles for each search item. Value format is
+ // ArrayList<String>
+ public static final String SEARCH_RESULT_SUB_TITLE_LIST = "search_result_sub_title_list";
+ // Key used to provide the list of resource id for each primary image in search item.
+ // ArrayList<Integer>. If bitmap and res id both are provided than bitmap will take
+ // precedence over res id.
+ public static final String SEARCH_RESULT_ICON_RES_ID_LIST =
+ "search_result_icon_res_id_list";
+ // Key used to provide the list of bitmap for each primary image in search item.
+ // ArrayList<Integer>. If bitmap and res id both are provided than bitmap will take
+ // precedence over res id.
+ public static final String SEARCH_RESULT_ICON_BITMAP_LIST =
+ "search_result_icon_bitmap_list";
+ // Key used to provide the list of bitmap for each secondary image in search item.
+ // ArrayList<Integer>. If bitmap and res id both are provided than bitmap will take
+ // precedence over res id.
+ public static final String SEARCH_RESULT_SUPPLEMENTAL_ICON_BITMAP_LIST =
+ "search_result_supplemental_icon_bitmap_list";
+ // Key used to provide the list of id for each secondary image in search item.
+ // ArrayList<String>. If bitmap and res id both are provided than bitmap will take
+ // precedence over res id.
+ public static final String SEARCH_RESULT_SUPPLEMENTAL_ICON_ID_LIST =
+ "search_result_supplemental_icon_id_list";
+ // Key used to provide the list of resource id for each secondary image in search item.
+ // ArrayList<Integer>
+ public static final String SEARCH_RESULT_SUPPLEMENTAL_ICON_RES_ID_LIST =
+ "search_result_supplemental_icon_res_id_list";
+ // key used to provide the surface package information by the application to the IME. IME
+ // will send the surface info each time its being displayed.
+ public static final String CONTENT_AREA_SURFACE_PACKAGE = "content_area_surface_package";
+ // key to provide the host token of surface view by IME to the application.
+ public static final String CONTENT_AREA_SURFACE_HOST_TOKEN = "content_area_surface_host_token";
+ // key to provide the display id of surface view by IME to the application.
+ public static final String CONTENT_AREA_SURFACE_DISPLAY_ID = "content_area_surface_display_id";
+ // key to provide the height of surface view by IME to the application.
+ public static final String CONTENT_AREA_SURFACE_HEIGHT = "content_area_surface_height";
+ // key to provide the width of surface view by IME to the application.
+ public static final String CONTENT_AREA_SURFACE_WIDTH = "content_area_surface_width";
+
+ private View mRootView;
+ private final Context mContext;
+ @Nullable
+ private View mExtractActionAutomotive;
+ @NonNull
+ private View mContentAreaAutomotive;
+ // whether to render the content area for automotive when in wide screen mode.
+ private boolean mImeRendersAllContent = true;
+ private boolean mAllowAppToHideContentArea;
+ @Nullable
+ private ArrayList<CarUiContentListItem> mAutomotiveSearchItems;
+ @NonNull
+ private TextView mWideScreenDescriptionTitle;
+ @NonNull
+ private TextView mWideScreenDescription;
+ @NonNull
+ private TextView mWideScreenErrorMessage;
+ @NonNull
+ private ImageView mWideScreenErrorImage;
+ @NonNull
+ private ImageView mWideScreenClearData;
+ @NonNull
+ private RecyclerView mRecyclerView;
+ @Nullable
+ private ImageView mWideScreenExtractedTextIcon;
+ private boolean mIsExtractIconProvidedByApp;
+ @NonNull
+ private FrameLayout mInputFrame;
+ @NonNull
+ private ExtractEditText mExtractEditText;
+ private EditorInfo mInputEditorInfo;
+ private InputConnection mInputConnection;
+ private boolean mExtractViewHidden;
+ @NonNull
+ private View mFullscreenArea;
+ @NonNull
+ private SurfaceView mContentAreaSurfaceView;
+ @NonNull
+ private FrameLayout mInputExtractEditTextContainer;
+ private final InputMethodService mInputMethodService;
+
+ public CarUiImeWideScreenController(@NonNull Context context, @NonNull InputMethodService ims) {
+ mContext = context;
+ mInputMethodService = ims;
+ }
+
+ /**
+ * Create and return the view hierarchy used for the input area in wide screen mode. This method
+ * will inflate the templates with the inputView provided.
+ *
+ * @param inputView view of the keyboard created by application.
+ * @return view to be used by {@link InputMethodService}.
+ */
+ public View createWideScreenImeView(@NonNull View inputView) {
+ if (!isWideScreenMode()) {
+ return inputView;
+ }
+ mRootView = View.inflate(mContext, R.layout.car_ui_ims_wide_screen_input_view, null);
+
+ mInputFrame = mRootView.requireViewById(R.id.car_ui_wideScreenInputArea);
+ mInputFrame.addView(inputView, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ mAllowAppToHideContentArea =
+ mContext.getResources().getBoolean(
+ R.bool.car_ui_ime_wide_screen_allow_app_hide_content_area);
+
+ mContentAreaSurfaceView = mRootView.requireViewById(R.id.car_ui_ime_surface);
+ mContentAreaSurfaceView.setZOrderOnTop(true);
+ mWideScreenDescriptionTitle =
+ mRootView.requireViewById(R.id.car_ui_wideScreenDescriptionTitle);
+ mWideScreenDescription = mRootView.requireViewById(R.id.car_ui_wideScreenDescription);
+ mExtractActionAutomotive =
+ mRootView.findViewById(R.id.car_ui_inputExtractActionAutomotive);
+ mContentAreaAutomotive = mRootView.requireViewById(R.id.car_ui_contentAreaAutomotive);
+ mRecyclerView = mRootView.requireViewById(R.id.car_ui_wideScreenSearchResultList);
+ mWideScreenErrorMessage = mRootView.requireViewById(R.id.car_ui_wideScreenErrorMessage);
+ mWideScreenExtractedTextIcon =
+ mRootView.findViewById(R.id.car_ui_wideScreenExtractedTextIcon);
+ mWideScreenErrorImage = mRootView.requireViewById(R.id.car_ui_wideScreenError);
+ mWideScreenClearData = mRootView.requireViewById(R.id.car_ui_wideScreenClearData);
+ mFullscreenArea = mRootView.requireViewById(R.id.car_ui_fullscreenArea);
+ mInputExtractEditTextContainer = mRootView.requireViewById(
+ R.id.car_ui_inputExtractEditTextContainer);
+ mWideScreenClearData.setOnClickListener(
+ v -> {
+ // notify the app to clear the data.
+ mInputConnection.performPrivateCommand(WIDE_SCREEN_CLEAR_DATA_ACTION, null);
+ });
+ mExtractViewHidden = false;
+
+ return mRootView;
+ }
+
+ /**
+ * Compute the interesting insets into your UI. When the content view is shown the default
+ * touchable insets are {@link InputMethodService.Insets#TOUCHABLE_INSETS_FRAME}. When content
+ * view is hidden then that area of the application is interactable by user.
+ *
+ * @param outInsets Fill in with the current UI insets.
+ */
+ public void onComputeInsets(@NonNull InputMethodService.Insets outInsets) {
+ if (!isWideScreenMode()) {
+ return;
+ }
+ Rect tempRect = new Rect();
+ int[] tempLocation = new int[2];
+ outInsets.contentTopInsets = outInsets.visibleTopInsets =
+ mInputMethodService.getWindow().getWindow().getDecorView().getHeight();
+ if (mImeRendersAllContent) {
+ outInsets.touchableRegion.setEmpty();
+ outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_FRAME;
+ } else {
+ mInputFrame.getLocationOnScreen(tempLocation);
+ tempRect.set(/* left= */0, /* top= */ 0,
+ tempLocation[0] + mInputFrame.getWidth(),
+ tempLocation[1] + mInputFrame.getHeight());
+ outInsets.touchableRegion.set(tempRect);
+ outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
+ }
+ }
+
+ /**
+ * Actions passed by the application must be "automotive_wide_screen" with the corresponding
+ * data that application wants to display. See the comments associated with each bundle key to
+ * know what view is rendered.
+ *
+ * <p> Each bundle key renders or updates/controls a particular view in the template. For
+ * example, if application rendered the description title and later also wanted to render an
+ * actual description with it then application should use both "add_desc_title_to_content_area"
+ * and "add_desc_to_content_area" to provide the data. Sending action with only
+ * "add_desc_to_content_area" bundle key will not add an extra view but will display only the
+ * description and not the title.
+ *
+ * When the IME window is closed all the views are reset. For the default view visibility see
+ * {@link #resetAutomotiveWideScreenViews()}.
+ *
+ * @param action Name of the command to be performed.
+ * @param data Any data to include with the command.
+ */
+ public void onAppPrivateCommand(String action, Bundle data) {
+ if (!isWideScreenMode() || !WIDE_SCREEN_ACTION.equals(action)) {
+ return;
+ }
+ resetAutomotiveWideScreenViews();
+ if (data == null) {
+ return;
+ }
+ if (mAllowAppToHideContentArea || (mInputEditorInfo != null && isPackageAuthorized(
+ mInputEditorInfo.packageName))) {
+ mImeRendersAllContent = data.getBoolean(REQUEST_RENDER_CONTENT_AREA, true);
+ }
+
+ if (data.getParcelable(CONTENT_AREA_SURFACE_PACKAGE) != null
+ && Build.VERSION.SDK_INT >= VERSION_CODES.R) {
+ SurfacePackage surfacePackage = (SurfacePackage) data.getParcelable(
+ CONTENT_AREA_SURFACE_PACKAGE);
+ mContentAreaSurfaceView.setChildSurfacePackage(surfacePackage);
+ mContentAreaSurfaceView.setVisibility(View.VISIBLE);
+ mContentAreaAutomotive.setVisibility(View.GONE);
+ }
+
+ String discTitle = data.getString(ADD_DESC_TITLE_TO_CONTENT_AREA);
+ if (!TextUtils.isEmpty(discTitle)) {
+ mWideScreenDescriptionTitle.setText(discTitle);
+ mWideScreenDescriptionTitle.setVisibility(View.VISIBLE);
+ mContentAreaAutomotive.setBackground(
+ mContext.getDrawable(R.drawable.car_ui_ime_wide_screen_background));
+ }
+
+ String disc = data.getString(ADD_DESC_TO_CONTENT_AREA);
+ if (!TextUtils.isEmpty(disc)) {
+ mWideScreenDescription.setText(disc);
+ mWideScreenDescription.setVisibility(View.VISIBLE);
+ mContentAreaAutomotive.setBackground(
+ mContext.getDrawable(R.drawable.car_ui_ime_wide_screen_background));
+ }
+
+ String errorMessage = data.getString(ADD_ERROR_DESC_TO_INPUT_AREA);
+ if (!TextUtils.isEmpty(errorMessage)) {
+ mWideScreenErrorMessage.setVisibility(View.VISIBLE);
+ mWideScreenClearData.setVisibility(View.GONE);
+ mWideScreenErrorImage.setVisibility(View.VISIBLE);
+ setExtractedEditTextBackground(
+ R.drawable.car_ui_ime_wide_screen_input_area_tint_error_color);
+ mWideScreenErrorMessage.setText(errorMessage);
+ mContentAreaAutomotive.setBackground(
+ mContext.getDrawable(R.drawable.car_ui_ime_wide_screen_background));
+ }
+
+ if (TextUtils.isEmpty(errorMessage)) {
+ mWideScreenErrorMessage.setVisibility(View.INVISIBLE);
+ mWideScreenErrorMessage.setText("");
+ mWideScreenClearData.setVisibility(View.VISIBLE);
+ mWideScreenErrorImage.setVisibility(View.GONE);
+ setExtractedEditTextBackground(
+ R.drawable.car_ui_ime_wide_screen_input_area_tint_color);
+ }
+
+ int extractedTextIcon = data.getInt(WIDE_SCREEN_EXTRACTED_TEXT_ICON_RES_ID);
+ if (extractedTextIcon != 0) {
+ setWideScreenExtractedIcon(extractedTextIcon);
+ }
+
+ loadSearchItems(data);
+
+ if (mExtractActionAutomotive != null) {
+ mExtractActionAutomotive.setVisibility(View.VISIBLE);
+ }
+ if (mAutomotiveSearchItems != null) {
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
+ mRecyclerView.setVerticalScrollBarEnabled(true);
+ mRecyclerView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_LEFT);
+ mRecyclerView.setVisibility(View.VISIBLE);
+ mRecyclerView.setAdapter(new CarUiListItemAdapter(mAutomotiveSearchItems));
+ mContentAreaAutomotive.setBackground(
+ mContext.getDrawable(R.drawable.car_ui_ime_wide_screen_background));
+ if (mExtractActionAutomotive != null) {
+ mExtractActionAutomotive.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ private void loadSearchItems(Bundle data) {
+ ArrayList<String> itemIdList = data.getStringArrayList(SEARCH_RESULT_ITEM_ID_LIST);
+ ArrayList<String> titleList = data.getStringArrayList(SEARCH_RESULT_TITLE_LIST);
+ ArrayList<String> subTitleList = data.getStringArrayList(SEARCH_RESULT_SUB_TITLE_LIST);
+ ArrayList<Bitmap> bitmapList = data.getParcelableArrayList(
+ SEARCH_RESULT_ICON_BITMAP_LIST);
+ ArrayList<Integer> primaryImageResIdList =
+ data.getIntegerArrayList(SEARCH_RESULT_ICON_RES_ID_LIST);
+ ArrayList<Bitmap> secondaryBitmapList = data.getParcelableArrayList(
+ SEARCH_RESULT_SUPPLEMENTAL_ICON_BITMAP_LIST);
+ ArrayList<String> secondaryImageIdList =
+ data.getStringArrayList(SEARCH_RESULT_SUPPLEMENTAL_ICON_ID_LIST);
+ ArrayList<Integer> secondaryImageResIdList =
+ data.getIntegerArrayList(SEARCH_RESULT_SUPPLEMENTAL_ICON_RES_ID_LIST);
+ if (itemIdList == null) {
+ return;
+ }
+ mAutomotiveSearchItems = new ArrayList<>();
+ for (int i = 0; i < itemIdList.size(); i++) {
+ int index = i;
+ CarUiContentListItem searchItem;
+ if (secondaryImageIdList == null) {
+ searchItem = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+ } else {
+ searchItem = new CarUiContentListItem(CarUiContentListItem.Action.ICON);
+ }
+
+ searchItem.setOnItemClickedListener(v -> {
+ Bundle bundle = new Bundle();
+ bundle.putString(SEARCH_RESULT_ITEM_ID_LIST, itemIdList.get(index));
+ mInputConnection.performPrivateCommand(WIDE_SCREEN_ACTION, bundle);
+ });
+
+ if (titleList != null) {
+ searchItem.setTitle(titleList.get(i));
+ }
+
+ if (subTitleList != null) {
+ searchItem.setBody(subTitleList.get(i));
+ }
+
+ if (primaryImageResIdList != null) {
+ searchItem.setPrimaryIconType(CarUiContentListItem.IconType.CONTENT);
+ if (bitmapList != null && bitmapList.get(i) != null) {
+ searchItem.setIcon(
+ new BitmapDrawable(mContext.getResources(), bitmapList.get(i)));
+ } else {
+ searchItem.setIcon(loadDrawableFromPackage(primaryImageResIdList.get(i)));
+ }
+ }
+
+ if (secondaryBitmapList != null && secondaryBitmapList.get(i) != null) {
+ searchItem.setSupplementalIcon(
+ new BitmapDrawable(mContext.getResources(), secondaryBitmapList.get(i)));
+ } else if (secondaryImageResIdList != null && secondaryImageIdList != null) {
+ searchItem.setSupplementalIcon(
+ loadDrawableFromPackage(secondaryImageResIdList.get(i)), v -> {
+ Bundle bundle = new Bundle();
+ bundle.putString(SEARCH_RESULT_SUPPLEMENTAL_ICON_ID_LIST,
+ secondaryImageIdList.get(index));
+ mInputConnection.performPrivateCommand(WIDE_SCREEN_ACTION, bundle);
+ });
+ }
+
+ mAutomotiveSearchItems.add(searchItem);
+ }
+ }
+
+ /**
+ * Evaluate if IME should launch in a fullscreen mode. In wide screen mode IME should always
+ * launch in a fullscreen mode so that {@link ExtractEditText} is inflated. Later the controller
+ * will detach the {@link ExtractEditText} from its original parent and inflate into the
+ * appropriate container in wide screen.
+ *
+ * @param isFullScreen value evaluated to be in fullscreen mode or not by the app.
+ */
+ public boolean onEvaluateFullscreenMode(boolean isFullScreen) {
+ return isWideScreenMode() || isFullScreen;
+ }
+
+ /**
+ * Initialize the view in the wide screen template based on the data provided by the app through
+ * {@link #onAppPrivateCommand(String, Bundle)}
+ */
+ public void onStartInputView(@NonNull EditorInfo editorInfo,
+ @Nullable InputConnection inputConnection, @Nullable CharSequence textForImeAction) {
+ if (!isWideScreenMode()) {
+ return;
+ }
+ mInputEditorInfo = editorInfo;
+ mInputConnection = inputConnection;
+ View header = mRootView.requireViewById(R.id.car_ui_imeWideScreenInputArea);
+
+ header.setVisibility(View.VISIBLE);
+ if (mExtractViewHidden) {
+ mFullscreenArea.setVisibility(View.INVISIBLE);
+ } else {
+ mFullscreenArea.setVisibility(View.VISIBLE);
+ }
+ if (!mImeRendersAllContent) {
+ mContentAreaAutomotive.setVisibility(View.GONE);
+ } else {
+ mContentAreaAutomotive.setVisibility(View.VISIBLE);
+ }
+
+ // This view is rendered by the framework when IME is in full screen mode. For more info
+ // see {@link #onEvaluateFullscreenMode}
+ mExtractEditText = mRootView.getRootView().requireViewById(
+ android.R.id.inputExtractEditText);
+
+ mExtractEditText.setPadding(
+ mContext.getResources().getDimensionPixelSize(
+ R.dimen.car_ui_ime_wide_screen_input_edit_text_padding_left),
+ /* top= */0,
+ mContext.getResources().getDimensionPixelSize(
+ R.dimen.car_ui_ime_wide_screen_input_edit_text_padding_right),
+ /* bottom= */0);
+ mExtractEditText.setTextSize(mContext.getResources().getDimensionPixelSize(
+ R.dimen.car_ui_ime_wide_screen_input_edit_text_size));
+ mExtractEditText.setGravity(Gravity.START | Gravity.CENTER);
+
+ ViewGroup parent = (ViewGroup) mExtractEditText.getParent();
+ parent.removeViewInLayout(mExtractEditText);
+
+ FrameLayout.LayoutParams params =
+ new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+
+ mInputExtractEditTextContainer.addView(mExtractEditText, params);
+
+ ImageView close = mRootView.findViewById(R.id.car_ui_closeKeyboard);
+ if (close != null) {
+ close.setOnClickListener(
+ (v) -> {
+ mInputMethodService.requestHideSelf(0);
+ });
+ }
+
+ if (!mIsExtractIconProvidedByApp) {
+ setWideScreenExtractedIcon(/* iconResId= */0);
+ }
+
+ boolean hasAction = (mInputEditorInfo.imeOptions & EditorInfo.IME_MASK_ACTION)
+ != EditorInfo.IME_ACTION_NONE;
+ boolean hasInputType = mInputEditorInfo.inputType != InputType.TYPE_NULL;
+ boolean hasNoAccessoryAction =
+ (mInputEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION) == 0;
+
+ boolean hasLabel =
+ mInputEditorInfo.actionLabel != null || (hasAction && hasNoAccessoryAction
+ && hasInputType);
+
+ if (hasLabel) {
+ intiExtractAction(textForImeAction);
+ }
+
+ sendSurfaceInfo();
+ }
+
+ /**
+ * Sends the information for surface view to the application on which they can draw on. This
+ * information will ONLY be sent if OEM allows an application to hide the content area and let
+ * it draw its own content.
+ */
+ private void sendSurfaceInfo() {
+ if (!mAllowAppToHideContentArea && !(mInputEditorInfo != null
+ && isPackageAuthorized(mInputEditorInfo.packageName))) {
+ return;
+ }
+ int displayId = mContentAreaSurfaceView.getDisplay().getDisplayId();
+ IBinder hostToken = mContentAreaSurfaceView.getHostToken();
+
+ Bundle bundle = new Bundle();
+ bundle.putBinder(CONTENT_AREA_SURFACE_HOST_TOKEN, hostToken);
+ bundle.putInt(CONTENT_AREA_SURFACE_DISPLAY_ID, displayId);
+ bundle.putInt(CONTENT_AREA_SURFACE_HEIGHT,
+ mContentAreaSurfaceView.getHeight() + getNavBarHeight());
+ bundle.putInt(CONTENT_AREA_SURFACE_WIDTH, mContentAreaSurfaceView.getWidth());
+
+ mInputConnection.performPrivateCommand(WIDE_SCREEN_ACTION, bundle);
+ }
+
+ private int getNavBarHeight() {
+ Resources resources = mContext.getResources();
+ int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
+ if (resourceId > 0) {
+ return resources.getDimensionPixelSize(resourceId);
+ }
+ return 0;
+ }
+
+ /**
+ * To support wide screen mode, IME should always call
+ * {@link InputMethodService#setExtractViewShown} with false and pass the flag to this method.
+ *
+ * For example, within the IMS service call
+ * <pre>
+ * @Override
+ * public void setExtractViewShown(boolean shown) {
+ * if (!carUiImeWideScreenController.isWideScreenMode()) {
+ * super.setExtractViewShown(shown);
+ * return;
+ * }
+ * super.setExtractViewShown(false);
+ * mImeWideScreenController.setExtractViewShown(shown);
+ * }
+ * </pre>
+ *
+ * This is required as IMS checks for ExtractViewIsShown and if that is true then set the
+ * touchable insets to the entire screen rather than a region. If an app hides the content area
+ * in that case we want the user to be able to interact with the application.
+ */
+ public void setExtractViewShown(boolean shown) {
+ if (!isWideScreenMode()) {
+ return;
+ }
+ if (mExtractViewHidden == !shown) {
+ return;
+ }
+ mExtractViewHidden = !shown;
+ if (mExtractViewHidden) {
+ mFullscreenArea.setVisibility(View.INVISIBLE);
+ } else {
+ mFullscreenArea.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void intiExtractAction(CharSequence textForImeAction) {
+ if (mExtractActionAutomotive == null) {
+ return;
+ }
+ if (mInputEditorInfo.actionLabel != null) {
+ ((TextView) mExtractActionAutomotive).setText(mInputEditorInfo.actionLabel);
+ } else {
+ ((TextView) mExtractActionAutomotive).setText(textForImeAction);
+ }
+
+ // click listener for the action button shown in the content area.
+ mExtractActionAutomotive.setOnClickListener(v -> {
+ final EditorInfo editorInfo = mInputEditorInfo;
+ final InputConnection inputConnection = mInputConnection;
+ if (editorInfo == null || inputConnection == null) {
+ return;
+ }
+ if (editorInfo.actionId != 0) {
+ inputConnection.performEditorAction(editorInfo.actionId);
+ } else if ((editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION)
+ != EditorInfo.IME_ACTION_NONE) {
+ inputConnection.performEditorAction(
+ editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION);
+ }
+ });
+ }
+
+ private boolean isPackageAuthorized(String packageName) {
+ String[] packages = mContext.getResources()
+ .getStringArray(R.array.car_ui_ime_wide_screen_allowed_package_list);
+
+ PackageInfo packageInfo = getPackageInfo(mContext, packageName);
+ // Checks if the application of the given context is installed in the system image. I.e.
+ // if it's a bundled app.
+ if (packageInfo != null && (packageInfo.applicationInfo.flags & (ApplicationInfo.FLAG_SYSTEM
+ | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0) {
+ return true;
+ }
+
+ for (String pattern : packages) {
+ String regex = createRegexFromGlob(pattern);
+ if (packageName.matches(regex)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the package info for a particular package.
+ */
+ @Nullable
+ private static PackageInfo getPackageInfo(Context context,
+ String packageName) {
+ PackageManager packageManager = context.getPackageManager();
+ PackageInfo packageInfo = null;
+ try {
+ packageInfo = packageManager.getPackageInfo(
+ packageName, /* flags= */ 0);
+ } catch (PackageManager.NameNotFoundException ex) {
+ Log.e(TAG, "package not found: " + packageName);
+ }
+ return packageInfo;
+ }
+
+ private static String createRegexFromGlob(String glob) {
+ Pattern reg = Pattern.compile(NOT_ASTERISK_OR_CAPTURED_ASTERISK);
+ Matcher m = reg.matcher(glob);
+ StringBuffer b = new StringBuffer();
+ while (m.find()) {
+ if (m.group(1) != null) {
+ m.appendReplacement(b, ".*");
+ } else {
+ m.appendReplacement(b, Matcher.quoteReplacement(m.group(0)));
+ }
+ }
+ m.appendTail(b);
+ return b.toString();
+ }
+
+ private void setExtractedEditTextBackground(int drawableResId) {
+ mExtractEditText.setBackgroundTintList(mContext.getColorStateList(drawableResId));
+ }
+
+ @VisibleForTesting
+ void setExtractEditText(ExtractEditText editText) {
+ mExtractEditText = editText;
+ }
+
+ /**
+ * Sets the icon in the input area. If the icon resource Id is not provided by the application
+ * then application icon will be used instead.
+ *
+ * @param iconResId icon resource id for the image drawable to load.
+ */
+ private void setWideScreenExtractedIcon(@DrawableRes int iconResId) {
+ if (mInputEditorInfo == null || mWideScreenExtractedTextIcon == null) {
+ return;
+ }
+ try {
+ if (iconResId == 0) {
+ mWideScreenExtractedTextIcon.setImageDrawable(
+ mContext.getPackageManager().getApplicationIcon(
+ mInputEditorInfo.packageName));
+ } else {
+ mIsExtractIconProvidedByApp = true;
+ mWideScreenExtractedTextIcon.setImageDrawable(
+ mContext.createPackageContext(mInputEditorInfo.packageName, 0).getDrawable(
+ iconResId));
+ }
+ mWideScreenExtractedTextIcon.setVisibility(View.VISIBLE);
+ } catch (PackageManager.NameNotFoundException ex) {
+ Log.w(TAG, "setWideScreenExtractedIcon: package name not found ", ex);
+ mWideScreenExtractedTextIcon.setVisibility(View.GONE);
+ } catch (Resources.NotFoundException ex) {
+ Log.w(TAG, "setWideScreenExtractedIcon: resource not found with id " + iconResId, ex);
+ mWideScreenExtractedTextIcon.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Called when IME window closes. Reset all the views once that happens.
+ */
+ public void onFinishInputView() {
+ if (!isWideScreenMode()) {
+ return;
+ }
+ resetAutomotiveWideScreenViews();
+ }
+
+ private void resetAutomotiveWideScreenViews() {
+ mWideScreenDescriptionTitle.setVisibility(View.GONE);
+ mContentAreaSurfaceView.setVisibility(View.GONE);
+ mWideScreenErrorMessage.setVisibility(View.GONE);
+ mRecyclerView.setVisibility(View.GONE);
+ mWideScreenDescription.setVisibility(View.GONE);
+ mFullscreenArea.setVisibility(View.VISIBLE);
+ if (mWideScreenExtractedTextIcon != null) {
+ mWideScreenExtractedTextIcon.setVisibility(View.VISIBLE);
+ }
+ mWideScreenClearData.setVisibility(View.VISIBLE);
+ mWideScreenErrorImage.setVisibility(View.GONE);
+ if (mExtractActionAutomotive != null) {
+ mExtractActionAutomotive.setVisibility(View.GONE);
+ }
+ mContentAreaAutomotive.setVisibility(View.VISIBLE);
+ mContentAreaAutomotive.setBackground(
+ mContext.getDrawable(R.drawable.car_ui_ime_wide_screen_no_content_background));
+ setExtractedEditTextBackground(R.drawable.car_ui_ime_wide_screen_input_area_tint_color);
+ mImeRendersAllContent = true;
+ mIsExtractIconProvidedByApp = false;
+ mExtractViewHidden = false;
+ mAutomotiveSearchItems = new ArrayList<>();
+ }
+
+ /**
+ * Returns whether or not system is running in a wide screen mode.
+ */
+ public boolean isWideScreenMode() {
+ return CarUiUtils.getBooleanSystemProperty(mContext.getResources(),
+ R.string.car_ui_ime_wide_screen_system_property_name, false);
+ }
+
+ private Drawable loadDrawableFromPackage(int resId) {
+ try {
+ if (mInputEditorInfo != null) {
+ return mContext.createPackageContext(mInputEditorInfo.packageName, 0)
+ .getDrawable(resId);
+ }
+ } catch (PackageManager.NameNotFoundException ex) {
+ Log.e(TAG, "loadDrawableFromPackage: package name not found: ", ex);
+ } catch (Resources.NotFoundException ex) {
+ Log.w(TAG, "loadDrawableFromPackage: resource not found with id " + resId, ex);
+ }
+ return null;
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ListPreferenceFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ListPreferenceFragment.java
index 5a34dcd..b9ad789 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ListPreferenceFragment.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ListPreferenceFragment.java
@@ -30,6 +30,7 @@
import androidx.preference.ListPreference;
import androidx.preference.Preference;
+import com.android.car.ui.FocusArea;
import com.android.car.ui.R;
import com.android.car.ui.baselayout.Insets;
import com.android.car.ui.baselayout.InsetsChangedListener;
@@ -51,20 +52,23 @@
*/
public class ListPreferenceFragment extends Fragment implements InsetsChangedListener {
- private ToolbarController mToolbar;
+ private static final String ARG_FULLSCREEN = "fullscreen";
+
private ListPreference mPreference;
private CarUiContentListItem mSelectedItem;
private int mSelectedIndex = -1;
+ private boolean mFullScreen;
/**
* Returns a new instance of {@link ListPreferenceFragment} for the {@link ListPreference} with
* the given {@code key}.
*/
@NonNull
- static ListPreferenceFragment newInstance(String key) {
+ static ListPreferenceFragment newInstance(String key, boolean fullScreen) {
ListPreferenceFragment fragment = new ListPreferenceFragment();
Bundle b = new Bundle(/* capacity= */ 1);
b.putString(ARG_KEY, key);
+ b.putBoolean(ARG_FULLSCREEN, fullScreen);
fragment.setArguments(b);
return fragment;
}
@@ -85,29 +89,40 @@
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final CarUiRecyclerView carUiRecyclerView = CarUiUtils.requireViewByRefId(view, R.id.list);
- mToolbar = CarUi.getToolbar(getActivity());
+ mFullScreen = requireArguments().getBoolean(ARG_FULLSCREEN, true);
+ ToolbarController toolbar = mFullScreen ? CarUi.getToolbar(getActivity()) : null;
// TODO(b/150230923) remove the code for the old toolbar height change when apps are ready
- if (mToolbar == null) {
- Toolbar toolbarView = CarUiUtils.requireViewByRefId(view, R.id.toolbar);
- mToolbar = toolbarView;
+ if (toolbar == null) {
+ Toolbar toolbarView = CarUiUtils.findViewByRefId(view, R.id.toolbar);
+ toolbar = toolbarView;
- carUiRecyclerView.setPadding(0, toolbarView.getHeight(), 0, 0);
- toolbarView.registerToolbarHeightChangeListener(newHeight -> {
- if (carUiRecyclerView.getPaddingTop() == newHeight) {
- return;
- }
+ if (toolbarView != null) {
+ carUiRecyclerView.setPadding(0, toolbarView.getHeight(), 0, 0);
+ toolbarView.registerToolbarHeightChangeListener(newHeight -> {
+ if (carUiRecyclerView.getPaddingTop() == newHeight) {
+ return;
+ }
- int oldHeight = carUiRecyclerView.getPaddingTop();
- carUiRecyclerView.setPadding(0, newHeight, 0, 0);
- carUiRecyclerView.scrollBy(0, oldHeight - newHeight);
- });
+ int oldHeight = carUiRecyclerView.getPaddingTop();
+ carUiRecyclerView.setPadding(0, newHeight, 0, 0);
+ carUiRecyclerView.scrollBy(0, oldHeight - newHeight);
+
+ FocusArea focusArea = view.findViewById(R.id.car_ui_focus_area);
+ if (focusArea != null) {
+ focusArea.setHighlightPadding(0, newHeight, 0, 0);
+ focusArea.setBoundsOffset(0, newHeight, 0, 0);
+ }
+ });
+ }
}
carUiRecyclerView.setClipToPadding(false);
mPreference = getListPreference();
- mToolbar.setTitle(mPreference.getTitle());
- mToolbar.setState(Toolbar.State.SUBPAGE);
+ if (toolbar != null) {
+ toolbar.setTitle(mPreference.getTitle());
+ toolbar.setState(Toolbar.State.SUBPAGE);
+ }
CharSequence[] entries = mPreference.getEntries();
CharSequence[] entryValues = mPreference.getEntryValues();
@@ -178,11 +193,7 @@
}
private ListPreference getListPreference() {
- if (getArguments() == null) {
- throw new IllegalStateException("Preference arguments cannot be null");
- }
-
- String key = getArguments().getString(ARG_KEY);
+ String key = requireArguments().getString(ARG_KEY);
DialogPreference.TargetFragment fragment =
(DialogPreference.TargetFragment) getTargetFragment();
@@ -210,9 +221,17 @@
@Override
public void onCarUiInsetsChanged(@NonNull Insets insets) {
+ if (!mFullScreen) {
+ return;
+ }
View view = requireView();
CarUiUtils.requireViewByRefId(view, R.id.list)
.setPadding(0, insets.getTop(), 0, insets.getBottom());
view.setPadding(insets.getLeft(), 0, insets.getRight(), 0);
+ FocusArea focusArea = view.findViewById(R.id.car_ui_focus_area);
+ if (focusArea != null) {
+ focusArea.setHighlightPadding(0, insets.getTop(), 0, insets.getBottom());
+ focusArea.setBoundsOffset(0, insets.getTop(), 0, insets.getBottom());
+ }
}
}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
index 44c5f43..f9cf8a2 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
@@ -29,6 +29,7 @@
import androidx.preference.DialogPreference;
import androidx.preference.Preference;
+import com.android.car.ui.FocusArea;
import com.android.car.ui.R;
import com.android.car.ui.baselayout.Insets;
import com.android.car.ui.baselayout.InsetsChangedListener;
@@ -52,19 +53,23 @@
*/
public class MultiSelectListPreferenceFragment extends Fragment implements InsetsChangedListener {
+ private static final String ARG_FULLSCREEN = "fullscreen";
+
private CarUiMultiSelectListPreference mPreference;
private Set<String> mNewValues;
private ToolbarController mToolbar;
+ private boolean mFullScreen;
/**
* Returns a new instance of {@link MultiSelectListPreferenceFragment} for the {@link
* CarUiMultiSelectListPreference} with the given {@code key}.
*/
@NonNull
- static MultiSelectListPreferenceFragment newInstance(String key) {
+ static MultiSelectListPreferenceFragment newInstance(String key, boolean fullScreen) {
MultiSelectListPreferenceFragment fragment = new MultiSelectListPreferenceFragment();
Bundle b = new Bundle(/* capacity= */ 1);
b.putString(ARG_KEY, key);
+ b.putBoolean(ARG_FULLSCREEN, fullScreen);
fragment.setArguments(b);
return fragment;
}
@@ -85,30 +90,41 @@
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final CarUiRecyclerView recyclerView = CarUiUtils.requireViewByRefId(view, R.id.list);
- mToolbar = CarUi.getToolbar(requireActivity());
+ mFullScreen = requireArguments().getBoolean(ARG_FULLSCREEN, true);
+ mToolbar = mFullScreen ? CarUi.getToolbar(requireActivity()) : null;
// TODO(b/150230923) remove the code for the old toolbar height change when apps are ready
if (mToolbar == null) {
- Toolbar toolbarView = CarUiUtils.requireViewByRefId(view, R.id.toolbar);
+ Toolbar toolbarView = CarUiUtils.findViewByRefId(view, R.id.toolbar);
mToolbar = toolbarView;
- recyclerView.setPadding(0, toolbarView.getHeight(), 0, 0);
- toolbarView.registerToolbarHeightChangeListener(newHeight -> {
- if (recyclerView.getPaddingTop() == newHeight) {
- return;
- }
+ if (toolbarView != null) {
+ recyclerView.setPadding(0, toolbarView.getHeight(), 0, 0);
+ toolbarView.registerToolbarHeightChangeListener(newHeight -> {
+ if (recyclerView.getPaddingTop() == newHeight) {
+ return;
+ }
- int oldHeight = recyclerView.getPaddingTop();
- recyclerView.setPadding(0, newHeight, 0, 0);
- recyclerView.scrollBy(0, oldHeight - newHeight);
- });
+ int oldHeight = recyclerView.getPaddingTop();
+ recyclerView.setPadding(0, newHeight, 0, 0);
+ recyclerView.scrollBy(0, oldHeight - newHeight);
+
+ FocusArea focusArea = view.findViewById(R.id.car_ui_focus_area);
+ if (focusArea != null) {
+ focusArea.setHighlightPadding(0, newHeight, 0, 0);
+ focusArea.setBoundsOffset(0, newHeight, 0, 0);
+ }
+ });
+ }
}
mPreference = getPreference();
recyclerView.setClipToPadding(false);
- mToolbar.setTitle(mPreference.getTitle());
- mToolbar.setState(Toolbar.State.SUBPAGE);
+ if (mToolbar != null) {
+ mToolbar.setTitle(mPreference.getTitle());
+ mToolbar.setState(Toolbar.State.SUBPAGE);
+ }
mNewValues = new HashSet<>(mPreference.getValues());
CharSequence[] entries = mPreference.getEntries();
@@ -205,9 +221,17 @@
@Override
public void onCarUiInsetsChanged(@NonNull Insets insets) {
+ if (!mFullScreen) {
+ return;
+ }
View view = requireView();
CarUiUtils.requireViewByRefId(view, R.id.list)
.setPadding(0, insets.getTop(), 0, insets.getBottom());
view.setPadding(insets.getLeft(), 0, insets.getRight(), 0);
+ FocusArea focusArea = view.findViewById(R.id.car_ui_focus_area);
+ if (focusArea != null) {
+ focusArea.setHighlightPadding(0, insets.getTop(), 0, insets.getBottom());
+ focusArea.setBoundsOffset(0, insets.getTop(), 0, insets.getBottom());
+ }
}
}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/PreferenceFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/PreferenceFragment.java
index fbd6856..3f5b5cb 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/PreferenceFragment.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/PreferenceFragment.java
@@ -72,15 +72,29 @@
private static final String DIALOG_FRAGMENT_TAG =
"com.android.car.ui.PreferenceFragment.DIALOG";
+ /**
+ * This method can be overridden to indicate whether or not this fragment covers the
+ * whole screen. When it returns false, the preference fragment will not attempt to change
+ * the CarUi base layout toolbar (but will still have its own toolbar and change it when using
+ * non-baselayout toolbars), and will also not take into account CarUi insets.
+ *
+ * @return Whether to PreferenceFragment takes up the whole app's space. Defaults to true.
+ */
+ protected boolean isFullScreenFragment() {
+ return true;
+ }
+
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- ToolbarController baseLayoutToolbar = CarUi.getToolbar(getActivity());
- if (baseLayoutToolbar != null) {
- baseLayoutToolbar.setState(Toolbar.State.SUBPAGE);
- if (getPreferenceScreen() != null) {
- baseLayoutToolbar.setTitle(getPreferenceScreen().getTitle());
+ if (isFullScreenFragment()) {
+ ToolbarController baseLayoutToolbar = CarUi.getToolbar(getActivity());
+ if (baseLayoutToolbar != null) {
+ baseLayoutToolbar.setState(Toolbar.State.SUBPAGE);
+ if (getPreferenceScreen() != null) {
+ baseLayoutToolbar.setTitle(getPreferenceScreen().getTitle());
+ }
}
}
@@ -103,6 +117,7 @@
FocusArea focusArea = CarUiUtils.requireViewByRefId(view, R.id.car_ui_focus_area);
focusArea.setHighlightPadding(0, newHeight, 0, 0);
+ focusArea.setBoundsOffset(0, newHeight, 0, 0);
});
recyclerView.setClipToPadding(false);
@@ -122,9 +137,14 @@
@Override
public void onCarUiInsetsChanged(@NonNull Insets insets) {
+ if (!isFullScreenFragment()) {
+ return;
+ }
+
View view = requireView();
FocusArea focusArea = CarUiUtils.requireViewByRefId(view, R.id.car_ui_focus_area);
focusArea.setHighlightPadding(0, insets.getTop(), 0, insets.getBottom());
+ focusArea.setBoundsOffset(0, insets.getTop(), 0, insets.getBottom());
CarUiUtils.requireViewByRefId(view, R.id.recycler_view)
.setPadding(0, insets.getTop(), 0, insets.getBottom());
view.setPadding(insets.getLeft(), 0, insets.getRight(), 0);
@@ -158,9 +178,10 @@
if (preference instanceof EditTextPreference) {
f = EditTextPreferenceDialogFragment.newInstance(preference.getKey());
} else if (preference instanceof ListPreference) {
- f = ListPreferenceFragment.newInstance(preference.getKey());
+ f = ListPreferenceFragment.newInstance(preference.getKey(), isFullScreenFragment());
} else if (preference instanceof MultiSelectListPreference) {
- f = MultiSelectListPreferenceFragment.newInstance(preference.getKey());
+ f = MultiSelectListPreferenceFragment
+ .newInstance(preference.getKey(), isFullScreenFragment());
} else if (preference instanceof CarUiSeekBarDialogPreference) {
f = SeekbarPreferenceDialogFragment.newInstance(preference.getKey());
} else {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiContentListItem.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiContentListItem.java
index 71f5d1a..e6a14d7 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiContentListItem.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiContentListItem.java
@@ -17,7 +17,6 @@
package com.android.car.ui.recyclerview;
import android.graphics.drawable.Drawable;
-import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -114,7 +113,7 @@
private boolean mIsActivated;
private OnClickListener mOnClickListener;
private OnCheckedChangeListener mOnCheckedChangeListener;
- private View.OnClickListener mSupplementalIconOnClickListener;
+ private OnClickListener mSupplementalIconOnClickListener;
public CarUiContentListItem(Action action) {
@@ -301,7 +300,7 @@
* @param listener the callback that is invoked when the icon is clicked.
*/
public void setSupplementalIcon(@Nullable Drawable icon,
- @Nullable View.OnClickListener listener) {
+ @Nullable OnClickListener listener) {
if (mAction != Action.ICON) {
throw new IllegalStateException(
"Cannot set supplemental icon on list item that does not have an action of "
@@ -313,7 +312,7 @@
}
@Nullable
- public View.OnClickListener getSupplementalIconOnClickListener() {
+ public OnClickListener getSupplementalIconOnClickListener() {
return mSupplementalIconOnClickListener;
}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
index 8bc39eb..a45b1e8 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
@@ -283,6 +283,7 @@
case ICON:
mSupplementalIcon.setVisibility(View.VISIBLE);
mSupplementalIcon.setImageDrawable(item.getSupplementalIcon());
+
mActionContainer.setVisibility(View.VISIBLE);
// If the icon has a click listener, use a reduced touch interceptor to create
@@ -309,8 +310,7 @@
mActionContainerTouchInterceptor.setOnClickListener(
(container) -> {
if (item.getSupplementalIconOnClickListener() != null) {
- item.getSupplementalIconOnClickListener().onClick(
- mSupplementalIcon);
+ item.getSupplementalIconOnClickListener().onClick(item);
}
});
mTouchInterceptor.setVisibility(View.GONE);
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
index 627ab95..93b7736 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
@@ -16,6 +16,7 @@
package com.android.car.ui.recyclerview;
import static com.android.car.ui.utils.CarUiUtils.findViewByRefId;
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_CONTAINER;
import static com.android.car.ui.utils.RotaryConstants.ROTARY_HORIZONTALLY_SCROLLABLE;
import static com.android.car.ui.utils.RotaryConstants.ROTARY_VERTICALLY_SCROLLABLE;
@@ -25,8 +26,6 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -36,7 +35,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
+import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
@@ -53,15 +52,16 @@
import com.android.car.ui.recyclerview.decorations.linear.LinearDividerItemDecoration;
import com.android.car.ui.recyclerview.decorations.linear.LinearOffsetItemDecoration;
import com.android.car.ui.recyclerview.decorations.linear.LinearOffsetItemDecoration.OffsetPosition;
+import com.android.car.ui.utils.CarUiUtils;
import com.android.car.ui.utils.CarUxRestrictionsUtil;
import java.lang.annotation.Retention;
import java.util.Objects;
/**
- * View that extends a {@link RecyclerView} and wraps itself into a {@link LinearLayout} which
- * could potentially include a scrollbar that has page up and down arrows. Interaction with this
- * view is similar to a {@code RecyclerView} as it takes the same adapter and the layout manager.
+ * View that extends a {@link RecyclerView} and wraps itself into a {@link LinearLayout} which could
+ * potentially include a scrollbar that has page up and down arrows. Interaction with this view is
+ * similar to a {@code RecyclerView} as it takes the same adapter and the layout manager.
*/
public final class CarUiRecyclerView extends RecyclerView {
@@ -77,22 +77,21 @@
private String mScrollBarClass;
private int mScrollBarPaddingTop;
private int mScrollBarPaddingBottom;
- private boolean mHasScrolledToTop = false;
@Nullable
private ScrollBar mScrollBar;
- @NonNull
+ @Nullable
private GridOffsetItemDecoration mTopOffsetItemDecorationGrid;
- @NonNull
+ @Nullable
private GridOffsetItemDecoration mBottomOffsetItemDecorationGrid;
- @NonNull
+ @Nullable
private RecyclerView.ItemDecoration mTopOffsetItemDecorationLinear;
- @NonNull
+ @Nullable
private RecyclerView.ItemDecoration mBottomOffsetItemDecorationLinear;
- @NonNull
+ @Nullable
private GridDividerItemDecoration mDividerItemDecorationGrid;
- @NonNull
+ @Nullable
private RecyclerView.ItemDecoration mDividerItemDecorationLinear;
private int mNumOfColumns;
private boolean mInstallingExtScrollBar = false;
@@ -104,22 +103,24 @@
@Nullable
private LinearLayout mContainer;
+ // Set to true when when styled attributes are read and initialized.
+ private boolean mIsInitialized;
private boolean mEnableDividers;
private int mTopOffset;
private int mBottomOffset;
- private ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener = () -> {
- if (!mHasScrolledToTop && getLayoutManager() != null) {
- // Scroll to the top after the first global layout, so that
- // we can set padding for the insets and still have the
- // recyclerview start at the top.
- new Handler(Objects.requireNonNull(Looper.myLooper())).post(() ->
- getLayoutManager().scrollToPosition(0));
- mHasScrolledToTop = true;
+ private boolean mHasScrolled = false;
+
+ private OnScrollListener mOnScrollListener = new OnScrollListener() {
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ if (dx > 0 || dy > 0) {
+ mHasScrolled = true;
+ removeOnScrollListener(this);
+ }
}
};
-
/**
* The possible values for setScrollBarPosition. The default value is actually {@link
* CarUiRecyclerViewLayout#LINEAR}.
@@ -131,13 +132,15 @@
@Retention(SOURCE)
public @interface CarUiRecyclerViewLayout {
/**
- * Arranges items either horizontally in a single row or vertically in a single column.
- * This is default.
+ * Arranges items either horizontally in a single row or vertically in a single column. This
+ * is default.
*/
int LINEAR = 0;
- /** Arranges items in a Grid. */
- int GRID = 2;
+ /**
+ * Arranges items in a Grid.
+ */
+ int GRID = 1;
}
/**
@@ -164,8 +167,7 @@
/**
* Sets the maximum number of items available in the adapter. A value less than '0' means
- * the
- * list should not be capped.
+ * the list should not be capped.
*/
void setMaxItems(int maxItems);
}
@@ -186,13 +188,13 @@
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
- initRotaryScroll(context, attrs, defStyleAttr);
setClipToPadding(false);
TypedArray a = context.obtainStyledAttributes(
attrs,
R.styleable.CarUiRecyclerView,
defStyleAttr,
R.style.Widget_CarUi_CarUiRecyclerView);
+ initRotaryScroll(a);
mScrollBarEnabled = context.getResources().getBoolean(R.bool.car_ui_scrollbar_enable);
@@ -229,19 +231,26 @@
mBottomOffsetItemDecorationGrid =
new GridOffsetItemDecoration(mBottomOffset, mNumOfColumns,
OffsetPosition.END);
- if (carUiRecyclerViewLayout == CarUiRecyclerViewLayout.LINEAR) {
+
+ mIsInitialized = true;
+
+ // Check if a layout manager has already been set via XML
+ boolean isLayoutMangerSet = getLayoutManager() != null;
+ if (!isLayoutMangerSet && carUiRecyclerViewLayout == CarUiRecyclerViewLayout.LINEAR) {
setLayoutManager(new LinearLayoutManager(getContext()));
- } else {
+ } else if (!isLayoutMangerSet && carUiRecyclerViewLayout == CarUiRecyclerViewLayout.GRID) {
setLayoutManager(new GridLayoutManager(getContext(), mNumOfColumns));
}
+ addOnScrollListener(mOnScrollListener);
a.recycle();
-
if (!mScrollBarEnabled) {
return;
}
+ mContainer = new LinearLayout(getContext());
+
setVerticalScrollBarEnabled(false);
setHorizontalScrollBarEnabled(false);
@@ -249,59 +258,65 @@
}
@Override
- public void setLayoutManager(@Nullable LayoutManager layout) {
- addItemDecorations(layout);
- super.setLayoutManager(layout);
+ public void setLayoutManager(@Nullable LayoutManager layoutManager) {
+ // Cannot setup item decorations before stylized attributes have been read.
+ if (mIsInitialized) {
+ addItemDecorations(layoutManager);
+ }
+ super.setLayoutManager(layoutManager);
}
- private void addItemDecorations(LayoutManager layout) {
- // remove existing Item decorations
- removeItemDecoration(mDividerItemDecorationGrid);
- removeItemDecoration(mTopOffsetItemDecorationGrid);
- removeItemDecoration(mBottomOffsetItemDecorationGrid);
- removeItemDecoration(mDividerItemDecorationLinear);
- removeItemDecoration(mTopOffsetItemDecorationLinear);
- removeItemDecoration(mBottomOffsetItemDecorationLinear);
+ // This method should not be invoked before item decorations are initialized by the #init()
+ // method.
+ private void addItemDecorations(LayoutManager layoutManager) {
+ // remove existing Item decorations.
+ removeItemDecoration(Objects.requireNonNull(mDividerItemDecorationGrid));
+ removeItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationGrid));
+ removeItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationGrid));
+ removeItemDecoration(Objects.requireNonNull(mDividerItemDecorationLinear));
+ removeItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationLinear));
+ removeItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationLinear));
- if (layout instanceof GridLayoutManager) {
+ if (layoutManager instanceof GridLayoutManager) {
if (mEnableDividers) {
- addItemDecoration(mDividerItemDecorationGrid);
+ addItemDecoration(Objects.requireNonNull(mDividerItemDecorationGrid));
}
- addItemDecoration(mTopOffsetItemDecorationGrid);
- addItemDecoration(mBottomOffsetItemDecorationGrid);
- setNumOfColumns(((GridLayoutManager) layout).getSpanCount());
+ addItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationGrid));
+ addItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationGrid));
+ setNumOfColumns(((GridLayoutManager) layoutManager).getSpanCount());
} else {
if (mEnableDividers) {
- addItemDecoration(mDividerItemDecorationLinear);
+ addItemDecoration(Objects.requireNonNull(mDividerItemDecorationLinear));
}
- addItemDecoration(mTopOffsetItemDecorationLinear);
- addItemDecoration(mBottomOffsetItemDecorationLinear);
+ addItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationLinear));
+ addItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationLinear));
}
}
/**
- * If this view's content description isn't set to opt out of scrolling via the rotary
- * controller, initialize it accordingly.
+ * If this view's {@code rotaryScrollEnabled} attribute is set to true, sets the content
+ * description so that the {@code RotaryService} will treat it as a scrollable container and
+ * initializes this view accordingly.
*/
- private void initRotaryScroll(Context context, AttributeSet attrs, int defStyleAttr) {
- CharSequence contentDescription = getContentDescription();
- if (contentDescription == null) {
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
- defStyleAttr, /* defStyleRes= */ 0);
- int orientation = a.getInt(R.styleable.RecyclerView_android_orientation,
+ private void initRotaryScroll(@Nullable TypedArray styledAttributes) {
+ boolean rotaryScrollEnabled = styledAttributes != null && styledAttributes.getBoolean(
+ R.styleable.CarUiRecyclerView_rotaryScrollEnabled, /* defValue=*/ false);
+ if (rotaryScrollEnabled) {
+ int orientation = styledAttributes.getInt(R.styleable.RecyclerView_android_orientation,
LinearLayout.VERTICAL);
- setContentDescription(
- orientation == LinearLayout.HORIZONTAL
- ? ROTARY_HORIZONTALLY_SCROLLABLE
- : ROTARY_VERTICALLY_SCROLLABLE);
- } else if (!ROTARY_HORIZONTALLY_SCROLLABLE.contentEquals(contentDescription)
- && !ROTARY_VERTICALLY_SCROLLABLE.contentEquals(contentDescription)) {
- return;
+ CarUiUtils.setRotaryScrollEnabled(
+ this, /* isVertical= */ orientation == LinearLayout.VERTICAL);
+ } else {
+ CharSequence contentDescription = getContentDescription();
+ rotaryScrollEnabled = contentDescription != null
+ && (ROTARY_HORIZONTALLY_SCROLLABLE.contentEquals(contentDescription)
+ || ROTARY_VERTICALLY_SCROLLABLE.contentEquals(contentDescription));
}
- // Convert SOURCE_ROTARY_ENCODER scroll events into SOURCE_MOUSE scroll events that
- // RecyclerView knows how to handle.
- setOnGenericMotionListener((v, event) -> {
+ // If rotary scrolling is enabled, set a generic motion event listener to convert
+ // SOURCE_ROTARY_ENCODER scroll events into SOURCE_MOUSE scroll events that RecyclerView
+ // knows how to handle.
+ setOnGenericMotionListener(rotaryScrollEnabled ? (v, event) -> {
if (event.getAction() == MotionEvent.ACTION_SCROLL) {
if (event.getSource() == InputDevice.SOURCE_ROTARY_ENCODER) {
MotionEvent mouseEvent = MotionEvent.obtain(event);
@@ -311,11 +326,11 @@
}
}
return false;
- });
+ } : null);
- // Mark this view as focusable. This view will be focused when no focusable elements are
- // visible.
- setFocusable(true);
+ // If rotary scrolling is enabled, mark this view as focusable. This view will be focused
+ // when no focusable elements are visible.
+ setFocusable(rotaryScrollEnabled);
// Focus this view before descendants so that the RotaryService can focus this view when it
// wants to.
@@ -324,15 +339,20 @@
// Disable the default focus highlight. No highlight should appear when this view is
// focused.
setDefaultFocusHighlightEnabled(false);
+
+ // This view is a rotary container if it's not a scrollable container.
+ if (!rotaryScrollEnabled) {
+ super.setContentDescription(ROTARY_CONTAINER);
+ }
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
- // If we're restoring an existing RecyclerView, we don't want
- // to do the initial scroll to top
- mHasScrolledToTop = true;
+ // If we're restoring an existing RecyclerView, consider
+ // it as having already scrolled some.
+ mHasScrolled = true;
}
@Override
@@ -369,7 +389,6 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mCarUxRestrictionsUtil.register(mListener);
- this.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
if (mInstallingExtScrollBar || !mScrollBarEnabled) {
return;
}
@@ -385,11 +404,10 @@
/**
* This method will detach the current recycler view from its parent and attach it to the
- * container which is a LinearLayout. Later the entire container is attached to the
- * parent where the recycler view was set with the same layout params.
+ * container which is a LinearLayout. Later the entire container is attached to the parent where
+ * the recycler view was set with the same layout params.
*/
private void installExternalScrollBar() {
- mContainer = new LinearLayout(getContext());
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.car_ui_recycler_view, mContainer, true);
mContainer.setVisibility(mContainerVisibility);
@@ -444,10 +462,23 @@
}
@Override
+ public void setAlpha(float value) {
+ if (mScrollBarEnabled) {
+ mContainer.setAlpha(value);
+ } else {
+ super.setAlpha(value);
+ }
+ }
+
+ @Override
+ public ViewPropertyAnimator animate() {
+ return mScrollBarEnabled ? mContainer.animate() : super.animate();
+ }
+
+ @Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mCarUxRestrictionsUtil.unregister(mListener);
- this.getViewTreeObserver().removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
}
@Override
@@ -455,6 +486,13 @@
mContainerPaddingRelative = null;
if (mScrollBarEnabled) {
super.setPadding(0, top, 0, bottom);
+ if (!mHasScrolled) {
+ // If we haven't scrolled, and thus are still at the top of the screen,
+ // we should stay scrolled to the top after applying padding. Without this
+ // scroll, the padding will start scrolled offscreen. We need the padding
+ // to be onscreen to shift the content into a good visible range.
+ scrollToPosition(0);
+ }
mContainerPadding = new Rect(left, 0, right, 0);
if (mContainer != null) {
mContainer.setPadding(left, 0, right, 0);
@@ -470,6 +508,13 @@
mContainerPadding = null;
if (mScrollBarEnabled) {
super.setPaddingRelative(0, top, 0, bottom);
+ if (!mHasScrolled) {
+ // If we haven't scrolled, and thus are still at the top of the screen,
+ // we should stay scrolled to the top after applying padding. Without this
+ // scroll, the padding will start scrolled offscreen. We need the padding
+ // to be onscreen to shift the content into a good visible range.
+ scrollToPosition(0);
+ }
mContainerPaddingRelative = new Rect(start, 0, end, 0);
if (mContainer != null) {
mContainer.setPaddingRelative(start, 0, end, 0);
@@ -481,8 +526,8 @@
}
/**
- * Sets the scrollbar's padding top and bottom.
- * This padding is applied in addition to the padding of the RecyclerView.
+ * Sets the scrollbar's padding top and bottom. This padding is applied in addition to the
+ * padding of the RecyclerView.
*/
public void setScrollBarPadding(int paddingTop, int paddingBottom) {
if (mScrollBarEnabled) {
@@ -518,6 +563,12 @@
removeItemDecoration(mDividerItemDecorationGrid);
}
+ @Override
+ public void setContentDescription(CharSequence contentDescription) {
+ super.setContentDescription(contentDescription);
+ initRotaryScroll(/* styledAttributes= */ null);
+ }
+
private static RuntimeException andLog(String msg, Throwable t) {
Log.e(TAG, msg, t);
throw new RuntimeException(msg, t);
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiSnapHelper.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiSnapHelper.java
index 136dc6e..fd532b5 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiSnapHelper.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiSnapHelper.java
@@ -246,7 +246,7 @@
* @param helper An {@link OrientationHelper} to aid with calculation.
* @return A float indicating the percentage of the given view that is visible.
*/
- private static float getPercentageVisible(View view, OrientationHelper helper) {
+ static float getPercentageVisible(View view, OrientationHelper helper) {
int start = helper.getStartAfterPadding();
int end = helper.getEndAfterPadding();
@@ -340,6 +340,80 @@
}
/**
+ * Estimates a position to which CarUiSnapHelper will try to snap to for a requested scroll
+ * distance.
+ *
+ * @param helper The {@link OrientationHelper} that is created from the LayoutManager.
+ * @param scrollDistance The intended scroll distance.
+ *
+ * @return The diff between the target snap position and the current position.
+ */
+ public int estimateNextPositionDiffForScrollDistance(OrientationHelper helper,
+ int scrollDistance) {
+ float distancePerChild = computeDistancePerChild(helper.getLayoutManager(), helper);
+ if (distancePerChild <= 0) {
+ return 0;
+ }
+ return (int) Math.round(scrollDistance / distancePerChild);
+ }
+
+ /**
+ * This method is taken verbatim from the [androidx] {@link LinearSnapHelper} private method
+ * implementation.
+ *
+ * Computes an average pixel value to pass a single child.
+ * <p>
+ * Returns a negative value if it cannot be calculated.
+ *
+ * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
+ * {@link RecyclerView}.
+ * @param helper The relevant {@link OrientationHelper} for the attached
+ * {@link RecyclerView.LayoutManager}.
+ *
+ * @return A float value that is the average number of pixels needed to scroll by one view in
+ * the relevant direction.
+ */
+ float computeDistancePerChild(RecyclerView.LayoutManager layoutManager,
+ OrientationHelper helper) {
+ View minPosView = null;
+ View maxPosView = null;
+ int minPos = Integer.MAX_VALUE;
+ int maxPos = Integer.MIN_VALUE;
+ int childCount = layoutManager.getChildCount();
+ if (childCount == 0) {
+ return 1;
+ }
+
+ for (int i = 0; i < childCount; i++) {
+ View child = layoutManager.getChildAt(i);
+ final int pos = layoutManager.getPosition(child);
+ if (pos == RecyclerView.NO_POSITION) {
+ continue;
+ }
+ if (pos < minPos) {
+ minPos = pos;
+ minPosView = child;
+ }
+ if (pos > maxPos) {
+ maxPos = pos;
+ maxPosView = child;
+ }
+ }
+ if (minPosView == null || maxPosView == null) {
+ return 1;
+ }
+ int start = Math.min(helper.getDecoratedStart(minPosView),
+ helper.getDecoratedStart(maxPosView));
+ int end = Math.max(helper.getDecoratedEnd(minPosView),
+ helper.getDecoratedEnd(maxPosView));
+ int distance = end - start;
+ if (distance == 0) {
+ return 0;
+ }
+ return 1f * distance / ((maxPos - minPos) + 1);
+ }
+
+ /**
* Returns {@code true} if the RecyclerView is completely displaying the first item.
*/
public boolean isAtStart(@Nullable LayoutManager layoutManager) {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java
index b27aab0..2bed61c 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java
@@ -19,6 +19,7 @@
import android.content.res.Resources;
import android.os.Handler;
+import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
@@ -26,6 +27,7 @@
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.recyclerview.widget.OrientationHelper;
import androidx.recyclerview.widget.RecyclerView;
@@ -35,8 +37,8 @@
/**
* The default scroll bar widget for the {@link CarUiRecyclerView}.
*
- * <p>Inspired by {@link androidx.car.widget.PagedListView}. Most pagination and scrolling logic has
- * been ported from the PLV with minor updates.
+ * <p>Inspired by {@link androidx.car.widget.PagedListView}. Most pagination and scrolling logic
+ * has been ported from the PLV with minor updates.
*/
class DefaultScrollBar implements ScrollBar {
@@ -59,6 +61,9 @@
private OrientationHelper mOrientationHelper;
+ private OnContinuousScrollListener mPageUpOnContinuousScrollListener;
+ private OnContinuousScrollListener mPageDownOnContinuousScrollListener;
+
@Override
public void initialize(RecyclerView rv, View scrollView) {
mRecyclerView = rv;
@@ -77,14 +82,17 @@
mUpButton = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_page_up);
View.OnClickListener paginateUpButtonOnClickListener = v -> pageUp();
mUpButton.setOnClickListener(paginateUpButtonOnClickListener);
- mUpButton.setOnTouchListener(
- new OnContinuousScrollListener(rv.getContext(), paginateUpButtonOnClickListener));
+ mPageUpOnContinuousScrollListener = new OnContinuousScrollListener(rv.getContext(),
+ paginateUpButtonOnClickListener);
+ mUpButton.setOnTouchListener(mPageUpOnContinuousScrollListener);
+
mDownButton = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_page_down);
View.OnClickListener paginateDownButtonOnClickListener = v -> pageDown();
mDownButton.setOnClickListener(paginateDownButtonOnClickListener);
- mDownButton.setOnTouchListener(
- new OnContinuousScrollListener(rv.getContext(), paginateDownButtonOnClickListener));
+ mPageDownOnContinuousScrollListener = new OnContinuousScrollListener(rv.getContext(),
+ paginateDownButtonOnClickListener);
+ mDownButton.setOnTouchListener(mPageDownOnContinuousScrollListener);
mScrollTrack = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_track);
mScrollThumb = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_thumb);
@@ -131,6 +139,13 @@
* @param enabled {@code true} if the up button is enabled.
*/
private void setUpEnabled(boolean enabled) {
+ // If the button is held down the button is disabled, the MotionEvent.ACTION_UP event on
+ // button release will not be sent to cancel pending scrolls. Manually cancel any pending
+ // scroll.
+ if (!enabled) {
+ mPageUpOnContinuousScrollListener.cancelPendingScroll();
+ }
+
mUpButton.setEnabled(enabled);
mUpButton.setAlpha(enabled ? 1f : mButtonDisabledAlpha);
}
@@ -141,6 +156,13 @@
* @param enabled {@code true} if the down button is enabled.
*/
private void setDownEnabled(boolean enabled) {
+ // If the button is held down the button is disabled, the MotionEvent.ACTION_UP event on
+ // button release will not be sent to cancel pending scrolls. Manually cancel any pending
+ // scroll.
+ if (!enabled) {
+ mPageDownOnContinuousScrollListener.cancelPendingScroll();
+ }
+
mDownButton.setEnabled(enabled);
mDownButton.setAlpha(enabled ? 1f : mButtonDisabledAlpha);
}
@@ -160,10 +182,9 @@
* where the thumb should be; and finally, extent is the size of the thumb.
*
* <p>These values can be expressed in arbitrary units, so long as they share the same units.
- * The
- * values should also be positive.
+ * The values should also be positive.
*
- * @param range The range of the scrollbar's thumb
+ * @param range The range of the scrollbar's thumb
* @param offset The offset of the scrollbar's thumb
* @param extent The extent of the scrollbar's thumb
*/
@@ -199,7 +220,7 @@
* Calculates and returns how big the scroll bar thumb should be based on the given range and
* extent.
*
- * @param range The total amount of space the scroll bar is allowed to roam over.
+ * @param range The total amount of space the scroll bar is allowed to roam over.
* @param extent The amount of space that the scroll bar takes up relative to the range.
* @return The height of the scroll bar thumb in pixels.
*/
@@ -213,9 +234,9 @@
* Calculates and returns how much the scroll thumb should be offset from the top of where it
* has been laid out.
*
- * @param range The total amount of space the scroll bar is allowed to roam over.
- * @param offset The amount the scroll bar should be offset, expressed in the same units as
- * the given range.
+ * @param range The total amount of space the scroll bar is allowed to roam over.
+ * @param offset The amount the scroll bar should be offset, expressed in the same units as
+ * the given range.
* @param thumbLength The current length of the thumb in pixels.
* @return The amount the thumb should be offset in pixels.
*/
@@ -230,7 +251,9 @@
: mScrollTrack.getHeight() - thumbLength);
}
- /** Moves the given view to the specified 'y' position. */
+ /**
+ * Moves the given view to the specified 'y' position.
+ */
private void moveY(final View view, float newPosition) {
view.animate()
.y(newPosition)
@@ -239,13 +262,93 @@
.start();
}
+ private boolean mIsAdapterChangeObserverRegistered = false;
+ @Nullable
+ private RecyclerView.Adapter mCurrentAdapter;
private final RecyclerView.OnScrollListener mRecyclerViewOnScrollListener =
new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
updatePaginationButtons();
+ cacheChildrenHeight(recyclerView.getLayoutManager());
+ if (mCurrentAdapter != recyclerView.getAdapter()) {
+ mIsAdapterChangeObserverRegistered = false;
+ if (mCurrentAdapter != null) {
+ mCurrentAdapter.unregisterAdapterDataObserver(mAdapterChangeObserver);
+ }
+ }
+ if (!mIsAdapterChangeObserverRegistered
+ && recyclerView.getAdapter() != null) {
+ mIsAdapterChangeObserverRegistered = true;
+ mCurrentAdapter = recyclerView.getAdapter();
+ mCurrentAdapter.registerAdapterDataObserver(mAdapterChangeObserver);
+ }
}
};
+ private final SparseArray<Integer> mChildHeightByAdapterPosition = new SparseArray();
+
+ private final RecyclerView.AdapterDataObserver mAdapterChangeObserver =
+ new RecyclerView.AdapterDataObserver() {
+ @Override
+ public void onChanged() {
+ clearCachedHeights();
+ }
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ clearCachedHeights();
+ }
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ clearCachedHeights();
+ }
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ clearCachedHeights();
+ }
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ clearCachedHeights();
+ }
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ clearCachedHeights();
+ }
+ };
+
+ private void clearCachedHeights() {
+ mChildHeightByAdapterPosition.clear();
+ cacheChildrenHeight(mRecyclerView.getLayoutManager());
+ }
+
+ private void cacheChildrenHeight(RecyclerView.LayoutManager layoutManager) {
+ for (int i = 0; i < layoutManager.getChildCount(); i++) {
+ View child = layoutManager.getChildAt(i);
+ int childPosition = layoutManager.getPosition(child);
+ if (mChildHeightByAdapterPosition.indexOfKey(childPosition) < 0) {
+ mChildHeightByAdapterPosition.put(childPosition, child.getHeight());
+ }
+ }
+ }
+
+ private int estimateNextPositionScrollUp(int currentPos, int scrollDistance,
+ OrientationHelper orientationHelper) {
+ int nextPos = 0;
+ int distance = 0;
+ for (int i = currentPos - 1; i >= 0; i--) {
+ if (mChildHeightByAdapterPosition.indexOfKey(i) < 0) {
+ // Use the average height estimate when there is not enough data
+ nextPos = mSnapHelper.estimateNextPositionDiffForScrollDistance(orientationHelper,
+ -scrollDistance);
+ break;
+ }
+ if ((distance + mChildHeightByAdapterPosition.get(i)) > Math.abs(scrollDistance)) {
+ nextPos = i - currentPos + 1;
+ break;
+ }
+ distance += mChildHeightByAdapterPosition.get(i);
+ }
+ return nextPos;
+ }
private OrientationHelper getOrientationHelper(RecyclerView.LayoutManager layoutManager) {
if (mOrientationHelper == null || mOrientationHelper.getLayoutManager() != layoutManager) {
@@ -260,49 +363,52 @@
* {@code CarUiRecyclerView}.
*
* <p>The resulting first item in the list will be snapped to so that it is completely visible.
- * If
- * this is not possible due to the first item being taller than the containing {@code
+ * If this is not possible due to the first item being taller than the containing {@code
* CarUiRecyclerView}, then the snapping will not occur.
*/
void pageUp() {
int currentOffset = getRecyclerView().computeVerticalScrollOffset();
- if (getRecyclerView().getLayoutManager() == null
- || getRecyclerView().getChildCount() == 0
- || currentOffset == 0) {
+ RecyclerView.LayoutManager layoutManager = getRecyclerView().getLayoutManager();
+ if (layoutManager == null || layoutManager.getChildCount() == 0 || currentOffset == 0) {
return;
}
// Use OrientationHelper to calculate scroll distance in order to match snapping behavior.
- OrientationHelper orientationHelper =
- getOrientationHelper(getRecyclerView().getLayoutManager());
+ OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
int screenSize = orientationHelper.getTotalSpace();
int scrollDistance = screenSize;
- // The iteration order matters. In case where there are 2 items longer than screen size, we
- // want to focus on upcoming view.
- for (int i = 0; i < getRecyclerView().getChildCount(); i++) {
- /*
- * We treat child View longer than screen size differently:
- * 1) When it enters screen, next pageUp will align its bottom with parent bottom;
- * 2) When it leaves screen, next pageUp will align its top with parent top.
- */
- View child = getRecyclerView().getChildAt(i);
- if (child.getHeight() > screenSize) {
- if (orientationHelper.getDecoratedEnd(child) < screenSize) {
- // Child view bottom is entering screen. Align its bottom with parent bottom.
- scrollDistance = screenSize - orientationHelper.getDecoratedEnd(child);
- } else if (-screenSize < orientationHelper.getDecoratedStart(child)
- && orientationHelper.getDecoratedStart(child) < 0) {
- // Child view top is about to enter screen - its distance to parent top
- // is less than a full scroll. Align child top with parent top.
- scrollDistance = Math.abs(orientationHelper.getDecoratedStart(child));
- }
- // There can be two items that are longer than the screen. We stop at the first one.
- // This is affected by the iteration order.
+
+ View currentPosView = getFirstMostVisibleChild(orientationHelper);
+ int currentPos = currentPosView != null ? mRecyclerView.getLayoutManager().getPosition(
+ currentPosView) : 0;
+ int nextPos = estimateNextPositionScrollUp(currentPos,
+ scrollDistance - Math.max(0, orientationHelper.getStartAfterPadding()
+ - orientationHelper.getDecoratedStart(currentPosView)), orientationHelper);
+ if (nextPos == 0) {
+ // Distance should always be positive. Negate its value to scroll up.
+ mRecyclerView.smoothScrollBy(0, -scrollDistance);
+ } else {
+ mRecyclerView.smoothScrollToPosition(Math.max(0, currentPos + nextPos));
+ }
+ }
+
+ private View getFirstMostVisibleChild(OrientationHelper helper) {
+ float mostVisiblePercent = 0;
+ View mostVisibleView = null;
+
+ for (int i = 0; i < getRecyclerView().getLayoutManager().getChildCount(); i++) {
+ View child = getRecyclerView().getLayoutManager().getChildAt(i);
+ float visiblePercentage = CarUiSnapHelper.getPercentageVisible(child, helper);
+ if (visiblePercentage == 1f) {
+ mostVisibleView = child;
break;
+ } else if (visiblePercentage > mostVisiblePercent) {
+ mostVisiblePercent = visiblePercentage;
+ mostVisibleView = child;
}
}
- // Distance should always be positive. Negate its value to scroll up.
- mRecyclerView.smoothScrollBy(0, -scrollDistance);
+
+ return mostVisibleView;
}
/**
@@ -314,53 +420,55 @@
* scrolled the length of a page, but not snapped to.
*/
void pageDown() {
- if (getRecyclerView().getLayoutManager() == null
- || getRecyclerView().getChildCount() == 0) {
+ RecyclerView.LayoutManager layoutManager = getRecyclerView().getLayoutManager();
+ if (layoutManager == null || layoutManager.getChildCount() == 0) {
return;
}
- OrientationHelper orientationHelper =
- getOrientationHelper(getRecyclerView().getLayoutManager());
+ OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
int screenSize = orientationHelper.getTotalSpace();
int scrollDistance = screenSize;
- // If the last item is partially visible, page down should bring it to the top.
- View lastChild = getRecyclerView().getChildAt(getRecyclerView().getChildCount() - 1);
- if (getRecyclerView().getLayoutManager().isViewPartiallyVisible(lastChild,
- /* completelyVisible= */ false, /* acceptEndPointInclusion= */ false)) {
- scrollDistance = orientationHelper.getDecoratedStart(lastChild)
- - orientationHelper.getStartAfterPadding();
- if (scrollDistance <= 0) {
- // - Scroll value is zero if the top of last item is aligned with top of the screen;
- // - Scroll value can be negative if the child is longer than the screen size and
- // the visible area of the screen does not show the start of the child.
- // Scroll to the next screen in both cases.
- scrollDistance = screenSize;
- }
+ View currentPosView = getFirstMostVisibleChild(orientationHelper);
+
+ // If current view is partially visible and bottom of the view is below visible area of
+ // the recyclerview either scroll down one page (screenSize) or enough to align the bottom
+ // of the view with the bottom of the recyclerview. Note that this will not cause a snap,
+ // because the current view is already snapped to the top or it wouldn't be the most
+ // visible view.
+ if (layoutManager.isViewPartiallyVisible(currentPosView,
+ /* completelyVisible= */ false, /* acceptEndPointInclusion= */ false)
+ && orientationHelper.getDecoratedEnd(currentPosView)
+ > orientationHelper.getEndAfterPadding()) {
+ scrollDistance = Math.min(screenSize,
+ orientationHelper.getDecoratedEnd(currentPosView)
+ - orientationHelper.getEndAfterPadding());
}
- // The iteration order matters. In case where there are 2 items longer than screen size, we
- // want to focus on upcoming view (the one at the bottom of screen).
- for (int i = getRecyclerView().getChildCount() - 1; i >= 0; i--) {
- /* We treat child View longer than screen size differently:
- * 1) When it enters screen, next pageDown will align its top with parent top;
- * 2) When it leaves screen, next pageDown will align its bottom with parent bottom.
- */
- View child = getRecyclerView().getChildAt(i);
- if (child.getHeight() > screenSize) {
- if (orientationHelper.getDecoratedStart(child)
- - orientationHelper.getStartAfterPadding() > 0) {
- // Child view top is entering screen. Align its top with parent top.
- scrollDistance = orientationHelper.getDecoratedStart(lastChild)
+ // Iterate over the childview (bottom to top) and stop when we find the first
+ // view that we can snap to and the scroll size is less than max scroll size (screenSize)
+ for (int i = layoutManager.getChildCount() - 1; i >= 0; i--) {
+ View child = layoutManager.getChildAt(i);
+
+ // Ignore the child if it's above the currentview, as scrolldown will only move down.
+ // Note that in case of gridview, child will not be the same as the currentview.
+ if (orientationHelper.getDecoratedStart(child)
+ <= orientationHelper.getDecoratedStart(currentPosView)) {
+ break;
+ }
+
+ // Ignore the child if the scroll distance is bigger than the max scroll size
+ if (orientationHelper.getDecoratedStart(child)
+ - orientationHelper.getStartAfterPadding() <= screenSize) {
+ // If the child is already fully visible we can scroll even further.
+ if (orientationHelper.getDecoratedEnd(child)
+ <= orientationHelper.getEndAfterPadding()) {
+ scrollDistance = orientationHelper.getDecoratedEnd(child)
- orientationHelper.getStartAfterPadding();
- } else if (screenSize < orientationHelper.getDecoratedEnd(child)
- && orientationHelper.getDecoratedEnd(child) < 2 * screenSize) {
- // Child view bottom is about to enter screen - its distance to parent bottom
- // is less than a full scroll. Align child bottom with parent bottom.
- scrollDistance = orientationHelper.getDecoratedEnd(child) - screenSize;
+ } else {
+ scrollDistance = orientationHelper.getDecoratedStart(child)
+ - orientationHelper.getStartAfterPadding();
}
- // There can be two items that are longer than the screen. We stop at the first one.
- // This is affected by the iteration order.
break;
}
}
@@ -371,12 +479,9 @@
/**
* Determines if scrollbar should be visible or not and shows/hides it accordingly. If this is
* being called as a result of adapter changes, it should be called after the new layout has
- * been
- * calculated because the method of determining scrollbar visibility uses the current layout.
- * If
- * this is called after an adapter change but before the new layout, the visibility
- * determination
- * may not be correct.
+ * been calculated because the method of determining scrollbar visibility uses the current
+ * layout. If this is called after an adapter change but before the new layout, the visibility
+ * determination may not be correct.
*/
private void updatePaginationButtons() {
@@ -387,6 +492,7 @@
// enable/disable the button before the view is shown. So there is no flicker.
setUpEnabled(!isAtStart);
setDownEnabled(!isAtEnd);
+
if ((isAtStart && isAtEnd) || layoutManager == null || layoutManager.getItemCount() == 0) {
mScrollView.setVisibility(View.INVISIBLE);
} else {
@@ -412,12 +518,16 @@
mScrollView.invalidate();
}
- /** Returns {@code true} if the RecyclerView is completely displaying the first item. */
+ /**
+ * Returns {@code true} if the RecyclerView is completely displaying the first item.
+ */
boolean isAtStart() {
return mSnapHelper.isAtStart(getRecyclerView().getLayoutManager());
}
- /** Returns {@code true} if the RecyclerView is completely displaying the last item. */
+ /**
+ * Returns {@code true} if the RecyclerView is completely displaying the last item.
+ */
boolean isAtEnd() {
return mSnapHelper.isAtEnd(getRecyclerView().getLayoutManager());
}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/OnContinuousScrollListener.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/OnContinuousScrollListener.java
index 4fafc8d..48570c6 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/OnContinuousScrollListener.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/OnContinuousScrollListener.java
@@ -28,16 +28,15 @@
import com.android.car.ui.R;
/**
- * A class, that can be used as a TouchListener on any view (e.g. a Button).
- * It periodically calls the provided clickListener. The first callback is fired after the
- * initial Delay, and subsequent ones after the defined interval.
+ * A class, that can be used as a TouchListener on any view (e.g. a Button). It periodically calls
+ * the provided clickListener. The first callback is fired after the initial Delay, and subsequent
+ * ones after the defined interval.
*/
public class OnContinuousScrollListener implements OnTouchListener {
- private Handler mHandler = new Handler();
-
- private int mInitialDelay;
- private int mRepeatInterval;
+ private final Handler mHandler = new Handler();
+ private final int mInitialDelay;
+ private final int mRepeatInterval;
private final OnClickListener mOnClickListener;
private View mTouchedView;
private boolean mIsLongPressed;
@@ -45,7 +44,7 @@
/**
* Notifies listener and self schedules to be re-run at next callback interval.
*/
- private Runnable mPeriodicRunnable = new Runnable() {
+ private final Runnable mPeriodicRunnable = new Runnable() {
@Override
public void run() {
if (mTouchedView.isEnabled()) {
@@ -59,14 +58,12 @@
};
/**
- * @param clickListener The OnClickListener, that will be called
- * periodically
+ * @param clickListener The OnClickListener, that will be called periodically
*/
public OnContinuousScrollListener(@NonNull Context context,
@NonNull OnClickListener clickListener) {
this.mInitialDelay = context.getResources().getInteger(
R.integer.car_ui_scrollbar_longpress_initial_delay);
-
this.mRepeatInterval = context.getResources().getInteger(
R.integer.car_ui_scrollbar_longpress_repeat_interval);
@@ -76,6 +73,15 @@
this.mOnClickListener = clickListener;
}
+ /**
+ * Cancel pending scroll operations. Any scroll operations that were scheduled to possibly be
+ * performed, as part of a continuous scroll, will be cancelled.
+ */
+ public void cancelPendingScroll() {
+ mHandler.removeCallbacks(mPeriodicRunnable);
+ mIsLongPressed = false;
+ }
+
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
mTouchedView = view;
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/grid/GridDividerItemDecoration.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/grid/GridDividerItemDecoration.java
index abb90ca..5eb090a 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/grid/GridDividerItemDecoration.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/grid/GridDividerItemDecoration.java
@@ -25,6 +25,8 @@
import com.android.car.ui.R;
+import java.util.Objects;
+
/** Adds interior dividers to a RecyclerView with a GridLayoutManager. */
public class GridDividerItemDecoration extends RecyclerView.ItemDecoration {
@@ -89,7 +91,9 @@
* @param parent The RecyclerView onto which dividers are being added
*/
private void drawHorizontalDividers(Canvas canvas, RecyclerView parent) {
- int childCount = parent.getChildCount();
+ RecyclerView.LayoutManager layoutManager = Objects.requireNonNull(
+ parent.getLayoutManager());
+ int childCount = layoutManager.getChildCount();
int rowCount = childCount / mNumColumns;
int lastRowChildCount = childCount % mNumColumns;
int lastColumn = Math.min(childCount, mNumColumns);
@@ -102,8 +106,9 @@
lastRowChildIndex = i + ((rowCount - 1) * mNumColumns);
}
- View firstRowChild = parent.getChildAt(i);
- View lastRowChild = parent.getChildAt(lastRowChildIndex);
+
+ View firstRowChild = layoutManager.getChildAt(i);
+ View lastRowChild = layoutManager.getChildAt(lastRowChildIndex);
int dividerTop =
firstRowChild.getTop() + (int) parent.getContext().getResources().getDimension(
@@ -130,7 +135,9 @@
* @param parent The RecyclerView onto which dividers are being added
*/
private void drawVerticalDividers(Canvas canvas, RecyclerView parent) {
- double childCount = parent.getChildCount();
+ RecyclerView.LayoutManager layoutManager = Objects.requireNonNull(
+ parent.getLayoutManager());
+ double childCount = layoutManager.getChildCount();
double rowCount = Math.ceil(childCount / mNumColumns);
int rightmostChildIndex;
for (int i = 1; i <= rowCount; i++) {
@@ -144,8 +151,8 @@
rightmostChildIndex = (i * mNumColumns) - 1;
}
- View leftmostChild = parent.getChildAt(mNumColumns * (i - 1));
- View rightmostChild = parent.getChildAt(rightmostChildIndex);
+ View leftmostChild = layoutManager.getChildAt(mNumColumns * (i - 1));
+ View rightmostChild = layoutManager.getChildAt(rightmostChildIndex);
// draws on top of each row.
int dividerLeft =
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java
index 4d5e6bd..3ab24aa 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearDividerItemDecoration.java
@@ -104,9 +104,10 @@
- (int) parent.getContext().getResources().getDimension(
R.dimen.car_ui_recyclerview_divider_bottom_margin);
- int childCount = parent.getChildCount();
+ RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
+ int childCount = layoutManager.getChildCount();
for (int i = 0; i < childCount - 1; i++) {
- View child = parent.getChildAt(i);
+ View child = layoutManager.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
@@ -133,9 +134,10 @@
- (int) parent.getContext().getResources().getDimension(
R.dimen.car_ui_recyclerview_divider_end_margin);
- int childCount = parent.getChildCount();
+ RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
+ int childCount = layoutManager.getChildCount();
for (int i = 0; i < childCount - 1; i++) {
- View child = parent.getChildAt(i);
+ View child = layoutManager.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java
index 33e14db..62d361e 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java
@@ -169,7 +169,8 @@
if (mOffsetPosition == OffsetPosition.START) {
parentLeft = parent.getPaddingLeft();
} else {
- View lastChild = parent.getChildAt(parent.getChildCount() - 1);
+ RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
+ View lastChild = layoutManager.getChildAt(layoutManager.getChildCount() - 1);
RecyclerView.LayoutParams lastChildLayoutParams =
(RecyclerView.LayoutParams) lastChild.getLayoutParams();
parentLeft = lastChild.getRight() + lastChildLayoutParams.rightMargin;
@@ -190,7 +191,8 @@
if (mOffsetPosition == OffsetPosition.START) {
parentTop = parent.getPaddingTop();
} else {
- View lastChild = parent.getChildAt(parent.getChildCount() - 1);
+ RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
+ View lastChild = layoutManager.getChildAt(layoutManager.getChildCount() - 1);
RecyclerView.LayoutParams lastChildLayoutParams =
(RecyclerView.LayoutParams) lastChild.getLayoutParams();
parentTop = lastChild.getBottom() + lastChildLayoutParams.bottomMargin;
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/CarUiEditText.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/CarUiEditText.java
new file mode 100644
index 0000000..1f1a07c
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/CarUiEditText.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.toolbar;
+
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.CONTENT_AREA_SURFACE_DISPLAY_ID;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.CONTENT_AREA_SURFACE_HEIGHT;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.CONTENT_AREA_SURFACE_HOST_TOKEN;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.CONTENT_AREA_SURFACE_WIDTH;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_ITEM_ID_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_SUPPLEMENTAL_ICON_ID_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_CLEAR_DATA_ACTION;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.AttributeSet;
+import android.widget.EditText;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Edit text supporting the callbacks from the IMS. This will be useful in widescreen IME mode to
+ * allow car-ui-lib to receive responses (like onClick events) from the IMS
+ */
+class CarUiEditText extends EditText {
+
+ private final Set<PrivateImeCommandCallback> mPrivateImeCommandCallback = new HashSet<>();
+
+ // These need to be public for the layout inflater to inflate them, but
+ // checkstyle complains about a public constructor on a package-private class
+ //CHECKSTYLE:OFF Generated code
+ public CarUiEditText(Context context) {
+ super(context);
+ }
+
+ public CarUiEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CarUiEditText(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CarUiEditText(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+ //CHECKSTYLE:ON Generated code
+
+ @Override
+ public boolean onPrivateIMECommand(String action, Bundle data) {
+
+ if (WIDE_SCREEN_CLEAR_DATA_ACTION.equals(action)) {
+ // clear the text.
+ setText("");
+ }
+
+ if (data == null || mPrivateImeCommandCallback == null) {
+ return false;
+ }
+
+ if (data.getString(SEARCH_RESULT_ITEM_ID_LIST) != null) {
+ for (PrivateImeCommandCallback listener : mPrivateImeCommandCallback) {
+ listener.onItemClicked(data.getString(SEARCH_RESULT_ITEM_ID_LIST));
+ }
+ }
+
+ if (data.getString(SEARCH_RESULT_SUPPLEMENTAL_ICON_ID_LIST) != null) {
+ for (PrivateImeCommandCallback listener : mPrivateImeCommandCallback) {
+ listener.onSecondaryImageClicked(
+ data.getString(SEARCH_RESULT_SUPPLEMENTAL_ICON_ID_LIST));
+ }
+ }
+
+ int displayId = data.getInt(CONTENT_AREA_SURFACE_DISPLAY_ID);
+ int height = data.getInt(CONTENT_AREA_SURFACE_HEIGHT);
+ int width = data.getInt(CONTENT_AREA_SURFACE_WIDTH);
+ IBinder binder = data.getBinder(CONTENT_AREA_SURFACE_HOST_TOKEN);
+
+ if (binder != null) {
+ for (PrivateImeCommandCallback listener : mPrivateImeCommandCallback) {
+ listener.onSurfaceInfo(displayId, binder, height, width);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Registers a new {@link PrivateImeCommandCallback} to the list of
+ * listeners.
+ */
+ public void registerOnPrivateImeCommandListener(PrivateImeCommandCallback listener) {
+ mPrivateImeCommandCallback.add(listener);
+ }
+
+ /**
+ * Unregisters an existing {@link PrivateImeCommandCallback} from the list
+ * of listeners.
+ */
+ public boolean unregisterOnPrivateImeCommandListener(PrivateImeCommandCallback listener) {
+ return mPrivateImeCommandCallback.remove(listener);
+ }
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItem.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItem.java
index 6ee35d3..27f8140 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItem.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItem.java
@@ -54,6 +54,7 @@
private final boolean mIsSearch;
private final boolean mShowIconAndTitle;
private final boolean mIsTinted;
+ private final boolean mIsPrimary;
@CarUxRestrictions.CarUxRestrictionsInfo
private int mId;
@@ -88,6 +89,7 @@
mIsSearch = builder.mIsSearch;
mShowIconAndTitle = builder.mShowIconAndTitle;
mIsTinted = builder.mIsTinted;
+ mIsPrimary = builder.mIsPrimary;
mUxRestrictions = builder.mUxRestrictions;
mCurrentRestrictions = CarUxRestrictionsUtil.getInstance(mContext).getCurrentRestrictions();
@@ -300,6 +302,14 @@
: mContext.getDrawable(resId));
}
+ /**
+ * Returns if this MenuItem is a primary MenuItem, which means it should be visually
+ * distinct to indicate that.
+ */
+ public boolean isPrimary() {
+ return mIsPrimary;
+ }
+
/** Returns if this is the search MenuItem, which has special behavior when searching */
boolean isSearch() {
return mIsSearch;
@@ -329,6 +339,7 @@
private boolean mIsActivated = false;
private boolean mIsSearch = false;
private boolean mIsSettings = false;
+ private boolean mIsPrimary = false;
@CarUxRestrictions.CarUxRestrictionsInfo
private int mUxRestrictions = CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
@@ -508,6 +519,14 @@
}
/**
+ * Sets whether the MenuItem is primary. This is just a visual change.
+ */
+ public Builder setPrimary(boolean primary) {
+ mIsPrimary = primary;
+ return this;
+ }
+
+ /**
* Sets under what {@link android.car.drivingstate.CarUxRestrictions.CarUxRestrictionsInfo}
* the MenuItem should be restricted.
*/
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemRenderer.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemRenderer.java
index 85141f1..41b6bef 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemRenderer.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemRenderer.java
@@ -32,6 +32,7 @@
import android.widget.Switch;
import android.widget.TextView;
+import androidx.annotation.LayoutRes;
import androidx.annotation.XmlRes;
import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
import androidx.core.util.Consumer;
@@ -98,7 +99,10 @@
void createView(Consumer<View> callback) {
AsyncLayoutInflater inflater = new AsyncLayoutInflater(mParentView.getContext());
- inflater.inflate(R.layout.car_ui_toolbar_menu_item, mParentView, (View view, int resid,
+ @LayoutRes int layout = mMenuItem.isPrimary()
+ ? R.layout.car_ui_toolbar_menu_item_primary
+ : R.layout.car_ui_toolbar_menu_item;
+ inflater.inflate(layout, mParentView, (View view, int resid,
ViewGroup parent) -> {
mView = view;
@@ -149,6 +153,8 @@
mSwitch.setChecked(mMenuItem.isChecked());
mSwitch.setVisibility(View.VISIBLE);
if (mIsRotaryEnabledLayout) {
+ textContainerVisibility = View.VISIBLE;
+ } else {
iconContainerVisibility = View.VISIBLE;
}
} else if (hasText && hasIcon && textAndIcon) {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/PrivateImeCommandCallback.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/PrivateImeCommandCallback.java
new file mode 100644
index 0000000..49e7fa4
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/PrivateImeCommandCallback.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.toolbar;
+
+import android.os.IBinder;
+
+/**
+ * Interface for {@link CarUiEditText} to support different actions and callbacks from IME
+ * when running in wide screen mode.
+ */
+public interface PrivateImeCommandCallback {
+ /**
+ * Called when user clicks on an item in the search results.
+ *
+ * @param itemId the id of the item clicked. This will be the same id that was passed by the
+ * application to the IMS template to display search results.
+ */
+ void onItemClicked(String itemId);
+
+ /**
+ * Called when user clicks on a secondary image within item in the search results.
+ *
+ * @param secondaryImageId the id of the secondary image clicked. This will be the same id
+ * that was passed by the application to the IMS template to display
+ * search results.
+ */
+ void onSecondaryImageClicked(String secondaryImageId);
+
+ /**
+ * Called when the edit text is clicked and IME is about to launch. IME provides the surface
+ * view information through this call that applications can use to display a view on the
+ * IME surface.
+ *
+ * This method will NOT be called if an OEM has not allowed an application to hide the
+ * content area.
+ */
+ void onSurfaceInfo(int displayId, IBinder binder, int height, int width);
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java
index 9506fe1..62511f0 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java
@@ -15,16 +15,39 @@
*/
package com.android.car.ui.toolbar;
+import static android.view.WindowInsets.Type.ime;
+
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.CONTENT_AREA_SURFACE_PACKAGE;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_ICON_BITMAP_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_ICON_RES_ID_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_ITEM_ID_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_SUB_TITLE_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_SUPPLEMENTAL_ICON_BITMAP_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_SUPPLEMENTAL_ICON_ID_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_SUPPLEMENTAL_ICON_RES_ID_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_TITLE_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_ACTION;
import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManager;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
+import android.view.Display;
import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
@@ -32,11 +55,19 @@
import android.widget.ImageView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.car.ui.R;
+import com.android.car.ui.imewidescreen.CarUiImeSearchListItem;
+import com.android.car.ui.recyclerview.CarUiContentListItem;
+import com.android.car.ui.recyclerview.CarUiListItem;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -50,6 +81,16 @@
private final int mStartPaddingWithoutIcon;
private final int mStartPadding;
private final int mEndPadding;
+ @Nullable
+ private View mWideScreenImeContentAreaView;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ private SurfaceControlViewHost mSurfaceControlViewHost;
+ private int mSurfaceHeight;
+ private int mSurfaceWidth;
+ private List<? extends CarUiImeSearchListItem> mWideScreenSearchItemList;
+ private final Map<String, CarUiImeSearchListItem> mIdToListItem = new HashMap<>();
+
private Set<Toolbar.OnSearchListener> mSearchListeners = Collections.emptySet();
private Set<Toolbar.OnSearchCompletedListener> mSearchCompletedListeners =
Collections.emptySet();
@@ -82,7 +123,7 @@
super(context, attrs, defStyleAttr);
mInputMethodManager = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.car_ui_toolbar_search_view, this, true);
@@ -128,6 +169,46 @@
}
return false;
});
+
+ if (mSearchText instanceof CarUiEditText) {
+ ((CarUiEditText) mSearchText).registerOnPrivateImeCommandListener(
+ new SearchViewImeCallback());
+ }
+ }
+
+ /**
+ * Apply window inset listener to the search container.
+ */
+ void installWindowInsetsListener(View searchContainer) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ // WindowInsets.isVisible() is only available on R or above
+ return;
+ }
+
+ searchContainer.getRootView().setOnApplyWindowInsetsListener((v, insets) -> {
+ if (insets.isVisible(ime())) {
+ displaySearchWideScreen();
+ mHandler.post(() -> {
+ if (mSurfaceControlViewHost != null && mWideScreenImeContentAreaView != null
+ && mSurfaceControlViewHost.getView() == null) {
+ mSurfaceControlViewHost.setView(
+ mWideScreenImeContentAreaView, mSurfaceWidth, mSurfaceHeight);
+ }
+ });
+ }
+ return v.onApplyWindowInsets(insets);
+ });
+ }
+
+ void setViewToImeWideScreenSurface(View view) {
+ if (view == null && mSurfaceControlViewHost != null) {
+ mSurfaceControlViewHost.release();
+ }
+
+ if (view != null && view.getParent() != null) {
+ throw new IllegalStateException("view should not have a parent");
+ }
+ mWideScreenImeContentAreaView = view;
}
private boolean isEnter(KeyEvent event) {
@@ -164,28 +245,83 @@
}
/**
- * Adds a listener for the search text changing.
- * See also {@link #unregisterOnSearchListener(Toolbar.OnSearchListener)}
+ * Sets a listener for the search text changing.
*/
public void setSearchListeners(Set<Toolbar.OnSearchListener> listeners) {
mSearchListeners = listeners;
}
/**
- * Removes a search listener.
- * See also {@link #registerOnSearchListener(Toolbar.OnSearchListener)}
+ * Sets a listener for the user completing their search, for example by clicking the
+ * enter/search button on the keyboard.
*/
public void setSearchCompletedListeners(Set<Toolbar.OnSearchCompletedListener> listeners) {
mSearchCompletedListeners = listeners;
}
/**
- * Sets the search hint.
- *
- * @param resId A string resource id of the search hint.
+ * Sets list of search item {@link CarUiListItem} to be displayed in the IMS
+ * template.
*/
- public void setHint(int resId) {
- mSearchText.setHint(resId);
+ public void setSearchItemsForWideScreen(List<? extends CarUiImeSearchListItem> searchItems) {
+ mWideScreenSearchItemList = searchItems != null ? new ArrayList<>(searchItems) : null;
+ displaySearchWideScreen();
+ }
+
+ private void displaySearchWideScreen() {
+ mIdToListItem.clear();
+ // mWideScreenImeContentAreaView will only be set when running in widescreen mode and
+ // apps allowed by OEMs are trying to set their own view. In that case we did not want to
+ // send the information to IME for templatized solution.
+ if (mWideScreenImeContentAreaView != null) {
+ return;
+ }
+
+ if (mWideScreenSearchItemList == null) {
+ mInputMethodManager.sendAppPrivateCommand(mSearchText, WIDE_SCREEN_ACTION, null);
+ return;
+ }
+
+ ArrayList<String> itemIdList = new ArrayList<>();
+ ArrayList<String> titleList = new ArrayList<>();
+ ArrayList<String> subTitleList = new ArrayList<>();
+ ArrayList<Bitmap> primaryImageBitmapList = new ArrayList<>();
+ ArrayList<Integer> primaryImageResId = new ArrayList<>();
+ ArrayList<Bitmap> secondaryImageBitmapList = new ArrayList<>();
+ ArrayList<String> secondaryItemId = new ArrayList<>();
+ ArrayList<Integer> secondaryImageResId = new ArrayList<>();
+ int id = 0;
+ for (CarUiImeSearchListItem item : mWideScreenSearchItemList) {
+ String idString = String.valueOf(id);
+ itemIdList.add(idString);
+ titleList.add(item.getTitle() != null ? item.getTitle().toString() : null);
+ subTitleList.add(item.getBody() != null ? item.getBody().toString() : null);
+ primaryImageResId.add(item.getIconResId());
+ secondaryItemId.add(idString);
+ secondaryImageResId.add(item.getSupplementalIconResId());
+ BitmapDrawable icon = (BitmapDrawable) item.getIcon();
+ primaryImageBitmapList.add(icon != null ? icon.getBitmap() : null);
+ BitmapDrawable supplementalIcon = (BitmapDrawable) item.getSupplementalIcon();
+ secondaryImageBitmapList.add(
+ supplementalIcon != null ? supplementalIcon.getBitmap() : null);
+
+ mIdToListItem.put(idString, item);
+ id++;
+ }
+
+ Bundle bundle = new Bundle();
+ bundle.putStringArrayList(SEARCH_RESULT_ITEM_ID_LIST, itemIdList);
+ bundle.putStringArrayList(SEARCH_RESULT_TITLE_LIST, titleList);
+ bundle.putStringArrayList(SEARCH_RESULT_SUB_TITLE_LIST, subTitleList);
+ bundle.putParcelableArrayList(SEARCH_RESULT_ICON_BITMAP_LIST,
+ primaryImageBitmapList);
+ bundle.putParcelableArrayList(SEARCH_RESULT_SUPPLEMENTAL_ICON_BITMAP_LIST,
+ secondaryImageBitmapList);
+ bundle.putIntegerArrayList(SEARCH_RESULT_ICON_RES_ID_LIST, primaryImageResId);
+ bundle.putStringArrayList(SEARCH_RESULT_SUPPLEMENTAL_ICON_ID_LIST, secondaryItemId);
+ bundle.putIntegerArrayList(SEARCH_RESULT_SUPPLEMENTAL_ICON_RES_ID_LIST,
+ secondaryImageResId);
+ mInputMethodManager.sendAppPrivateCommand(mSearchText, WIDE_SCREEN_ACTION, bundle);
}
/**
@@ -197,11 +333,6 @@
mSearchText.setHint(hint);
}
- /** Gets the search hint */
- public CharSequence getHint() {
- return mSearchText.getHint();
- }
-
/**
* Sets a custom icon to display in the search box.
*/
@@ -261,4 +392,62 @@
mSearchText.setText(query);
mSearchText.setSelection(mSearchText.getText().length());
}
+
+ private class SearchViewImeCallback implements PrivateImeCommandCallback {
+
+ @Override
+ public void onItemClicked(String itemId) {
+ CarUiImeSearchListItem item = mIdToListItem.get(itemId);
+ if (item != null) {
+ CarUiContentListItem.OnClickListener listener =
+ item.getOnClickListener();
+ if (listener != null) {
+ listener.onClick(item);
+ }
+ }
+ }
+
+ @Override
+ public void onSecondaryImageClicked(String secondaryImageId) {
+ CarUiImeSearchListItem item = mIdToListItem.get(secondaryImageId);
+ if (item != null) {
+ CarUiContentListItem.OnClickListener listener =
+ item.getSupplementalIconOnClickListener();
+ if (listener != null) {
+ listener.onClick(item);
+ }
+ }
+ }
+
+ @Override
+ public void onSurfaceInfo(int displayId, IBinder binder, int height,
+ int width) {
+ if (Build.VERSION.SDK_INT < VERSION_CODES.R
+ || mWideScreenImeContentAreaView == null) {
+ // SurfaceControlViewHost is only available on R and above
+ return;
+ }
+
+ DisplayManager dm = (DisplayManager) getContext().getSystemService(
+ Context.DISPLAY_SERVICE);
+
+ Display display = dm.getDisplay(displayId);
+
+ if (mSurfaceControlViewHost != null) {
+ mSurfaceControlViewHost.release();
+ }
+
+ mSurfaceControlViewHost = new SurfaceControlViewHost(getContext(),
+ display, binder);
+
+ mSurfaceHeight = height;
+ mSurfaceWidth = width;
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(CONTENT_AREA_SURFACE_PACKAGE,
+ mSurfaceControlViewHost.getSurfacePackage());
+ mInputMethodManager.sendAppPrivateCommand(mSearchText,
+ WIDE_SCREEN_ACTION, bundle);
+ }
+ }
}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Toolbar.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Toolbar.java
index b069fb0..e527a52 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Toolbar.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Toolbar.java
@@ -22,6 +22,7 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.DrawableRes;
@@ -31,6 +32,8 @@
import androidx.annotation.XmlRes;
import com.android.car.ui.R;
+import com.android.car.ui.imewidescreen.CarUiImeSearchListItem;
+import com.android.car.ui.recyclerview.CarUiListItem;
import java.util.List;
@@ -42,8 +45,12 @@
* {@link android.app.Activity#setActionBar(android.widget.Toolbar)} with it)
*
* <p>The toolbar supports a navigation button, title, tabs, search, and {@link MenuItem MenuItems}
+ *
+ * @deprecated Instead of creating this class, use Theme.CarUi.WithToolbar, and get access to it
+ * via {@link com.android.car.ui.core.CarUi#requireToolbar(android.app.Activity)}
*/
-public class Toolbar extends FrameLayout implements ToolbarController {
+@Deprecated
+public final class Toolbar extends FrameLayout implements ToolbarController {
/** Callback that will be issued whenever the height of toolbar is changed. */
public interface OnHeightChangedListener {
@@ -268,13 +275,33 @@
/**
* Gets the {@link TabLayout} for this toolbar.
+ *
+ * @deprecated Use other tab-related functions in the ToolbarController interface.
*/
+ @Deprecated
@Override
public TabLayout getTabLayout() {
return mController.getTabLayout();
}
/**
+ * Gets the number of tabs in the toolbar. The tabs can be retrieved using
+ * {@link #getTab(int)}.
+ */
+ @Override
+ public int getTabCount() {
+ return mController.getTabCount();
+ }
+
+ /**
+ * Gets the index of the tab.
+ */
+ @Override
+ public int getTabPosition(TabLayout.Tab tab) {
+ return mController.getTabPosition(tab);
+ }
+
+ /**
* Adds a tab to this toolbar. You can listen for when it is selected via
* {@link #registerOnTabSelectedListener(OnTabSelectedListener)}.
*/
@@ -628,6 +655,50 @@
return mController.unregisterOnSearchListener(listener);
}
+ /**
+ * Returns true if the toolbar can display search result items. One example of this is when the
+ * system is configured to display search items in the IME instead of in the app.
+ */
+ @Override
+ public boolean canShowSearchResultItems() {
+ return mController.canShowSearchResultItems();
+ }
+
+ /**
+ * Returns true if the app is allowed to set search results view.
+ */
+ @Override
+ public boolean canShowSearchResultsView() {
+ return mController.canShowSearchResultsView();
+ }
+
+ /**
+ * Add a view within a container that will animate with the wide screen IME to display search
+ * results.
+ *
+ * <p>Note: Apps can only call this method if the package name is allowed via OEM to render
+ * their view. To check if the application have the permission to do so or not first call
+ * {@link #canShowSearchResultsView()}. If the app is not allowed this method will throw an
+ * {@link IllegalStateException}
+ *
+ * @param view to be added in the container.
+ */
+ @Override
+ public void setSearchResultsView(View view) {
+ mController.setSearchResultsView(view);
+ }
+
+ /**
+ * Sets list of search item {@link CarUiListItem} to be displayed in the IMS
+ * template. This method should be called when system is running in a wide screen mode. Apps
+ * can check that by using {@link #canShowSearchResultItems()}
+ * Else, this method will throw an {@link IllegalStateException}
+ */
+ @Override
+ public void setSearchResultItems(List<? extends CarUiImeSearchListItem> searchItems) {
+ mController.setSearchResultItems(searchItems);
+ }
+
/** Registers a new {@link OnSearchCompletedListener} to the list of listeners. */
@Override
public void registerOnSearchCompletedListener(OnSearchCompletedListener listener) {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java
index 4eb009d..6b38718 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java
@@ -17,6 +17,7 @@
package com.android.car.ui.toolbar;
import android.graphics.drawable.Drawable;
+import android.view.View;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
@@ -24,6 +25,9 @@
import androidx.annotation.StringRes;
import androidx.annotation.XmlRes;
+import com.android.car.ui.imewidescreen.CarUiImeSearchListItem;
+import com.android.car.ui.recyclerview.CarUiListItem;
+
import java.util.List;
/**
@@ -77,10 +81,24 @@
/**
* Gets the {@link TabLayout} for this toolbar.
+ *
+ * @deprecated Use other tab-related functions in this interface.
*/
+ @Deprecated
TabLayout getTabLayout();
/**
+ * Gets the number of tabs in the toolbar. The tabs can be retrieved using
+ * {@link #getTab(int)}.
+ */
+ int getTabCount();
+
+ /**
+ * Gets the index of the tab.
+ */
+ int getTabPosition(TabLayout.Tab tab);
+
+ /**
* Adds a tab to this toolbar. You can listen for when it is selected via
* {@link #registerOnTabSelectedListener(Toolbar.OnTabSelectedListener)}.
*/
@@ -242,8 +260,10 @@
*/
void registerToolbarHeightChangeListener(Toolbar.OnHeightChangedListener listener);
- /** Unregisters an existing {@link Toolbar.OnHeightChangedListener} from the list of
- * listeners. */
+ /**
+ * Unregisters an existing {@link Toolbar.OnHeightChangedListener} from the list of
+ * listeners.
+ */
boolean unregisterToolbarHeightChangeListener(Toolbar.OnHeightChangedListener listener);
/** Registers a new {@link Toolbar.OnTabSelectedListener} to the list of listeners. */
@@ -258,11 +278,45 @@
/** Unregisters an existing {@link Toolbar.OnSearchListener} from the list of listeners. */
boolean unregisterOnSearchListener(Toolbar.OnSearchListener listener);
+ /**
+ * Returns true if the toolbar can display search result items. One example of this is when the
+ * system is configured to display search items in the IME instead of in the app.
+ */
+ boolean canShowSearchResultItems();
+
+ /**
+ * Returns true if the app is allowed to set search results view.
+ */
+ boolean canShowSearchResultsView();
+
+ /**
+ * Add a view within a container that will animate with the wide screen IME to display search
+ * results.
+ *
+ * <p>Note: Apps can only call this method if the package name is allowed via OEM to render
+ * their view. To check if the application have the permission to do so or not first call
+ * {@link #canShowSearchResultsView()}. If the app is not allowed this method will throw an
+ * {@link IllegalStateException}
+ *
+ * @param view to be added in the container.
+ */
+ void setSearchResultsView(View view);
+
+ /**
+ * Sets list of search item {@link CarUiListItem} to be displayed in the IMS
+ * template. This method should be called when system is running in a wide screen mode. Apps
+ * can check that by using {@link #canShowSearchResultItems()}
+ * Else, this method will throw an {@link IllegalStateException}
+ */
+ void setSearchResultItems(List<? extends CarUiImeSearchListItem> searchItems);
+
/** Registers a new {@link Toolbar.OnSearchCompletedListener} to the list of listeners. */
void registerOnSearchCompletedListener(Toolbar.OnSearchCompletedListener listener);
- /** Unregisters an existing {@link Toolbar.OnSearchCompletedListener} from the list of
- * listeners. */
+ /**
+ * Unregisters an existing {@link Toolbar.OnSearchCompletedListener} from the list of
+ * listeners.
+ */
boolean unregisterOnSearchCompletedListener(Toolbar.OnSearchCompletedListener listener);
/** Registers a new {@link Toolbar.OnBackListener} to the list of listeners. */
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java
index 413beaa..02b3a1a 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java
@@ -21,6 +21,7 @@
import static android.view.View.VISIBLE;
import static com.android.car.ui.utils.CarUiUtils.findViewByRefId;
+import static com.android.car.ui.utils.CarUiUtils.getBooleanSystemProperty;
import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
import android.app.Activity;
@@ -43,6 +44,7 @@
import com.android.car.ui.AlertDialogBuilder;
import com.android.car.ui.R;
+import com.android.car.ui.imewidescreen.CarUiImeSearchListItem;
import com.android.car.ui.recyclerview.CarUiContentListItem;
import com.android.car.ui.recyclerview.CarUiListItem;
import com.android.car.ui.recyclerview.CarUiListItemAdapter;
@@ -50,6 +52,7 @@
import com.android.car.ui.utils.CarUxRestrictionsUtil;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -61,7 +64,7 @@
* The implementation of {@link ToolbarController}. This class takes a ViewGroup, and looks
* in the ViewGroup to find all the toolbar-related views to control.
*/
-public class ToolbarControllerImpl implements ToolbarController {
+public final class ToolbarControllerImpl implements ToolbarController {
private static final String TAG = "CarUiToolbarController";
@Nullable
@@ -111,7 +114,9 @@
private AlertDialog mOverflowDialog;
private boolean mNavIconSpaceReserved;
private boolean mLogoFillsNavIconSpace;
+ private View mViewForContentAreaInWideScreenMode;
private boolean mShowLogo;
+ private List<? extends CarUiImeSearchListItem> mSearchItems;
private final ProgressBarController mProgressBar;
private final MenuItem.Listener mOverflowItemListener = item -> {
updateOverflowDialog(item);
@@ -266,13 +271,33 @@
/**
* Gets the {@link TabLayout} for this toolbar.
+ *
+ * @deprecated Use other tab-related functions in the ToolbarController interface.
*/
+ @Deprecated
@Override
public TabLayout getTabLayout() {
return mTabLayout;
}
/**
+ * Gets the number of tabs in the toolbar. The tabs can be retrieved using
+ * {@link #getTab(int)}.
+ */
+ @Override
+ public int getTabCount() {
+ return mTabLayout.getTabCount();
+ }
+
+ /**
+ * Gets the index of the tab.
+ */
+ @Override
+ public int getTabPosition(TabLayout.Tab tab) {
+ return mTabLayout.getTabPosition(tab);
+ }
+
+ /**
* Adds a tab to this toolbar. You can listen for when it is selected via
* {@link #registerOnTabSelectedListener(Toolbar.OnTabSelectedListener)}.
*/
@@ -693,6 +718,15 @@
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mSearchViewContainer.addView(searchView, layoutParams);
+ if (canShowSearchResultsView()) {
+ searchView.setViewToImeWideScreenSurface(mViewForContentAreaInWideScreenMode);
+ }
+
+ searchView.installWindowInsetsListener(mSearchViewContainer);
+
+ if (mSearchItems != null) {
+ searchView.setSearchItemsForWideScreen(mSearchItems);
+ }
mSearchView = searchView;
}
@@ -790,13 +824,94 @@
mOverflowButton.setVisible(showButtons && countVisibleOverflowItems() > 0);
}
+ /**
+ * Return the list of package names allowed to hide the content area in wide screen IME.
+ */
+ private List<String> allowPackageList(Context context) {
+ String[] packages = context.getResources()
+ .getStringArray(R.array.car_ui_ime_wide_screen_allowed_package_list);
+ return Arrays.asList(packages);
+ }
+
+ /**
+ * Returns true if the toolbar can display search result items. One example of this is when the
+ * system is configured to display search items in the IME instead of in the app.
+ */
+ @Override
+ public boolean canShowSearchResultItems() {
+ return isWideScreenMode(mContext);
+ }
+
+ /**
+ * Returns whether or not system is running in a wide screen mode.
+ */
+ private static boolean isWideScreenMode(Context context) {
+ return getBooleanSystemProperty(context.getResources(),
+ R.string.car_ui_ime_wide_screen_system_property_name, false);
+ }
+
+ /**
+ * Returns true if the app is allowed to set search results view.
+ */
+ @Override
+ public boolean canShowSearchResultsView() {
+ boolean allowAppsToHideContentArea = mContext.getResources().getBoolean(
+ R.bool.car_ui_ime_wide_screen_allow_app_hide_content_area);
+ return isWideScreenMode(mContext) && (allowPackageList(mContext).contains(
+ mContext.getPackageName()) || allowAppsToHideContentArea);
+ }
+
+ /**
+ * Add a view within a container that will animate with the wide screen IME to display search
+ * results.
+ *
+ * <p>Note: Apps can only call this method if the package name is allowed via OEM to render
+ * their view. To check if the application have the permission to do so or not first call
+ * {@link #canShowSearchResultsView()}. If the app is not allowed this method will throw an
+ * {@link IllegalStateException}
+ *
+ * @param view to be added in the container.
+ */
+ @Override
+ public void setSearchResultsView(View view) {
+ if (!canShowSearchResultsView()) {
+ throw new IllegalStateException(
+ "not allowed to add view to wide screen IME, package name: "
+ + mContext.getPackageName());
+ }
+
+ if (mSearchView != null) {
+ mSearchView.setViewToImeWideScreenSurface(view);
+ }
+
+ mViewForContentAreaInWideScreenMode = view;
+ }
+
+ /**
+ * Sets list of search item {@link CarUiListItem} to be displayed in the IMS
+ * template. This method should be called when system is running in a wide screen mode. Apps
+ * can check that by using {@link #canShowSearchResultItems()}
+ * Else, this method will throw an {@link IllegalStateException}
+ */
+ @Override
+ public void setSearchResultItems(List<? extends CarUiImeSearchListItem> searchItems) {
+ if (!canShowSearchResultItems()) {
+ throw new IllegalStateException(
+ "system not in wide screen mode, not allowed to set search result items ");
+ }
+ mSearchItems = searchItems;
+ if (mSearchView != null) {
+ mSearchView.setSearchItemsForWideScreen(searchItems);
+ }
+ }
+
+
/** Gets the current {@link Toolbar.State} of the toolbar. */
@Override
public Toolbar.State getState() {
return mState;
}
-
/**
* Registers a new {@link Toolbar.OnHeightChangedListener} to the list of listeners. Register a
* {@link com.android.car.ui.recyclerview.CarUiRecyclerView} only if there is a toolbar at
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java
index b27a81a..ffa191b 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java
@@ -15,13 +15,22 @@
*/
package com.android.car.ui.utils;
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_HORIZONTALLY_SCROLLABLE;
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_VERTICALLY_SCROLLABLE;
+
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
@@ -34,10 +43,18 @@
import androidx.annotation.UiThread;
import androidx.core.view.ViewCompat;
+import java.lang.reflect.Method;
+
/**
* Collection of utility methods
*/
public final class CarUiUtils {
+
+ private static final String TAG = "CarUiUtils";
+ private static final String READ_ONLY_SYSTEM_PROPERTY_PREFIX = "ro.";
+ /** A map to cache read-only system properties. */
+ private static final SparseArray<String> READ_ONLY_SYSTEM_PROPERTY_MAP = new SparseArray<>();
+
/** This is a utility class */
private CarUiUtils() {
}
@@ -46,7 +63,7 @@
* Reads a float value from a dimens resource. This is necessary as {@link Resources#getFloat}
* is not currently public.
*
- * @param res {@link Resources} to read values from
+ * @param res {@link Resources} to read values from
* @param resId Id of the dimens resource to read
*/
public static float getFloat(Resources res, @DimenRes int resId) {
@@ -134,11 +151,11 @@
/**
* Updates the ripple state on the given preference.
*
- * @param isEnabled whether the preference is enabled or not
+ * @param isEnabled whether the preference is enabled or not
* @param shouldShowRippleOnDisabledPreference should ripple be displayed when the preference is
- * clicked
- * @param background drawable that represents the ripple
- * @param preference preference on which drawable will be applied
+ * clicked
+ * @param background drawable that represents the ripple
+ * @param preference preference on which drawable will be applied
*/
public static void updateRippleStateOnDisabledPreference(boolean isEnabled,
boolean shouldShowRippleOnDisabledPreference, Drawable background, View preference) {
@@ -153,6 +170,19 @@
}
/**
+ * Enables rotary scrolling for {@code view}, either vertically (if {@code isVertical} is true)
+ * or horizontally (if {@code isVertical} is false). With rotary scrolling enabled, rotating the
+ * rotary controller will scroll rather than moving the focus when moving the focus would cause
+ * a lot of scrolling. Rotary scrolling should be enabled for scrolling views which contain
+ * content which the user may want to see but can't interact with, either alone or along with
+ * interactive (focusable) content.
+ */
+ public static void setRotaryScrollEnabled(@NonNull View view, boolean isVertical) {
+ view.setContentDescription(
+ isVertical ? ROTARY_VERTICALLY_SCROLLABLE : ROTARY_HORIZONTALLY_SCROLLABLE);
+ }
+
+ /**
* It behaves similarly to {@link View#findViewById(int)}, except that on Q and below,
* it will first resolve the id to whatever it references.
*
@@ -199,4 +229,105 @@
}
return view;
}
+
+ /**
+ * Returns the system property of type boolean. This method converts the boolean value in string
+ * returned by {@link #getSystemProperty(Resources, int)}
+ */
+ public static boolean getBooleanSystemProperty(
+ @NonNull Resources resources, int propertyResId, boolean defaultValue) {
+ String value = getSystemProperty(resources, propertyResId);
+
+ if (!TextUtils.isEmpty(value)) {
+ return Boolean.parseBoolean(value);
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Use reflection to interact with the hidden API <code>android.os.SystemProperties</code>.
+ *
+ * <p>This method caches read-only properties. CAVEAT: Please do not set read-only properties
+ * by 'adb setprop' after app started. Read-only properties CAN BE SET ONCE if it is unset.
+ * Thus, read-only properties MAY BE CHANGED from unset to set during application's lifetime if
+ * you use 'adb setprop' command to set read-only properties after app started. For the sake of
+ * performance, this method also caches the unset state. Otherwise, cache may not effective if
+ * the system property is unset (which is most-likely).
+ *
+ * @param resources resources object to fetch string
+ * @param propertyResId the property resource id.
+ * @return The value of the property if defined, else null. Does not return empty strings.
+ */
+ @Nullable
+ public static String getSystemProperty(@NonNull Resources resources, int propertyResId) {
+ String propertyName = resources.getString(propertyResId);
+ boolean isReadOnly = propertyName.startsWith(READ_ONLY_SYSTEM_PROPERTY_PREFIX);
+ if (!isReadOnly) {
+ return readSystemProperty(propertyName);
+ }
+ synchronized (READ_ONLY_SYSTEM_PROPERTY_MAP) {
+ // readOnlySystemPropertyMap may contain null values.
+ if (READ_ONLY_SYSTEM_PROPERTY_MAP.indexOfKey(propertyResId) >= 0) {
+ return READ_ONLY_SYSTEM_PROPERTY_MAP.get(propertyResId);
+ }
+ String value = readSystemProperty(propertyName);
+ READ_ONLY_SYSTEM_PROPERTY_MAP.put(propertyResId, value);
+ return value;
+ }
+ }
+
+ @Nullable
+ private static String readSystemProperty(String propertyName) {
+ Class<?> systemPropertiesClass;
+ try {
+ systemPropertiesClass = Class.forName("android.os.SystemProperties");
+ } catch (ClassNotFoundException e) {
+ Log.w(TAG, "Cannot find android.os.SystemProperties: ", e);
+ return null;
+ }
+
+ Method getMethod;
+ try {
+ getMethod = systemPropertiesClass.getMethod("get", String.class);
+ } catch (NoSuchMethodException e) {
+ Log.w(TAG, "Cannot find SystemProperties.get(): ", e);
+ return null;
+ }
+
+ try {
+ Object[] params = new Object[]{propertyName};
+ String value = (String) getMethod.invoke(systemPropertiesClass, params);
+ return TextUtils.isEmpty(value) ? null : value;
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to invoke SystemProperties.get(): ", e);
+ return null;
+ }
+ }
+
+ /**
+ * Converts a drawable to bitmap. This value should not be null.
+ */
+ public static Bitmap drawableToBitmap(@NonNull Drawable drawable) {
+ Bitmap bitmap;
+
+ if (drawable instanceof BitmapDrawable) {
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+ if (bitmapDrawable.getBitmap() != null) {
+ return bitmapDrawable.getBitmap();
+ }
+ }
+
+ if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+ bitmap = Bitmap.createBitmap(1, 1,
+ Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
+ } else {
+ bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ }
+
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ }
}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/RotaryConstants.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/RotaryConstants.java
index 99154df..e6c62c9 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/RotaryConstants.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/RotaryConstants.java
@@ -23,19 +23,47 @@
*/
public final class RotaryConstants {
/**
- * Content description indicating that the rotary controller should scroll this view
- * horizontally.
+ * Content description indicating that the view is a rotary container.
+ * <p>
+ * A rotary container contains focusable elements. When initializing focus, the first element
+ * in the rotary container is prioritized to take focus. When searching for nudge target, the
+ * bounds of the rotary container is the minimum bounds containing its descendants.
+ * <p>
+ * A rotary container shouldn't be focusable unless it's a scrollable container. Though it
+ * can't be focused, it can be scrolled as a side-effect of moving the focus within it.
+ */
+ public static final String ROTARY_CONTAINER =
+ "com.android.car.ui.utils.ROTARY_CONTAINER";
+
+ /**
+ * Content description indicating that the view is a scrollable container and can be scrolled
+ * horizontally by the rotary controller.
+ * <p>
+ * A scrollable container is a focusable rotary container. When it's focused, it can be scrolled
+ * when the rotary controller rotates. A scrollable container is often used to show long text.
*/
public static final String ROTARY_HORIZONTALLY_SCROLLABLE =
"com.android.car.ui.utils.HORIZONTALLY_SCROLLABLE";
/**
- * Content description indicating that the rotary controller should scroll this view
- * vertically.
+ * Content description indicating that the view is a scrollable container and can be scrolled
+ * vertically by the rotary controller.
+ * <p>
+ * A scrollable container is a focusable rotary container. When it's focused, it can be scrolled
+ * when the rotary controller rotates. A scrollable container is often used to show long text.
*/
public static final String ROTARY_VERTICALLY_SCROLLABLE =
"com.android.car.ui.utils.VERTICALLY_SCROLLABLE";
+ /**
+ * Content description indicating that the view is a focus delegating container. When
+ * restoring focus, FocusParkingView and FocusArea will skip non-focusable views unless it's
+ * a focus delegating container. The focus delegating container can delegate focus to one of
+ * its descendants.
+ */
+ public static final String ROTARY_FOCUS_DELEGATING_CONTAINER =
+ "com.android.car.ui.utils.FOCUS_DELEGATING_CONTAINER";
+
/** The key to store the offset of the FocusArea's left bound in the node's extras. */
public static final String FOCUS_AREA_LEFT_BOUND_OFFSET =
"com.android.car.ui.utils.FOCUS_AREA_LEFT_BOUND_OFFSET";
@@ -70,7 +98,7 @@
/** Action performed on a FocusArea to move focus to another FocusArea. */
public static final int ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA = 0x02000000;
- /** Action performed on a FocusParkingView to restore the default focus. */
+ /** Action performed on a FocusParkingView to restore the focus in the window. */
public static final int ACTION_RESTORE_DEFAULT_FOCUS = 0x04000000;
/** Action performed on a FocusParkingView to hide the IME. */
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/ViewUtils.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/ViewUtils.java
index 6dd993b..62c189f 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/ViewUtils.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/ViewUtils.java
@@ -16,17 +16,28 @@
package com.android.car.ui.utils;
-import static android.view.View.VISIBLE;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_CONTAINER;
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_FOCUS_DELEGATING_CONTAINER;
import static com.android.car.ui.utils.RotaryConstants.ROTARY_HORIZONTALLY_SCROLLABLE;
import static com.android.car.ui.utils.RotaryConstants.ROTARY_VERTICALLY_SCROLLABLE;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.ui.FocusArea;
+import com.android.car.ui.FocusParkingView;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Utility class used by {@link com.android.car.ui.FocusArea} and {@link
@@ -36,50 +47,350 @@
*/
public final class ViewUtils {
- /** This is a utility class */
+ /**
+ * No view is focused, the focused view is not shown, or the focused view is a FocusParkingView.
+ */
+ public static final int NO_FOCUS = 1;
+
+ /** A scrollable container is focused. */
+ public static final int SCROLLABLE_CONTAINER_FOCUS = 2;
+
+ /**
+ * A regular view is focused. A regular View is a View that is neither a FocusParkingView nor a
+ * scrollable container.
+ */
+ public static final int REGULAR_FOCUS = 3;
+
+ /**
+ * An implicit default focus view (i.e., the first focusable item in a scrollable container) is
+ * focused.
+ */
+ public static final int IMPLICIT_DEFAULT_FOCUS = 4;
+
+ /** The {@code app:defaultFocus} view is focused. */
+ public static final int DEFAULT_FOCUS = 5;
+
+ /** The {@code android:focusedByDefault} view is focused. */
+ public static final int FOCUSED_BY_DEFAULT = 6;
+
+ /**
+ * Focus level of a view. When adjusting the focus, the view with the highest focus level will
+ * be focused.
+ */
+ @IntDef(flag = true, value = {NO_FOCUS, SCROLLABLE_CONTAINER_FOCUS, REGULAR_FOCUS,
+ IMPLICIT_DEFAULT_FOCUS, DEFAULT_FOCUS, FOCUSED_BY_DEFAULT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusLevel {
+ }
+
+ /** This is a utility class. */
private ViewUtils() {
}
/**
- * Searches the {@code view} and its descendants in depth first order, and returns the first
- * view that is focused by default, can take focus, but has no invisible ancestors. Returns null
- * if not found.
+ * This is a functional interface and can therefore be used as the assignment target for a
+ * lambda expression or method reference.
+ *
+ * @param <T> the type of the input to the predicate
+ */
+ private interface Predicate<T> {
+ /** Evaluates this predicate on the given argument. */
+ boolean test(@NonNull T t);
+ }
+
+ /** Gets the ancestor FocusArea of the {@code view}, if any. Returns null if not found. */
+ @Nullable
+ public static FocusArea getAncestorFocusArea(@NonNull View view) {
+ ViewParent parent = view.getParent();
+ while (parent != null) {
+ if (parent instanceof FocusArea) {
+ return (FocusArea) parent;
+ }
+ parent = parent.getParent();
+ }
+ return null;
+ }
+
+ /**
+ * Gets the ancestor scrollable container of the {@code view}, if any. Returns null if not
+ * found.
*/
@Nullable
- public static View findFocusedByDefaultView(@NonNull View view) {
- return depthFirstSearch(view,
- /* targetPredicate= */ v -> v.isFocusedByDefault() && canTakeFocus(v),
- /* skipPredicate= */ v -> v.getVisibility() != VISIBLE);
+ public static ViewGroup getAncestorScrollableContainer(@Nullable View view) {
+ if (view == null) {
+ return null;
+ }
+ ViewParent parent = view.getParent();
+ // A scrollable container can't contain a FocusArea, so let's return earlier if we found
+ // a FocusArea.
+ while (parent != null && parent instanceof ViewGroup && !(parent instanceof FocusArea)) {
+ ViewGroup viewGroup = (ViewGroup) parent;
+ if (isScrollableContainer(viewGroup)) {
+ return viewGroup;
+ }
+ parent = parent.getParent();
+ }
+ return null;
+ }
+
+ /**
+ * Focuses on the {@code view} if it can be focused.
+ *
+ * @return whether it was successfully focused or already focused
+ */
+ public static boolean requestFocus(@Nullable View view) {
+ if (view == null || !canTakeFocus(view)) {
+ return false;
+ }
+ if (view.isFocused()) {
+ return true;
+ }
+ // Exit touch mode and focus the view. The view may not be focusable in touch mode, so we
+ // need to exit touch mode before focusing it.
+ return view.performAccessibilityAction(ACTION_FOCUS, /* arguments= */ null);
+ }
+
+ /**
+ * Searches the {@code root}'s descendants for a view with the highest {@link FocusLevel}. If
+ * the view's FocusLevel is higher than the {@code currentFocus}'s FocusLevel, focuses on the
+ * view.
+ *
+ * @return whether the view is focused
+ */
+ public static boolean adjustFocus(@NonNull View root, @Nullable View currentFocus) {
+ @FocusLevel int level = getFocusLevel(currentFocus);
+ return adjustFocus(root, level);
+ }
+
+ /**
+ * Searches the {@code root}'s descendants for a view with the highest {@link FocusLevel}. If
+ * the view's FocusLevel is higher than {@code currentLevel}, focuses on the view.
+ *
+ * @return whether the view is focused
+ */
+ public static boolean adjustFocus(@NonNull View root, @FocusLevel int currentLevel) {
+ if (currentLevel < FOCUSED_BY_DEFAULT && focusOnFocusedByDefaultView(root)) {
+ return true;
+ }
+ if (currentLevel < DEFAULT_FOCUS && focusOnDefaultFocusView(root)) {
+ return true;
+ }
+ if (currentLevel < IMPLICIT_DEFAULT_FOCUS && focusOnImplicitDefaultFocusView(root)) {
+ return true;
+ }
+ if (currentLevel < REGULAR_FOCUS && focusOnFirstRegularView(root)) {
+ return true;
+ }
+ if (currentLevel < SCROLLABLE_CONTAINER_FOCUS) {
+ return focusOnScrollableContainer(root);
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ @FocusLevel
+ static int getFocusLevel(@Nullable View view) {
+ if (view == null || view instanceof FocusParkingView || !view.isShown()) {
+ return NO_FOCUS;
+ }
+ if (view.isFocusedByDefault()) {
+ return FOCUSED_BY_DEFAULT;
+ }
+ if (isDefaultFocus(view)) {
+ return DEFAULT_FOCUS;
+ }
+ if (isImplicitDefaultFocusView(view)) {
+ return IMPLICIT_DEFAULT_FOCUS;
+ }
+ if (isScrollableContainer(view)) {
+ return SCROLLABLE_CONTAINER_FOCUS;
+ }
+ return REGULAR_FOCUS;
+ }
+
+ /** Returns whether the {@code view} is a {@code app:defaultFocus} view. */
+ private static boolean isDefaultFocus(@NonNull View view) {
+ FocusArea parent = getAncestorFocusArea(view);
+ return parent != null && view == parent.getDefaultFocusView();
+ }
+
+ /**
+ * Returns whether the {@code view} is an implicit default focus view, i.e., the first focusable
+ * item in a rotary container.
+ */
+ @VisibleForTesting
+ static boolean isImplicitDefaultFocusView(@NonNull View view) {
+ ViewGroup rotaryContainer = null;
+ ViewParent parent = view.getParent();
+ while (parent != null && parent instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) parent;
+ if (isRotaryContainer(viewGroup)) {
+ rotaryContainer = viewGroup;
+ break;
+ }
+ parent = parent.getParent();
+ }
+ if (rotaryContainer == null) {
+ return false;
+ }
+ return findFirstFocusableDescendant(rotaryContainer) == view;
+ }
+
+ private static boolean isRotaryContainer(@NonNull View view) {
+ CharSequence contentDescription = view.getContentDescription();
+ return TextUtils.equals(contentDescription, ROTARY_CONTAINER)
+ || TextUtils.equals(contentDescription, ROTARY_VERTICALLY_SCROLLABLE)
+ || TextUtils.equals(contentDescription, ROTARY_HORIZONTALLY_SCROLLABLE);
+ }
+
+ private static boolean isScrollableContainer(@NonNull View view) {
+ CharSequence contentDescription = view.getContentDescription();
+ return TextUtils.equals(contentDescription, ROTARY_VERTICALLY_SCROLLABLE)
+ || TextUtils.equals(contentDescription, ROTARY_HORIZONTALLY_SCROLLABLE);
+ }
+
+ private static boolean isFocusDelegatingContainer(@NonNull View view) {
+ CharSequence contentDescription = view.getContentDescription();
+ return TextUtils.equals(contentDescription, ROTARY_FOCUS_DELEGATING_CONTAINER);
+ }
+
+ /**
+ * Focuses on the first {@code app:defaultFocus} view in the view tree, if any.
+ *
+ * @param root the root of the view tree
+ * @return whether succeeded
+ */
+ private static boolean focusOnDefaultFocusView(@NonNull View root) {
+ View defaultFocus = findDefaultFocusView(root);
+ return requestFocus(defaultFocus);
+ }
+
+ /**
+ * Focuses on the first {@code android:focusedByDefault} view in the view tree, if any.
+ *
+ * @param root the root of the view tree
+ * @return whether succeeded
+ */
+ private static boolean focusOnFocusedByDefaultView(@NonNull View root) {
+ View focusedByDefault = findFocusedByDefaultView(root);
+ return requestFocus(focusedByDefault);
+ }
+
+ /**
+ * Focuses on the first implicit default focus view in the view tree, if any.
+ *
+ * @param root the root of the view tree
+ * @return whether succeeded
+ */
+ private static boolean focusOnImplicitDefaultFocusView(@NonNull View root) {
+ View implicitDefaultFocus = findImplicitDefaultFocusView(root);
+ return requestFocus(implicitDefaultFocus);
+ }
+
+ /**
+ * Tries to focus on the first focusable view in the view tree in depth first order, excluding
+ * the FocusParkingView and scrollable containers. If focusing on the first such view fails,
+ * keeps trying other views in depth first order until succeeds or there are no more such views.
+ *
+ * @param root the root of the view tree
+ * @return whether succeeded
+ */
+ private static boolean focusOnFirstRegularView(@NonNull View root) {
+ View focusedView = ViewUtils.depthFirstSearch(root,
+ /* targetPredicate= */
+ v -> !isScrollableContainer(v) && canTakeFocus(v) && requestFocus(v),
+ /* skipPredicate= */ v -> !v.isShown());
+ return focusedView != null;
+ }
+
+ /**
+ * Focuses on the first scrollable container in the view tree, if any.
+ *
+ * @param root the root of the view tree
+ * @return whether succeeded
+ */
+ private static boolean focusOnScrollableContainer(@NonNull View root) {
+ View focusedView = ViewUtils.depthFirstSearch(root,
+ /* targetPredicate= */ v -> isScrollableContainer(v) && canTakeFocus(v),
+ /* skipPredicate= */ v -> !v.isShown());
+ return requestFocus(focusedView);
+ }
+
+ /**
+ * Searches the {@code root}'s descendants in depth first order, and returns the first
+ * {@code app:defaultFocus} view that can take focus. Returns null if not found.
+ */
+ @Nullable
+ private static View findDefaultFocusView(@NonNull View view) {
+ if (!view.isShown()) {
+ return null;
+ }
+ if (view instanceof FocusArea) {
+ FocusArea focusArea = (FocusArea) view;
+ View defaultFocus = focusArea.getDefaultFocusView();
+ if (defaultFocus != null && canTakeFocus(defaultFocus)) {
+ return defaultFocus;
+ }
+ } else if (view instanceof ViewGroup) {
+ ViewGroup parent = (ViewGroup) view;
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ View child = parent.getChildAt(i);
+ View defaultFocus = findDefaultFocusView(child);
+ if (defaultFocus != null) {
+ return defaultFocus;
+ }
+ }
+ }
+ return null;
}
/**
* Searches the {@code view} and its descendants in depth first order, and returns the first
- * primary focus view, i.e., the first focusable item in a scrollable container. Returns null
- * if not found.
+ * {@code android:focusedByDefault} view that can take focus. Returns null if not found.
*/
- public static View findPrimaryFocusView(@NonNull View view) {
- View scrollableContainer = findScrollableContainer(view);
- return scrollableContainer == null ? null : findFocusableDescendant(scrollableContainer);
+ @VisibleForTesting
+ @Nullable
+ static View findFocusedByDefaultView(@NonNull View view) {
+ return depthFirstSearch(view,
+ /* targetPredicate= */ v -> v.isFocusedByDefault() && canTakeFocus(v),
+ /* skipPredicate= */ v -> !v.isShown());
+ }
+
+ /**
+ * Searches the {@code view} and its descendants in depth first order, and returns the first
+ * implicit default focus view, i.e., the first focusable item in the first rotary container.
+ * Returns null if not found.
+ */
+ @VisibleForTesting
+ @Nullable
+ static View findImplicitDefaultFocusView(@NonNull View view) {
+ View rotaryContainer = findRotaryContainer(view);
+ return rotaryContainer == null
+ ? null
+ : findFirstFocusableDescendant(rotaryContainer);
}
/**
* Searches the {@code view}'s descendants in depth first order, and returns the first view
- * that can take focus but has no invisible ancestors, or null if not found.
+ * that can take focus, or null if not found.
*/
+ @VisibleForTesting
@Nullable
- public static View findFocusableDescendant(@NonNull View view) {
+ static View findFirstFocusableDescendant(@NonNull View view) {
return depthFirstSearch(view,
/* targetPredicate= */ v -> v != view && canTakeFocus(v),
- /* skipPredicate= */ v -> v.getVisibility() != VISIBLE);
+ /* skipPredicate= */ v -> !v.isShown());
}
/**
* Searches the {@code view} and its descendants in depth first order, and returns the first
- * view that meets the given condition. Returns null if not found.
+ * rotary container shown on the screen. Returns null if not found.
*/
@Nullable
- public static View depthFirstSearch(@NonNull View view, @NonNull Predicate<View> predicate) {
- return depthFirstSearch(view, predicate, /* skipPredicate= */ null);
+ private static View findRotaryContainer(@NonNull View view) {
+ return depthFirstSearch(view,
+ /* targetPredicate= */ v -> isRotaryContainer(v),
+ /* skipPredicate= */ v -> !v.isShown());
}
/**
@@ -90,7 +401,7 @@
@Nullable
private static View depthFirstSearch(@NonNull View view,
@NonNull Predicate<View> targetPredicate,
- @NonNull Predicate<View> skipPredicate) {
+ @Nullable Predicate<View> skipPredicate) {
if (skipPredicate != null && skipPredicate.test(view)) {
return null;
}
@@ -110,35 +421,14 @@
return null;
}
- /**
- * This is a functional interface and can therefore be used as the assignment target for a
- * lambda expression or method reference.
- *
- * @param <T> the type of the input to the predicate
- */
- public interface Predicate<T> {
- /** Evaluates this predicate on the given argument. */
- boolean test(@NonNull T t);
- }
-
- /**
- * Searches the {@code view} and its descendants in depth first order, and returns the first
- * scrollable container that has no invisible ancestors. Returns null if not found.
- */
- @Nullable
- private static View findScrollableContainer(@NonNull View view) {
- return depthFirstSearch(view,
- /* targetPredicate= */ v -> {
- CharSequence contentDescription = v.getContentDescription();
- return TextUtils.equals(contentDescription, ROTARY_VERTICALLY_SCROLLABLE)
- || TextUtils.equals(contentDescription, ROTARY_HORIZONTALLY_SCROLLABLE);
- },
- /* skipPredicate= */ v -> v.getVisibility() != VISIBLE);
- }
-
/** Returns whether {@code view} can be focused. */
private static boolean canTakeFocus(@NonNull View view) {
- return view.isFocusable() && view.isEnabled() && view.getVisibility() == VISIBLE
- && view.getWidth() > 0 && view.getHeight() > 0;
+ boolean focusable = view.isFocusable() || isFocusDelegatingContainer(view);
+ return focusable && view.isEnabled() && view.isShown()
+ && view.getWidth() > 0 && view.getHeight() > 0 && view.isAttachedToWindow()
+ && !(view instanceof FocusParkingView)
+ // If it's a scrollable container, it can be focused only when it has no focusable
+ // descendants. We focus on it so that the rotary controller can scroll it.
+ && (!isScrollableContainer(view) || findFirstFocusableDescendant(view) == null);
}
}
diff --git a/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml b/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml
index e9b706d..3ad5398 100644
--- a/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml
@@ -16,6 +16,7 @@
<resources>
<overlayable name="car-ui-lib">
<policy type="public">
+ <item type="array" name="car_ui_ime_wide_screen_allowed_package_list"/>
<item type="attr" name="CarUiToolbarStyle"/>
<item type="attr" name="barrierDirection"/>
<item type="attr" name="carUiPreferenceStyle"/>
@@ -70,11 +71,14 @@
<item type="attr" name="layout_optimizationLevel"/>
<item type="attr" name="state_ux_restricted"/>
<item type="attr" name="title"/>
+ <item type="bool" name="car_ui_alert_dialog_force_dismiss_button"/>
<item type="bool" name="car_ui_clear_focus_area_history_when_rotating"/>
<item type="bool" name="car_ui_enable_focus_area_background_highlight"/>
<item type="bool" name="car_ui_enable_focus_area_foreground_highlight"/>
<item type="bool" name="car_ui_escrow_check_components_automatically"/>
<item type="bool" name="car_ui_focus_area_default_focus_overrides_history"/>
+ <item type="bool" name="car_ui_ime_wide_screen_aligned_left"/>
+ <item type="bool" name="car_ui_ime_wide_screen_allow_app_hide_content_area"/>
<item type="bool" name="car_ui_list_item_single_line_title"/>
<item type="bool" name="car_ui_preference_list_show_full_screen"/>
<item type="bool" name="car_ui_preference_show_chevron"/>
@@ -87,6 +91,12 @@
<item type="color" name="car_ui_activity_background_color"/>
<item type="color" name="car_ui_color_accent"/>
<item type="color" name="car_ui_dialog_icon_color"/>
+ <item type="color" name="car_ui_ime_wide_screen_description_color"/>
+ <item type="color" name="car_ui_ime_wide_screen_description_title_color"/>
+ <item type="color" name="car_ui_ime_wide_screen_divider_color"/>
+ <item type="color" name="car_ui_ime_wide_screen_error_text_color"/>
+ <item type="color" name="car_ui_ime_wide_screen_search_item_sub_title_color"/>
+ <item type="color" name="car_ui_ime_wide_screen_search_item_title_color"/>
<item type="color" name="car_ui_list_item_divider"/>
<item type="color" name="car_ui_preference_icon_color"/>
<item type="color" name="car_ui_preference_two_action_divider_color"/>
@@ -94,6 +104,8 @@
<item type="color" name="car_ui_ripple_color"/>
<item type="color" name="car_ui_rotary_focus_fill_color"/>
<item type="color" name="car_ui_rotary_focus_fill_secondary_color"/>
+ <item type="color" name="car_ui_rotary_focus_pressed_fill_color"/>
+ <item type="color" name="car_ui_rotary_focus_pressed_stroke_color"/>
<item type="color" name="car_ui_rotary_focus_stroke_color"/>
<item type="color" name="car_ui_rotary_focus_stroke_secondary_color"/>
<item type="color" name="car_ui_scrollbar_thumb"/>
@@ -120,6 +132,37 @@
<item type="dimen" name="car_ui_dialog_title_margin"/>
<item type="dimen" name="car_ui_divider_width"/>
<item type="dimen" name="car_ui_header_list_item_text_start_margin"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_action_button_height"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_action_button_margin_bottom"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_action_button_margin_left"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_action_button_text_size"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_description_padding_top"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_description_text_size"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_description_title_margin_top"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_description_title_padding_left"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_description_title_text_size"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_divider_width"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_error_text_padding_start"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_error_text_size"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_input_area_height"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_input_area_margin_top"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_input_edit_text_padding_left"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_input_edit_text_padding_right"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_input_edit_text_size"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_input_padding_start"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_keyboard_area_padding_bottom"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_keyboard_area_padding_end"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_keyboard_area_padding_start"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_keyboard_width"/>
+ <item type="dimen" name="car_ui_ime_wide_screen_recycler_view_padding_top"/>
+ <item type="dimen" name="car_ui_ime_wide_search_item_icon_size"/>
+ <item type="dimen" name="car_ui_ime_wide_search_item_secondary_image_padding_left"/>
+ <item type="dimen" name="car_ui_ime_wide_search_item_sub_title_padding_left"/>
+ <item type="dimen" name="car_ui_ime_wide_search_item_sub_title_padding_top"/>
+ <item type="dimen" name="car_ui_ime_wide_search_item_sub_title_text_size"/>
+ <item type="dimen" name="car_ui_ime_wide_search_item_title_padding_left"/>
+ <item type="dimen" name="car_ui_ime_wide_search_item_title_padding_top"/>
+ <item type="dimen" name="car_ui_ime_wide_search_item_title_text_size"/>
<item type="dimen" name="car_ui_list_item_action_divider_height"/>
<item type="dimen" name="car_ui_list_item_action_divider_width"/>
<item type="dimen" name="car_ui_list_item_avatar_icon_height"/>
@@ -173,6 +216,7 @@
<item type="dimen" name="car_ui_recyclerview_divider_height"/>
<item type="dimen" name="car_ui_recyclerview_divider_start_margin"/>
<item type="dimen" name="car_ui_recyclerview_divider_top_margin"/>
+ <item type="dimen" name="car_ui_rotary_focus_pressed_stroke_width"/>
<item type="dimen" name="car_ui_rotary_focus_stroke_width"/>
<item type="dimen" name="car_ui_scrollbar_button_size"/>
<item type="dimen" name="car_ui_scrollbar_container_width"/>
@@ -231,12 +275,19 @@
<item type="drawable" name="car_ui_icon_delete"/>
<item type="drawable" name="car_ui_icon_down"/>
<item type="drawable" name="car_ui_icon_edit"/>
+ <item type="drawable" name="car_ui_icon_error"/>
<item type="drawable" name="car_ui_icon_lock"/>
<item type="drawable" name="car_ui_icon_overflow_menu"/>
<item type="drawable" name="car_ui_icon_save"/>
<item type="drawable" name="car_ui_icon_search"/>
<item type="drawable" name="car_ui_icon_search_nav_icon"/>
<item type="drawable" name="car_ui_icon_settings"/>
+ <item type="drawable" name="car_ui_ime_wide_screen_background"/>
+ <item type="drawable" name="car_ui_ime_wide_screen_content_area_background"/>
+ <item type="drawable" name="car_ui_ime_wide_screen_input_area_background"/>
+ <item type="drawable" name="car_ui_ime_wide_screen_input_area_tint_color"/>
+ <item type="drawable" name="car_ui_ime_wide_screen_input_area_tint_error_color"/>
+ <item type="drawable" name="car_ui_ime_wide_screen_no_content_background"/>
<item type="drawable" name="car_ui_list_header_background"/>
<item type="drawable" name="car_ui_list_item_avatar_icon_outline"/>
<item type="drawable" name="car_ui_list_item_background"/>
@@ -245,6 +296,7 @@
<item type="drawable" name="car_ui_preference_icon_chevron"/>
<item type="drawable" name="car_ui_preference_icon_chevron_disabled"/>
<item type="drawable" name="car_ui_preference_icon_chevron_enabled"/>
+ <item type="drawable" name="car_ui_recycler_view_ime_wide_screen_thumb"/>
<item type="drawable" name="car_ui_recyclerview_button_ripple_background"/>
<item type="drawable" name="car_ui_recyclerview_divider"/>
<item type="drawable" name="car_ui_recyclerview_ic_down"/>
@@ -266,7 +318,14 @@
<item type="id" name="car_ui_alert_subtitle"/>
<item type="id" name="car_ui_alert_title"/>
<item type="id" name="car_ui_base_layout_content_container"/>
+ <item type="id" name="car_ui_closeKeyboard"/>
+ <item type="id" name="car_ui_contentAreaAutomotive"/>
<item type="id" name="car_ui_focus_area"/>
+ <item type="id" name="car_ui_fullscreenArea"/>
+ <item type="id" name="car_ui_imeWideScreenInputArea"/>
+ <item type="id" name="car_ui_ime_surface"/>
+ <item type="id" name="car_ui_inputExtractActionAutomotive"/>
+ <item type="id" name="car_ui_inputExtractEditTextContainer"/>
<item type="id" name="car_ui_list_item_end_guideline"/>
<item type="id" name="car_ui_list_item_start_guideline"/>
<item type="id" name="car_ui_list_limiting_message"/>
@@ -310,6 +369,14 @@
<item type="id" name="car_ui_toolbar_title_logo"/>
<item type="id" name="car_ui_toolbar_title_logo_container"/>
<item type="id" name="car_ui_toolbar_top_guideline"/>
+ <item type="id" name="car_ui_wideScreenClearData"/>
+ <item type="id" name="car_ui_wideScreenDescription"/>
+ <item type="id" name="car_ui_wideScreenDescriptionTitle"/>
+ <item type="id" name="car_ui_wideScreenError"/>
+ <item type="id" name="car_ui_wideScreenErrorMessage"/>
+ <item type="id" name="car_ui_wideScreenExtractedTextIcon"/>
+ <item type="id" name="car_ui_wideScreenInputArea"/>
+ <item type="id" name="car_ui_wideScreenSearchResultList"/>
<item type="id" name="checkbox_widget"/>
<item type="id" name="container"/>
<item type="id" name="content_icon"/>
@@ -351,6 +418,7 @@
<item type="layout" name="car_ui_base_layout_toolbar"/>
<item type="layout" name="car_ui_base_layout_toolbar_legacy"/>
<item type="layout" name="car_ui_header_list_item"/>
+ <item type="layout" name="car_ui_ims_wide_screen_input_view"/>
<item type="layout" name="car_ui_list_item"/>
<item type="layout" name="car_ui_list_limiting_message"/>
<item type="layout" name="car_ui_list_preference"/>
@@ -372,6 +440,7 @@
<item type="layout" name="car_ui_seekbar_dialog"/>
<item type="layout" name="car_ui_toolbar"/>
<item type="layout" name="car_ui_toolbar_menu_item"/>
+ <item type="layout" name="car_ui_toolbar_menu_item_primary"/>
<item type="layout" name="car_ui_toolbar_search_view"/>
<item type="layout" name="car_ui_toolbar_tab_item"/>
<item type="layout" name="car_ui_toolbar_tab_item_flexible"/>
@@ -383,6 +452,7 @@
<item type="string" name="car_ui_dialog_preference_negative"/>
<item type="string" name="car_ui_dialog_preference_positive"/>
<item type="string" name="car_ui_ellipsis"/>
+ <item type="string" name="car_ui_ime_wide_screen_system_property_name"/>
<item type="string" name="car_ui_installer_process_name"/>
<item type="string" name="car_ui_preference_switch_off"/>
<item type="string" name="car_ui_preference_switch_on"/>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_error.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_error.xml
new file mode 100644
index 0000000..b0f4a34
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_icon_error.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M15.73,3L8.27,3L3,8.27v7.46L8.27,21h7.46L21,15.73L21,8.27L15.73,3zM12,17.3c-0.72,0 -1.3,-0.58 -1.3,-1.3 0,-0.72 0.58,-1.3 1.3,-1.3 0.72,0 1.3,0.58 1.3,1.3 0,0.72 -0.58,1.3 -1.3,1.3zM13,13h-2L11,7h2v6z"
+ android:fillColor="#F00"/>
+</vector>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_item_background.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_item_background.xml
index f10416e..4d33475 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_item_background.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_list_item_background.xml
@@ -13,22 +13,20 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:state_pressed="true">
+ <shape android:shape="rectangle">
+ <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
+ <stroke android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
+ android:color="@color/car_ui_rotary_focus_pressed_stroke_color"/>
+ </shape>
+ </item>
<item android:state_focused="true">
- <layer-list>
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <stroke android:width="@dimen/car_ui_rotary_focus_stroke_width"
- android:color="@color/car_ui_rotary_focus_stroke_color"/>
- </shape>
- </item>
- </layer-list>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
+ <stroke android:width="@dimen/car_ui_rotary_focus_stroke_width"
+ android:color="@color/car_ui_rotary_focus_stroke_color"/>
+ </shape>
</item>
<item>
<ripple android:color="?android:attr/colorControlHighlight">
diff --git a/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_recycler_view_ime_wide_screen_thumb.xml b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_recycler_view_ime_wide_screen_thumb.xml
new file mode 100644
index 0000000..5cb78e4
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/drawable/car_ui_recycler_view_ime_wide_screen_thumb.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <gradient android:startColor="#60A8F0" android:endColor="#60A8F0"
+ android:angle="45"/>
+ <corners android:radius="6dp" />
+</shape>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_alert_dialog_edit_text.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_alert_dialog_edit_text.xml
index d654b2b..985b7d8 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_alert_dialog_edit_text.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_alert_dialog_edit_text.xml
@@ -16,7 +16,7 @@
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android">
- <EditText
+ <com.android.car.ui.toolbar.CarUiEditText
android:id="@+id/textbox"
android:layout_width="match_parent"
android:layout_height="@dimen/car_ui_dialog_edittext_height"
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_header_list_item.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_header_list_item.xml
index 51204de..7ac4732 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_header_list_item.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_header_list_item.xml
@@ -46,6 +46,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/car_ui_header_list_item_text_start_margin"
+ android:textDirection="locale"
android:textAppearance="@style/TextAppearance.CarUi.ListItem.Header" />
<TextView
@@ -53,6 +54,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/car_ui_list_item_text_no_icon_start_margin"
+ android:textDirection="locale"
android:textAppearance="@style/TextAppearance.CarUi.ListItem.Body" />
</LinearLayout>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_ims_wide_screen_input_view.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_ims_wide_screen_input_view.xml
new file mode 100644
index 0000000..fd7539b
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_ims_wide_screen_input_view.xml
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <RelativeLayout
+ android:layout_width="@dimen/car_ui_ime_wide_screen_keyboard_width"
+ android:layout_height="match_parent"
+ android:gravity="bottom"
+ android:paddingStart="@dimen/car_ui_ime_wide_screen_keyboard_area_padding_start"
+ android:paddingEnd="@dimen/car_ui_ime_wide_screen_keyboard_area_padding_end"
+ android:paddingBottom="@dimen/car_ui_ime_wide_screen_keyboard_area_padding_bottom"
+ android:layout_gravity="bottom"
+ android:background="@drawable/car_ui_ime_wide_screen_background">
+
+ <RelativeLayout
+ android:id="@id/car_ui_imeWideScreenInputArea"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_ui_ime_wide_screen_input_area_height"
+ android:layout_marginTop="@dimen/car_ui_ime_wide_screen_input_area_margin_top"
+ android:layout_alignParentTop="true">
+ <ImageView
+ android:id="@id/car_ui_closeKeyboard"
+ android:layout_width="@dimen/car_ui_primary_icon_size"
+ android:layout_height="@dimen/car_ui_primary_icon_size"
+ android:layout_centerVertical="true"
+ style="@style/Widget.CarUi.Toolbar.NavIcon"
+ android:layout_alignParentLeft="true"/>
+
+ <FrameLayout
+ android:id="@id/car_ui_fullscreenArea"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_centerVertical="true"
+ android:layout_toRightOf="@id/car_ui_closeKeyboard"
+ android:paddingLeft="@dimen/car_ui_ime_wide_screen_input_padding_start"
+ android:background="@drawable/car_ui_ime_wide_screen_input_area_background"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@id/car_ui_inputExtractEditTextContainer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:scrollbars="vertical"
+ android:gravity="left|center"
+ android:backgroundTint="@drawable/car_ui_ime_wide_screen_input_area_tint_color"
+ android:minLines="1"
+ android:inputType="text"/>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent">
+ <ImageView
+ android:id="@id/car_ui_wideScreenExtractedTextIcon"
+ android:layout_width="@dimen/car_ui_primary_icon_size"
+ android:layout_height="@dimen/car_ui_primary_icon_size"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"/>
+
+ <ImageView
+ android:id="@id/car_ui_wideScreenClearData"
+ android:layout_width="@dimen/car_ui_primary_icon_size"
+ android:layout_height="@dimen/car_ui_primary_icon_size"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:background="@drawable/car_ui_icon_close"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"/>
+
+ <ImageView
+ android:id="@id/car_ui_wideScreenError"
+ android:layout_width="@dimen/car_ui_primary_icon_size"
+ android:layout_height="@dimen/car_ui_primary_icon_size"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:background="@drawable/car_ui_icon_error"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:visibility="gone"/>
+ </RelativeLayout>
+
+ </FrameLayout>
+ </RelativeLayout>
+
+ <TextView
+ android:id="@id/car_ui_wideScreenErrorMessage"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_below="@id/car_ui_imeWideScreenInputArea"
+ android:paddingLeft="@dimen/car_ui_ime_wide_screen_error_text_padding_start"
+ android:textColor="@color/car_ui_ime_wide_screen_error_text_color"
+ android:visibility="gone"
+ android:textSize="@dimen/car_ui_ime_wide_screen_error_text_size"/>
+
+ <FrameLayout
+ android:id="@id/car_ui_wideScreenInputArea"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true">
+ </FrameLayout>
+ </RelativeLayout>
+
+ <View
+ android:layout_width="@dimen/car_ui_ime_wide_screen_divider_width"
+ android:layout_height="match_parent"
+ android:background="@color/car_ui_ime_wide_screen_divider_color"/>
+
+ <SurfaceView
+ android:id="@id/car_ui_ime_surface"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:focusable="false"/>
+
+ <RelativeLayout
+ android:id="@id/car_ui_contentAreaAutomotive"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:background="@drawable/car_ui_ime_wide_screen_no_content_background">
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@id/car_ui_wideScreenSearchResultList"
+ android:scrollbarThumbVertical="@drawable/car_ui_recycler_view_ime_wide_screen_thumb"
+ android:scrollbars="vertical"
+ android:requiresFadingEdge="vertical"
+ android:paddingTop="@dimen/car_ui_ime_wide_screen_recycler_view_padding_top"
+ android:layout_width="match_parent"
+ android:visibility="gone"
+ android:layout_height="match_parent"/>
+ <TextView
+ android:id="@id/car_ui_wideScreenDescriptionTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_marginTop="@dimen/car_ui_ime_wide_screen_description_title_margin_top"
+ android:paddingLeft="@dimen/car_ui_ime_wide_screen_description_title_padding_left"
+ android:textColor="@color/car_ui_ime_wide_screen_description_title_color"
+ android:textSize="@dimen/car_ui_ime_wide_screen_description_title_text_size"
+ android:visibility="gone"/>
+ <TextView
+ android:id="@id/car_ui_wideScreenDescription"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/car_ui_wideScreenDescriptionTitle"
+ android:layout_alignParentLeft="true"
+ android:paddingLeft="@dimen/car_ui_ime_wide_screen_description_title_padding_left"
+ android:paddingTop="@dimen/car_ui_ime_wide_screen_description_padding_top"
+ android:textColor="@color/car_ui_ime_wide_screen_description_color"
+ android:textSize="@dimen/car_ui_ime_wide_screen_description_text_size"
+ android:visibility="gone"/>
+
+ <Button
+ android:id="@id/car_ui_inputExtractActionAutomotive"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/car_ui_ime_wide_screen_action_button_height"
+ android:theme="@android:style/Theme.DeviceDefault"
+ android:textSize="@dimen/car_ui_ime_wide_screen_action_button_text_size"
+ android:layout_alignParentBottom="true"
+ android:visibility="gone"
+ android:layout_marginBottom="@dimen/car_ui_ime_wide_screen_action_button_margin_bottom"
+ android:layout_marginLeft="@dimen/car_ui_ime_wide_screen_action_button_margin_left"
+ android:layout_gravity="center"/>
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item.xml
index 8b5b281..d0f179e 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item.xml
@@ -109,12 +109,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="@bool/car_ui_list_item_single_line_title"
+ android:textDirection="locale"
android:textAppearance="@style/TextAppearance.CarUi.ListItem" />
<TextView
android:id="@+id/body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:textDirection="locale"
android:textAppearance="@style/TextAppearance.CarUi.ListItem.Body" />
</LinearLayout>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_preference.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_preference.xml
index 70bf96d..d56fb38 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_preference.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_preference.xml
@@ -22,11 +22,16 @@
android:layout_height="match_parent"
android:background="@drawable/car_ui_activity_background">
- <com.android.car.ui.recyclerview.CarUiRecyclerView
- android:id="@+id/list"
+ <com.android.car.ui.FocusArea
+ android:id="@+id/car_ui_focus_area"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:tag="carUiPreferenceRecyclerView"
- app:enableDivider="true" />
+ android:layout_height="match_parent">
+ <com.android.car.ui.recyclerview.CarUiRecyclerView
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:tag="carUiPreferenceRecyclerView"
+ app:enableDivider="true" />
+ </com.android.car.ui.FocusArea>
</FrameLayout>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_preference_with_toolbar.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_preference_with_toolbar.xml
index f42c9f1..c83528b 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_preference_with_toolbar.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_preference_with_toolbar.xml
@@ -16,23 +16,32 @@
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/car_ui_activity_background">
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/car_ui_activity_background">
- <com.android.car.ui.recyclerview.CarUiRecyclerView
- android:id="@+id/list"
+ <com.android.car.ui.FocusArea
+ android:id="@+id/car_ui_focus_area"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:tag="carUiPreferenceRecyclerView"
- app:enableDivider="true" />
+ android:layout_height="match_parent">
+ <com.android.car.ui.recyclerview.CarUiRecyclerView
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:tag="carUiPreferenceRecyclerView"
+ app:enableDivider="true"/>
+ </com.android.car.ui.FocusArea>
- <com.android.car.ui.toolbar.Toolbar
- android:id="@+id/toolbar"
+ <com.android.car.ui.FocusArea
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:car_ui_state="subpage" />
+ android:layout_height="match_parent">
+ <com.android.car.ui.toolbar.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:car_ui_state="subpage"/>
+ </com.android.car.ui.FocusArea>
</FrameLayout>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_dialog_edittext.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_dialog_edittext.xml
index 04c1c37..86474a9 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_dialog_edittext.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_dialog_edittext.xml
@@ -33,7 +33,7 @@
android:layout_marginEnd="@dimen/car_ui_preference_edit_text_dialog_message_margin_end"
android:visibility="gone"/>
- <EditText
+ <com.android.car.ui.toolbar.CarUiEditText
android:id="@android:id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view.xml
index 1aa70fa..a0c955f 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view.xml
@@ -20,7 +20,7 @@
<com.android.car.ui.recyclerview.CarUiRecyclerViewContainer
android:id="@+id/car_ui_recycler_view"
- android:layout_width="0dp"
+ android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="@dimen/car_ui_scrollbar_margin"
android:tag="carUiRecyclerView"
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_search_view.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_search_view.xml
index c3ad68d..6a3d9ec 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_search_view.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_search_view.xml
@@ -19,7 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
- <EditText
+ <com.android.car.ui.toolbar.CarUiEditText
android:id="@+id/car_ui_toolbar_search_bar"
android:layout_height="match_parent"
android:layout_width="match_parent"
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml b/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml
index e823bdf..0ca5abe 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml
@@ -113,6 +113,13 @@
<!-- Bottom offset for car ui recycler view for linear layout. -->
<attr name="bottomOffset" format="integer" />
+ <!-- Whether to enable rotary scrolling. Disabled by default. With rotary scrolling enabled,
+ rotating the rotary controller will scroll rather than moving the focus when moving the
+ focus would cause a lot of scrolling. Rotary scrolling should be enabled when the recycler
+ view contains content which the user may want to see but can't interact with, either alone
+ or along with interactive (focusable) content. -->
+ <attr name="rotaryScrollEnabled" format="boolean" />
+
<!-- Number of columns in a grid layout. -->
<attr name="numOfColumns" format="integer" />
@@ -147,9 +154,30 @@
<!-- Attributes for FocusArea. -->
<declare-styleable name="FocusArea">
- <!-- The ID of a focusable descendant view which should be focused when the user nudges to
- this FocusArea, if there was no view focused in the FocusArea or
- car_ui_focus_area_default_focus_overrides_history is true. -->
+ <!-- The ID of the default focus view. The view will be prioritized when searching for a
+ focus target.
+ (1) When the user nudges the rotary controller, it will search for a target FocusArea,
+ then search for a target view within the target FocusArea, and focus on the target
+ view. The target view is chosen in the following order:
+ 1. the "android:focusedByDefault" view, if any
+ 2. the "app:defaultFocus" view, if any
+ 3. the first focusable item in a scrollable container, if any
+ 4. previously focused view, if any and the cache is not stale
+ 5. the first focusable view, if any
+ Note that 4 will be prioritized over 1&2&3 when
+ car_ui_focus_area_default_focus_overrides_history is false.
+ (2) When it needs to initialize the focus (such as when a window is opened), it will
+ search for a view in the window and focus on it. The view is chosen in the
+ following order:
+ 1. the first "android:focusedByDefault" view, if any
+ 2. the first "app:defaultFocus" view, if any
+ 3. the first focusable item in a scrollable container, if any
+ 4. the first focusable view that is not a FocusParkingView, if any
+ If there is only one FocusArea that needs to set default focus, you can use either
+ "app:defaultFocus" or "android:focusedByDefault". If there are more than one, you
+ should use "android:focusedByDefault" in the primary FocusArea, and use
+ "app:defaultFocus" in other FocusAreas. -->
+
<attr name="defaultFocus" format="reference"/>
<!-- The paddings of FocusArea highlight. It does't impact the paddings on its child views,
@@ -215,4 +243,12 @@
<!-- The ID of the target FocusArea when nudging down. -->
<attr name="nudgeDown" format="reference"/>
</declare-styleable>
+
+ <!-- Attributes for FocusParkingView. -->
+ <declare-styleable name="FocusParkingView">
+ <!-- Whether to restore focus when the frameworks wants to focus the FocusParkingView. When
+ false, the FocusParkingView allows itself to be focused instead. This should be false
+ for the FocusParkingView in an ActivityView. The default value is true. -->
+ <attr name="shouldRestoreFocus" format="boolean"/>
+ </declare-styleable>
</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/bools.xml b/car-ui-lib/car-ui-lib/src/main/res/values/bools.xml
index 67efdca..b34d620 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/bools.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/bools.xml
@@ -63,4 +63,13 @@
<!-- Whether to log the escrow components check automatically for all the activities or not. -->
<bool name="car_ui_escrow_check_components_automatically">false</bool>
+
+ <!-- If there is no positive/negative/neutral button, should we add one that says "dismiss"? -->
+ <bool name="car_ui_alert_dialog_force_dismiss_button">true</bool>
+ <!-- Whether or not to allow application to hide the content area in IME wide screen. -->
+ <bool name="car_ui_ime_wide_screen_allow_app_hide_content_area">true</bool>
+
+ <!-- Whether IME is aligned left or not(which means its on right). This value will be used by
+ the applications not using car-ui-lib components and are hiding the content area. -->
+ <bool name="car_ui_ime_wide_screen_aligned_left">true</bool>
</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/colors.xml b/car-ui-lib/car-ui-lib/src/main/res/values/colors.xml
index d943920..b407cb6 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/colors.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/colors.xml
@@ -48,6 +48,17 @@
<color name="car_ui_rotary_focus_stroke_color">#94CBFF</color>
<color name="car_ui_rotary_focus_fill_color">#3D94CBFF</color>
+ <color name="car_ui_rotary_focus_pressed_stroke_color">#94CBFF</color>
+ <color name="car_ui_rotary_focus_pressed_fill_color">#8A94CBFF</color>
<color name="car_ui_rotary_focus_stroke_secondary_color">#0059B3</color>
<color name="car_ui_rotary_focus_fill_secondary_color">#3D0059B3</color>
+
+ <!-- IME wide screen -->
+
+ <color name="car_ui_ime_wide_screen_error_text_color">#F00</color>
+ <color name="car_ui_ime_wide_screen_divider_color">#2E3134</color>
+ <color name="car_ui_ime_wide_screen_description_title_color">#FFF</color>
+ <color name="car_ui_ime_wide_screen_description_color">#FFF</color>
+ <color name="car_ui_ime_wide_screen_search_item_title_color">#FFF</color>
+ <color name="car_ui_ime_wide_screen_search_item_sub_title_color">#FFF</color>
</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/dimens.xml b/car-ui-lib/car-ui-lib/src/main/res/values/dimens.xml
index f0c2a8b..d355f99 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/dimens.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/dimens.xml
@@ -210,5 +210,41 @@
<!-- Rotary focus highlight -->
<dimen name="car_ui_rotary_focus_stroke_width">8dp</dimen>
+ <dimen name="car_ui_rotary_focus_pressed_stroke_width">4dp</dimen>
+ <!-- IME wide screen -->
+ <dimen name="car_ui_ime_wide_screen_keyboard_width">1250dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_keyboard_area_padding_start">36dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_keyboard_area_padding_end">36dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_keyboard_area_padding_bottom">40dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_input_area_height">96dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_input_area_margin_top">56dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_input_padding_start">36dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_input_edit_text_padding_left">68dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_input_edit_text_padding_right">0dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_input_edit_text_size">68dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_error_text_padding_start">68dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_error_text_size">24dp</dimen>
+
+ <dimen name="car_ui_ime_wide_screen_divider_width">5dp</dimen>
+
+ <dimen name="car_ui_ime_wide_screen_recycler_view_padding_top">100dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_description_title_margin_top">190dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_description_title_padding_left">36dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_description_title_text_size">44dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_description_text_size">32dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_description_padding_top">16dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_action_button_text_size">32dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_action_button_margin_bottom">40dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_action_button_margin_left">36dp</dimen>
+ <dimen name="car_ui_ime_wide_screen_action_button_height">88dp</dimen>
+
+ <dimen name="car_ui_ime_wide_search_item_icon_size">116dp</dimen>
+ <dimen name="car_ui_ime_wide_search_item_title_text_size">32dp</dimen>
+ <dimen name="car_ui_ime_wide_search_item_title_padding_left">24dp</dimen>
+ <dimen name="car_ui_ime_wide_search_item_title_padding_top">22dp</dimen>
+ <dimen name="car_ui_ime_wide_search_item_sub_title_padding_left">24dp</dimen>
+ <dimen name="car_ui_ime_wide_search_item_sub_title_padding_top">22dp</dimen>
+ <dimen name="car_ui_ime_wide_search_item_sub_title_text_size">24dp</dimen>
+ <dimen name="car_ui_ime_wide_search_item_secondary_image_padding_left">36dp</dimen>
</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/drawables.xml b/car-ui-lib/car-ui-lib/src/main/res/values/drawables.xml
index 181846c..34ee538 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/drawables.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/drawables.xml
@@ -36,4 +36,25 @@
<item name="car_ui_preference_icon_chevron_enabled" type="drawable">@null</item>
<!-- Overlayable drawable to use for the preference chevron when preference is disabled -->
<item name="car_ui_preference_icon_chevron_disabled" type="drawable">@null</item>
+
+ <!-- IME wide screen -->
+
+ <!-- Background of the entire area other than content area which is
+ input text box and keyboard. -->
+ <drawable name="car_ui_ime_wide_screen_background">#000</drawable>
+
+ <!-- Background of the content area. -->
+ <drawable name="car_ui_ime_wide_screen_content_area_background">#000</drawable>
+
+ <!-- Background of the content area when there is no content. -->
+ <drawable name="car_ui_ime_wide_screen_no_content_background">#cc000000</drawable>
+
+ <!-- Background of the input area. -->
+ <drawable name="car_ui_ime_wide_screen_input_area_background">#000</drawable>
+
+ <!-- Tint color of input area in wide screen with error string. -->
+ <drawable name="car_ui_ime_wide_screen_input_area_tint_error_color">#f00</drawable>
+
+ <!-- Tint color of input area in wide screen. -->
+ <drawable name="car_ui_ime_wide_screen_input_area_tint_color">#f5f5f5</drawable>
</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/ids.xml b/car-ui-lib/car-ui-lib/src/main/res/values/ids.xml
index 3f0c9b1..44a162e 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/ids.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/ids.xml
@@ -19,4 +19,21 @@
<!-- Id used for in car_ui_toolbar_menu_item.xml -->
<item name="car_ui_toolbar_menu_item_text_container" type="id"/>
+
+ <!-- WideScreen Keyboard IDs-->
+ <item type="id" name="car_ui_wideScreenInputArea"/>
+ <item type="id" name="car_ui_imeWideScreenInputArea"/>
+ <item type="id" name="car_ui_closeKeyboard"/>
+ <item type="id" name="car_ui_ime_surface"/>
+ <item type="id" name="car_ui_fullscreenArea"/>
+ <item type="id" name="car_ui_wideScreenErrorMessage"/>
+ <item type="id" name="car_ui_contentAreaAutomotive"/>
+ <item type="id" name="car_ui_wideScreenSearchResultList"/>
+ <item type="id" name="car_ui_wideScreenDescriptionTitle"/>
+ <item type="id" name="car_ui_wideScreenDescription"/>
+ <item type="id" name="car_ui_inputExtractActionAutomotive"/>
+ <item type="id" name="car_ui_wideScreenExtractedTextIcon"/>
+ <item type="id" name="car_ui_wideScreenClearData"/>
+ <item type="id" name="car_ui_wideScreenError"/>
+ <item type="id" name="car_ui_inputExtractEditTextContainer"/>
</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/integers.xml b/car-ui-lib/car-ui-lib/src/main/res/values/integers.xml
index b2c737b..976a634 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/integers.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/integers.xml
@@ -25,7 +25,7 @@
<integer name="car_ui_focus_history_cache_type">2</integer>
<!-- How many milliseconds before the entry in FocusHistoryCache expires. Must be positive value
when car_ui_focus_history_cache_type is 2. -->
- <integer name="car_ui_focus_history_expiration_period_ms">10000</integer>
+ <integer name="car_ui_focus_history_expiration_period_ms">300000</integer>
<!-- Type of FocusAreaHistoryCache. The values are defined in RotaryCache. 1 means the
cache is disabled, 2 means entries in the cache will expire after a period of time, and 3 means
@@ -33,5 +33,5 @@
<integer name="car_ui_focus_area_history_cache_type">2</integer>
<!-- How many milliseconds before an entry in FocusAreaHistoryCache expires. Must be positive
value when car_ui_focus_area_history_cache_type is 2. -->
- <integer name="car_ui_focus_area_history_expiration_period_ms">10000</integer>
+ <integer name="car_ui_focus_area_history_expiration_period_ms">3000</integer>
</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml
index 46a29fe..a3f0539 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml
@@ -39,9 +39,11 @@
<string name="car_ui_toolbar_menu_item_overflow_title">Overflow</string>
<!-- Positive option for a preference dialog. [CHAR_LIMIT=30] -->
- <string name="car_ui_dialog_preference_positive" translatable="false">@android:string/ok</string>
+ <string name="car_ui_dialog_preference_positive" translatable="false">@android:string/ok
+ </string>
<!-- Negative option for a preference dialog. [CHAR_LIMIT=30] -->
- <string name="car_ui_dialog_preference_negative" translatable="false">@android:string/cancel</string>
+ <string name="car_ui_dialog_preference_negative" translatable="false">@android:string/cancel
+ </string>
<!-- Text to show when a preference switch is on. [CHAR_LIMIT=30] -->
<string name="car_ui_preference_switch_on">On</string>
<!-- Text to show when a preference switch is off. [CHAR_LIMIT=30] -->
@@ -61,4 +63,18 @@
<!-- Clients should override this value instead of changing the process name -->
<!-- from manifest file. -->
<string name="car_ui_installer_process_name" translatable="false"></string>
+
+ <!--
+ List of packages allowed to hide the content area in wide screen mode when
+ bool/car_ui_ime_wide_screen_allow_app_hide_content_area is set to false. Each package name
+ should be separated by a ",". For example, "com.package1,com.package2,com.package3" will allow
+ packages "com.package1", "com.package2" and "com.package3" to hide the content area.
+ -->
+ <string-array name="car_ui_ime_wide_screen_allowed_package_list" translatable="false">
+ </string-array>
+
+ <!-- Name of system property used to determine when wide screen mode is used. -->
+ <string name="car_ui_ime_wide_screen_system_property_name" translatable="false">
+ ro.build.automotive.ime.wide_screen.enabled
+ </string>
</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/themes.xml b/car-ui-lib/car-ui-lib/src/main/res/values/themes.xml
index 34bbf3c..a599fa0 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/themes.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/themes.xml
@@ -155,6 +155,7 @@
<item name="seekBarStyle">?android:attr/seekBarStyle</item>
<!-- Button styles -->
+ <item name="android:buttonStyle">@style/Widget.CarUi.Button</item>
<item name="buttonStyle">?android:attr/buttonStyle</item>
<item name="buttonStyleSmall">?android:attr/buttonStyleSmall</item>
@@ -204,6 +205,9 @@
<!-- Used by CarUiRecyclerView -->
<item name="carUiRecyclerViewStyle">@style/Widget.CarUi.CarUiRecyclerView</item>
+
+ <!-- textAppearance -->
+ <item name="android:textAppearance">@style/TextAppearance.CarUi</item>
</style>
<!-- TODO(b/150230923) remove this when other apps are ready -->
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/values.xml b/car-ui-lib/car-ui-lib/src/main/res/values/values.xml
index 82a4d65..35f033c 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/values.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/values.xml
@@ -19,6 +19,7 @@
<!-- Toolbar -->
<!-- Layout to be used for toolbar tabs -->
- <item name="car_ui_toolbar_tab_item_layout" type="layout">@layout/car_ui_toolbar_tab_item</item>
- <item name="car_ui_toolbar_tab_item_layout_flexible" type="layout">@layout/car_ui_toolbar_tab_item_flexible</item>
+ <layout name="car_ui_toolbar_tab_item_layout">@layout/car_ui_toolbar_tab_item</layout>
+ <layout name="car_ui_toolbar_tab_item_layout_flexible">@layout/car_ui_toolbar_tab_item_flexible</layout>
+ <layout name="car_ui_toolbar_menu_item_primary">@layout/car_ui_toolbar_menu_item</layout>
</resources>
diff --git a/car-ui-lib/paintbooth/AndroidManifest-gradle.xml b/car-ui-lib/paintbooth/AndroidManifest-gradle.xml
index 3184bd7..36823ec 100644
--- a/car-ui-lib/paintbooth/AndroidManifest-gradle.xml
+++ b/car-ui-lib/paintbooth/AndroidManifest-gradle.xml
@@ -49,18 +49,27 @@
android:exported="false"
android:parentActivityName=".MainActivity"/>
<activity
+ android:name=".widescreenime.WideScreenImeActivity"
+ android:windowSoftInputMode="stateHidden|adjustNothing"
+ android:exported="false"
+ android:parentActivityName=".MainActivity">
+ </activity>
+ <activity
android:name=".toolbar.ToolbarActivity"
android:exported="false"
android:parentActivityName=".MainActivity">
<meta-data android:name="distractionOptimized" android:value="true"/>
</activity>
<activity
+ android:name=".toolbar.NoCarUiToolbarActivity"
+ android:exported="false"
+ android:parentActivityName=".MainActivity"
+ android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
+ <activity
android:name=".toolbar.OldToolbarActivity"
android:exported="false"
android:parentActivityName=".MainActivity"
- android:theme="@style/Theme.CarUi">
- <meta-data android:name="distractionOptimized" android:value="true"/>
- </activity>
+ android:theme="@style/Theme.CarUi"/>
<activity
android:name=".overlays.OverlayActivity"
android:exported="false"
diff --git a/car-ui-lib/paintbooth/AndroidManifest.xml b/car-ui-lib/paintbooth/AndroidManifest.xml
index c277ff5..9763b47 100644
--- a/car-ui-lib/paintbooth/AndroidManifest.xml
+++ b/car-ui-lib/paintbooth/AndroidManifest.xml
@@ -72,6 +72,12 @@
android:exported="false"
android:parentActivityName=".MainActivity"/>
<activity
+ android:name=".widescreenime.WideScreenImeActivity"
+ android:windowSoftInputMode="stateHidden|adjustNothing"
+ android:exported="false"
+ android:parentActivityName=".MainActivity">
+ </activity>
+ <activity
android:name=".toolbar.ToolbarActivity"
android:exported="false"
android:parentActivityName=".MainActivity">
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/MainActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/MainActivity.java
index 02b8088..bf50162 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/MainActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/MainActivity.java
@@ -46,6 +46,7 @@
import com.android.car.ui.paintbooth.toolbar.NoCarUiToolbarActivity;
import com.android.car.ui.paintbooth.toolbar.OldToolbarActivity;
import com.android.car.ui.paintbooth.toolbar.ToolbarActivity;
+import com.android.car.ui.paintbooth.widescreenime.WideScreenImeActivity;
import com.android.car.ui.paintbooth.widgets.WidgetActivity;
import com.android.car.ui.recyclerview.CarUiRecyclerView;
import com.android.car.ui.toolbar.ToolbarController;
@@ -76,6 +77,7 @@
new ActivityElement("Old toolbar sample", OldToolbarActivity.class),
new ActivityElement("No CarUiToolbar sample", NoCarUiToolbarActivity.class),
new ActivityElement("Widget sample", WidgetActivity.class),
+ new ActivityElement("Wide Screen IME", WideScreenImeActivity.class),
new ActivityElement("ListItem sample", CarUiListItemActivity.class));
private abstract static class ViewHolder extends RecyclerView.ViewHolder {
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/VisibleBoundsSimulator.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/VisibleBoundsSimulator.java
index 63a2aed..b3dfeb1 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/VisibleBoundsSimulator.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/VisibleBoundsSimulator.java
@@ -106,15 +106,12 @@
int screenHeight = displayMetrics.heightPixels;
int screenWidth = displayMetrics.widthPixels;
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
- WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
- // WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY is a hidden api, so
- // use its value here so we can still compile on gradle / google3
- WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW + 26,
+ WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT);
params.packageName = this.getPackageName();
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
index 1819427..68ec45b 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiListItemActivity.java
@@ -191,6 +191,11 @@
item = new CarUiContentListItem(CarUiContentListItem.Action.ICON);
item.setTitle("Supplemental icon with listener");
+ item.setPrimaryIconType(CarUiContentListItem.IconType.CONTENT);
+ item.setIcon(getDrawable(R.drawable.ic_launcher));
+ item.setBody("body");
+ item.setOnItemClickedListener(v -> Toast.makeText(context, "Clicked item",
+ Toast.LENGTH_SHORT).show());
item.setSupplementalIcon(getDrawable(R.drawable.ic_launcher),
v -> Toast.makeText(context, "Clicked supplemental icon",
Toast.LENGTH_SHORT).show());
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
index 7cf1cce..3d390fc 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.app.Activity;
+import android.app.AlertDialog;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Pair;
@@ -35,6 +36,8 @@
import com.android.car.ui.baselayout.InsetsChangedListener;
import com.android.car.ui.core.CarUi;
import com.android.car.ui.paintbooth.R;
+import com.android.car.ui.recyclerview.CarUiContentListItem;
+import com.android.car.ui.recyclerview.CarUiListItemAdapter;
import com.android.car.ui.recyclerview.CarUiRadioButtonListItem;
import com.android.car.ui.recyclerview.CarUiRadioButtonListItemAdapter;
import com.android.car.ui.recyclerview.CarUiRecyclerView;
@@ -83,6 +86,8 @@
v -> showDialogWithLongSubtitleAndIcon()));
mButtons.add(Pair.create(R.string.dialog_show_single_choice,
v -> showDialogWithSingleChoiceItems()));
+ mButtons.add(Pair.create(R.string.dialog_show_list_items_without_default_button,
+ v -> showDialogWithListItemsWithoutDefaultButton()));
mButtons.add(Pair.create(R.string.dialog_show_permission_dialog,
v -> showPermissionDialog()));
mButtons.add(Pair.create(R.string.dialog_show_multi_permission_dialog,
@@ -142,6 +147,7 @@
new AlertDialogBuilder(this)
.setTitle("Standard Alert Dialog")
.setEditBox("Edit me please", null, null)
+ .setEditTextTitleAndDescForWideScreen("title", "desc from app")
.setPositiveButton("OK", (dialogInterface, i) -> {
})
.show();
@@ -199,6 +205,35 @@
.show();
}
+
+ private void showDialogWithListItemsWithoutDefaultButton() {
+ ArrayList<CarUiContentListItem> data = new ArrayList<>();
+ AlertDialog[] dialog = new AlertDialog[1];
+
+ CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+ item.setTitle("First item");
+ item.setOnItemClickedListener(i -> dialog[0].dismiss());
+ data.add(item);
+
+
+ item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+ item.setTitle("Second item");
+ item.setOnItemClickedListener(i -> dialog[0].dismiss());
+ data.add(item);
+
+ item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+ item.setTitle("Third item");
+ item.setOnItemClickedListener(i -> dialog[0].dismiss());
+ data.add(item);
+
+ dialog[0] = new AlertDialogBuilder(this)
+ .setTitle("Select one option.")
+ .setSubtitle("Ony one option may be selected at a time")
+ .setAdapter(new CarUiListItemAdapter(data))
+ .setAllowDismissButton(false)
+ .show();
+ }
+
private void showDialogWithSubtitleAndIcon() {
new AlertDialogBuilder(this)
.setTitle("My Title!")
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
index f32818a..b9a9daa 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
@@ -159,6 +159,30 @@
toolbar.setMenuItems(mMenuItems);
}));
+ mButtons.add(Pair.create(getString(R.string.toolbar_add_bordered_text), v -> {
+ mMenuItems.add(MenuItem.builder(this)
+ .setTitle("Baz")
+ .setPrimary(true)
+ .setOnClickListener(
+ i -> Toast.makeText(this, "Clicked",
+ Toast.LENGTH_SHORT).show())
+ .build());
+ toolbar.setMenuItems(mMenuItems);
+ }));
+
+ mButtons.add(Pair.create(getString(R.string.toolbar_add_bordered_icon_text), v -> {
+ mMenuItems.add(MenuItem.builder(this)
+ .setIcon(R.drawable.ic_tracklist)
+ .setTitle("Bar")
+ .setPrimary(true)
+ .setShowIconAndTitle(true)
+ .setOnClickListener(
+ i -> Toast.makeText(this, "Clicked",
+ Toast.LENGTH_SHORT).show())
+ .build());
+ toolbar.setMenuItems(mMenuItems);
+ }));
+
mButtons.add(Pair.create(getString(R.string.toolbar_add_untinted_icon_and_text), v -> {
mMenuItems.add(MenuItem.builder(this)
.setIcon(R.drawable.ic_tracklist)
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenImeActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenImeActivity.java
new file mode 100644
index 0000000..8fd206d
--- /dev/null
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenImeActivity.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.paintbooth.widescreenime;
+
+import static android.view.inputmethod.EditorInfo.IME_FLAG_NO_EXTRACT_UI;
+
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.ADD_DESC_TITLE_TO_CONTENT_AREA;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.ADD_DESC_TO_CONTENT_AREA;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.ADD_ERROR_DESC_TO_INPUT_AREA;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.REQUEST_RENDER_CONTENT_AREA;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_ICON_RES_ID_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_ITEM_ID_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_SUB_TITLE_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_SUPPLEMENTAL_ICON_ID_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_SUPPLEMENTAL_ICON_RES_ID_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_TITLE_LIST;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_ACTION;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_EXTRACTED_TEXT_ICON_RES_ID;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.baselayout.Insets;
+import com.android.car.ui.baselayout.InsetsChangedListener;
+import com.android.car.ui.core.CarUi;
+import com.android.car.ui.imewidescreen.CarUiImeSearchListItem;
+import com.android.car.ui.paintbooth.R;
+import com.android.car.ui.recyclerview.CarUiContentListItem;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+import com.android.car.ui.toolbar.MenuItem;
+import com.android.car.ui.toolbar.Toolbar;
+import com.android.car.ui.toolbar.ToolbarController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Activity that shows different scenarios for wide screen ime.
+ */
+public class WideScreenImeActivity extends AppCompatActivity implements InsetsChangedListener {
+
+ private static final String TAG = "WideScreenImeActivity";
+
+ private final List<MenuItem> mMenuItems = new ArrayList<>();
+ private final List<Pair<CharSequence, View.OnFocusChangeListener>> mEditText =
+ new ArrayList<>();
+
+ private final ArrayList<String> mItemIdList = new ArrayList<>();
+ private final ArrayList<String> mTitleList = new ArrayList<>();
+ private final ArrayList<String> mSubTitleList = new ArrayList<>();
+ private final ArrayList<Integer> mPrimaryImageResId = new ArrayList<>();
+ private final ArrayList<String> mSecondaryItemId = new ArrayList<>();
+ private final ArrayList<Integer> mSecondaryImageResId = new ArrayList<>();
+
+ private InputMethodManager mInputMethodManager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.car_ui_recycler_view_activity);
+
+ mInputMethodManager = (InputMethodManager)
+ getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ ToolbarController toolbarNonFinal = CarUi.getToolbar(this);
+ if (toolbarNonFinal == null) {
+ toolbarNonFinal = requireViewById(R.id.toolbar);
+ }
+ ToolbarController toolbar = toolbarNonFinal;
+ toolbar.setTitle(getTitle());
+ toolbar.setState(Toolbar.State.SUBPAGE);
+ toolbar.setLogo(R.drawable.ic_launcher);
+ toolbar.registerOnBackListener(
+ () -> {
+ if (toolbar.getState() == Toolbar.State.SEARCH
+ || toolbar.getState() == Toolbar.State.EDIT) {
+ toolbar.setState(Toolbar.State.SUBPAGE);
+ return true;
+ }
+ return false;
+ });
+
+ CarUiContentListItem.OnClickListener mainClickListener = i ->
+ Toast.makeText(this, "Item clicked!", Toast.LENGTH_SHORT).show();
+
+ CarUiContentListItem.OnClickListener secondaryClickListener = i ->
+ Toast.makeText(this, "Item's secondary action clicked!", Toast.LENGTH_SHORT).show();
+
+ final int[] count = {1};
+ CarUiImeSearchListItem item = new CarUiImeSearchListItem(CarUiContentListItem.Action.ICON);
+ item.setTitle("Title " + count[0]);
+ item.setBody("Sub title " + count[0]);
+ item.setIconResId(R.drawable.ic_launcher);
+ item.setSupplementalIconResId(R.drawable.ic_launcher);
+ item.setSupplementalIcon(getDrawable(R.drawable.ic_launcher), secondaryClickListener);
+ item.setOnItemClickedListener(mainClickListener);
+
+ List<CarUiImeSearchListItem> searchItems = new ArrayList<>();
+
+ searchItems.add(item);
+
+ // initial list to display in search view.
+ if (toolbar.canShowSearchResultItems()) {
+ toolbar.setSearchResultItems(searchItems);
+ }
+
+ LayoutInflater inflater = LayoutInflater.from(this);
+ View contentArea = inflater.inflate(R.layout.ime_wide_screen_dummy_view, null, true);
+
+ if (toolbar.canShowSearchResultsView()) {
+ toolbar.setSearchResultsView(contentArea);
+ }
+
+ contentArea.findViewById(R.id.button_1).setOnClickListener(v ->
+ Toast.makeText(this, "Button 1 clicked", Toast.LENGTH_SHORT).show()
+ );
+
+ contentArea.findViewById(R.id.button_2).setOnClickListener(v -> {
+ Toast.makeText(this, "Clearing the view...", Toast.LENGTH_SHORT).show();
+ toolbar.setSearchResultsView(null);
+ }
+ );
+
+ toolbar.registerOnSearchListener((query) -> {
+ count[0]++;
+ CarUiImeSearchListItem item1 = new CarUiImeSearchListItem(
+ CarUiContentListItem.Action.ICON);
+ item1.setTitle("Title " + count[0]);
+ item1.setBody("Sub title " + count[0]);
+ item1.setIconResId(R.drawable.ic_launcher);
+ item1.setSupplementalIconResId(R.drawable.ic_launcher);
+ item1.setSupplementalIcon(getDrawable(R.drawable.ic_launcher), secondaryClickListener);
+ item1.setOnItemClickedListener(mainClickListener);
+ searchItems.add(item1);
+
+ if (toolbar.canShowSearchResultItems()) {
+ toolbar.setSearchResultItems(searchItems);
+ }
+ });
+
+ mMenuItems.add(MenuItem.builder(this)
+ .setToSearch()
+ .setOnClickListener(i -> {
+ toolbar.setState(Toolbar.State.SEARCH);
+ })
+ .build());
+
+ toolbar.setMenuItems(mMenuItems);
+
+ mEditText.add(Pair.create("Default Input Edit Text field", null));
+
+ mEditText.add(Pair.create("Add Desc to content area",
+ this::addDescToContentArea));
+
+ mEditText.add(Pair.create("Hide the content area",
+ this::hideContentArea));
+
+ mEditText.add(Pair.create("Hide extraction view",
+ this::hideExtractionView));
+
+ for (int i = 0; i < 7; i++) {
+ mItemIdList.add("itemId" + i);
+ mTitleList.add("Title " + i);
+ mSubTitleList.add("subtitle " + i);
+ mPrimaryImageResId.add(R.drawable.ic_launcher);
+ mSecondaryItemId.add("imageId" + i);
+ mSecondaryImageResId.add(R.drawable.ic_launcher);
+ }
+
+ mEditText.add(Pair.create("Show IME list view", this::showImeListView));
+
+ mEditText.add(Pair.create("Add icon to extracted view", this::addIconToExtractedView));
+
+ mEditText.add(
+ Pair.create("Add error message to content area", this::addErrorDescToContentArea));
+
+ CarUiRecyclerView recyclerView = requireViewById(R.id.list);
+ recyclerView.setAdapter(mAdapter);
+ }
+
+ private void addIconToExtractedView(View view, boolean hasFocus) {
+ if (!hasFocus) {
+ return;
+ }
+
+ Bundle bundle = new Bundle();
+ bundle.putInt(WIDE_SCREEN_EXTRACTED_TEXT_ICON_RES_ID, R.drawable.car_ui_icon_edit);
+ mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle);
+ }
+
+ private void addErrorDescToContentArea(View view, boolean hasFocus) {
+ if (!hasFocus) {
+ return;
+ }
+
+ Bundle bundle = new Bundle();
+ bundle.putString(ADD_ERROR_DESC_TO_INPUT_AREA, "Some error message");
+ bundle.putString(ADD_DESC_TITLE_TO_CONTENT_AREA, "Title");
+ bundle.putString(ADD_DESC_TO_CONTENT_AREA, "Description provided by the application");
+ mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle);
+ }
+
+ private void showImeListView(View view, boolean hasFocus) {
+ if (!hasFocus) {
+ return;
+ }
+
+ Bundle bundle = new Bundle();
+
+ EditText editText = (EditText) view;
+ editText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ mItemIdList.add("itemId " + s.toString());
+ mTitleList.add("Title " + s.toString());
+ mSubTitleList.add("subtitle ");
+ mPrimaryImageResId.add(R.drawable.ic_launcher);
+ mSecondaryItemId.add("imageId" + s.toString());
+ mSecondaryImageResId.add(R.drawable.ic_launcher);
+
+ bundle.putStringArrayList(SEARCH_RESULT_TITLE_LIST, mTitleList);
+ bundle.putStringArrayList(SEARCH_RESULT_SUB_TITLE_LIST, mSubTitleList);
+ bundle.putIntegerArrayList(SEARCH_RESULT_ICON_RES_ID_LIST,
+ mPrimaryImageResId);
+ mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle);
+ }
+ });
+
+ bundle.putStringArrayList(SEARCH_RESULT_ITEM_ID_LIST, mItemIdList);
+ bundle.putStringArrayList(SEARCH_RESULT_TITLE_LIST, mTitleList);
+ bundle.putStringArrayList(SEARCH_RESULT_SUB_TITLE_LIST, mSubTitleList);
+ bundle.putStringArrayList(SEARCH_RESULT_SUPPLEMENTAL_ICON_ID_LIST, mSecondaryItemId);
+ bundle.putIntegerArrayList(SEARCH_RESULT_ICON_RES_ID_LIST, mPrimaryImageResId);
+ bundle.putIntegerArrayList(SEARCH_RESULT_SUPPLEMENTAL_ICON_RES_ID_LIST,
+ mSecondaryImageResId);
+ mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle);
+ }
+
+ private void hideExtractionView(View view, boolean hasFocus) {
+ if (!hasFocus) {
+ return;
+ }
+
+ EditText editText = (EditText) view;
+ editText.setImeOptions(IME_FLAG_NO_EXTRACT_UI);
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(REQUEST_RENDER_CONTENT_AREA, false);
+ mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle);
+ }
+
+ private void addDescToContentArea(View view, boolean hasFocus) {
+ if (!hasFocus) {
+ return;
+ }
+
+ Bundle bundle = new Bundle();
+ bundle.putString(ADD_DESC_TITLE_TO_CONTENT_AREA, "Title");
+ bundle.putString(ADD_DESC_TO_CONTENT_AREA, "Description provided by the application");
+ mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle);
+ }
+
+ private void hideContentArea(View view, boolean hasFocus) {
+ if (!hasFocus) {
+ return;
+ }
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(REQUEST_RENDER_CONTENT_AREA, false);
+ mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle);
+ }
+
+
+ private static class ViewHolder extends RecyclerView.ViewHolder {
+
+ private final EditText mEditText;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+ mEditText = itemView.requireViewById(R.id.edit_text);
+ }
+
+ public void bind(CharSequence title, View.OnFocusChangeListener listener) {
+ mEditText.setText(title);
+ mEditText.setOnFocusChangeListener(listener);
+ }
+ }
+
+ private final RecyclerView.Adapter<ViewHolder> mAdapter =
+ new RecyclerView.Adapter<ViewHolder>() {
+ @Override
+ public int getItemCount() {
+ return mEditText.size();
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int position) {
+ View item =
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.edit_text_list_item,
+ parent, false);
+
+ return new ViewHolder(item);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ Pair<CharSequence, View.OnFocusChangeListener> pair = mEditText.get(position);
+ holder.bind(pair.first, pair.second);
+ }
+ };
+
+ @Override
+ public void onCarUiInsetsChanged(@NonNull Insets insets) {
+ requireViewById(R.id.list)
+ .setPadding(0, insets.getTop(), 0, insets.getBottom());
+ requireViewById(android.R.id.content)
+ .setPadding(insets.getLeft(), 0, insets.getRight(), 0);
+ }
+}
diff --git a/car-ui-lib/paintbooth/src/main/res/layout/edit_text_list_item.xml b/car-ui-lib/paintbooth/src/main/res/layout/edit_text_list_item.xml
new file mode 100644
index 0000000..a04edd7
--- /dev/null
+++ b/car-ui-lib/paintbooth/src/main/res/layout/edit_text_list_item.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <EditText
+ android:id="@+id/edit_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="text"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="20dp"
+ android:text="Edit Text Box"/>
+</RelativeLayout>
diff --git a/car-ui-lib/paintbooth/src/main/res/layout/ime_wide_screen_dummy_view.xml b/car-ui-lib/paintbooth/src/main/res/layout/ime_wide_screen_dummy_view.xml
new file mode 100644
index 0000000..1118ce9
--- /dev/null
+++ b/car-ui-lib/paintbooth/src/main/res/layout/ime_wide_screen_dummy_view.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/car_ui_activity_background">
+ <Button
+ android:id="@+id/button_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Button 1"
+ android:theme="@android:style/Theme.DeviceDefault"
+ android:textSize="@dimen/car_ui_ime_wide_screen_action_button_text_size"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+ <Button
+ android:id="@+id/button_2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:text="Clear View"
+ android:theme="@android:style/Theme.DeviceDefault"
+ android:textSize="@dimen/car_ui_ime_wide_screen_action_button_text_size"
+ app:layout_constraintTop_toBottomOf="@+id/button_1"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
+
+
diff --git a/car-ui-lib/paintbooth/src/main/res/values/strings.xml b/car-ui-lib/paintbooth/src/main/res/values/strings.xml
index f347784..5965d68 100644
--- a/car-ui-lib/paintbooth/src/main/res/values/strings.xml
+++ b/car-ui-lib/paintbooth/src/main/res/values/strings.xml
@@ -180,6 +180,13 @@
<!-- Text for add icon text button [CHAR_LIMIT=45]-->
<string name="toolbar_add_icon_text">MenuItem: Add icon and text</string>
+
+ <!-- Text for add text button [CHAR_LIMIT=30]-->
+ <string name="toolbar_add_bordered_text">MenuItem: Add bordered text</string>
+
+ <!-- Text for add icon text button [CHAR_LIMIT=45]-->
+ <string name="toolbar_add_bordered_icon_text">MenuItem: Add bordered icon and text</string>
+
<!-- Text for add untined icon and text button [CHAR_LIMIT=60]-->
<string name="toolbar_add_untinted_icon_and_text">MenuItem: Add untinted icon and text</string>
@@ -270,6 +277,9 @@
<!-- Text to show Dialog with single choice items-->
<string name="dialog_show_single_choice">Show with single choice items</string>
+ <!-- Text to show a dialog with single choice items and no default button [CHAR_LIMIT=200] -->
+ <string name="dialog_show_list_items_without_default_button">Show with single choice items and no default button</string>
+
<!-- Text to show a permission Dialog [CHAR_LIMIT=50] -->
<string name="dialog_show_permission_dialog">Show permission dialog</string>
diff --git a/car-ui-lib/referencedesign/Android.mk b/car-ui-lib/referencedesign/Android.mk
index ac85df4..ef0ad97 100644
--- a/car-ui-lib/referencedesign/Android.mk
+++ b/car-ui-lib/referencedesign/Android.mk
@@ -20,12 +20,14 @@
com.android.car.settings \
com.android.car.voicecontrol \
com.android.car.faceenroll \
- com.android.permissioncontroller \
+ com.android.managedprovisioning \
com.android.settings.intelligence \
com.google.android.apps.automotive.inputmethod \
com.google.android.apps.automotive.inputmethod.dev \
+ com.google.android.apps.automotive.templates.host \
com.google.android.embedded.projection \
com.google.android.gms \
+ com.google.android.gsf \
com.google.android.packageinstaller \
com.google.android.carassistant \
com.google.android.tts \
@@ -39,9 +41,12 @@
CAR_UI_RRO_TARGETS := \
com.google.android.apps.automotive.inputmethod \
com.google.android.apps.automotive.inputmethod.dev \
+ com.google.android.apps.automotive.templates.host \
com.google.android.embedded.projection \
com.google.android.gms \
+ com.google.android.gsf \
com.google.android.packageinstaller \
+ com.google.android.permissioncontroller \
com.google.android.carassistant \
com.google.android.tts \
com.android.vending \
diff --git a/car-ui-lib/referencedesign/AndroidManifest-overlayable.xml b/car-ui-lib/referencedesign/AndroidManifest-overlayable.xml
index 214755c..1b0585a 100644
--- a/car-ui-lib/referencedesign/AndroidManifest-overlayable.xml
+++ b/car-ui-lib/referencedesign/AndroidManifest-overlayable.xml
@@ -6,6 +6,6 @@
android:targetName="car-ui-lib"
android:resourcesMap="@xml/overlays"
android:isStatic="true"
- android:requiredSystemPropertyName="ro.build.characteristics"
- android:requiredSystemPropertyValue="automotive"/>
+ android:requiredSystemPropertyName="ro.build.car_ui_rros_enabled"
+ android:requiredSystemPropertyValue="true"/>
</manifest>
diff --git a/car-ui-lib/referencedesign/AndroidManifest.xml b/car-ui-lib/referencedesign/AndroidManifest.xml
index a6dbae3..0994382 100644
--- a/car-ui-lib/referencedesign/AndroidManifest.xml
+++ b/car-ui-lib/referencedesign/AndroidManifest.xml
@@ -5,6 +5,6 @@
android:targetPackage="{{TARGET_PACKAGE_NAME}}"
android:resourcesMap="@xml/overlays"
android:isStatic="true"
- android:requiredSystemPropertyName="ro.build.characteristics"
- android:requiredSystemPropertyValue="automotive"/>
+ android:requiredSystemPropertyName="ro.build.car_ui_rros_enabled"
+ android:requiredSystemPropertyValue="true"/>
</manifest>
diff --git a/car-ui-lib/referencedesign/product.mk b/car-ui-lib/referencedesign/product.mk
index 92e0bea..e834714 100644
--- a/car-ui-lib/referencedesign/product.mk
+++ b/car-ui-lib/referencedesign/product.mk
@@ -17,15 +17,17 @@
googlecarui-com-android-car-settings \
googlecarui-com-android-car-voicecontrol \
googlecarui-com-android-car-faceenroll \
- googlecarui-com-android-permissioncontroller \
googlecarui-com-android-settings-intelligence \
googlecarui-com-google-android-apps-automotive-inputmethod \
googlecarui-com-google-android-apps-automotive-inputmethod-dev \
+ googlecarui-com-google-android-apps-automotive-templates-host \
googlecarui-com-google-android-embedded-projection \
googlecarui-com-google-android-gms \
+ googlecarui-com-google-android-gsf \
googlecarui-com-google-android-packageinstaller \
googlecarui-com-google-android-carassistant \
googlecarui-com-google-android-tts \
+ googlecarui-com-android-managedprovisioning \
googlecarui-com-android-vending \
@@ -33,9 +35,16 @@
PRODUCT_PACKAGES += \
googlecarui-overlayable-com-google-android-apps-automotive-inputmethod \
googlecarui-overlayable-com-google-android-apps-automotive-inputmethod-dev \
+ googlecarui-overlayable-com-google-android-apps-automotive-templates-host \
googlecarui-overlayable-com-google-android-embedded-projection \
googlecarui-overlayable-com-google-android-gms \
+ googlecarui-overlayable-com-google-android-gsf \
googlecarui-overlayable-com-google-android-packageinstaller \
+ googlecarui-overlayable-com-google-android-permissioncontroller \
googlecarui-overlayable-com-google-android-carassistant \
googlecarui-overlayable-com-google-android-tts \
googlecarui-overlayable-com-android-vending \
+
+# This system property is used to enable the RROs on startup via
+# the requiredSystemPropertyName/Value attributes in the manifest
+PRODUCT_PRODUCT_PROPERTIES += ro.build.car_ui_rros_enabled=true
diff --git a/car-ui-lib/referencedesign/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml b/car-ui-lib/referencedesign/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml
index 1f89ff2..14f6cd0 100644
--- a/car-ui-lib/referencedesign/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml
+++ b/car-ui-lib/referencedesign/res/drawable/car_ui_toolbar_menu_item_icon_ripple.xml
@@ -17,6 +17,15 @@
~
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:state_pressed="true">
+ <shape android:shape="oval">
+ <solid android:color="#8A94CBFF"/>
+ <stroke android:width="4dp"
+ android:color="#94CBFF"/>
+ <size android:width="48dp"
+ android:height="48dp"/>
+ </shape>
+ </item>
<item android:state_focused="true">
<shape android:shape="oval">
<solid android:color="#3D94CBFF"/>
diff --git a/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item.xml b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item.xml
index 18f2e16..be95cc1 100644
--- a/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item.xml
+++ b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item.xml
@@ -38,14 +38,6 @@
android:layout_gravity="center"
android:tint="@color/car_ui_toolbar_menu_item_icon_color"
android:tintMode="src_in"/>
- <com.android.car.ui.uxr.DrawableStateSwitch
- android:id="@+id/car_ui_toolbar_menu_item_switch"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:background="@null"
- android:focusable="false"
- android:clickable="false"/>
</FrameLayout>
<FrameLayout
@@ -55,6 +47,14 @@
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground">
<!-- These buttons must have clickable="false" or they will steal the click events from the container -->
+ <com.android.car.ui.uxr.DrawableStateSwitch
+ android:id="@+id/car_ui_toolbar_menu_item_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@null"
+ android:focusable="false"
+ android:clickable="false"/>
<com.android.car.ui.uxr.DrawableStateButton
android:id="@+id/car_ui_toolbar_menu_item_text"
style="@style/Widget.CarUi.Toolbar.TextButton"
diff --git a/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item_primary.xml b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item_primary.xml
new file mode 100644
index 0000000..4364271
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item_primary.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2020, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:focusable="false">
+ <FrameLayout
+ android:id="@+id/car_ui_toolbar_menu_item_icon_container"
+ style="@style/Widget.CarUi.Toolbar.MenuItem.IndividualContainer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/car_ui_toolbar_menu_item_icon_ripple">
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:src="@drawable/car_ui_toolbar_menu_item_icon_background"
+ android:scaleType="center"/>
+ <ImageView
+ android:id="@+id/car_ui_toolbar_menu_item_icon"
+ android:layout_width="44dp"
+ android:layout_height="44dp"
+ android:layout_gravity="center"
+ android:tint="@color/car_ui_toolbar_menu_item_icon_color"
+ android:tintMode="src_in"/>
+ <com.android.car.ui.uxr.DrawableStateSwitch
+ android:id="@+id/car_ui_toolbar_menu_item_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@null"
+ android:focusable="false"
+ android:clickable="false"/>
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/car_ui_toolbar_menu_item_text_container"
+ style="@style/Widget.CarUi.Toolbar.MenuItem.IndividualContainer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <!-- These buttons must have clickable="false" or they will steal the click events from the container -->
+ <com.android.car.ui.uxr.DrawableStateButton
+ android:id="@+id/car_ui_toolbar_menu_item_text"
+ style="@style/Widget.CarUi.Toolbar.TextButton.Primary"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:focusable="false"
+ android:clickable="false"/>
+ <com.android.car.ui.uxr.DrawableStateButton
+ android:id="@+id/car_ui_toolbar_menu_item_text_with_icon"
+ style="@style/Widget.CarUi.Toolbar.TextButton.Primary"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:focusable="false"
+ android:clickable="false"/>
+ </FrameLayout>
+</FrameLayout>
diff --git a/car-ui-lib/referencedesign/res/values/styles.xml b/car-ui-lib/referencedesign/res/values/styles.xml
index 433911c..ba37aac 100644
--- a/car-ui-lib/referencedesign/res/values/styles.xml
+++ b/car-ui-lib/referencedesign/res/values/styles.xml
@@ -41,6 +41,9 @@
<style name="Widget.CarUi.Button.Borderless.Colored"
parent="android:Widget.DeviceDefault.Button.Borderless.Colored"/>
+ <style name="Widget.CarUi.Button"
+ parent="android:Widget.DeviceDefault.Button"/>
+
<style name="Widget.CarUi.Toolbar.TextButton" parent="Widget.CarUi.Button.Borderless.Colored">
<item name="android:drawableTint">@color/car_ui_toolbar_menu_item_icon_color</item>
<item name="android:drawablePadding">10dp</item>
@@ -51,6 +54,12 @@
<item name="android:textColor">@color/car_ui_toolbar_menu_item_icon_color</item>
</style>
+ <style name="Widget.CarUi.Toolbar.TextButton.Primary" parent="Widget.CarUi.Button">
+ <item name="android:drawableTint">@color/car_ui_toolbar_menu_item_icon_color</item>
+ <item name="android:drawablePadding">10dp</item>
+ <item name="android:maxWidth">350dp</item>
+ </style>
+
<style name="Widget.CarUi.SeekbarPreference"/>
<!-- Style applied to the seekbar widget within the seekbar preference -->
diff --git a/car-ui-lib/referencedesign/res/xml/overlays.xml b/car-ui-lib/referencedesign/res/xml/overlays.xml
index cc9b092..363d389 100644
--- a/car-ui-lib/referencedesign/res/xml/overlays.xml
+++ b/car-ui-lib/referencedesign/res/xml/overlays.xml
@@ -3,6 +3,7 @@
<item target="layout/car_ui_toolbar" value="@layout/car_ui_toolbar"/>
<item target="layout/car_ui_toolbar_two_row" value="@layout/car_ui_toolbar_two_row"/>
<item target="layout/car_ui_toolbar_menu_item" value="@layout/car_ui_toolbar_menu_item"/>
+ <item target="layout/car_ui_toolbar_menu_item_primary" value="@layout/car_ui_toolbar_menu_item_primary"/>
<item target="layout/car_ui_preference_widget_seekbar" value="@layout/car_ui_preference_widget_seekbar"/>
<item target="drawable/car_ui_icon_arrow_back" value="@drawable/car_ui_icon_arrow_back"/>
diff --git a/car-ui-lib/tests/apitest/current.xml b/car-ui-lib/tests/apitest/current.xml
index 76eb5a2..461ce2c 100644
--- a/car-ui-lib/tests/apitest/current.xml
+++ b/car-ui-lib/tests/apitest/current.xml
@@ -1,15 +1,19 @@
<?xml version='1.0' encoding='UTF-8'?>
<!--This file is AUTO GENERATED, DO NOT EDIT MANUALLY.-->
<resources>
+ <public type="array" name="car_ui_ime_wide_screen_allowed_package_list"/>
<public type="attr" name="CarUiToolbarStyle"/>
<public type="attr" name="carUiPreferenceStyle"/>
<public type="attr" name="carUiRecyclerViewStyle"/>
<public type="attr" name="state_ux_restricted"/>
+ <public type="bool" name="car_ui_alert_dialog_force_dismiss_button"/>
<public type="bool" name="car_ui_clear_focus_area_history_when_rotating"/>
<public type="bool" name="car_ui_enable_focus_area_background_highlight"/>
<public type="bool" name="car_ui_enable_focus_area_foreground_highlight"/>
<public type="bool" name="car_ui_escrow_check_components_automatically"/>
<public type="bool" name="car_ui_focus_area_default_focus_overrides_history"/>
+ <public type="bool" name="car_ui_ime_wide_screen_aligned_left"/>
+ <public type="bool" name="car_ui_ime_wide_screen_allow_app_hide_content_area"/>
<public type="bool" name="car_ui_list_item_single_line_title"/>
<public type="bool" name="car_ui_preference_list_show_full_screen"/>
<public type="bool" name="car_ui_preference_show_chevron"/>
@@ -22,6 +26,12 @@
<public type="color" name="car_ui_activity_background_color"/>
<public type="color" name="car_ui_color_accent"/>
<public type="color" name="car_ui_dialog_icon_color"/>
+ <public type="color" name="car_ui_ime_wide_screen_description_color"/>
+ <public type="color" name="car_ui_ime_wide_screen_description_title_color"/>
+ <public type="color" name="car_ui_ime_wide_screen_divider_color"/>
+ <public type="color" name="car_ui_ime_wide_screen_error_text_color"/>
+ <public type="color" name="car_ui_ime_wide_screen_search_item_sub_title_color"/>
+ <public type="color" name="car_ui_ime_wide_screen_search_item_title_color"/>
<public type="color" name="car_ui_list_item_divider"/>
<public type="color" name="car_ui_preference_icon_color"/>
<public type="color" name="car_ui_preference_two_action_divider_color"/>
@@ -29,6 +39,8 @@
<public type="color" name="car_ui_ripple_color"/>
<public type="color" name="car_ui_rotary_focus_fill_color"/>
<public type="color" name="car_ui_rotary_focus_fill_secondary_color"/>
+ <public type="color" name="car_ui_rotary_focus_pressed_fill_color"/>
+ <public type="color" name="car_ui_rotary_focus_pressed_stroke_color"/>
<public type="color" name="car_ui_rotary_focus_stroke_color"/>
<public type="color" name="car_ui_rotary_focus_stroke_secondary_color"/>
<public type="color" name="car_ui_scrollbar_thumb"/>
@@ -55,6 +67,37 @@
<public type="dimen" name="car_ui_dialog_title_margin"/>
<public type="dimen" name="car_ui_divider_width"/>
<public type="dimen" name="car_ui_header_list_item_text_start_margin"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_action_button_height"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_action_button_margin_bottom"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_action_button_margin_left"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_action_button_text_size"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_description_padding_top"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_description_text_size"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_description_title_margin_top"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_description_title_padding_left"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_description_title_text_size"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_divider_width"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_error_text_padding_start"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_error_text_size"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_input_area_height"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_input_area_margin_top"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_input_edit_text_padding_left"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_input_edit_text_padding_right"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_input_edit_text_size"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_input_padding_start"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_keyboard_area_padding_bottom"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_keyboard_area_padding_end"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_keyboard_area_padding_start"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_keyboard_width"/>
+ <public type="dimen" name="car_ui_ime_wide_screen_recycler_view_padding_top"/>
+ <public type="dimen" name="car_ui_ime_wide_search_item_icon_size"/>
+ <public type="dimen" name="car_ui_ime_wide_search_item_secondary_image_padding_left"/>
+ <public type="dimen" name="car_ui_ime_wide_search_item_sub_title_padding_left"/>
+ <public type="dimen" name="car_ui_ime_wide_search_item_sub_title_padding_top"/>
+ <public type="dimen" name="car_ui_ime_wide_search_item_sub_title_text_size"/>
+ <public type="dimen" name="car_ui_ime_wide_search_item_title_padding_left"/>
+ <public type="dimen" name="car_ui_ime_wide_search_item_title_padding_top"/>
+ <public type="dimen" name="car_ui_ime_wide_search_item_title_text_size"/>
<public type="dimen" name="car_ui_list_item_action_divider_height"/>
<public type="dimen" name="car_ui_list_item_action_divider_width"/>
<public type="dimen" name="car_ui_list_item_avatar_icon_height"/>
@@ -108,6 +151,7 @@
<public type="dimen" name="car_ui_recyclerview_divider_height"/>
<public type="dimen" name="car_ui_recyclerview_divider_start_margin"/>
<public type="dimen" name="car_ui_recyclerview_divider_top_margin"/>
+ <public type="dimen" name="car_ui_rotary_focus_pressed_stroke_width"/>
<public type="dimen" name="car_ui_rotary_focus_stroke_width"/>
<public type="dimen" name="car_ui_scrollbar_button_size"/>
<public type="dimen" name="car_ui_scrollbar_container_width"/>
@@ -166,12 +210,19 @@
<public type="drawable" name="car_ui_icon_delete"/>
<public type="drawable" name="car_ui_icon_down"/>
<public type="drawable" name="car_ui_icon_edit"/>
+ <public type="drawable" name="car_ui_icon_error"/>
<public type="drawable" name="car_ui_icon_lock"/>
<public type="drawable" name="car_ui_icon_overflow_menu"/>
<public type="drawable" name="car_ui_icon_save"/>
<public type="drawable" name="car_ui_icon_search"/>
<public type="drawable" name="car_ui_icon_search_nav_icon"/>
<public type="drawable" name="car_ui_icon_settings"/>
+ <public type="drawable" name="car_ui_ime_wide_screen_background"/>
+ <public type="drawable" name="car_ui_ime_wide_screen_content_area_background"/>
+ <public type="drawable" name="car_ui_ime_wide_screen_input_area_background"/>
+ <public type="drawable" name="car_ui_ime_wide_screen_input_area_tint_color"/>
+ <public type="drawable" name="car_ui_ime_wide_screen_input_area_tint_error_color"/>
+ <public type="drawable" name="car_ui_ime_wide_screen_no_content_background"/>
<public type="drawable" name="car_ui_list_header_background"/>
<public type="drawable" name="car_ui_list_item_avatar_icon_outline"/>
<public type="drawable" name="car_ui_list_item_background"/>
@@ -180,6 +231,7 @@
<public type="drawable" name="car_ui_preference_icon_chevron"/>
<public type="drawable" name="car_ui_preference_icon_chevron_disabled"/>
<public type="drawable" name="car_ui_preference_icon_chevron_enabled"/>
+ <public type="drawable" name="car_ui_recycler_view_ime_wide_screen_thumb"/>
<public type="drawable" name="car_ui_recyclerview_button_ripple_background"/>
<public type="drawable" name="car_ui_recyclerview_divider"/>
<public type="drawable" name="car_ui_recyclerview_ic_down"/>
@@ -201,7 +253,14 @@
<public type="id" name="car_ui_alert_subtitle"/>
<public type="id" name="car_ui_alert_title"/>
<public type="id" name="car_ui_base_layout_content_container"/>
+ <public type="id" name="car_ui_closeKeyboard"/>
+ <public type="id" name="car_ui_contentAreaAutomotive"/>
<public type="id" name="car_ui_focus_area"/>
+ <public type="id" name="car_ui_fullscreenArea"/>
+ <public type="id" name="car_ui_imeWideScreenInputArea"/>
+ <public type="id" name="car_ui_ime_surface"/>
+ <public type="id" name="car_ui_inputExtractActionAutomotive"/>
+ <public type="id" name="car_ui_inputExtractEditTextContainer"/>
<public type="id" name="car_ui_list_item_end_guideline"/>
<public type="id" name="car_ui_list_item_start_guideline"/>
<public type="id" name="car_ui_list_limiting_message"/>
@@ -245,6 +304,14 @@
<public type="id" name="car_ui_toolbar_title_logo"/>
<public type="id" name="car_ui_toolbar_title_logo_container"/>
<public type="id" name="car_ui_toolbar_top_guideline"/>
+ <public type="id" name="car_ui_wideScreenClearData"/>
+ <public type="id" name="car_ui_wideScreenDescription"/>
+ <public type="id" name="car_ui_wideScreenDescriptionTitle"/>
+ <public type="id" name="car_ui_wideScreenError"/>
+ <public type="id" name="car_ui_wideScreenErrorMessage"/>
+ <public type="id" name="car_ui_wideScreenExtractedTextIcon"/>
+ <public type="id" name="car_ui_wideScreenInputArea"/>
+ <public type="id" name="car_ui_wideScreenSearchResultList"/>
<public type="id" name="checkbox_widget"/>
<public type="id" name="container"/>
<public type="id" name="content_icon"/>
@@ -286,6 +353,7 @@
<public type="layout" name="car_ui_base_layout_toolbar"/>
<public type="layout" name="car_ui_base_layout_toolbar_legacy"/>
<public type="layout" name="car_ui_header_list_item"/>
+ <public type="layout" name="car_ui_ims_wide_screen_input_view"/>
<public type="layout" name="car_ui_list_item"/>
<public type="layout" name="car_ui_list_limiting_message"/>
<public type="layout" name="car_ui_list_preference"/>
@@ -307,6 +375,7 @@
<public type="layout" name="car_ui_seekbar_dialog"/>
<public type="layout" name="car_ui_toolbar"/>
<public type="layout" name="car_ui_toolbar_menu_item"/>
+ <public type="layout" name="car_ui_toolbar_menu_item_primary"/>
<public type="layout" name="car_ui_toolbar_search_view"/>
<public type="layout" name="car_ui_toolbar_tab_item"/>
<public type="layout" name="car_ui_toolbar_tab_item_flexible"/>
@@ -318,6 +387,7 @@
<public type="string" name="car_ui_dialog_preference_negative"/>
<public type="string" name="car_ui_dialog_preference_positive"/>
<public type="string" name="car_ui_ellipsis"/>
+ <public type="string" name="car_ui_ime_wide_screen_system_property_name"/>
<public type="string" name="car_ui_installer_process_name"/>
<public type="string" name="car_ui_preference_switch_off"/>
<public type="string" name="car_ui_preference_switch_on"/>
diff --git a/car-ui-lib/tests/apitest/resource_utils.py b/car-ui-lib/tests/apitest/resource_utils.py
index 763c5a0..7da5642 100755
--- a/car-ui-lib/tests/apitest/resource_utils.py
+++ b/car-ui-lib/tests/apitest/resource_utils.py
@@ -108,6 +108,8 @@
resName = resource.get('name')
resType = resource.tag
+ if resType == "string-array":
+ resType = "array"
if resource.tag == 'item' or resource.tag == 'public':
resType = resource.get('type')