Update CarLauncher Owners am: 4d9257df23
Original change: https://android-review.googlesource.com/c/platform/packages/apps/Car/Launcher/+/2278186
Change-Id: I972869c7b9fbf5e67de038535e81e068ceadee18
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1d3fc7b..8c990c2 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -125,6 +125,8 @@
Dialer application.-->
<meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="false"/>
<meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI" android:value="false"/>
+ <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+ android:value="true" />
<intent-filter>
<action android:name="android.telecom.InCallService"/>
</intent-filter>
diff --git a/res/drawable-night/ic_launcher_home.png b/res/drawable-night/ic_launcher_home.png
deleted file mode 100644
index 2cc02a6..0000000
--- a/res/drawable-night/ic_launcher_home.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-night/ic_launcher_home.xml b/res/drawable-night/ic_launcher_home.xml
new file mode 100644
index 0000000..72e70be
--- /dev/null
+++ b/res/drawable-night/ic_launcher_home.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+
+ <path
+ android:fillColor="@color/launcher_home_icon_color"
+ android:fillType="evenOdd"
+ android:pathData="M24 48C37.2548 48 48 37.2548 48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 37.2548 10.7452 48 24 48ZM13.4395 20.16V35.52H17.3995V22.72H30.5995V35.52H34.5595V20.16L23.9995 12.48L13.4395 20.16ZM27.9595 30.4V32.96H20.0395V30.4H27.9595ZM27.9595 25.28V27.84H20.0395V25.28H27.9595Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_launcher_home.png b/res/drawable/ic_launcher_home.png
deleted file mode 100644
index da4fe58..0000000
--- a/res/drawable/ic_launcher_home.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/ic_launcher_home.xml b/res/drawable/ic_launcher_home.xml
new file mode 100644
index 0000000..72e70be
--- /dev/null
+++ b/res/drawable/ic_launcher_home.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+
+ <path
+ android:fillColor="@color/launcher_home_icon_color"
+ android:fillType="evenOdd"
+ android:pathData="M24 48C37.2548 48 48 37.2548 48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 37.2548 10.7452 48 24 48ZM13.4395 20.16V35.52H17.3995V22.72H30.5995V35.52H34.5595V20.16L23.9995 12.48L13.4395 20.16ZM27.9595 30.4V32.96H20.0395V30.4H27.9595ZM27.9595 25.28V27.84H20.0395V25.28H27.9595Z" />
+</vector>
\ No newline at end of file
diff --git a/res/layout/app_item.xml b/res/layout/app_item.xml
index cecbc65..cae5797 100644
--- a/res/layout/app_item.xml
+++ b/res/layout/app_item.xml
@@ -23,6 +23,7 @@
android:layout_marginBottom="@dimen/app_grid_row_margin"
android:layout_marginEnd="@dimen/app_touch_target_margin"
android:layout_marginStart="@dimen/app_touch_target_margin"
+ android:background="?android:attr/selectableItemBackground"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/app_touch_target_padding">
diff --git a/res/layout/descriptive_text.xml b/res/layout/descriptive_text.xml
index 317bd4f..f1aa5b0 100644
--- a/res/layout/descriptive_text.xml
+++ b/res/layout/descriptive_text.xml
@@ -34,7 +34,7 @@
android:id="@+id/primary_text"
android:layout_height="wrap_content"
android:layout_width="0dp"
- android:singleLine="true"
+ android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_marginStart="@dimen/card_content_image_margin"
app:layout_goneMarginStart="0dp"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 212c1a7..c300dc9 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Bel tans …"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Begin Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Kan nie Android Auto begin nie. Geen aktiwiteit gevind nie."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# toestel}other{# toestelle}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Weer"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° meestal sonnig"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Bergaansig • H: --° L: --°"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 24484e2..8e602e7 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"በመደወል ላይ…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Autoን አስጀምር"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Autoን ማስጀመር አልተቻለም። ምንም እንቅስቃሴ አልተገኘም።"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# መሣሪያ}one{# መሣሪያዎች}other{# መሣሪያዎች}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"የአየር ሁኔታ"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° በአብዛኛው ጸሐያማ"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"የተራራ እይታ • ከ፦ --° ዝ፦ --°"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 7c43663..968f3ad 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"جارٍ طلب الرقم…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"تشغيل Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"يتعذّر تشغيل Android Auto. لم يتم العثور على أي أنشطة."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{جهاز واحد (#)}zero{# جهاز}two{جهازان (#)}few{# أجهزة}many{# جهازًا}other{# جهاز}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"الطقس"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° مشمس في أغلب الأوقات"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"مدينة القاهرة • درجة الحرارة الأعلى: --° الأدنى: --°"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 7412d08..00c1f3e 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"ডায়েল কৰি থকা হৈছে…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto লঞ্চ কৰক"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto লঞ্চ কৰিব পৰা নগ’ল। কোনো কাৰ্যকলাপ পোৱা নগ’ল।"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# টা ডিভাইচ}one{# টা ডিভাইচ}other{# টা ডিভাইচ}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"বতৰ"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° অধিক সময় ৰৌদ্ৰোজ্জ্বল"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"মাউণ্টেইন ভিউ • H: --° L: --°"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 83a2333..fc13524 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Nömrə yığılır…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto\'nu başladın"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto\'nu başlatmaq mümkün deyil. Fəaliyyət tapılmadı."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# cihaz}other{# cihaz}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Hava"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Əksərən günəşli"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Dağ Görünüşü • Y: --° U: --°"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index f25b7ed..bc39d61 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Poziva se…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Pokreni Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Pokretanje usluge Android Auto nije uspelo. Nije pronađena nijedna aktivnost."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# uređaj}one{# uređaj}few{# uređaja}other{# uređaja}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Vreme"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° uglavnom sunčano"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Novi Sad • Najviša: --° Najniža: --°"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 2d7f0b9..ec0f7f8 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Ідзе набор нумара…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Запусціць Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Не ўдалося запусціць Android Auto. Дзеянні не знойдзены."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# прылада}one{# прылада}few{# прылады}many{# прылад}other{# прылады}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Надвор\'е"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--°, пераважна сонечна"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Маўтын-В\'ю • макс.: --°, мін.: --°"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 4c3cef9..dc0a5f2 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Набиране…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Стартиране на Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto не може да се стартира. Няма намерена активност."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# устройство}other{# устройства}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Времето"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Предимно слънчево"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Макс: --° Мин: --°"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index d685aba..dad3d42 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"ডায়াল করা হচ্ছে…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto লঞ্চ করুন"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto লঞ্চ করা যায়নি। কোনও অ্যাক্টিভিটি পাওয়া যায়নি।"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{#টি ডিভাইস}one{#টি ডিভাইস}other{#টি ডিভাইস}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Weather"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° সাধারণত রৌদ্রোজ্জ্বল"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"পাহাড়ের দৃশ্য • H: --° L: --°"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 20d014f..1860247 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Biranje…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Pokreni Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Nije moguće pokrenuti Android Auto. Nije pronađena nijedna aktivnost."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# uređaj}one{# uređaj}few{# uređaja}other{# uređaja}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Vrijeme"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Pretežno sunčano"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • V: --° N: --°"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 4fb7682..86d8ca8 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"S\'està marcant…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Inicia Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"No es pot iniciar Android Auto. No s\'ha trobat cap activitat."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# dispositiu}other{# dispositius}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Temps"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Principalment assolellat"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Màx.: --° Mín.: --°"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index a9a2e88..079f968 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Vytáčení…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Spustit Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto se nepodařilo spustit. Nebyla nalezena žádná aktivita."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# zařízení}few{# zařízení}many{# zařízení}other{# zařízení}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Počasí"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Většinou slunečno"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Nejvyšší: --° Nejnižší: --°"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 428e190..a1656a2 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Ringer op…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Start Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto kunne ikke startes. Der blev ikke fundet nogen aktivitet."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# enhed}one{# enhed}other{# enheder}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Vejret"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"-- ° Overvejende solskin"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: -- ° L: -- °"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index d67ff3f..df2bda5 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Rufaufbau…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto starten"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto kann nicht gestartet werden. Keine Aktivität gefunden."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# Gerät}other{# Geräte}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Wetter"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Größtenteils sonnig"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: --° T: --°"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index f51198b..de0bffb 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Κλήση…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Εκκίνηση του Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Δεν είναι δυνατή η εκκίνηση τη εφαρμογής Android Auto. Δεν βρέθηκαν δραστηριότητες."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# συσκευή}other{# συσκευές}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Καιρός"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Κυρίως ηλιοφάνεια"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Μ: --° Ε: --°"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index aea87d3..154182d 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Dialling…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Launch Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Unable to launch Android Auto. No activity found."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# device}other{# devices}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Weather"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"–° Mostly sunny"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: –° L: –°"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index aea87d3..154182d 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Dialling…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Launch Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Unable to launch Android Auto. No activity found."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# device}other{# devices}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Weather"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"–° Mostly sunny"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: –° L: –°"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index aea87d3..154182d 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Dialling…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Launch Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Unable to launch Android Auto. No activity found."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# device}other{# devices}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Weather"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"–° Mostly sunny"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: –° L: –°"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index aea87d3..154182d 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Dialling…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Launch Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Unable to launch Android Auto. No activity found."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# device}other{# devices}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Weather"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"–° Mostly sunny"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: –° L: –°"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index e7041c3..1833004 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Dialing…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Launch Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Unable to launch Android Auto. No activity found."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# device}other{# devices}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Weather"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Mostly sunny"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: --° L: --°"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 3695fff..5086144 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Marcando…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Iniciar Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"No se puede iniciar Android Auto. No se encontró ninguna actividad."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# dispositivo}other{# dispositivos}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Tiempo"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Mayormente soleado"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • M: --° M: --°"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 4bacf9f..f5730ac 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Marcando…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Iniciar Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"No se ha podido iniciar Android Auto. No se ha encontrado ninguna actividad."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# dispositivo}other{# dispositivos}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Tiempo"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--°, mayormente soleado"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Máx.: --° Mín.: --°"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 2d3b569..5401812 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Valimine …"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto käivitamine"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Autot ei õnnestu käivitada. Ühtegi tegevust ei leitud."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# seade}other{# seadet}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Ilm"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° – enamasti päikeseline"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • K: --° M: --°"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 71694ca..3200fa5 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Markatzen…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Abiarazi Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Ezin da abiarazi Android Auto. Ez da jarduerarik aurkitu."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# gailu}other{# gailu}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Eguraldia"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"-- ° Gehienbat eguzkitsua"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: -- ° L: -- °"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 758c667..057fbe4 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"درحال شمارهگیری…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"راهاندازی Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto راهاندازی نشد. فعالیتی پیدا نشد."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# دستگاه}one{# دستگاه}other{# دستگاه}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"آبوهوا"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° بیشتر آفتابی"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"مانتین ویو • بیشینه: --° کمینه: --°"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index ff51fff..2cef252 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Soitetaan…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Käynnistä Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Autoa ei voi käynnistää. Tapahtumia ei löytynyt."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# laite}other{# laitetta}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Sää"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Enimmäkseen aurinkoista"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Maks.: --° Min.: --°"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index afc69b8..88d6141 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Composition en cours…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Lancez Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Impossible de lancer Android Auto. Aucune activité trouvée."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# appareil}one{# appareil}other{# appareils}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Météo"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° et principalement ensoleillé"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Montréal • Max. : --° Min. : --°"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index b229688..64c5ead 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Appel…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Lancer Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Impossible de lancer Android Auto. Aucune activité trouvée."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# appareil}one{# appareil}other{# appareils}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Météo"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"-- ° Ensoleillé dans l\'ensemble"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Max. : -- ° - Min. : -- °"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 976a67d..c2d830e 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Marcando…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Iniciar Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Non se puido iniciar Android Auto. Non se atopou ningunha actividade."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# dispositivo}other{# dispositivos}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Información meteorolóxica"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--°, principalmente solleiro"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Máxima: --°; mínima: --°"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 34422f3..eb1f3ad 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"ડાયલ કરી રહ્યાં છીએ…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto લૉન્ચ કરો"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto લૉન્ચ કરી શકાતું નથી. કોઈ પ્રવૃત્તિ મળી નથી."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# ડિવાઇસ}one{# ડિવાઇસ}other{# ડિવાઇસ}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Weather"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° મોટેભાગે તડકો"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"માઉન્ટેન વ્યૂ • H: --° L: --°"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index ac31389..8f2bc10 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"डायल किया जा रहा है…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto लॉन्च करें"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto लॉन्च नहीं किया जा सका. कोई गतिविधि नहीं मिली."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# डिवाइस}one{# डिवाइस}other{# डिवाइस}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Weather"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° ज़्यादातर समय धूप रहेगी"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"माउंट आबू • H: --° L: --°"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index ed37d64..7e1fe23 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Biranje broja…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Pokrenite Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Pokretanje Android Auta nije uspjelo. Nije pronađena nijedna aktivnost."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# uređaj}one{# uređaj}few{# uređaja}other{# uređaja}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Vrijeme"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° uglavnom sunčano"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • V: --° N: --°"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 76d3089..bfa3664 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Tárcsázás…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto indítása"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Az Android Auto indítása nem sikerült. Nem található tevékenység."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# eszköz}other{# eszköz}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Időjárás"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Túlnyomóan napos"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Max.: --° Min.: --°"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 7a3b3db..cdd44d5 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Համարհավաքում…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Գործարկել Android Auto-ն"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Չհաջողվեց գործարկել Android Auto-ն։ Ոչ մի գործողություն չի գտնվել։"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# սարք}one{# սարք}other{# սարք}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Եղանակ"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Հիմնականում արևոտ"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Մաունթին Վյու • Առավ.՝ --° Նվազ.՝ --°"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 86212ac..1a2ee89 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Memanggil…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Luncurkan Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Tidak dapat meluncurkan Android Auto. Tidak ditemukan aktivitas."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# perangkat}other{# perangkat}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Cuaca"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Sebagian besar cerah"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: --° L: --°"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 5d0d782..5395bac 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Hringir…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Ræsa Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Ekki er hægt að ræsa Android Auto. Engin virkni fannst."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# tæki}one{# tæki}other{# tæki}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Veður"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Sólskin að mestu"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Fjallasýn • H: --° L: --°"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index c818555..8b6381e 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Chiamata in corso…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Avvia Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Impossibile avviare Android Auto. Non è stata trovata alcuna attività."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# dispositivo}other{# dispositivi}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Meteo"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--°, prevalentemente soleggiato"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Max: --° Min: --°"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 0bc2471..d7543d6 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"החיוג מתבצע…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"הפעלה של Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"לא ניתן להפעיל את Android Auto. לא נמצאה פעילות."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{מכשיר אחד}two{# מכשירים}many{# מכשירים}other{# מכשירים}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"מזג אוויר"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° שמשי"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: --° L: --°"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 18d30c0..a1d5493 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"発信中…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto の起動"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto を起動できません。アクティビティが見つかりません。"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# 台のデバイス}other{# 台のデバイス}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"天気情報"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° 晴れ"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: --° L: --°"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index a24b9cf..21b6c0d 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"მიმდინარეობს აკრეფა…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"გაუშვით Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto-ს გაშვება შეუძლებელია. აქტივობა ვერ მოიძებნა."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# მოწყობილობა}other{# მოწყობილობა}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"ამინდი"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° უმეტესად მზიანი"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"მთების ხედი • მაქს.: --° მინ.: --°"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index f4fe182..1ee7800 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Терілуде…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto қолданбасын іске қосу"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto қолданбасы іске қосылмады. Ешқандай әрекет табылмады."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# құрылғы}other{# құрылғы}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Ауа райы"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° көбінесе күн ашық"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: --° L: --°"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 67fe569..bbec4ce 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"កំពុងហៅ…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"ចាប់ផ្ដើម Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"មិនអាចចាប់ផ្ដើម Android Auto បានទេ។ រកមិនឃើញសកម្មភាពទេ។"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{ឧបករណ៍ #}other{ឧបករណ៍ #}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"អាកាសធាតុ"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° ភាគច្រើនបើកថ្ងៃ"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: --° L: --°"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 9e4f319..aa1e4e9 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"ಡಯಲ್ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto ಪ್ರಾರಂಭಿಸಿ"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto ಪ್ರಾರಂಭಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ. ಯಾವುದೇ ಚಟುವಟಿಕೆ ಕಂಡುಬಂದಿಲ್ಲ."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# ಸಾಧನ}one{# ಸಾಧನಗಳು}other{# ಸಾಧನಗಳು}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"ಹವಾಮಾನ"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° ಬಹುತೇಕ ಬಿಸಿಲಿನ ವಾತಾವರಣ"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"ಮೌಂಟೇನ್ ವ್ಯೂ • H: --° L: --°"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 820c414..72f9dfc 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"전화 거는 중…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto 시작"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto를 시작할 수 없습니다. 활동이 없습니다."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{기기 1개}other{기기 #개}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"날씨"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° 대체로 맑음"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"마운틴 뷰 • 최고: --° 최저: --°"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index b4db2a2..f2bdaf1 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Терилүүдө…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto\'ну ишке киргизүү"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto иштетилген жок. Аракеттер табылган жок."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# түзмөк}other{# түзмөк}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Аба ырайы"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Көбүнесе күн чайыттай ачык болот"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Маунтин-Вью • Ж: --° Т: --°"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 691a05b..52e584f 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"ກຳລັງໂທ…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"ເປີດໃຊ້ Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"ບໍ່ສາມາດເປີດໃຊ້ Android Auto ໄດ້. ບໍ່ພົບການເຄື່ອນໄຫວໃດໆ."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{ອຸປະກອນ # ເຄື່ອງ}other{ອຸປະກອນ # ເຄື່ອງ}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"ສະພາບອາກາດ"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° ແດດອອກເປັນສ່ວນໃຫຍ່"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"ວິວພູເຂົາ • ສູງສຸດ: --° ຕໍ່າສຸດ: --°"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 636dd58..130316b 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Renkamas numeris…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Paleisti „Android Auto“"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Nepavyko paleisti „Android Auto“. Nerasta jokios veiklos."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# įrenginys}one{# įrenginys}few{# įrenginiai}many{# įrenginio}other{# įrenginių}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Orai"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Daugiausia saulėta"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mauntin Vju • H: --° L: --°"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index a7cd62e..9fa0456 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Notiek numura sastādīšana…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Palaist Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Nevar palaist Android Auto. Netika atrasta neviena darbība."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# ierīce}zero{# ierīču}one{# ierīce}other{# ierīces}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Laikapstākļi"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--°; galvenokārt saulains laiks"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mauntinvjū • Augstākā: --°; zemākā: --°"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 482348f..c6a3a4e 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Бирање…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Стартувајте ја Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Не може да се стартува Android Auto. Не е најдена активност."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# уред}one{# уред}other{# уреди}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Временска прогноза"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"-- ° Претежно сончево"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Маунтин Вју • В: -- ° Н: -- °"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 7fc810a..c7ced4a 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"ഡയൽ ചെയ്യുന്നു…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto ലോഞ്ച് ചെയ്യുക"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto ലോഞ്ച് ചെയ്യാനാകുന്നില്ല. ആക്റ്റിവിറ്റിയൊന്നും കണ്ടെത്തിയില്ല."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# ഉപകരണം}other{# ഉപകരണങ്ങൾ}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"കാലാവസ്ഥ"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° കൂടുതലും തെളിഞ്ഞ ആകാശം"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • കൂടിയത്: --° കുറഞ്ഞത്: --°"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 2b07935..ade4d7c 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Залгаж байна…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Аuto-г эхлүүлэх"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto-г эхлүүлэх боломжгүй. Үйл ажиллагаа олдсонгүй."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# Төхөөрөмж}other{# Төхөөрөмжүүд}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Цаг агаар"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Ихэвчлэн нартай"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Дээд: --° Доод: --°"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index dc2b0cb..2813aba 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"डायल करत आहे…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto लाँच करा"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto लाँच करता आले नाही. कोणतीही अॅक्टिव्हिटी आढळली नाही."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# डिव्हाइस}other{# डिव्हाइस}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"हवामान"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° बहुतांशी सुर्यप्रकाशित"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"महाबळेश्वर • कमाल: --° किमान: --°"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 22b2c9b..4e52f4b 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Mendail…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Lancarkan Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Tidak dapat melancarkan Android Auto. Tiada aktiviti ditemukan."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# peranti}other{# peranti}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Cuaca"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Kebanyakannya cerah"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Pemandangan Gunung • H: --° L: --°"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 3a72e41..ebeb56e 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"ခေါ်ဆိုနေသည်…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto ဖွင့်ပါ"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto ဖွင့်၍မရပါ။ လုပ်ဆောင်ချက် မရှိပါ။"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{စက် # ခု}other{စက် # ခု}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"မိုးလေဝသ"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° အများအားဖြင့် နေသာသည်"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • မြင့်- --° နိမ့်- --°"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index fea699e..11f0381 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Slår nummeret …"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Start Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Kan ikke starte Android Auto. Fant ingen aktiviteter."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# enhet}other{# enheter}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Været"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° For det meste sol"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Høyeste temp.: --° Laveste temp.: --°"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 6189f08..33a96e5 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"डायल गरिँदै छ"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto लन्च गर्नुहोस्"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto लन्च गर्न सकिएन। कुनै गतिविधि भेट्टिएन।"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# डिभाइस}other{# वटा डिभाइस}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"मौसम"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° अधिकांश घमाइलो"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"माउन्टेन भ्यू • H: --° L: --°"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 569950a..93291d2 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Kiezen…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto starten"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Kan Android Auto niet starten. Geen activiteit gevonden."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# apparaat}other{# apparaten}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Weer"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"-- ° Grotendeels zonnig"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Max: -- ° Min: -- °"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index f738f69..60717e3 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"ଡାଏଲ କରାଯାଉଛି…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Autoକୁ ଲଞ୍ଚ କରନ୍ତୁ"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Autoକୁ ଲଞ୍ଚ କରିବା ପାଇଁ ଅସମର୍ଥ। କୌଣସି କାର୍ଯ୍ୟକଳାପ ମିଳୁ ନାହିଁ।"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{#ଟି ଡିଭାଇସ}other{#ଟି ଡିଭାଇସ}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"ପାଣିପାଗ"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° ପ୍ରାୟତଃ ଖରା"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"ମାଉଣ୍ଟେନ୍ ଭ୍ୟୁ • H: --° L: --°"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 2660ff7..ba08803 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"ਡਾਇਲ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto ਲਾਂਚ ਕਰੋ"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto ਲਾਂਚ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਕੋਈ ਸਰਗਰਮੀ ਨਹੀਂ ਮਿਲੀ।"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# ਡੀਵਾਈਸ}one{# ਡੀਵਾਈਸ}other{# ਡੀਵਾਈਸ}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"ਮੌਸਮ"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° ਜ਼ਿਆਦਾਤਰ ਧੁੱਪ"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"ਅੰਮ੍ਰਿਤਸਰ • ਉੱਚ: --° ਨਿਮਨ: --°"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index e4b2293..3c8b96c 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Wybieram numer…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Uruchom Androida Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Nie można uruchomić Androida Auto. Nie znaleziono żadnych działań."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# urządzenie}few{# urządzenia}many{# urządzeń}other{# urządzenia}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Pogoda"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--°, liczne przejaśnienia"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • maks.: --° min.: --°"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 02ecb5d..0e34870 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"A marcar…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Iniciar o Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Não é possível iniciar o Android Auto. Não foram encontradas atividades."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# dispositivo}other{# dispositivos}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Meteorologia"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Céu maioritariamente limpo"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Máx.: --° Mín.: --°"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index d18cc93..f7b60d3 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Chamando…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Iniciar o Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Não foi possível iniciar o Android Auto. Nenhuma atividade foi encontrada."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# dispositivo}one{# dispositivo}other{# dispositivos}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Clima"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Predominantemente ensolarado"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Mínima: --° Máxima: --°"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index d25222b..276e817 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -20,18 +20,19 @@
<string name="app_title" msgid="1995858460392177468">"Lansator de aplicații pentru mașină"</string>
<string name="app_launcher_title_all_apps" msgid="3215107396036838217">"Toate aplicațiile"</string>
<string name="app_launcher_title_media_only" msgid="4400886555907422816">"Aplicații media"</string>
- <string name="driving_toast_text" msgid="149483091947120092">"Nu poți folosi <xliff:g id="APP_NAME">%1$s</xliff:g> în timp ce conduci."</string>
- <string name="tap_for_more_info_text" msgid="2043549383814415585">"Pentru mai multe informații, atinge cardul"</string>
- <string name="tap_to_launch_text" msgid="6910695705119260847">"Pentru a lansa, atinge cardul"</string>
+ <string name="driving_toast_text" msgid="149483091947120092">"Nu puteți folosi <xliff:g id="APP_NAME">%1$s</xliff:g> în timp ce conduceți."</string>
+ <string name="tap_for_more_info_text" msgid="2043549383814415585">"Pentru mai multe informații, atingeți cardul"</string>
+ <string name="tap_to_launch_text" msgid="6910695705119260847">"Pentru a lansa, atingeți cardul"</string>
<string name="ongoing_call_duration_text_separator" msgid="6019453854809413561">" • "</string>
<string name="ongoing_call_text" msgid="2394626676478708070">"Apel în desfășurare"</string>
<string name="dialing_call_text" msgid="415326503409745490">"Se apelează…"</string>
- <string name="projected_launch_text" msgid="5809132353957907500">"Lansează Android Auto"</string>
+ <string name="projected_launch_text" msgid="5809132353957907500">"Lansați Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Nu se poate lansa Android Auto. Nu a fost găsită nicio activitate."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# dispozitiv}few{# dispozitive}other{# de dispozitive}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Meteo"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° În mare parte însorit"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Max: --° Min: --°"</string>
- <string name="hide_debug_apps" msgid="6128313544379622475">"Ascunde aplicațiile de remediere a erorilor"</string>
- <string name="show_debug_apps" msgid="4521073121286325786">"Afișează aplicațiile de remediere a erorilor"</string>
+ <string name="hide_debug_apps" msgid="6128313544379622475">"Ascundeți aplicațiile de remediere a erorilor"</string>
+ <string name="show_debug_apps" msgid="4521073121286325786">"Afișați aplicațiile de remediere a erorilor"</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 9a92fcf..90a5808 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Набор номера…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Запустить Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Не удается запустить Android Auto. Ничего не найдено."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# устройство}one{# устройство}few{# устройства}many{# устройств}other{# устройства}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Погода"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Преим. солнечно"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Маунтин-Вью • Макс. --°, мин. --°"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 5696d29..36bfede 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"අමතමින්…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto දියත් කරන්න"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto දියත් කිරීමට නොහැකිය. ක්රියාකාරකම් නොමැත."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{උපාංග #ක්}one{උපාංග #ක්}other{උපාංග #ක්}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"කාලගුණය"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° බොහෝ විට අව්ව"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"කඳුකර දර්ශනය • H: --° L: --°"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 1add2ba..083520b 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Vytáča sa…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Spustiť Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto sa nepodarilo spustiť. Nebola nájdená sa žiadna aktivita."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# zariadenie}few{# zariadenia}many{# devices}other{# zariadení}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Počasie"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Väčšinou slnečno"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: --° L: --°"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 5e17c9a..31a983f 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Klicanje …"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Zaženite storitev Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Ni mogoče zagnati Androida Auto. Najdena ni bila nobena dejavnost."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# naprava}one{# naprava}two{# napravi}few{# naprave}other{# naprav}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Vreme"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° pretežno sončno"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • V: --° N: --°"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 4416119..2bc0b4a 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Po formon numrin…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Hap Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto nuk mund të niset. Nuk u gjet asnjë aktivitet."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# pajisje}other{# pajisje}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Moti"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Kryesisht me diell"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • L: --° U: --°"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 634d94a..c81bfda 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Позива се…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Покрени Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Покретање услуге Android Auto није успело. Није пронађена ниједна активност."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# уређај}one{# уређај}few{# уређаја}other{# уређаја}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Време"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° углавном сунчано"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Нови Сад • Највиша: --° Најнижа: --°"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index f9f9247..dbffb81 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Ringer upp …"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Starta Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Det gick inte att starta Android Auto. Ingen aktivitet hittades."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# enhet}other{# enheter}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Väder"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Mestadels soligt"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • max: --° min: --°"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index df698de..24c7959 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Inapiga…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Fungua Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Imeshindwa kufungua Android Auto. Hamna shughuli iliyopatikana."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{Kifaa #}other{Vifaa #}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Hali ya Hewa"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Jua mara nyingi"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mwonekano wa Mlima • H: --° L: --°"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index b979d2a..c464b25 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"டயல் செய்கிறது…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Autoவைத் தொடங்கு"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Autoவைத் தொடங்க முடியவில்லை. செயல்பாடு எதுவுமில்லை."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# சாதனம்}other{# சாதனங்கள்}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Weather"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° உடன் பெரும்பாலும் வெயிலடிக்கும்"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"மவுண்டன் வியூ • H: --° L: --°"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 91954aa..f12f4c1 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"డయల్ చేస్తోంది…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Autoను లాంచ్ చేయండి"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Autoను లాంచ్ చేయడం సాధ్యం కాలేదు. ఏ యాక్టివిటీ కనుగొనబడలేదు."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# పరికరం}other{# పరికరాలు}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"వాతావరణం"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"చాలావరకు --° ఎండగా ఉంటుంది"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"మౌంటెయిన్ వ్యూ • H: --° L: --°"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 6c4ee1c..20506ad 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"กำลังโทรออก…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"เปิด Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"เปิด Android Auto ไม่ได้ ไม่พบกิจกรรม"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{อุปกรณ์ # เครื่อง}other{อุปกรณ์ # เครื่อง}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"สภาพอากาศ"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° แดดจัดเป็นส่วนใหญ่"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"เมาน์เทนวิว • สูงสุด: --° ต่ำสุด: --°"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 717db2c..8653b8e 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Dina-dial…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Ilunsad ang Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Hindi mailunsad ang Android Auto. Walang nakitang aktibidad."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# device}one{# device}other{# na device}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Lagay ng Panahon"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Maaraw sa pangkalahatan"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: --° L: --°"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 564df99..6359c16 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Aranıyor…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto\'yu başlatın"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto başlatılamadı. Hiçbir etkinlik bulunamadı."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# cihaz}other{# cihaz}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Hava durumu"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Çoğunlukla güneşli"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • E.Y.: --° E.D.: --°"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 71cf26e..16af560 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Набір номера…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Запустити Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Не вдається запустити Android Auto. Не знайдено жодних дій."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# пристрій}one{# пристрій}few{# пристрої}many{# пристроїв}other{# пристрою}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Погода"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--°, в основному сонячно"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Маунтін-В\'ю • Макс. --°, мін. --°"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index fd629ee..5733061 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"ڈائل ہو رہی ہے…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto شروع کریں"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto شروع کرنے سے قاصر۔ کوئی سرگرمی نہیں ملی۔"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# آلہ}other{# آلات}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"موسم"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° زیادہ تر دھوپ"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: --° L: --°"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 46eec29..9b1c0c2 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Raqam terilmoqda…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Android Auto xizmatini ishga tushirish"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Android Auto ishga tushmadi. Hech qanday harakat topilmadi."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# ta qurilma}other{# ta qurilma}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Ob-havo"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Asosan quyoshli"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • Maks: --° Min: --°"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 5a006ba..7ad0bea 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Đang gọi…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Chạy Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Không thể chạy Android Auto. Không tìm thấy hoạt động nào."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# thiết bị}other{# thiết bị}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Thời tiết"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Nhiều nắng"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Mountain View • H: --° L: --°"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 1556192..b424cb2 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"正在拨号…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"启动 Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"无法启动 Android Auto。未找到任何活动。"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# 个设备}other{# 个设备}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"天气"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° 以晴为主"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"山景城 • 高:--° 低:--°"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 725f273..ee4b0de 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"正在撥號…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"啟動 Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"無法啟動 Android Auto。找不到活動。"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# 部裝置}other{# 部裝置}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"天氣"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° 大致晴朗"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"山景城 • H: --° L: --°"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 49fe232..6df32bc 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"撥號中…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"啟動 Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"無法啟動 Android Auto。找不到任何活動。"</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{# 部裝置}other{# 部裝置}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"天氣"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° 晴時多雲"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"山景城 • 高:--° 低:--°"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 60052e7..bc66686 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -28,7 +28,8 @@
<string name="dialing_call_text" msgid="415326503409745490">"Iyadayela…"</string>
<string name="projected_launch_text" msgid="5809132353957907500">"Qalisa i-Android Auto"</string>
<string name="projected_onclick_launch_error_toast_text" msgid="7462478007773137082">"Ayikwazi ukuqalisa i-Android Auto. Awukho umsebenzi otholakalayo."</string>
- <string name="projection_devices" msgid="916314090031116598">"{count,plural, =1{idivayisi #}one{amadivayisi #}other{amadivayisi #}}"</string>
+ <!-- no translation found for projection_devices (916314090031116598) -->
+ <skip />
<string name="weather_app_name" msgid="8048146303519139993">"Isimo sezulu"</string>
<string name="fake_weather_main_text" msgid="8117240999340284429">"--° Kuzoshisa isikhathi esiningi"</string>
<string name="fake_weather_footer_text" msgid="4570172025869749926">"Ukubuka Kwentaba • H: --° L: --°"</string>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index fe15153..9367957 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -23,4 +23,5 @@
<color name="dialer_button_icon_color">#FFFFFF</color>
<color name="dialer_end_call_button_color">#EE675C</color>
<color name="minimized_progress_bar_background">#5CFFFFFF</color>
+ <color name="launcher_home_icon_color">#66B5FF</color>
</resources>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
index b1ab52f..e4af574 100644
--- a/res/values/overlayable.xml
+++ b/res/values/overlayable.xml
@@ -25,6 +25,7 @@
<item type="color" name="dialer_button_icon_color"/>
<item type="color" name="dialer_end_call_button_color"/>
<item type="color" name="icon_tint"/>
+ <item type="color" name="launcher_home_icon_color"/>
<item type="color" name="media_button_tint"/>
<item type="color" name="minimized_progress_bar_background"/>
<item type="color" name="recent_apps_line_divider_color"/>
@@ -84,6 +85,7 @@
<item type="drawable" name="ic_call_end_button"/>
<item type="drawable" name="ic_clear_black"/>
<item type="drawable" name="ic_dialpad"/>
+ <item type="drawable" name="ic_launcher_home"/>
<item type="drawable" name="ic_mic_off"/>
<item type="drawable" name="ic_mic_on"/>
<item type="drawable" name="ic_mute_activatable"/>
@@ -151,6 +153,7 @@
<item type="string" name="app_launcher_title_media_only"/>
<item type="string" name="app_title"/>
<item type="string" name="config_smallCanvasOptimizedMapIntent"/>
+ <item type="string" name="default_media_song_title"/>
<item type="string" name="dialing_call_text"/>
<item type="string" name="driving_toast_text"/>
<item type="string" name="fake_weather_footer_text"/>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 949794b..71b8017 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -20,6 +20,8 @@
<string name="app_launcher_title_all_apps">All apps</string>
<string name="app_launcher_title_media_only">Media apps</string>
+ <string name="default_media_song_title"></string>
+
<string name="driving_toast_text">
<xliff:g id="app_name" example="Settings">%1$s</xliff:g> can\'t be used while driving.
</string>
diff --git a/src/com/android/car/carlauncher/AppLauncherUtils.java b/src/com/android/car/carlauncher/AppLauncherUtils.java
index 3b4eda0..0d637bf 100644
--- a/src/com/android/car/carlauncher/AppLauncherUtils.java
+++ b/src/com/android/car/carlauncher/AppLauncherUtils.java
@@ -261,15 +261,7 @@
carMediaManager);
}
},
- contextArg -> {
- // getLaunchIntentForPackage looks for a main activity in the category
- // Intent.CATEGORY_INFO, then Intent.CATEGORY_LAUNCHER, and returns null
- // if neither are found
- Intent packageLaunchIntent =
- packageManager.getLaunchIntentForPackage(packageName);
- AppLauncherUtils.launchApp(contextArg,
- packageLaunchIntent != null ? packageLaunchIntent : intent);
- });
+ /* alternateLaunchCallback */ null);
launchablesMap.put(componentName, appMetaData);
}
}
@@ -298,7 +290,7 @@
info.getBadgedIcon(0),
isDistractionOptimized,
contextArg -> AppLauncherUtils.launchApp(contextArg, intent),
- null);
+ /* alternateLaunchCallback */ null);
launchablesMap.put(componentName, appMetaData);
}
}
@@ -342,7 +334,7 @@
Log.i(TAG, "Successfully enabled package [" + packageName + "]");
AppLauncherUtils.launchApp(contextArg, intent);
},
- null);
+ /* alternateLaunchCallback */ null);
launchablesMap.put(componentName, appMetaData);
}
}
diff --git a/src/com/android/car/carlauncher/CarFullscreenTaskMonitorListener.java b/src/com/android/car/carlauncher/CarFullscreenTaskMonitorListener.java
index 90c6224..5dbcd44 100644
--- a/src/com/android/car/carlauncher/CarFullscreenTaskMonitorListener.java
+++ b/src/com/android/car/carlauncher/CarFullscreenTaskMonitorListener.java
@@ -23,7 +23,6 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
@@ -37,9 +36,8 @@
public CarFullscreenTaskMonitorListener(
AtomicReference<CarActivityManager> carActivityManagerRef,
- SyncTransactionQueue syncQueue,
- Optional<FullscreenUnfoldController> unfoldController) {
- super(syncQueue, unfoldController);
+ SyncTransactionQueue syncQueue) {
+ super(syncQueue);
mCarActivityManagerRef = carActivityManagerRef;
}
@Override
diff --git a/src/com/android/car/carlauncher/CarLauncher.java b/src/com/android/car/carlauncher/CarLauncher.java
index 7a62dc3..e045a4e 100644
--- a/src/com/android/car/carlauncher/CarLauncher.java
+++ b/src/com/android/car/carlauncher/CarLauncher.java
@@ -17,49 +17,32 @@
package com.android.car.carlauncher;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
-import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
import android.app.TaskStackListener;
-import android.car.Car;
-import android.car.app.CarActivityManager;
import android.car.user.CarUserManager;
-import android.car.user.CarUserManager.UserLifecycleListener;
-import android.car.user.UserLifecycleEventFilter;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.Configuration;
-import android.graphics.Rect;
import android.os.Bundle;
-import android.os.UserManager;
import android.util.Log;
-import android.view.Display;
import android.view.ViewGroup;
import android.view.WindowManager;
import androidx.collection.ArraySet;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentTransaction;
-import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.ViewModelProvider;
import com.android.car.carlauncher.homescreen.HomeCardModule;
import com.android.car.carlauncher.taskstack.TaskStackChangeListeners;
import com.android.car.internal.common.UserHelperLite;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.TaskView;
import com.android.wm.shell.common.HandlerExecutor;
+import com.google.common.annotations.VisibleForTesting;
+
import java.util.Set;
-import java.util.concurrent.atomic.AtomicReference;
/**
* Basic Launcher for Android Automotive which demonstrates the use of {@link TaskView} to host
@@ -76,82 +59,22 @@
*/
public class CarLauncher extends FragmentActivity {
public static final String TAG = "CarLauncher";
- private static final boolean DEBUG = false;
- private static final String SCHEME_PACKAGE = "package";
-
- private final AtomicReference<CarActivityManager> mCarActivityManagerRef =
- new AtomicReference<>();
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private ActivityManager mActivityManager;
- private UserManager mUserManager;
- private CarUserManager mCarUserManager;
private TaskViewManager mTaskViewManager;
- private TaskView mTaskView;
- private boolean mTaskViewReady;
- // Tracking this to check if the task in TaskView has crashed in the background.
- private int mTaskViewTaskId = INVALID_TASK_ID;
+ private CarTaskView mTaskView;
private int mCarLauncherTaskId = INVALID_TASK_ID;
private Set<HomeCardModule> mHomeCardModules;
/** Set to {@code true} once we've logged that the Activity is fully drawn. */
private boolean mIsReadyLogged;
-
private boolean mUseSmallCanvasOptimizedMap;
- // The callback methods in {@code mTaskViewListener} are running under MainThread.
- private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
- @Override
- public void onInitialized() {
- if (DEBUG) Log.d(TAG, "onInitialized(" + getUserId() + ")");
- mTaskViewReady = true;
- startMapsInTaskView();
- maybeLogReady();
- }
-
- @Override
- public void onReleased() {
- if (DEBUG) Log.d(TAG, "onReleased(" + getUserId() + ")");
- mTaskViewReady = false;
- }
-
- @Override
- public void onTaskCreated(int taskId, ComponentName name) {
- if (DEBUG) Log.d(TAG, "onTaskCreated: taskId=" + taskId);
- mTaskViewTaskId = taskId;
- if (isResumed()) {
- maybeBringEmbeddedTaskToForeground();
- }
- }
-
- @Override
- public void onTaskRemovalStarted(int taskId) {
- if (DEBUG) Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId);
- mTaskViewTaskId = INVALID_TASK_ID;
- // Don't restart the crashed Maps automatically, because it hinders lots of MultiXXX
- // CTS tests which cleans up all tasks but Home, then monitor Activity state
- // changes. If it restarts Maps, which causes unexpected Activity state changes.
- }
- };
-
private final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
- public void onTaskFocusChanged(int taskId, boolean focused) {
- boolean launcherFocused = taskId == mCarLauncherTaskId && focused;
- if (DEBUG) {
- Log.d(TAG, "onTaskFocusChanged: taskId=" + taskId
- + ", launcherFocused=" + launcherFocused
- + ", mTaskViewTaskId=" + mTaskViewTaskId);
- }
- if (!launcherFocused) {
- return;
- }
- if (mTaskViewTaskId == INVALID_TASK_ID) {
- // If the task in TaskView is crashed during CarLauncher is background,
- // We'd like to restart it when CarLauncher becomes foreground and focused.
- startMapsInTaskView();
- }
- }
+ public void onTaskFocusChanged(int taskId, boolean focused) {}
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
@@ -162,61 +85,23 @@
}
if (!mUseSmallCanvasOptimizedMap
&& !homeTaskVisible
- && mTaskViewTaskId == task.taskId) {
+ && mTaskView != null
+ && mTaskView.getTaskId() == task.taskId) {
// The embedded map component received an intent, therefore forcibly bringing the
// launcher to the foreground.
bringToForeground();
return;
}
- if (homeTaskVisible && mCarLauncherTaskId == task.taskId
- && mTaskViewTaskId == INVALID_TASK_ID) {
- // Interprets Home Intent while CarLauncher is foreground and Maps is crashed
- // as restarting Maps.
- startMapsInTaskView();
- }
- }
- };
-
- private final UserLifecycleListener mUserLifecycleListener = event -> {
- if (DEBUG) {
- Log.d(TAG, "UserLifecycleListener.onEvent: For User " + getUserId()
- + ", received an event " + event);
- }
- // When user-unlocked, if Maps isn't launched yet, then try to start it.
- if (event.getEventType() == USER_LIFECYCLE_EVENT_TYPE_UNLOCKED
- && getUserId() == event.getUserId()
- && mTaskViewTaskId == INVALID_TASK_ID) {
- startMapsInTaskView();
- return;
- }
-
- // When user-switching, onDestroy in the previous user's CarLauncher isn't called.
- // So tries to release the resource explicitly.
- if (event.getEventType() == USER_LIFECYCLE_EVENT_TYPE_SWITCHING
- && getUserId() == event.getPreviousUserId()) {
- release();
- return;
- }
- };
-
- private Set<String> mTaskViewPackages;
- private final BroadcastReceiver mPackageBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) Log.d(TAG, "onReceive: intent=" + intent);
- String packageName = intent.getData().getSchemeSpecificPart();
- boolean started = getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
- if (started // Don't start Maps in STOPPED, because it'll be started onRestart.
- && mTaskViewTaskId == INVALID_TASK_ID
- && mTaskViewPackages.contains(packageName)) {
- startMapsInTaskView();
- }
}
};
@VisibleForTesting
void setCarUserManager(CarUserManager carUserManager) {
- mCarUserManager = carUserManager;
+ if (mTaskViewManager == null) {
+ Log.w(TAG, "Task view manager is null, cannot set CarUserManager");
+ return;
+ }
+ mTaskViewManager.setCarUserManager(carUserManager);
}
@Override
@@ -237,27 +122,7 @@
mUseSmallCanvasOptimizedMap =
CarLauncherUtils.isSmallCanvasOptimizedMapIntentConfigured(this);
- Car.createCar(/* context= */ this, /* handler= */ null,
- Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
- (car, ready) -> {
- if (!ready) {
- Log.w(TAG, "CarService looks crashed");
- mCarActivityManagerRef.set(null);
- return;
- }
- setCarUserManager((CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE));
- UserLifecycleEventFilter filter = new UserLifecycleEventFilter.Builder()
- .addEventType(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
- .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).build();
- mCarUserManager.addListener(getMainExecutor(), filter, mUserLifecycleListener);
- CarActivityManager carAM = (CarActivityManager) car.getCarManager(
- Car.CAR_ACTIVITY_SERVICE);
- mCarActivityManagerRef.set(carAM);
- carAM.registerTaskMonitor();
- });
-
mActivityManager = getSystemService(ActivityManager.class);
- mUserManager = getSystemService(UserManager.class);
mCarLauncherTaskId = getTaskId();
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
@@ -283,30 +148,44 @@
}
initializeCards();
- mTaskViewPackages = new ArraySet<>(getResources().getStringArray(
- R.array.config_taskViewPackages));
- IntentFilter packageIntentFilter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED);
- packageIntentFilter.addDataScheme(SCHEME_PACKAGE);
- registerReceiver(mPackageBroadcastReceiver, packageIntentFilter);
}
private void setUpTaskView(ViewGroup parent) {
+ Set<String> taskViewPackages = new ArraySet<>(getResources().getStringArray(
+ R.array.config_taskViewPackages));
mTaskViewManager = new TaskViewManager(this,
- new HandlerExecutor(getMainThreadHandler()), mCarActivityManagerRef);
- mTaskViewManager.createTaskView(taskView -> {
- taskView.setListener(getMainExecutor(), mTaskViewListener);
- parent.addView(taskView);
- mTaskView = taskView;
- });
+ new HandlerExecutor(getMainThreadHandler()));
+
+ Intent mapIntent = mUseSmallCanvasOptimizedMap
+ ? CarLauncherUtils.getSmallCanvasOptimizedMapIntent(this)
+ : CarLauncherUtils.getMapsIntent(this);
+ mTaskViewManager.createControlledCarTaskView(
+ getMainExecutor(),
+ mapIntent,
+ /* autoRestartOnCrash = */ false,
+ new ControlledCarTaskViewCallbacks() {
+ @Override
+ public void onTaskViewCreated(CarTaskView taskView) {
+ parent.addView(taskView);
+ mTaskView = taskView;
+ }
+
+ @Override
+ public void onTaskViewReady() {
+ maybeLogReady();
+ }
+
+ @Override
+ public Set<String> getDependingPackageNames() {
+ return taskViewPackages;
+ }
+ });
}
@Override
protected void onResume() {
super.onResume();
maybeLogReady();
- if (DEBUG) {
- Log.d(TAG, "onResume(" + getUserId() + "): mTaskViewTaskId=" + mTaskViewTaskId);
- }
}
@Override
@@ -315,68 +194,12 @@
if (CarLauncherUtils.isCustomDisplayPolicyDefined(this)) {
return;
}
- if (DEBUG) {
- Log.d(TAG, "onDestroy(" + getUserId() + "): mTaskViewTaskId=" + mTaskViewTaskId);
- }
- unregisterReceiver(mPackageBroadcastReceiver);
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
- if (mCarUserManager != null) {
- mCarUserManager.removeListener(mUserLifecycleListener);
- }
release();
}
private void release() {
- if (mTaskView != null && mTaskViewReady) {
- mTaskView.release();
- mTaskView = null;
- }
- if (mTaskViewManager != null) {
- mTaskViewManager.release();
- }
- CarActivityManager carAM = mCarActivityManagerRef.get();
- if (carAM != null) {
- carAM.unregisterTaskMonitor();
- mCarActivityManagerRef.set(null);
- }
- }
-
- private void startMapsInTaskView() {
- if (mTaskView == null || !mTaskViewReady) {
- if (DEBUG) Log.d(TAG, "Can't start Maps due to TaskView isn't ready.");
- return;
- }
- if (!mUserManager.isUserUnlocked()) {
- if (DEBUG) Log.d(TAG, "Can't start Maps due to the user isn't unlocked.");
- return;
- }
- // If we happen to be be resurfaced into a multi display mode we skip launching content
- // in the activity view as we will get recreated anyway.
- if (isInMultiWindowMode() || isInPictureInPictureMode()) {
- if (DEBUG) Log.d(TAG, "Can't start Maps due to CarLauncher isn't in a correct mode");
- return;
- }
- // Don't start Maps when the display is off for ActivityVisibilityTests.
- if (getDisplay().getState() != Display.STATE_ON) {
- if (DEBUG) Log.d(TAG, "Can't start Maps due to the display is off");
- return;
- }
- try {
- ActivityOptions options = ActivityOptions.makeCustomAnimation(this,
- /* enterResId= */ 0, /* exitResId= */ 0);
- Intent mapIntent = mUseSmallCanvasOptimizedMap
- ? CarLauncherUtils.getSmallCanvasOptimizedMapIntent(this)
- : CarLauncherUtils.getMapsIntent(this);
- Rect launchBounds = new Rect();
- mTaskView.getBoundsOnScreen(launchBounds);
- mTaskView.startActivity(
- PendingIntent.getActivity(this, /* requestCode= */ 0,
- mapIntent,
- PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT),
- /* fillInIntent= */ null, options, launchBounds);
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "Maps activity not found", e);
- }
+ mTaskView = null;
}
@Override
@@ -420,11 +243,13 @@
/** Logs that the Activity is ready. Used for startup time diagnostics. */
private void maybeLogReady() {
boolean isResumed = isResumed();
+ boolean taskViewInitialized = mTaskView != null && mTaskView.isInitialized();
if (DEBUG) {
- Log.d(TAG, "maybeLogReady(" + getUserId() + "): activityReady=" + mTaskViewReady
- + ", started=" + isResumed + ", alreadyLogged: " + mIsReadyLogged);
+ Log.d(TAG, "maybeLogReady(" + getUserId() + "): mapsReady="
+ + taskViewInitialized + ", started=" + isResumed + ", alreadyLogged: "
+ + mIsReadyLogged);
}
- if (mTaskViewReady && isResumed) {
+ if (taskViewInitialized && isResumed) {
// We should report every time - the Android framework will take care of logging just
// when it's effectively drawn for the first time, but....
reportFullyDrawn();
@@ -438,16 +263,6 @@
}
}
- /** Brings embedded task to front, if the task view is created and the task is launched. */
- private void maybeBringEmbeddedTaskToForeground() {
- if (mTaskViewTaskId != INVALID_TASK_ID) {
- // The task in TaskView should be in top to make it visible.
- // NOTE: Tried setTaskAlwaysOnTop before, the flag has some side effect to hinder
- // AccessibilityService from finding the correct window geometry: b/197247311
- mActivityManager.moveTaskToFront(mTaskViewTaskId, /* flags= */ 0);
- }
- }
-
/** Brings the Car Launcher to the foreground. */
private void bringToForeground() {
if (mCarLauncherTaskId != INVALID_TASK_ID) {
diff --git a/src/com/android/car/carlauncher/CarLauncherUtils.java b/src/com/android/car/carlauncher/CarLauncherUtils.java
index 6083a83..fabec7e 100644
--- a/src/com/android/car/carlauncher/CarLauncherUtils.java
+++ b/src/com/android/car/carlauncher/CarLauncherUtils.java
@@ -31,10 +31,15 @@
public class CarLauncherUtils {
private static final String TAG = "CarLauncherUtils";
+ private static final String ACTION_APP_GRID = "com.android.car.carlauncher.ACTION_APP_GRID";
private CarLauncherUtils() {
}
+ public static Intent getAppsGridIntent() {
+ return new Intent(ACTION_APP_GRID);
+ }
+
/** Intent used to find/launch the maps activity to run in the relevant DisplayArea. */
public static Intent getMapsIntent(Context context) {
Intent defaultIntent =
diff --git a/src/com/android/car/carlauncher/CarTaskView.java b/src/com/android/car/carlauncher/CarTaskView.java
index da50f82..6d616d6 100644
--- a/src/com/android/car/carlauncher/CarTaskView.java
+++ b/src/com/android/car/carlauncher/CarTaskView.java
@@ -16,10 +16,17 @@
package com.android.car.carlauncher;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
+import static com.android.car.carlauncher.TaskViewManager.DBG;
+
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.SparseArray;
import android.view.SurfaceControl;
-import android.view.View;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -28,11 +35,19 @@
import com.android.wm.shell.common.SyncTransactionQueue;
/**
- * CarLauncher version of {@link TaskView} which solves some CarLauncehr specific issues.
+ * CarLauncher version of {@link TaskView} which solves some CarLauncher specific issues:
+ * <ul>
+ * <li>b/228092608: Clears the hidden flag to make it TopFocusedRootTask.</li>
+ * <li>b/225388469: Moves the embedded task to the top to make it resumed.</li>
+ * </ul>
*/
public class CarTaskView extends TaskView {
+ private static final String TAG = CarTaskView.class.getSimpleName();
+ @Nullable
private WindowContainerToken mTaskToken;
private final SyncTransactionQueue mSyncQueue;
+ private final SparseArray<Rect> mInsets = new SparseArray<>();
+ private boolean mTaskViewReadySent;
public CarTaskView(Context context, ShellTaskOrganizer organizer,
SyncTransactionQueue syncQueue) {
@@ -44,22 +59,76 @@
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
mTaskToken = taskInfo.token;
super.onTaskAppeared(taskInfo, leash);
+
+ applyInsets();
+ }
+
+ @Override
+ protected void notifyInitialized() {
+ super.notifyInitialized();
+ if (mTaskViewReadySent) {
+ if (DBG) Log.i(TAG, "car task view ready already sent");
+ return;
+ }
+ onCarTaskViewInitialized();
+ mTaskViewReadySent = true;
}
/**
- * Called when the visibility of the window containing the view has changed.
- * @param visibility The new visibility of the window.
+ * Called only once when the {@link CarTaskView} is ready.
*/
- @Override
- protected void onWindowVisibilityChanged(int visibility) {
- super.onWindowVisibilityChanged(visibility);
- if (visibility == View.VISIBLE && mTaskToken != null) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- // Clears the hidden flag to make it TopFocusedRootTask: b/228092608
- wct.setHidden(mTaskToken, /* hidden= */false);
- // Moves the embedded task to the top to make it resumed: b/225388469
- wct.reorder(mTaskToken, /* onTop= */ true);
- mSyncQueue.queue(wct);
+ protected void onCarTaskViewInitialized() {}
+
+ /**
+ * Moves the embedded task over the embedding task to make it shown.
+ */
+ public void showEmbeddedTask() {
+ if (mTaskToken == null) {
+ return;
}
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Clears the hidden flag to make it TopFocusedRootTask: b/228092608
+ wct.setHidden(mTaskToken, /* hidden= */ false);
+ // Moves the embedded task to the top to make it resumed: b/225388469
+ wct.reorder(mTaskToken, /* onTop= */ true);
+ mSyncQueue.queue(wct);
+ }
+
+ // TODO(b/238473897): Consider taking insets one by one instead of taking all insets.
+ /**
+ * Set & apply the given {@code insets} on the Task.
+ */
+ public void setInsets(SparseArray<Rect> insets) {
+ mInsets.clear();
+ for (int i = insets.size() - 1; i >= 0; i--) {
+ mInsets.append(insets.keyAt(i), insets.valueAt(i));
+ }
+ applyInsets();
+ }
+
+ private void applyInsets() {
+ if (mInsets == null || mInsets.size() == 0) {
+ Log.w(TAG, "Cannot apply null or empty insets");
+ return;
+ }
+ if (mTaskToken == null) {
+ Log.w(TAG, "Cannot apply insets as the task token is not present.");
+ return;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ for (int i = 0; i < mInsets.size(); i++) {
+ wct.addRectInsetsProvider(mTaskToken, mInsets.valueAt(i), new int[]{mInsets.keyAt(i)});
+ }
+ mSyncQueue.queue(wct);
+ }
+
+ /**
+ * @return the taskId of the currently running task.
+ */
+ public int getTaskId() {
+ if (mTaskInfo == null) {
+ return INVALID_TASK_ID;
+ }
+ return mTaskInfo.taskId;
}
}
diff --git a/src/com/android/car/carlauncher/CarTaskViewCallbacks.java b/src/com/android/car/carlauncher/CarTaskViewCallbacks.java
new file mode 100644
index 0000000..03edce5
--- /dev/null
+++ b/src/com/android/car/carlauncher/CarTaskViewCallbacks.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.carlauncher;
+
+/**
+ * A callback interface for the host activity that uses {@link CarTaskView} and its derivatives.
+ */
+interface CarTaskViewCallbacks {
+ /**
+ * Called when the underlying {@link CarTaskView} instance is created.
+ *
+ * @param taskView the new newly created {@link CarTaskView} instance.
+ */
+ void onTaskViewCreated(CarTaskView taskView);
+
+ /**
+ * Called when the underlying {@link CarTaskView} is ready. A {@link CarTaskView} can be
+ * considered ready when it has completed all the set up that is required.
+ * This callback is only triggered once.
+ *
+ * For {@link LaunchRootCarTaskView}, this is called once the launch root task has been
+ * fully set up.
+ * For {@link SemiControlledCarTaskView} & {@link ControlledCarTaskView} this is called when
+ * the surface is created.
+ */
+ void onTaskViewReady();
+}
+
+
diff --git a/src/com/android/car/carlauncher/ControlledCarTaskView.java b/src/com/android/car/carlauncher/ControlledCarTaskView.java
new file mode 100644
index 0000000..1d92267
--- /dev/null
+++ b/src/com/android/car/carlauncher/ControlledCarTaskView.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.carlauncher;
+
+import static com.android.car.carlauncher.TaskViewManager.DBG;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.Display;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * A controlled {@link CarTaskView} is fully managed by the {@link TaskViewManager}.
+ * The underlying task will be restarted if it is crashed.
+ *
+ * It should be used when:
+ * <ul>
+ * <li>The underlying task is meant to be started by the host and be there forever.</li>
+ * </ul>
+ */
+final class ControlledCarTaskView extends CarTaskView {
+ private static final String TAG = ControlledCarTaskView.class.getSimpleName();
+
+ private final Executor mCallbackExecutor;
+ private final Intent mActivityIntent;
+ private final boolean mAutoRestartOnCrash;
+ // TODO(b/242861717): When mAutoRestartOnCrash is enabled, mPackagesThatCanRestart doesn't make
+ // a lot of sense. Consider removing it when there is more confidence with mAutoRestartOnCrash.
+ private final ControlledCarTaskViewCallbacks mCallbacks;
+ private final UserManager mUserManager;
+
+ public ControlledCarTaskView(Activity context,
+ ShellTaskOrganizer organizer,
+ SyncTransactionQueue syncQueue,
+ Executor callbackExecutor,
+ Intent activityIntent,
+ Boolean autoRestartOnCrash,
+ ControlledCarTaskViewCallbacks callbacks,
+ UserManager userManager) {
+ super(context, organizer, syncQueue);
+ mCallbackExecutor = callbackExecutor;
+ mActivityIntent = activityIntent;
+ mAutoRestartOnCrash = autoRestartOnCrash;
+ mCallbacks = callbacks;
+ mUserManager = userManager;
+
+ mCallbackExecutor.execute(() -> mCallbacks.onTaskViewCreated(this));
+ }
+
+ @Override
+ protected void onCarTaskViewInitialized() {
+ super.onCarTaskViewInitialized();
+ startActivity();
+ mCallbackExecutor.execute(() -> mCallbacks.onTaskViewReady());
+ }
+
+ /**
+ * Starts the underlying activity.
+ */
+ public void startActivity() {
+ if (!mUserManager.isUserUnlocked()) {
+ if (DBG) Log.d(TAG, "Can't start activity due to user is isn't unlocked");
+ return;
+ }
+
+ // Don't start activity when the display is off. This can happen when the taskview is not
+ // attached to a window.
+ if (getDisplay() == null) {
+ Log.w(TAG, "Can't start activity because display is not available in "
+ + "taskview yet.");
+ return;
+ }
+ // Don't start activity when the display is off for ActivityVisibilityTests.
+ if (getDisplay().getState() != Display.STATE_ON) {
+ Log.w(TAG, "Can't start activity due to the display is off");
+ return;
+ }
+
+ ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext,
+ /* enterResId= */ 0, /* exitResId= */ 0);
+ Rect launchBounds = new Rect();
+ getBoundsOnScreen(launchBounds);
+ if (DBG) {
+ Log.d(TAG, "Starting (" + mActivityIntent.getComponent() + ") on " + launchBounds);
+ }
+ startActivity(
+ PendingIntent.getActivity(mContext, /* requestCode= */ 0,
+ mActivityIntent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT),
+ /* fillInIntent= */ null, options, launchBounds);
+ }
+
+ /**
+ * See {@link ControlledCarTaskViewCallbacks#getDependingPackageNames()}.
+ */
+ Set<String> getDependingPackageNames() {
+ return mCallbacks.getDependingPackageNames();
+ }
+
+ @Override
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ super.onTaskVanished(taskInfo);
+ if (mAutoRestartOnCrash) {
+ Log.i(TAG, "Restarting task " + taskInfo.baseActivity
+ + " in ControlledCarTaskView");
+ startActivity();
+ }
+ }
+}
diff --git a/src/com/android/car/carlauncher/ControlledCarTaskViewCallbacks.java b/src/com/android/car/carlauncher/ControlledCarTaskViewCallbacks.java
new file mode 100644
index 0000000..56ef8ea
--- /dev/null
+++ b/src/com/android/car/carlauncher/ControlledCarTaskViewCallbacks.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.carlauncher;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * A callback interface for {@link ControlledCarTaskView}.
+ */
+public interface ControlledCarTaskViewCallbacks extends
+ CarTaskViewCallbacks {
+ /**
+ * @return a set of package names which the task in the ControlledCarTaskView depends upon.
+ * When any of these packages are changed, it will lead to restart of the task.
+ */
+ default Set<String> getDependingPackageNames() {
+ return Collections.emptySet();
+ }
+}
diff --git a/src/com/android/car/carlauncher/LaunchRootCarTaskView.java b/src/com/android/car/carlauncher/LaunchRootCarTaskView.java
new file mode 100644
index 0000000..c0a4049
--- /dev/null
+++ b/src/com/android/car/carlauncher/LaunchRootCarTaskView.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.carlauncher;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.car.carlauncher.TaskViewManager.DBG;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A {@link CarTaskView} that can act as a default app container. A default app container is the
+ * container where all apps open by default.
+ */
+final class LaunchRootCarTaskView extends CarTaskView {
+ private static final String TAG = LaunchRootCarTaskView.class.getSimpleName();
+
+ private final Executor mCallbackExecutor;
+ private final LaunchRootCarTaskViewCallbacks mCallbacks;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final SyncTransactionQueue mSyncQueue;
+ private final ShellTaskOrganizer.TaskListener mRootTaskListener;
+
+ private ActivityManager.RunningTaskInfo mLaunchRootTask;
+
+ private final ShellTaskOrganizer.TaskListener mRootTaskListenerWrapper =
+ new ShellTaskOrganizer.TaskListener() {
+ @Override
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash) {
+ // The first call to onTaskAppeared() is always for the root-task.
+ if (mLaunchRootTask == null && !taskInfo.hasParentTask()) {
+ setRootTaskAsLaunchRoot(taskInfo);
+ LaunchRootCarTaskView.this.onTaskAppeared(taskInfo, leash);
+ mCallbackExecutor.execute(() -> mCallbacks.onTaskViewReady());
+ return;
+ }
+
+ if (DBG) {
+ Log.d(TAG, "onTaskAppeared " + taskInfo.taskId + " - "
+ + taskInfo.baseActivity);
+ }
+
+
+ mRootTaskListener.onTaskAppeared(taskInfo, leash);
+ }
+
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mLaunchRootTask != null
+ && mLaunchRootTask.taskId == taskInfo.taskId) {
+ LaunchRootCarTaskView.this.onTaskInfoChanged(taskInfo);
+ if (DBG) {
+ Log.d(TAG, "got onTaskInfoChanged for the launch root task. Not "
+ + "forwarding this to root task listener");
+ }
+ return;
+ }
+ if (DBG) {
+ Log.d(TAG, "onTaskInfoChanged " + taskInfo.taskId + " - "
+ + taskInfo.baseActivity);
+ }
+
+ mRootTaskListener.onTaskInfoChanged(taskInfo);
+ }
+
+ @Override
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ if (DBG) {
+ Log.d(TAG, "onTaskVanished " + taskInfo.taskId + " - "
+ + taskInfo.baseActivity);
+ }
+ if (mLaunchRootTask != null
+ && mLaunchRootTask.taskId == taskInfo.taskId) {
+ LaunchRootCarTaskView.this.onTaskVanished(taskInfo);
+ if (DBG) {
+ Log.d(TAG, "got onTaskVanished for the launch root task. Not "
+ + "forwarding this to root task listener");
+ }
+ return;
+ }
+
+ mRootTaskListener.onTaskVanished(taskInfo);
+ }
+ };
+
+ public LaunchRootCarTaskView(Activity context,
+ ShellTaskOrganizer organizer,
+ SyncTransactionQueue syncQueue,
+ Executor callbackExecutor,
+ LaunchRootCarTaskViewCallbacks callbacks,
+ ShellTaskOrganizer.TaskListener rootTaskListener) {
+ super(context, organizer, syncQueue);
+ mCallbacks = callbacks;
+ mCallbackExecutor = callbackExecutor;
+ mShellTaskOrganizer = organizer;
+ mSyncQueue = syncQueue;
+ mRootTaskListener = rootTaskListener;
+
+ mCallbackExecutor.execute(() -> mCallbacks.onTaskViewCreated(this));
+ }
+
+ @Override
+ protected void onCarTaskViewInitialized() {
+ super.onCarTaskViewInitialized();
+ mShellTaskOrganizer.getExecutor().execute(() -> {
+ // Should run on shell's executor
+ mShellTaskOrganizer.createRootTask(DEFAULT_DISPLAY,
+ WINDOWING_MODE_MULTI_WINDOW,
+ mRootTaskListenerWrapper);
+ });
+ }
+
+ @Override
+ protected void notifyReleased() {
+ super.notifyReleased();
+ clearLaunchRootTask();
+ }
+
+ private void clearLaunchRootTask() {
+ if (mLaunchRootTask == null) {
+ Log.w(TAG, "Unable to clear launch root task because it is not created.");
+ return;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setLaunchRoot(mLaunchRootTask.token, null, null);
+ mSyncQueue.queue(wct);
+ // Should run on shell's executor
+ mShellTaskOrganizer.deleteRootTask(mLaunchRootTask.token);
+ mLaunchRootTask = null;
+ }
+
+ private void setRootTaskAsLaunchRoot(ActivityManager.RunningTaskInfo taskInfo) {
+ mLaunchRootTask = taskInfo;
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setLaunchRoot(taskInfo.token,
+ new int[]{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED},
+ new int[]{ACTIVITY_TYPE_STANDARD})
+ .reorder(taskInfo.token, true);
+ mSyncQueue.queue(wct);
+ }
+}
diff --git a/src/com/android/car/carlauncher/LaunchRootCarTaskViewCallbacks.java b/src/com/android/car/carlauncher/LaunchRootCarTaskViewCallbacks.java
new file mode 100644
index 0000000..e30ffe5
--- /dev/null
+++ b/src/com/android/car/carlauncher/LaunchRootCarTaskViewCallbacks.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.carlauncher;
+
+/**
+ * A callbacks interface for {@link LaunchRootCarTaskView}.
+ */
+public interface LaunchRootCarTaskViewCallbacks extends
+ CarTaskViewCallbacks {}
diff --git a/src/com/android/car/carlauncher/SemiControlledCarTaskView.java b/src/com/android/car/carlauncher/SemiControlledCarTaskView.java
new file mode 100644
index 0000000..5b5ed85
--- /dev/null
+++ b/src/com/android/car/carlauncher/SemiControlledCarTaskView.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.carlauncher;
+
+import android.app.Activity;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A Semi-controlled {@link CarTaskView} is where the apps are meant to stay temporarily. It always
+ * works when a {@link LaunchRootCarTaskView} has been set up.
+ *
+ * It serves these use-cases:
+ * <ul>
+ * <li>Should be used when the apps that are meant to be in it can be started from anywhere
+ * in the system. i.e. when the host app has no control over their launching.</li>
+ * <li>Suitable for apps like Assistant or Setup-Wizard.</li>
+ * </ul>
+ */
+final class SemiControlledCarTaskView extends CarTaskView {
+ private final Executor mCallbackExecutor;
+ private final SemiControlledCarTaskViewCallbacks mCallbacks;
+
+ public SemiControlledCarTaskView(Activity context,
+ ShellTaskOrganizer organizer,
+ SyncTransactionQueue syncQueue,
+ Executor callbackExecutor,
+ SemiControlledCarTaskViewCallbacks callbacks) {
+ super(context, organizer, syncQueue);
+ mCallbacks = callbacks;
+ mCallbackExecutor = callbackExecutor;
+ mCallbackExecutor.execute(() -> mCallbacks.onTaskViewCreated(this));
+ }
+
+ @Override
+ protected void onCarTaskViewInitialized() {
+ super.onCarTaskViewInitialized();
+ mCallbackExecutor.execute(() -> mCallbacks.onTaskViewReady());
+ }
+
+ /**
+ * @return the underlying {@link SemiControlledCarTaskViewCallbacks}.
+ */
+ SemiControlledCarTaskViewCallbacks getCallbacks() {
+ return mCallbacks;
+ }
+}
diff --git a/src/com/android/car/carlauncher/SemiControlledCarTaskViewCallbacks.java b/src/com/android/car/carlauncher/SemiControlledCarTaskViewCallbacks.java
new file mode 100644
index 0000000..9576772
--- /dev/null
+++ b/src/com/android/car/carlauncher/SemiControlledCarTaskViewCallbacks.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.carlauncher;
+
+import android.app.TaskInfo;
+
+/**
+ * A callbacks interface for {@link SemiControlledCarTaskView}.
+ */
+public interface SemiControlledCarTaskViewCallbacks extends
+ CarTaskViewCallbacks {
+ /**
+ * Specifies whether or not the given {@code taskInfo} should start in this
+ * {@link SemiControlledCarTaskView}.
+ * @return true if the task should be started on this {@link SemiControlledCarTaskView}, false
+ * otherwise.
+ */
+ boolean shouldStartInTaskView(TaskInfo taskInfo);
+}
diff --git a/src/com/android/car/carlauncher/TaskViewManager.java b/src/com/android/car/carlauncher/TaskViewManager.java
index f7c403f..df1434f 100644
--- a/src/com/android/car/carlauncher/TaskViewManager.java
+++ b/src/com/android/car/carlauncher/TaskViewManager.java
@@ -16,79 +16,481 @@
package com.android.car.carlauncher;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
import static com.android.car.carlauncher.CarLauncher.TAG;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
-import android.annotation.UiContext;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.Application.ActivityLifecycleCallbacks;
import android.app.TaskInfo;
+import android.app.TaskStackListener;
+import android.car.Car;
import android.car.app.CarActivityManager;
+import android.car.user.CarUserManager;
+import android.car.user.UserLifecycleEventFilter;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.util.Log;
import android.util.Slog;
+import android.view.SurfaceControl;
import android.window.TaskAppearedInfo;
+import com.android.car.carlauncher.taskstack.TaskStackChangeListeners;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskView;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.startingsurface.StartingWindowController;
import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm;
+import com.android.wm.shell.sysui.ShellInit;
+import java.util.ArrayList;
import java.util.List;
-import java.util.Optional;
+import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
+
+/**
+ * A manager for creating {@link ControlledCarTaskView}, {@link LaunchRootCarTaskView} &
+ * {@link SemiControlledCarTaskView}.
+ */
public final class TaskViewManager {
- private static final boolean DBG = false;
+ static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String SCHEME_PACKAGE = "package";
- private final Context mContext;
- private final HandlerExecutor mExecutor;
+ private final AtomicReference<CarActivityManager> mCarActivityManagerRef =
+ new AtomicReference<>();
+ @ShellMainThread
+ private final HandlerExecutor mShellExecutor;
private final SyncTransactionQueue mSyncQueue;
private final ShellTaskOrganizer mTaskOrganizer;
+ private final int mHostTaskId;
- public TaskViewManager(@UiContext Context context, HandlerExecutor handlerExecutor,
- AtomicReference<CarActivityManager> carActivityManagerRef) {
- mContext = context;
- mExecutor = handlerExecutor;
- mTaskOrganizer = new ShellTaskOrganizer(mExecutor, mContext);
- TransactionPool transactionPool = new TransactionPool();
- mSyncQueue = new SyncTransactionQueue(transactionPool, mExecutor);
- initTaskOrganizer(carActivityManagerRef, transactionPool);
- if (DBG) Slog.d(TAG, "TaskViewManager.create");
+ // All TaskView are bound to the Host Activity if it exists.
+ @ShellMainThread
+ private final List<ControlledCarTaskView> mControlledTaskViews = new ArrayList<>();
+ @ShellMainThread
+ private final List<SemiControlledCarTaskView> mSemiControlledTaskViews = new ArrayList<>();
+ @ShellMainThread
+ private LaunchRootCarTaskView mLaunchRootCarTaskView = null;
+
+ private CarUserManager mCarUserManager;
+ private Activity mContext;
+
+ private final ShellTaskOrganizer.TaskListener mRootTaskListener =
+ new ShellTaskOrganizer.TaskListener() {
+ @Override
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash) {
+ // Called for a task appearing the launch root. Route it to the appropriate
+ // semi-controlled taskview;
+ for (SemiControlledCarTaskView taskView : mSemiControlledTaskViews) {
+ if (taskView.getCallbacks().shouldStartInTaskView(taskInfo)) {
+ if (taskView.isInitialized()) {
+ taskView.onTaskAppeared(taskInfo, leash);
+ }
+ return;
+ }
+ }
+
+ // TODO(b/228077499): Fix for the case when a task is started in the
+ // launch-root-task right after the initialization of launch-root-task, it
+ // remains blank.
+ mSyncQueue.runInSync(t -> t.show(leash));
+
+ CarActivityManager carAm = mCarActivityManagerRef.get();
+ if (carAm != null) {
+ carAm.onTaskAppeared(taskInfo);
+ } else {
+ Log.w(TAG, "CarActivityManager is null, skip onTaskAppeared: TaskInfo"
+ + " = " + taskInfo);
+ }
+ }
+
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ for (SemiControlledCarTaskView taskView : mSemiControlledTaskViews) {
+ if (taskView.getCallbacks().shouldStartInTaskView(taskInfo)) {
+ if (taskView.isInitialized()) {
+ // onLocationChanged() is crucial. If this is not called, the
+ // further activities opened by the current activity do not open in
+ // the correct size.
+ // TODO(b/234879199): Explore more for a better solution.
+ taskView.onLocationChanged();
+ taskView.onTaskInfoChanged(taskInfo);
+ }
+ // Semi-controlled apps are assumed to be Distraction optimised and
+ // hence not reported to CarActivityManager.
+ return;
+ }
+ }
+
+ // Uncontrolled apps by default launch in the launch root so nothing needs to
+ // be done here for them.
+ CarActivityManager carAm = mCarActivityManagerRef.get();
+ if (carAm != null) {
+ carAm.onTaskInfoChanged(taskInfo);
+ } else {
+ Log.w(TAG, "CarActivityManager is null, skip onTaskAppeared: TaskInfo"
+ + " = " + taskInfo);
+ }
+ }
+
+ @Override
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ for (SemiControlledCarTaskView taskView : mSemiControlledTaskViews) {
+ if (taskView.getCallbacks().shouldStartInTaskView(taskInfo)) {
+ if (taskView.isInitialized()) {
+ taskView.onTaskVanished(taskInfo);
+ }
+ return;
+ }
+ }
+
+ CarActivityManager carAm = mCarActivityManagerRef.get();
+ if (carAm != null) {
+ carAm.onTaskVanished(taskInfo);
+ } else {
+ Log.w(TAG, "CarActivityManager is null, skip onTaskAppeared: TaskInfo"
+ + " = " + taskInfo);
+ }
+ }
+ };
+
+ private final TaskStackListener mTaskStackListener = new TaskStackListener() {
+ @Override
+ public void onTaskFocusChanged(int taskId, boolean focused) {
+ boolean hostFocused = taskId == mHostTaskId && focused;
+ if (DBG) {
+ Log.d(TAG, "onTaskFocusChanged: taskId=" + taskId
+ + ", hostFocused=" + hostFocused);
+ }
+ if (!hostFocused) {
+ return;
+ }
+
+ for (int i = mControlledTaskViews.size() - 1; i >= 0; --i) {
+ ControlledCarTaskView taskView = mControlledTaskViews.get(i);
+ if (taskView.getTaskId() == INVALID_TASK_ID) {
+ // If the task in TaskView is crashed when host is in background,
+ // We'd like to restart it when host becomes foreground and focused.
+ taskView.startActivity();
+ }
+ }
+ }
+
+ @Override
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ if (DBG) {
+ Log.d(TAG, "onActivityRestartAttempt: taskId=" + task.taskId
+ + ", homeTaskVisible=" + homeTaskVisible + ", wasVisible=" + wasVisible);
+ }
+ if (homeTaskVisible && mHostTaskId == task.taskId) {
+ for (int i = mControlledTaskViews.size() - 1; i >= 0; --i) {
+ ControlledCarTaskView taskView = mControlledTaskViews.get(i);
+ // In the case of CarLauncher, this code handles the case where Home Intent is
+ // sent when CarLauncher is foreground and the task in a ControlledTaskView is
+ // crashed.
+ if (taskView.getTaskId() == INVALID_TASK_ID) {
+ taskView.startActivity();
+ }
+ }
+ }
+ }
+ };
+
+ private final CarUserManager.UserLifecycleListener mUserLifecycleListener = event -> {
+ if (DBG) {
+ Log.d(TAG, "UserLifecycleListener.onEvent: For User "
+ + mContext.getUserId()
+ + ", received an event " + event);
+ }
+
+ // When user-unlocked, if task isn't launched yet, then try to start it.
+ if (event.getEventType() == USER_LIFECYCLE_EVENT_TYPE_UNLOCKED
+ && mContext.getUserId() == event.getUserId()) {
+ for (int i = mControlledTaskViews.size() - 1; i >= 0; --i) {
+ ControlledCarTaskView taskView = mControlledTaskViews.get(i);
+ if (taskView.getTaskId() == INVALID_TASK_ID) {
+ taskView.startActivity();
+ }
+ }
+ }
+
+ // When user-switching, onDestroy in the previous user's Host app isn't called.
+ // So try to release the resource explicitly.
+ if (event.getEventType() == USER_LIFECYCLE_EVENT_TYPE_SWITCHING
+ && mContext.getUserId() == event.getPreviousUserId()) {
+ release();
+ }
+ };
+
+ private final BroadcastReceiver mPackageBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DBG) Log.d(TAG, "onReceive: intent=" + intent);
+
+ if (isActivityStopped(mContext)) {
+ return;
+ }
+
+ String packageName = intent.getData().getSchemeSpecificPart();
+ for (int i = mControlledTaskViews.size() - 1; i >= 0; --i) {
+ ControlledCarTaskView taskView = mControlledTaskViews.get(i);
+ if (taskView.getTaskId() == INVALID_TASK_ID
+ && taskView.getDependingPackageNames().contains(packageName)) {
+ taskView.startActivity();
+ }
+ }
+ }
+ };
+
+ public TaskViewManager(Activity context, HandlerExecutor handlerExecutor) {
+ this(context, handlerExecutor, new ShellTaskOrganizer(handlerExecutor),
+ new SyncTransactionQueue(new TransactionPool(), handlerExecutor));
}
- private void initTaskOrganizer(AtomicReference<CarActivityManager> carActivityManagerRef,
- TransactionPool transactionPool) {
+ @VisibleForTesting
+ TaskViewManager(Activity context, HandlerExecutor handlerExecutor,
+ ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue) {
+ if (DBG) Slog.d(TAG, "TaskViewManager(): " + context);
+ mContext = context;
+ mShellExecutor = handlerExecutor;
+ mTaskOrganizer = shellTaskOrganizer;
+ mHostTaskId = mContext.getTaskId();
+ mSyncQueue = syncQueue;
+
+ initCar();
+ initTaskOrganizer(mCarActivityManagerRef);
+ mContext.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
+ }
+
+ private void initCar() {
+ Car.createCar(/* context= */ mContext, /* handler= */ null,
+ Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
+ (car, ready) -> {
+ if (!ready) {
+ Log.w(TAG, "CarService looks crashed");
+ mCarActivityManagerRef.set(null);
+ return;
+ }
+ setCarUserManager((CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE));
+ UserLifecycleEventFilter filter = new UserLifecycleEventFilter.Builder()
+ .addEventType(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+ .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).build();
+ mCarUserManager.addListener(mContext.getMainExecutor(), filter,
+ mUserLifecycleListener);
+ CarActivityManager carAM = (CarActivityManager) car.getCarManager(
+ Car.CAR_ACTIVITY_SERVICE);
+ mCarActivityManagerRef.set(carAM);
+
+ carAM.registerTaskMonitor();
+ });
+
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
+
+ IntentFilter packageIntentFilter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED);
+ packageIntentFilter.addDataScheme(SCHEME_PACKAGE);
+ mContext.registerReceiver(mPackageBroadcastReceiver, packageIntentFilter);
+ }
+
+ // TODO(b/239958124A): Remove this method when unit tests for TaskViewManager have been added.
+ /**
+ * This method only exists for the container activity to set mock car user manager in tests.
+ */
+ void setCarUserManager(CarUserManager carUserManager) {
+ mCarUserManager = carUserManager;
+ }
+
+ private void initTaskOrganizer(AtomicReference<CarActivityManager> carActivityManagerRef) {
FullscreenTaskListener fullscreenTaskListener = new CarFullscreenTaskMonitorListener(
- carActivityManagerRef, mSyncQueue, Optional.empty());
+ carActivityManagerRef, mSyncQueue);
mTaskOrganizer.addListenerForType(fullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);
- StartingWindowController startingController =
- new StartingWindowController(mContext, mExecutor,
- new PhoneStartingWindowTypeAlgorithm(), new IconProvider(mContext),
- transactionPool);
- mTaskOrganizer.initStartingWindow(startingController);
+ ShellInit shellInit = new ShellInit(mShellExecutor);
+ // StartingWindowController needs to be initialized so that splash screen is displayed.
+ new StartingWindowController(mContext, shellInit, mTaskOrganizer, mShellExecutor,
+ new PhoneStartingWindowTypeAlgorithm(), new IconProvider(mContext),
+ new TransactionPool());
+ shellInit.init();
List<TaskAppearedInfo> taskAppearedInfos = mTaskOrganizer.registerOrganizer();
cleanUpExistingTaskViewTasks(taskAppearedInfos);
}
- void release() {
- if (DBG) Slog.d(TAG, "TaskViewManager.release");
- mTaskOrganizer.unregisterOrganizer();
- }
-
- void createTaskView(Consumer<TaskView> onCreate) {
- CarTaskView taskView = new CarTaskView(mContext, mTaskOrganizer, mSyncQueue);
- mExecutor.execute(() -> {
- onCreate.accept(taskView);
+ /**
+ * Creates a {@link ControlledCarTaskView}.
+ *
+ * @param callbackExecutor the executor which the {@link ControlledCarTaskViewCallbacks} will
+ * be executed on.
+ * @param activityIntent the intent of the activity that is meant to be started in this
+ * {@link ControlledCarTaskView}.
+ * @param taskViewCallbacks the callbacks for the underlying TaskView.
+ */
+ public void createControlledCarTaskView(
+ Executor callbackExecutor,
+ Intent activityIntent,
+ boolean autoRestartOnCrash,
+ ControlledCarTaskViewCallbacks taskViewCallbacks) {
+ mShellExecutor.execute(() -> {
+ ControlledCarTaskView taskView = new ControlledCarTaskView(mContext, mTaskOrganizer,
+ mSyncQueue, callbackExecutor, activityIntent, autoRestartOnCrash,
+ taskViewCallbacks, mContext.getSystemService(UserManager.class));
+ mControlledTaskViews.add(taskView);
});
}
+ /**
+ * Creates a {@link LaunchRootCarTaskView}.
+ *
+ * @param callbackExecutor the executor which the {@link LaunchRootCarTaskViewCallbacks} will be
+ * executed on.
+ * @param taskViewCallbacks the callbacks for the underlying TaskView.
+ */
+ public void createLaunchRootTaskView(Executor callbackExecutor,
+ LaunchRootCarTaskViewCallbacks taskViewCallbacks) {
+ mShellExecutor.execute(() -> {
+ if (mLaunchRootCarTaskView != null) {
+ throw new IllegalStateException("Cannot create more than one launch root task");
+ }
+ mLaunchRootCarTaskView = new LaunchRootCarTaskView(mContext, mTaskOrganizer,
+ mSyncQueue, callbackExecutor, taskViewCallbacks, mRootTaskListener);
+ });
+ }
+
+ /**
+ * Creates a {@link SemiControlledCarTaskView}.
+ *
+ * @param callbackExecutor the executor which the {@link SemiControlledCarTaskViewCallbacks}
+ * will be executed on.
+ * @param taskViewCallbacks the callbacks for the underlying TaskView.
+ */
+ public void createSemiControlledTaskView(Executor callbackExecutor,
+ SemiControlledCarTaskViewCallbacks taskViewCallbacks) {
+ mShellExecutor.execute(() -> {
+ if (mLaunchRootCarTaskView == null) {
+ throw new IllegalStateException("Cannot create a semi controlled taskview without a"
+ + " launch root taskview");
+ }
+ SemiControlledCarTaskView taskView = new SemiControlledCarTaskView(mContext,
+ mTaskOrganizer, mSyncQueue, callbackExecutor, taskViewCallbacks);
+ mSemiControlledTaskViews.add(taskView);
+ });
+ }
+
+ /**
+ * Releases {@link TaskViewManager} and unregisters the underlying {@link ShellTaskOrganizer}.
+ * It also removes all TaskViews which are created by this {@link TaskViewManager}.
+ */
+ private void release() {
+ mShellExecutor.execute(() -> {
+ if (DBG) Slog.d(TAG, "TaskViewManager.release");
+
+ if (mCarUserManager != null) {
+ mCarUserManager.removeListener(mUserLifecycleListener);
+ }
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ mContext.unregisterReceiver(mPackageBroadcastReceiver);
+
+ CarActivityManager carAM = mCarActivityManagerRef.get();
+ if (carAM != null) {
+ carAM.unregisterTaskMonitor();
+ mCarActivityManagerRef.set(null);
+ }
+
+ for (int i = mControlledTaskViews.size() - 1; i >= 0; --i) {
+ mControlledTaskViews.get(i).release();
+ }
+ mControlledTaskViews.clear();
+
+ for (int i = mSemiControlledTaskViews.size() - 1; i >= 0; --i) {
+ mSemiControlledTaskViews.get(i).release();
+ }
+ mSemiControlledTaskViews.clear();
+
+ if (mLaunchRootCarTaskView != null) {
+ mLaunchRootCarTaskView.release();
+ mLaunchRootCarTaskView = null;
+ }
+
+ mContext.unregisterActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
+ mTaskOrganizer.unregisterOrganizer();
+ });
+ }
+
+ private static boolean isActivityStopped(Activity activity) {
+ // This code relies on Activity#isVisibleForAutofill() instead of maintaining a custom
+ // activity state.
+ return !activity.isVisibleForAutofill();
+ }
+
+ private final ActivityLifecycleCallbacks mActivityLifecycleCallbacks =
+ new ActivityLifecycleCallbacks() {
+ @Override
+ public void onActivityCreated(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState) {
+ if (DBG) {
+ Log.d(TAG, "Host activity created");
+ }
+ }
+
+ @Override
+ public void onActivityStarted(@NonNull Activity activity) {
+ if (DBG) {
+ Log.d(TAG, "Host activity started");
+ }
+ }
+
+ @Override
+ public void onActivityResumed(@NonNull Activity activity) {
+ Log.d(TAG, "Host activity resumed");
+ if (activity != mContext) {
+ return;
+ }
+ mShellExecutor.execute(() -> {
+ for (int i = mControlledTaskViews.size() - 1; i >= 0; --i) {
+ mControlledTaskViews.get(i).showEmbeddedTask();
+ }
+ if (mLaunchRootCarTaskView != null) {
+ mLaunchRootCarTaskView.showEmbeddedTask();
+ }
+ for (int i = mSemiControlledTaskViews.size() - 1; i >= 0; --i) {
+ mSemiControlledTaskViews.get(i).showEmbeddedTask();
+ }
+ });
+ }
+
+ @Override
+ public void onActivityPaused(@NonNull Activity activity) {}
+
+ @Override
+ public void onActivityStopped(@NonNull Activity activity) {}
+
+ @Override
+ public void onActivitySaveInstanceState(@NonNull Activity activity,
+ @NonNull Bundle outState) {}
+
+ @Override
+ public void onActivityDestroyed(@NonNull Activity activity) {
+ release();
+ }
+ };
+
private static void cleanUpExistingTaskViewTasks(List<TaskAppearedInfo> taskAppearedInfos) {
ActivityTaskManager atm = ActivityTaskManager.getInstance();
for (TaskAppearedInfo taskAppearedInfo : taskAppearedInfos) {
@@ -100,4 +502,24 @@
}
}
}
+
+ @VisibleForTesting
+ List<ControlledCarTaskView> getControlledTaskViews() {
+ return mControlledTaskViews;
+ }
+
+ @VisibleForTesting
+ LaunchRootCarTaskView getLaunchRootCarTaskView() {
+ return mLaunchRootCarTaskView;
+ }
+
+ @VisibleForTesting
+ List<SemiControlledCarTaskView> getSemiControlledTaskViews() {
+ return mSemiControlledTaskViews;
+ }
+
+ @VisibleForTesting
+ BroadcastReceiver getPackageBroadcastReceiver() {
+ return mPackageBroadcastReceiver;
+ }
}
diff --git a/src/com/android/car/carlauncher/homescreen/audio/AudioFragment.java b/src/com/android/car/carlauncher/homescreen/audio/AudioFragment.java
index 5dd7813..977ccc0 100644
--- a/src/com/android/car/carlauncher/homescreen/audio/AudioFragment.java
+++ b/src/com/android/car/carlauncher/homescreen/audio/AudioFragment.java
@@ -19,6 +19,7 @@
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.text.TextUtils;
import android.util.Size;
import android.view.View;
import android.view.ViewStub;
@@ -137,6 +138,7 @@
getMediaLayoutView().setVisibility(View.VISIBLE);
mMediaTitle.setText(title);
mMediaSubtitle.setText(subtitle);
+ mMediaSubtitle.setVisibility(TextUtils.isEmpty(subtitle) ? View.GONE : View.VISIBLE);
}
private void updateAudioDuration(DescriptiveTextWithControlsView content) {
diff --git a/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenter.java b/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenter.java
index 3398389..5c14aaa 100644
--- a/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenter.java
+++ b/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenter.java
@@ -101,6 +101,10 @@
// empty content since that would hide the card.
return;
}
+ } else if (mCurrentModel != null && mCurrentModel.getClass() == InCallModel.class) {
+ // If the Model has content, check if currentModel on display is an ongoing phone call.
+ // If there is any ongoing phone call do not update the View.
+ return;
}
mCurrentModel = model;
super.onModelUpdated(model);
diff --git a/src/com/android/car/carlauncher/homescreen/audio/InCallModel.java b/src/com/android/car/carlauncher/homescreen/audio/InCallModel.java
index 5d0d664..48981ba 100644
--- a/src/com/android/car/carlauncher/homescreen/audio/InCallModel.java
+++ b/src/com/android/car/carlauncher/homescreen/audio/InCallModel.java
@@ -22,17 +22,22 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.os.IBinder;
import android.telecom.Call;
import android.telecom.CallAudioState;
+import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import android.text.TextUtils;
import android.util.Log;
import android.view.Display;
import android.view.View;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.android.car.carlauncher.R;
@@ -43,6 +48,7 @@
import com.android.car.carlauncher.homescreen.ui.DescriptiveTextWithControlsView;
import com.android.car.telephony.common.CallDetail;
import com.android.car.telephony.common.TelecomUtils;
+import com.android.car.ui.utils.CarUxRestrictionsUtil;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -61,13 +67,18 @@
private Context mContext;
private TelecomManager mTelecomManager;
+ private CarUxRestrictionsUtil mCarUxRestrictionsUtil;
+
+ private PackageManager mPackageManager;
private final Clock mElapsedTimeClock;
+
private Call mCurrentCall;
private CompletableFuture<Void> mPhoneNumberInfoFuture;
private InCallServiceImpl mInCallService;
private HomeCardInterface.Presenter mPresenter;
+ private CardHeader mDefaultDialerCardHeader;
private CardHeader mCardHeader;
private CardContent mCardContent;
private CharSequence mOngoingCallSubtitle;
@@ -107,19 +118,15 @@
public void onCreate(Context context) {
mContext = context;
mTelecomManager = context.getSystemService(TelecomManager.class);
+ mCarUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(context);
+
mOngoingCallSubtitle = context.getResources().getString(R.string.ongoing_call_text);
mDialingCallSubtitle = context.getResources().getString(R.string.dialing_call_text);
initializeAudioControls();
- try {
- PackageManager pm = context.getPackageManager();
- Drawable appIcon = pm.getApplicationIcon(mTelecomManager.getDefaultDialerPackage());
- CharSequence appName = pm.getApplicationLabel(
- pm.getApplicationInfo(mTelecomManager.getDefaultDialerPackage(), /* flags = */
- 0));
- mCardHeader = new CardHeader(appName, appIcon);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "No default dialer package found", e);
- }
+
+ mPackageManager = context.getPackageManager();
+ mDefaultDialerCardHeader = createCardHeader(mTelecomManager.getDefaultDialerPackage());
+ mCardHeader = mDefaultDialerCardHeader;
Intent intent = new Intent(context, InCallServiceImpl.class);
intent.setAction(InCallServiceImpl.ACTION_LOCAL_BIND);
@@ -160,8 +167,25 @@
*/
@Override
public void onClick(View view) {
- PackageManager pm = mContext.getPackageManager();
- Intent intent = pm.getLaunchIntentForPackage(mTelecomManager.getDefaultDialerPackage());
+ Intent intent = null;
+ if (isSelfManagedCall() && !isRequiresDistractionOptimization()) {
+ Bundle extras = mCurrentCall.getDetails().getExtras();
+ ComponentName componentName = extras == null ? null : extras.getParcelable(
+ Intent.EXTRA_COMPONENT_NAME, ComponentName.class);
+ if (componentName != null) {
+ intent = new Intent();
+ intent.setComponent(componentName);
+ } else {
+ String callingAppPackageName = getCallingAppPackageName();
+ if (!TextUtils.isEmpty(callingAppPackageName)) {
+ intent = mPackageManager.getLaunchIntentForPackage(callingAppPackageName);
+ }
+ }
+ } else {
+ intent = mPackageManager.getLaunchIntentForPackage(
+ mTelecomManager.getDefaultDialerPackage());
+ }
+
if (intent != null) {
// Launch activity in the default app task container: the display area where
// applications are launched by default.
@@ -171,8 +195,7 @@
mContext.startActivity(intent, options.toBundle());
} else {
if (DEBUG) {
- Log.d(TAG, "No launch intent found for dialer package: "
- + mTelecomManager.getDefaultDialerPackage());
+ Log.d(TAG, "No launch intent found to show in call ui for call : " + mCurrentCall);
}
}
}
@@ -196,6 +219,7 @@
@Override
public void onCallRemoved(Call call) {
mCurrentCall = null;
+ mCardHeader = null;
mCardContent = null;
mPresenter.onModelUpdated(this);
if (call != null) {
@@ -262,7 +286,23 @@
@VisibleForTesting
void updateModelWithContact(TelecomUtils.PhoneNumberInfo phoneNumberInfo,
@Call.CallState int callState) {
- String contactName = phoneNumberInfo.getDisplayName();
+ // If call has been removed, return.
+ if (mCurrentCall == null) {
+ return;
+ }
+
+ // Use the caller display name or contact display name from call details first.
+ String contactName = mCurrentCall.getDetails().getCallerDisplayName();
+ if (TextUtils.isEmpty(contactName)) {
+ contactName = mCurrentCall.getDetails().getContactDisplayName();
+ }
+ String initials = null;
+ if (TextUtils.isEmpty(contactName)) {
+ contactName = phoneNumberInfo.getDisplayName();
+ initials = phoneNumberInfo.getInitials();
+ } else {
+ initials = TelecomUtils.getInitials(contactName);
+ }
Drawable contactImage = null;
if (phoneNumberInfo.getAvatarUri() != null) {
try {
@@ -280,8 +320,7 @@
}
}
if (contactImage == null) {
- contactImage = TelecomUtils.createLetterTile(mContext,
- phoneNumberInfo.getInitials(), phoneNumberInfo.getDisplayName());
+ contactImage = TelecomUtils.createLetterTile(mContext, initials, contactName);
}
mCardContent = createPhoneCardContent(contactImage, contactName, callState);
@@ -289,12 +328,22 @@
}
private void handleActiveCall(@NonNull Call call) {
- @Call.CallState int callState = call.getState();
+ @Call.CallState int callState = call.getDetails().getState();
if (callState != Call.STATE_ACTIVE && callState != Call.STATE_DIALING) {
return;
}
mCurrentCall = call;
+
CallDetail callDetails = CallDetail.fromTelecomCallDetail(call.getDetails());
+ if (callDetails.isSelfManaged()) {
+ String packageName = getCallingAppPackageName();
+ mCardHeader = createCardHeader(packageName);
+ }
+ if (mCardHeader == null) {
+ // Default to show the default dialer app info
+ mCardHeader = mDefaultDialerCardHeader;
+ }
+
// If the home app does not have permission to read contacts, just display the
// phone number
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_CONTACTS)
@@ -302,6 +351,7 @@
updateModelWithPhoneNumber(callDetails.getNumber(), callState);
return;
}
+
if (mPhoneNumberInfoFuture != null) {
mPhoneNumberInfoFuture.cancel(/* mayInterruptIfRunning= */ true);
}
@@ -353,4 +403,37 @@
int[] getMuteButtonDrawableState() {
return mMuteButton.getIcon().getState();
}
+
+ @Nullable
+ private String getCallingAppPackageName() {
+ Call.Details callDetails = mCurrentCall == null ? null : mCurrentCall.getDetails();
+ PhoneAccountHandle phoneAccountHandle =
+ callDetails == null ? null : callDetails.getAccountHandle();
+ return phoneAccountHandle == null ? null
+ : phoneAccountHandle.getComponentName().getPackageName();
+ }
+
+ private boolean isRequiresDistractionOptimization() {
+ return mCarUxRestrictionsUtil.getCurrentRestrictions().isRequiresDistractionOptimization();
+ }
+
+ private boolean isSelfManagedCall() {
+ return mCurrentCall != null
+ && mCurrentCall.getDetails().hasProperty(Call.Details.PROPERTY_SELF_MANAGED);
+ }
+
+ private CardHeader createCardHeader(String packageName) {
+ if (!TextUtils.isEmpty(packageName)) {
+ try {
+ ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(
+ packageName, PackageManager.ApplicationInfoFlags.of(0));
+ Drawable appIcon = mPackageManager.getApplicationIcon(applicationInfo);
+ CharSequence appName = mPackageManager.getApplicationLabel(applicationInfo);
+ return new CardHeader(appName, appIcon);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "No such package found " + packageName, e);
+ }
+ }
+ return null;
+ }
}
diff --git a/src/com/android/car/carlauncher/homescreen/audio/MediaViewModel.java b/src/com/android/car/carlauncher/homescreen/audio/MediaViewModel.java
index fc848b8..5900fef 100644
--- a/src/com/android/car/carlauncher/homescreen/audio/MediaViewModel.java
+++ b/src/com/android/car/carlauncher/homescreen/audio/MediaViewModel.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.util.Log;
import android.util.Size;
import android.view.Display;
import android.view.View;
@@ -53,6 +54,8 @@
*/
public class MediaViewModel extends AndroidViewModel implements HomeCardInterface.Model {
+ private static final String TAG = "MediaViewModel";
+
private HomeCardInterface.Presenter mAudioPresenter;
// MediaSourceViewModel is for the current or last played media app
private MediaSourceViewModel mSourceViewModel;
@@ -169,16 +172,19 @@
// will switch to showing "no media playing" case.
if (mediaSource != null
&& !AppLauncherUtils.isVideoApp(mContext.getPackageManager(),
- mediaSource.getPackageName())) {
+ mediaSource.getPackageName())) {
+ if (Log.isLoggable(TAG, Log.INFO)) {
+ Log.i(TAG, "Setting Media view to source " + mediaSource.getDisplayName());
+ }
mAppName = mediaSource.getDisplayName();
mAppIcon = mediaSource.getIcon();
mCardHeader = new CardHeader(mAppName, mAppIcon);
updateMetadata();
} else {
- mAppName = null;
- mAppIcon = null;
- mCardHeader = null;
- clearMetadata();
+ if (Log.isLoggable(TAG, Log.INFO)) {
+ Log.i(TAG, "Not resetting media widget for video apps or apps "
+ + "that do not support media browse");
+ }
}
mAudioPresenter.onModelUpdated(this);
}
@@ -208,7 +214,7 @@
}
private void clearMetadata() {
- mSongTitle = null;
+ mSongTitle = mContext.getString(R.string.default_media_song_title);
mArtistName = null;
mAlbumArtBinder.setImage(mContext, /* newArtRef = */ null);
}
@@ -234,10 +240,16 @@
private boolean mediaSourceChanged() {
MediaSource mediaSource = mSourceViewModel.getPrimaryMediaSource().getValue();
if (mediaSource == null && (mAppName != null || mAppIcon != null)) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "new media source is null...");
+ }
return true;
}
if (mediaSource != null && (mAppName != mediaSource.getDisplayName()
|| mAppIcon != mediaSource.getIcon())) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "new media source is " + mediaSource.toString());
+ }
return true;
}
return false;
diff --git a/tests/Android.bp b/tests/Android.bp
index 1c79540..569f469 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -26,11 +26,17 @@
resource_dirs: ["res"],
libs: [
+ "android.car",
"android.test.base",
"android.car-system-stubs",
],
+ optimize: {
+ enabled: false,
+ },
+
static_libs: [
+ "android.car.testapi",
"android.car.test.utils",
"androidx.test.core",
"androidx.test.runner",
@@ -39,6 +45,7 @@
"androidx.test.ext.junit",
"hamcrest-library",
"mockito-target-extended",
+ "truth-prebuilt",
"testables",
"CarLauncher-core"
],
@@ -47,6 +54,8 @@
certificate: "platform",
+ privileged: true,
+
manifest: "AndroidManifest.xml",
instrumentation_for: "CarLauncher",
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index b27745a..7238180 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -24,6 +24,8 @@
<!-- This is used in AppLauncherUtilTests -->
<meta-data android:name="com.android.automotive"
android:resource="@xml/automotive_app_desc"/>
+
+ <activity android:name="com.android.car.carlauncher.TaskViewManagerTest$TestActivity" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/src/com/android/car/carlauncher/TaskViewManagerTest.java b/tests/src/com/android/car/carlauncher/TaskViewManagerTest.java
new file mode 100644
index 0000000..8dc1602
--- /dev/null
+++ b/tests/src/com/android/car/carlauncher/TaskViewManagerTest.java
@@ -0,0 +1,806 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.carlauncher;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
+
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.TaskStackListener;
+import android.car.Car;
+import android.car.app.CarActivityManager;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.user.CarUserManager;
+import android.content.Intent;
+import android.net.Uri;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.window.TaskAppearedInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.carlauncher.taskstack.TaskStackChangeListeners;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.TaskView;
+import com.android.wm.shell.common.HandlerExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class TaskViewManagerTest extends AbstractExtendedMockitoTestCase {
+ @Rule
+ public ActivityScenarioRule mActivityRule = new ActivityScenarioRule<>(TestActivity.class);
+
+ @Mock
+ private ShellTaskOrganizer mOrganizer;
+ @Mock
+ private SyncTransactionQueue mSyncQueue;
+ @Mock
+ private HandlerExecutor mShellExecutor;
+ @Mock
+ private CarActivityManager mCarActivityManager;
+ @Mock
+ private Car mCar;
+ @Mock
+ private TaskStackChangeListeners mTaskStackChangeListeners;
+ @Mock
+ private CarUserManager mCarUserManager;
+ @Mock
+ private WindowContainerToken mToken;
+
+ @Captor
+ private ArgumentCaptor<TaskStackListener> mTaskStackListenerArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<CarUserManager.UserLifecycleListener>
+ mUserLifecycleListenerArgumentCaptor;
+
+ private TestActivity mActivity;
+ private Car.CarServiceLifecycleListener mCarServiceLifecycleListener;
+ private ActivityTaskManager mSpyActivityTaskManager;
+ private CountDownLatch mIdleHandlerLatch = new CountDownLatch(1);
+ private SurfaceControl mLeash;
+
+ @Override
+ protected void onSessionBuilder(@NonNull CustomMockitoSessionBuilder builder) {
+ builder.spyStatic(ActivityTaskManager.class);
+ builder.spyStatic(Car.class);
+ builder.spyStatic(TaskStackChangeListeners.class);
+ }
+
+ @Before
+ public void setUp() {
+ ExtendedMockito.doAnswer(invocation -> {
+ mCarServiceLifecycleListener = invocation.getArgument(3);
+ return mCar;
+ }).when(() -> Car.createCar(any(), any(), anyLong(), any()));
+ when(mCar.getCarManager(eq(Car.CAR_ACTIVITY_SERVICE))).thenReturn(mCarActivityManager);
+ when(mCar.getCarManager(eq(Car.CAR_USER_SERVICE))).thenReturn(mCarUserManager);
+
+ ExtendedMockito.doReturn(mTaskStackChangeListeners).when(() ->
+ TaskStackChangeListeners.getInstance());
+ doNothing().when(mTaskStackChangeListeners).registerTaskStackListener(
+ mTaskStackListenerArgumentCaptor.capture());
+
+ doNothing().when(mCarUserManager).addListener(any(), any(),
+ mUserLifecycleListenerArgumentCaptor.capture());
+
+ mLeash = new SurfaceControl.Builder(null)
+ .setName("test")
+ .build();
+
+ doAnswer((InvocationOnMock invocationOnMock) -> {
+ SyncTransactionQueue.TransactionRunnable r =
+ invocationOnMock.getArgument(0);
+ r.runWithTransaction(new SurfaceControl.Transaction());
+ return null;
+ }).when(mSyncQueue).runInSync(any());
+
+ doAnswer((InvocationOnMock invocationOnMock) -> {
+ Runnable r = invocationOnMock.getArgument(0);
+ r.run();
+ return null;
+ }).when(mShellExecutor).execute(any());
+ doReturn(mShellExecutor).when(mOrganizer).getExecutor();
+
+ mSpyActivityTaskManager = spy(ActivityTaskManager.getInstance());
+ ExtendedMockito.doReturn(mSpyActivityTaskManager).when(() ->
+ ActivityTaskManager.getInstance());
+
+ ActivityScenario<TestActivity> scenario = mActivityRule.getScenario();
+ scenario.onActivity(activity -> mActivity = activity);
+ }
+
+ private TaskAppearedInfo createMultiWindowTask(int taskId) {
+ ActivityManager.RunningTaskInfo taskInfo =
+ new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(
+ WINDOWING_MODE_MULTI_WINDOW);
+ taskInfo.parentTaskId = INVALID_TASK_ID;
+ taskInfo.token = mToken;
+ return new TaskAppearedInfo(taskInfo, new SurfaceControl());
+ }
+
+ @Test
+ public void init_cleansUpExistingMultiWindowTasks() {
+ TaskAppearedInfo existingTask1 = createMultiWindowTask(/* taskId= */ 1);
+ TaskAppearedInfo existingTask2 = createMultiWindowTask(/* taskId= */ 2);
+ doReturn(ImmutableList.of(existingTask1, existingTask2))
+ .when(mOrganizer).registerOrganizer();
+ ExtendedMockito.doReturn(false).when(mSpyActivityTaskManager).removeTask(anyInt());
+
+ new TaskViewManager(mActivity, mShellExecutor, mOrganizer, mSyncQueue);
+
+ verify(mSpyActivityTaskManager).removeTask(eq(1));
+ verify(mSpyActivityTaskManager).removeTask(eq(2));
+ }
+
+ @Test
+ public void testCreateControlledTaskView() throws Exception {
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ Intent activityIntent = new Intent();
+ Set<String> packagesThatCanRestart = ImmutableSet.of("com.random.package");
+ ControlledCarTaskViewCallbacks controlledCarTaskViewCallbacks = mock(
+ ControlledCarTaskViewCallbacks.class);
+ when(controlledCarTaskViewCallbacks.getDependingPackageNames())
+ .thenReturn(packagesThatCanRestart);
+
+ taskViewManager.createControlledCarTaskView(
+ mActivity.getMainExecutor(),
+ activityIntent,
+ /* autoRestartOnCrash= */ false,
+ controlledCarTaskViewCallbacks
+ );
+
+ runOnMainAndWait(() -> {});
+ verify(controlledCarTaskViewCallbacks).onTaskViewCreated(any());
+ }
+
+ @Test
+ public void testCreateControlledTaskView_callsOnReadyWhenVisible() throws Exception {
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, mOrganizer,
+ mSyncQueue);
+ Intent activityIntent = new Intent("ACTION_VIEW");
+ Set<String> packagesThatCanRestart = ImmutableSet.of("com.random.package");
+ ControlledCarTaskViewCallbacks controlledCarTaskViewCallbacks = mock(
+ ControlledCarTaskViewCallbacks.class);
+ when(controlledCarTaskViewCallbacks.getDependingPackageNames())
+ .thenReturn(packagesThatCanRestart);
+ taskViewManager.createControlledCarTaskView(
+ mActivity.getMainExecutor(),
+ activityIntent,
+ /* autoRestartOnCrash= */ false,
+ controlledCarTaskViewCallbacks
+ );
+ ControlledCarTaskView taskView = spy(taskViewManager.getControlledTaskViews().get(0));
+ doNothing().when(taskView).startActivity();
+
+ taskView.surfaceCreated(mock(SurfaceHolder.class));
+
+ runOnMainAndWait(() -> {});
+ verify(controlledCarTaskViewCallbacks).onTaskViewCreated(any());
+ verify(controlledCarTaskViewCallbacks).onTaskViewReady();
+ }
+
+ @Test
+ public void testCreateLaunchRootTaskView() throws Exception {
+ LaunchRootCarTaskViewCallbacks taskViewCallbacks =
+ mock(LaunchRootCarTaskViewCallbacks.class);
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+
+ taskViewManager.createLaunchRootTaskView(
+ mActivity.getMainExecutor(),
+ taskViewCallbacks
+ );
+ runOnMainAndWait(() -> {});
+ TaskView taskView = taskViewManager.getLaunchRootCarTaskView();
+ taskView.surfaceCreated(mock(SurfaceHolder.class));
+
+ runOnMainAndWait(() -> {});
+ verify(taskViewCallbacks).onTaskViewCreated(any());
+ verify(mOrganizer).createRootTask(eq(DEFAULT_DISPLAY),
+ eq(WINDOWING_MODE_MULTI_WINDOW),
+ any(ShellTaskOrganizer.TaskListener.class));
+ }
+
+ @Test
+ public void testCreateLaunchRootTaskView_callsOnReadyWhenVisible() throws Exception {
+ TaskAppearedInfo fakeLaunchRootTaskInfo = createMultiWindowTask(1);
+ LaunchRootCarTaskViewCallbacks taskViewCallbacks =
+ mock(LaunchRootCarTaskViewCallbacks.class);
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ doAnswer(invocation -> {
+ ShellTaskOrganizer.TaskListener listener = invocation.getArgument(2);
+ listener.onTaskAppeared(fakeLaunchRootTaskInfo.getTaskInfo(), mLeash);
+ return null;
+ }).when(mOrganizer).createRootTask(eq(DEFAULT_DISPLAY),
+ eq(WINDOWING_MODE_MULTI_WINDOW),
+ any(ShellTaskOrganizer.TaskListener.class));
+
+ taskViewManager.createLaunchRootTaskView(
+ mActivity.getMainExecutor(),
+ taskViewCallbacks
+ );
+ runOnMainAndWait(() -> {});
+ TaskView taskView = taskViewManager.getLaunchRootCarTaskView();
+ taskView.surfaceCreated(mock(SurfaceHolder.class));
+
+ runOnMainAndWait(() -> {});
+ verify(taskViewCallbacks).onTaskViewCreated(any());
+ verify(taskViewCallbacks).onTaskViewReady();
+ ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ verify(mSyncQueue, atLeastOnce()).queue(wctCaptor.capture());
+ List<WindowContainerTransaction> wcts = wctCaptor.getAllValues();
+ assertWithMessage("There must be a WindowContainerTransaction to set the"
+ + " root task as the launch root.")
+ .that(wcts.stream()
+ .flatMap(wct -> wct.getHierarchyOps().stream())
+ .map(WindowContainerTransaction.HierarchyOp::getType)
+ .anyMatch(type -> type == HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT))
+ .isTrue();
+ }
+
+ @Test
+ public void testCreateSemiControlledTaskView_launchRootTaskViewAbsent_throwsError()
+ throws Exception {
+ SemiControlledCarTaskViewCallbacks taskViewCallbacks =
+ mock(SemiControlledCarTaskViewCallbacks.class);
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+
+ // The exception happens in the current stack because mShellExecutor simply calls .run()
+ assertThrows(IllegalStateException.class, () -> {
+ taskViewManager.createSemiControlledTaskView(
+ mActivity.getMainExecutor(),
+ taskViewCallbacks
+ );
+ });
+
+ runOnMainAndWait(() -> {});
+ verifyZeroInteractions(taskViewCallbacks);
+ }
+
+ @Test
+ public void testCreateSemiControlledTaskView() throws Exception {
+ SemiControlledCarTaskViewCallbacks taskViewCallbacks =
+ mock(SemiControlledCarTaskViewCallbacks.class);
+ when(taskViewCallbacks.shouldStartInTaskView(any())).thenReturn(true);
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ runOnMainAndWait(() -> {});
+ AtomicReference<ShellTaskOrganizer.TaskListener> listener = new AtomicReference<>();
+ setUpLaunchRootTaskView(taskViewManager, listener, /* rootTaskId = */ 1);
+
+ taskViewManager.createSemiControlledTaskView(
+ mActivity.getMainExecutor(),
+ taskViewCallbacks
+ );
+ runOnMainAndWait(() -> {});
+ // Trigger surfaceCreated on SemiControlledTaskView so that taskView can get into the
+ // initialized state.
+ SemiControlledCarTaskView semiControlledCarTaskView =
+ taskViewManager.getSemiControlledTaskViews().get(0);
+ semiControlledCarTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ runOnMainAndWait(() -> {});
+
+ verify(taskViewCallbacks).onTaskViewCreated(any());
+ verify(taskViewCallbacks).onTaskViewReady();
+ }
+
+ @Test
+ public void testSemiControlledTaskAppeared_reparentedCorrectly() throws Exception {
+ SemiControlledCarTaskViewCallbacks taskViewCallbacks =
+ mock(SemiControlledCarTaskViewCallbacks.class);
+ when(taskViewCallbacks.shouldStartInTaskView(any())).thenReturn(true);
+
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ runOnMainAndWait(() -> {});
+ // Set up a LaunchRootTaskView
+ AtomicReference<ShellTaskOrganizer.TaskListener> rootTaskListener = new AtomicReference<>();
+ setUpLaunchRootTaskView(taskViewManager, rootTaskListener, /* rootTaskId = */ 1);
+ // Set up a SemiControlledTaskView
+ taskViewManager.createSemiControlledTaskView(
+ mActivity.getMainExecutor(),
+ taskViewCallbacks
+ );
+ runOnMainAndWait(() -> {});
+ SemiControlledCarTaskView semiControlledCarTaskView =
+ taskViewManager.getSemiControlledTaskViews().get(0);
+ semiControlledCarTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ runOnMainAndWait(() -> {});
+ TaskView.Listener mockListener = mock(TaskView.Listener.class);
+ semiControlledCarTaskView.setListener(mActivity.getMainExecutor(), mockListener);
+
+ // Act
+ // Trigger a taskAppeared on the launch root task to mimic the task appearance.
+ rootTaskListener.get().onTaskAppeared(createMultiWindowTask(2).getTaskInfo(), mLeash);
+ runOnMainAndWait(() -> {});
+
+ // Assert
+ // Verify if the task was reparented in the SemiControlledTaskView
+ verify(mockListener).onTaskCreated(eq(2), any());
+ }
+
+ @Test
+ public void testSemiControlledTaskVanished_reparentedCorrectly() throws Exception {
+ SemiControlledCarTaskViewCallbacks taskViewCallbacks =
+ mock(SemiControlledCarTaskViewCallbacks.class);
+ when(taskViewCallbacks.shouldStartInTaskView(any())).thenReturn(true);
+
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ runOnMainAndWait(() -> {});
+ // Set up a LaunchRootTaskView
+ AtomicReference<ShellTaskOrganizer.TaskListener> rootTaskListener = new AtomicReference<>();
+ setUpLaunchRootTaskView(taskViewManager, rootTaskListener, /* rootTaskId = */ 1);
+ // Set up a SemiControlledTaskView
+ taskViewManager.createSemiControlledTaskView(
+ mActivity.getMainExecutor(),
+ taskViewCallbacks
+ );
+ runOnMainAndWait(() -> {});
+ SemiControlledCarTaskView semiControlledCarTaskView =
+ taskViewManager.getSemiControlledTaskViews().get(0);
+ semiControlledCarTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ runOnMainAndWait(() -> {});
+ TaskView.Listener mockListener = mock(TaskView.Listener.class);
+ semiControlledCarTaskView.setListener(mActivity.getMainExecutor(), mockListener);
+
+ ActivityManager.RunningTaskInfo semiControlledTaskInfo = createMultiWindowTask(2)
+ .getTaskInfo();
+ rootTaskListener.get().onTaskAppeared(semiControlledTaskInfo, mLeash);
+ runOnMainAndWait(() -> {});
+
+ // Act
+ // Trigger a taskVanished on the launch root task
+ rootTaskListener.get().onTaskVanished(semiControlledTaskInfo);
+ runOnMainAndWait(() -> {});
+
+ // Assert
+ // Verify if the task was removed from the SemiControlledTaskView
+ verify(mockListener).onTaskRemovalStarted(/* taskId = */ eq(2));
+ }
+
+ private void setUpLaunchRootTaskView(TaskViewManager taskViewManager,
+ AtomicReference<ShellTaskOrganizer.TaskListener> listener,
+ int rootTaskId) throws Exception {
+ doAnswer(invocation -> {
+ listener.set(invocation.getArgument(2));
+ listener.get().onTaskAppeared(createMultiWindowTask(rootTaskId).getTaskInfo(), mLeash);
+ return null;
+ }).when(mOrganizer).createRootTask(eq(DEFAULT_DISPLAY),
+ eq(WINDOWING_MODE_MULTI_WINDOW),
+ any(ShellTaskOrganizer.TaskListener.class));
+ taskViewManager.createLaunchRootTaskView(
+ mActivity.getMainExecutor(),
+ mock(LaunchRootCarTaskViewCallbacks.class)
+ );
+ runOnMainAndWait(() -> {});
+ LaunchRootCarTaskView launchRootCarTaskView = taskViewManager.getLaunchRootCarTaskView();
+ launchRootCarTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ runOnMainAndWait(() -> {});
+ }
+
+ @Test
+ public void testInit_registersTaskMonitor() throws Exception {
+ new TaskViewManager(mActivity, mShellExecutor, mOrganizer, mSyncQueue);
+ runOnMainAndWait(() -> {});
+
+ mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+
+ verify(mCarActivityManager).registerTaskMonitor();
+ }
+
+ @Test
+ public void testTaskAppeared_launchRootTaskView_updatesCarActivityManager() throws Exception {
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ runOnMainAndWait(() -> {});
+ mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+ // Set up a LaunchRootTaskView
+ AtomicReference<ShellTaskOrganizer.TaskListener> rootTaskListener = new AtomicReference<>();
+ setUpLaunchRootTaskView(taskViewManager, rootTaskListener, /* rootTaskId = */ 1);
+ ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo();
+
+ // Act
+ rootTaskListener.get().onTaskAppeared(taskInfo, mLeash);
+ runOnMainAndWait(() -> {});
+
+ // Assert
+ verify(mCarActivityManager).onTaskAppeared(taskInfo);
+ }
+
+ @Test
+ public void testTaskInfoChanged_launchRootTaskView_updatesCarActivityManager()
+ throws Exception {
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ runOnMainAndWait(() -> {});
+ mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+ // Set up a LaunchRootTaskView
+ AtomicReference<ShellTaskOrganizer.TaskListener> rootTaskListener = new AtomicReference<>();
+ setUpLaunchRootTaskView(taskViewManager, rootTaskListener, /* rootTaskId = */ 1);
+ ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo();
+
+ // Act
+ rootTaskListener.get().onTaskInfoChanged(taskInfo);
+ runOnMainAndWait(() -> {});
+
+ // Assert
+ verify(mCarActivityManager).onTaskInfoChanged(taskInfo);
+ }
+
+ @Test
+ public void testTaskVanished_launchRootTaskView_updatesCarActivityManager() throws Exception {
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ runOnMainAndWait(() -> {});
+ mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+ // Set up a LaunchRootTaskView
+ AtomicReference<ShellTaskOrganizer.TaskListener> rootTaskListener = new AtomicReference<>();
+ setUpLaunchRootTaskView(taskViewManager, rootTaskListener, /* rootTaskId = */ 1);
+ ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo();
+
+ // Act
+ rootTaskListener.get().onTaskVanished(taskInfo);
+ runOnMainAndWait(() -> {});
+
+ // Assert
+ verify(mCarActivityManager).onTaskVanished(taskInfo);
+ }
+
+ @Test
+ public void testHostActivityDestroyed_releasesAllTaskViews() throws Exception {
+ testReleaseAllTaskViews(() -> {
+ ActivityScenario<TestActivity> scenario = mActivityRule.getScenario();
+ scenario.moveToState(Lifecycle.State.DESTROYED);
+ });
+ }
+
+ private void setUpControlledTaskView(TaskViewManager taskViewManager, Intent activityIntent,
+ Set<String> packagesThatCanRestart) throws Exception {
+ ControlledCarTaskViewCallbacks controlledCarTaskViewCallbacks = mock(
+ ControlledCarTaskViewCallbacks.class);
+ when(controlledCarTaskViewCallbacks.getDependingPackageNames())
+ .thenReturn(packagesThatCanRestart);
+ taskViewManager.createControlledCarTaskView(
+ mActivity.getMainExecutor(),
+ activityIntent,
+ false,
+ controlledCarTaskViewCallbacks
+ );
+
+ int lastIndex = Math.min(0, taskViewManager.getControlledTaskViews().size() - 1);
+ ControlledCarTaskView taskView = spy(taskViewManager.getControlledTaskViews()
+ .get(lastIndex));
+ doNothing().when(taskView).startActivity();
+
+ taskView.surfaceCreated(mock(SurfaceHolder.class));
+ runOnMainAndWait(() -> {});
+ }
+
+ @Test
+ public void testRestartControlledTask_whenHostActivityFocussed() throws Exception {
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+
+ // Send onTaskVanished to mimic the task removal behavior.
+ setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"),
+ ImmutableSet.of("com.random.package2"));
+ ControlledCarTaskView controlledCarTaskView =
+ taskViewManager.getControlledTaskViews().get(0);
+ ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo();
+ controlledCarTaskView.onTaskAppeared(taskInfo, mLeash);
+ controlledCarTaskView.onTaskVanished(taskInfo);
+ assertThat(controlledCarTaskView.getTaskId()).isEqualTo(INVALID_TASK_ID);
+
+ // Stub the taskview with a spy to assert on startActivity.
+ ControlledCarTaskView spiedTaskView = spy(controlledCarTaskView);
+ doNothing().when(spiedTaskView).startActivity();
+ taskViewManager.getControlledTaskViews().set(0, spiedTaskView);
+
+ // Act
+ mTaskStackListenerArgumentCaptor.getValue().onTaskFocusChanged(mActivity.getTaskId(),
+ /* focused = */ true);
+
+ // Assert
+ verify(spiedTaskView).startActivity();
+ }
+
+ @Test
+ public void testControlledTaskNotRestarted_ifAlreadyRunning_whenHostActivityFocussed()
+ throws Exception {
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+
+ setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"),
+ ImmutableSet.of("com.random.package2"));
+ ControlledCarTaskView controlledCarTaskView =
+ taskViewManager.getControlledTaskViews().get(0);
+ ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo();
+ controlledCarTaskView.onTaskAppeared(taskInfo, mLeash);
+
+ // Stub the taskview with a spy to assert on startActivity.
+ ControlledCarTaskView spiedTaskView = spy(controlledCarTaskView);
+ doNothing().when(spiedTaskView).startActivity();
+ taskViewManager.getControlledTaskViews().set(0, spiedTaskView);
+
+ // Act
+ mTaskStackListenerArgumentCaptor.getValue().onTaskFocusChanged(mActivity.getTaskId(),
+ /* focused = */ true);
+
+ // Assert
+ verify(spiedTaskView, times(0)).startActivity();
+ }
+
+ @Test
+ public void testRestartControlledTask_whenHostActivityRestarted() throws Exception {
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+
+ // Send onTaskVanished to mimic the task removal behavior.
+ setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"),
+ ImmutableSet.of("com.random.package2"));
+ ControlledCarTaskView controlledCarTaskView =
+ taskViewManager.getControlledTaskViews().get(0);
+ ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo();
+ controlledCarTaskView.onTaskAppeared(taskInfo, mLeash);
+ controlledCarTaskView.onTaskVanished(taskInfo);
+ assertThat(controlledCarTaskView.getTaskId()).isEqualTo(INVALID_TASK_ID);
+
+ // Stub the taskview with a spy to assert on startActivity.
+ ControlledCarTaskView spiedTaskView = spy(controlledCarTaskView);
+ doNothing().when(spiedTaskView).startActivity();
+ taskViewManager.getControlledTaskViews().set(0, spiedTaskView);
+
+ // Act
+ mTaskStackListenerArgumentCaptor.getValue().onActivityRestartAttempt(
+ createMultiWindowTask(mActivity.getTaskId()).getTaskInfo(),
+ /* homeTaskVisible = */ true, false,
+ /* focused = */ true);
+
+ // Assert
+ verify(spiedTaskView).startActivity();
+ }
+
+ @Test
+ public void testRestartControlledTask_whenPackageThatCanRestartChanged() throws Exception {
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+
+ // Send onTaskVanished to mimic the task removal behavior.
+ setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"),
+ ImmutableSet.of("com.relevant.package"));
+ ControlledCarTaskView controlledCarTaskView =
+ taskViewManager.getControlledTaskViews().get(0);
+ ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo();
+ controlledCarTaskView.onTaskAppeared(taskInfo, mLeash);
+ controlledCarTaskView.onTaskVanished(taskInfo);
+ assertThat(controlledCarTaskView.getTaskId()).isEqualTo(INVALID_TASK_ID);
+
+ // Stub the taskview with a spy to assert on startActivity.
+ ControlledCarTaskView spiedTaskView = spy(controlledCarTaskView);
+ doNothing().when(spiedTaskView).startActivity();
+ taskViewManager.getControlledTaskViews().set(0, spiedTaskView);
+
+ // Act
+ taskViewManager.getPackageBroadcastReceiver().onReceive(mActivity,
+ new Intent().setData(Uri.parse("package:com.relevant.package")));
+
+ // Assert
+ verify(spiedTaskView).startActivity();
+ }
+
+ @Test
+ public void testControlledTaskNotRestarted_whenARandomPackageChanged() throws Exception {
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+
+ // Send onTaskVanished to mimic the task removal behavior.
+ setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"),
+ ImmutableSet.of("com.relevant.package"));
+ ControlledCarTaskView controlledCarTaskView =
+ taskViewManager.getControlledTaskViews().get(0);
+ ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo();
+ controlledCarTaskView.onTaskAppeared(taskInfo, mLeash);
+ controlledCarTaskView.onTaskVanished(taskInfo);
+ assertThat(controlledCarTaskView.getTaskId()).isEqualTo(INVALID_TASK_ID);
+
+ // Stub the taskview with a spy to assert on startActivity.
+ ControlledCarTaskView spiedTaskView = spy(controlledCarTaskView);
+ doNothing().when(spiedTaskView).startActivity();
+ taskViewManager.getControlledTaskViews().set(0, spiedTaskView);
+
+ // Act
+ taskViewManager.getPackageBroadcastReceiver().onReceive(mActivity,
+ new Intent().setData(Uri.parse("package:com.random.package")));
+
+ // Assert
+ verify(spiedTaskView, times(0)).startActivity();
+ }
+
+ // User switch related tests.
+
+ @Test
+ public void testRestartControlledTask_onUserUnlocked() throws Exception {
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+
+ // Send onTaskVanished to mimic the task removal behavior.
+ setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"),
+ ImmutableSet.of("com.random.package2"));
+ ControlledCarTaskView controlledCarTaskView =
+ taskViewManager.getControlledTaskViews().get(0);
+ ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo();
+ controlledCarTaskView.onTaskAppeared(taskInfo, mLeash);
+ controlledCarTaskView.onTaskVanished(taskInfo);
+ assertThat(controlledCarTaskView.getTaskId()).isEqualTo(INVALID_TASK_ID);
+
+ // Stub the taskview with a spy to assert on startActivity.
+ ControlledCarTaskView spiedTaskView = spy(controlledCarTaskView);
+ doNothing().when(spiedTaskView).startActivity();
+ taskViewManager.getControlledTaskViews().set(0, spiedTaskView);
+
+ // Act
+ mUserLifecycleListenerArgumentCaptor.getValue().onEvent(
+ new CarUserManager.UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED,
+ mActivity.getUserId()));
+
+ // Assert
+ verify(spiedTaskView).startActivity();
+ }
+
+ @Test
+ public void testUserSwitch_releasesAllTaskViews() throws Exception {
+ testReleaseAllTaskViews(/* actionBlock= */ () ->
+ mUserLifecycleListenerArgumentCaptor.getValue().onEvent(
+ new CarUserManager.UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+ /* from= */ mActivity.getUserId(), /* to= */ 20))
+ );
+ }
+
+ private void testReleaseAllTaskViews(Runnable actionBlock) throws Exception {
+ TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor,
+ mOrganizer, mSyncQueue);
+ runOnMainAndWait(() -> {});
+ mCarServiceLifecycleListener.onLifecycleChanged(mCar, true);
+ // Create a few TaskViews
+ AtomicReference<ShellTaskOrganizer.TaskListener> listener = new AtomicReference<>();
+ setUpLaunchRootTaskView(taskViewManager, listener, /* rootTaskId = */ 1);
+ LaunchRootCarTaskView launchRootCarTaskView = taskViewManager.getLaunchRootCarTaskView();
+
+ setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"),
+ ImmutableSet.of("com.random.package"));
+ ControlledCarTaskView controlledCarTaskView = taskViewManager.getControlledTaskViews()
+ .get(0);
+ setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"),
+ ImmutableSet.of("com.random.package2"));
+ ControlledCarTaskView controlledCarTaskView2 = taskViewManager.getControlledTaskViews()
+ .get(1);
+
+ SemiControlledCarTaskViewCallbacks taskViewCallbacks =
+ mock(SemiControlledCarTaskViewCallbacks.class);
+ when(taskViewCallbacks.shouldStartInTaskView(any())).thenReturn(true);
+ taskViewManager.createSemiControlledTaskView(
+ mActivity.getMainExecutor(),
+ taskViewCallbacks
+ );
+ runOnMainAndWait(() -> {});
+ taskViewManager.createSemiControlledTaskView(
+ mActivity.getMainExecutor(),
+ taskViewCallbacks
+ );
+ runOnMainAndWait(() -> {});
+ // Trigger surfaceCreated on SemiControlledTaskView.
+ SemiControlledCarTaskView semiControlledCarTaskView =
+ taskViewManager.getSemiControlledTaskViews().get(0);
+ semiControlledCarTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ runOnMainAndWait(() -> {});
+ SemiControlledCarTaskView semiControlledCarTaskView2 =
+ taskViewManager.getSemiControlledTaskViews().get(1);
+ semiControlledCarTaskView2.surfaceCreated(mock(SurfaceHolder.class));
+ runOnMainAndWait(() -> {});
+
+ // Act
+ actionBlock.run();
+
+ // Assert
+ assertThat(launchRootCarTaskView.isInitialized()).isFalse();
+ assertThat(controlledCarTaskView.isInitialized()).isFalse();
+ assertThat(controlledCarTaskView2.isInitialized()).isFalse();
+ assertThat(semiControlledCarTaskView.isInitialized()).isFalse();
+ assertThat(semiControlledCarTaskView2.isInitialized()).isFalse();
+
+ assertThat(taskViewManager.getSemiControlledTaskViews()).isEmpty();
+ assertThat(taskViewManager.getLaunchRootCarTaskView()).isNull();
+ assertThat(taskViewManager.getControlledTaskViews()).isEmpty();
+
+ verify(mOrganizer).unregisterOrganizer();
+ }
+
+ private void runOnMainAndWait(Runnable r) throws Exception {
+ mActivity.getMainExecutor().execute(() -> {
+ r.run();
+ mIdleHandlerLatch.countDown();
+ mIdleHandlerLatch = new CountDownLatch(1);
+ });
+ mIdleHandlerLatch.await(5, TimeUnit.SECONDS);
+ }
+
+ public static class TestActivity extends Activity {}
+}
diff --git a/tests/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenterTest.java b/tests/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenterTest.java
index 602e364..b53d5bd 100644
--- a/tests/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenterTest.java
+++ b/tests/src/com/android/car/carlauncher/homescreen/audio/HomeAudioCardPresenterTest.java
@@ -27,6 +27,7 @@
import com.android.car.carlauncher.homescreen.HomeCardInterface;
import com.android.car.carlauncher.homescreen.ui.CardHeader;
import com.android.car.carlauncher.homescreen.ui.DescriptiveTextView;
+import com.android.car.carlauncher.homescreen.ui.DescriptiveTextWithControlsView;
import org.junit.Before;
import org.junit.Test;
@@ -35,8 +36,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Collections;
-
@RunWith(JUnit4.class)
public class HomeAudioCardPresenterTest {
@@ -92,6 +91,28 @@
}
@Test
+ public void onModelUpdated_activePhoneCall_doesNotUpdateFragment() {
+ //setUpActivePhoneCall in presenter
+ CardHeader callModelHeader = new CardHeader("dialer", /* appIcon = */
+ null);
+ DescriptiveTextWithControlsView callModelContent = new DescriptiveTextWithControlsView(
+ /* image = */ null, "callerNumber", "ongoingCall");
+ when(mOtherModel.getCardHeader()).thenReturn(callModelHeader);
+ when(mOtherModel.getCardContent()).thenReturn(callModelContent);
+ mPresenter.onModelUpdated(mOtherModel);
+
+ // send MediaModel update during ongoing call
+ mPresenter.onModelUpdated(mModel);
+
+ //verify call
+ verify(mView).updateHeaderView(callModelHeader);
+ verify(mView).updateContentView(callModelContent);
+ verify(mView, never()).hideCard();
+ verify(mView, never()).updateHeaderView(CARD_HEADER);
+ verify(mView, never()).updateContentView(CARD_CONTENT);
+ }
+
+ @Test
public void onModelUpdated_nullSameModel_updatesFragment() {
mPresenter.onModelUpdated(mModel);
reset(mView);