[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: d15498a7e7 -s ours
am skip reason: contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Car/+/28039185
Change-Id: I0125572f13d2bfb6768163cfa389c0d62995d52f
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index f1caf30..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-LOCAL_PATH := $(call my-dir)
-
-$(eval $(call declare-1p-copy-files,packages/services/Car,))
-
-include $(CLEAR_VARS)
-
-# Include car_ui_portrait
-include $(LOCAL_PATH)/car_product/car_ui_portrait/Android.mk
-
-# Include the sub-makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/FrameworkPackageStubs/AndroidManifest.xml b/FrameworkPackageStubs/AndroidManifest.xml
index 9317d21..65d6415 100644
--- a/FrameworkPackageStubs/AndroidManifest.xml
+++ b/FrameworkPackageStubs/AndroidManifest.xml
@@ -240,6 +240,17 @@
</intent-filter>
</activity>
+ <!-- Dream settings stub -->
+ <activity android:name=".Stubs$DreamSettingsStub"
+ android:label="@string/stub_name"
+ android:excludeFromRecents="true"
+ android:exported="true">
+ <intent-filter android:priority="-1">
+ <action android:name="android.settings.DREAM_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<!-- Contacts package stubs -->
<activity
android:name=".Stubs$ContactsStub"
@@ -266,66 +277,5 @@
</intent-filter>
</activity>
- <!-- DocumentsUI stub to handle app file access requests.
- For devices planning to properly support File Management,
- DocumentsUIStubWithResult & DocumentsUIStub should be removed. -->
- <activity
- android:name=".Stubs$DocumentsUIStubWithResult"
- android:label="@string/stub_name"
- android:excludeFromRecents="true"
- android:visibleToInstantApps="true"
- android:exported="true">
- <!-- .picker.PickActivity
- set android:priority="101" to avoid other apps to take the priority,
- and in case of conflicting with DocumentsUI's 100. -->
- <intent-filter android:priority="101">
- <action android:name="android.intent.action.OPEN_DOCUMENT" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.OPENABLE" />
- <data android:mimeType="*/*" />
- </intent-filter>
- <intent-filter android:priority="101">
- <action android:name="android.intent.action.CREATE_DOCUMENT" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.OPENABLE" />
- <data android:mimeType="*/*" />
- </intent-filter>
- <intent-filter android:priority="101">
- <action android:name="android.intent.action.GET_CONTENT" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.OPENABLE" />
- <data android:mimeType="*/*" />
- </intent-filter>
- <intent-filter android:priority="101">
- <action android:name="android.intent.action.OPEN_DOCUMENT_TREE" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
-
- <activity
- android:name=".Stubs$DocumentsUIStub"
- android:label="@string/stub_name"
- android:excludeFromRecents="true"
- android:exported="true">
- <!-- .files.FilesActivity
- For VIEW actions, ComponentResolver always adjustPriority to 0. -->
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.document/root" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.document/directory" />
- </intent-filter>
- <!-- .ViewDownloadsActivity
- Set android:priority="101" to avoid other apps to take the priority,
- nor conflicting with DocumentsUI's 0. -->
- <intent-filter android:priority="101">
- <action android:name="android.intent.action.VIEW_DOWNLOADS" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
</application>
</manifest>
diff --git a/FrameworkPackageStubs/res/values-af/strings.xml b/FrameworkPackageStubs/res/values-af/strings.xml
index 9495a6c..1a03cab 100644
--- a/FrameworkPackageStubs/res/values-af/strings.xml
+++ b/FrameworkPackageStubs/res/values-af/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Geen program kan hierdie handeling hanteer nie"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Prent-in-prent word nie op hierdie toestel gesteun nie"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Bestuur Onbekende Appbronne word nie op hierdie toestel gesteun nie"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Lêerbestuur word nie op hierdie toestel gesteun nie"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Sluimerskerms word nie op hierdie toestel gesteun nie"</string>
<string name="stub_name" msgid="3987164490218189006">"Geen"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-am/strings.xml b/FrameworkPackageStubs/res/values-am/strings.xml
index 1c51f32..e533865 100644
--- a/FrameworkPackageStubs/res/values-am/strings.xml
+++ b/FrameworkPackageStubs/res/values-am/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"ይህን ድርጊት ምንም መተግበሪያዎች አያስተናግዱትም"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"ሥዕል-ላይ-ሥዕል በዚህ መሣሪያ ላይ አይደገፍም"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"ያልታወቁ የመተግበሪያ ምንጮች አስተዳደር በዚህ መሣሪያ ላይ አይደገፍም"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"የፋይል አስተዳደር በዚህ መሣሪያ ላይ አይደገፍም"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"የገፀ ማያ አሳራፊዎች እዚህ መሣሪያ ላይ አይደገፉም"</string>
<string name="stub_name" msgid="3987164490218189006">"ምንም"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-ar/strings.xml b/FrameworkPackageStubs/res/values-ar/strings.xml
index 61fc01b..fb1ce80 100644
--- a/FrameworkPackageStubs/res/values-ar/strings.xml
+++ b/FrameworkPackageStubs/res/values-ar/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"يتعذر هذا الإجراء على أي تطبيق."</string>
<string name="pip_not_supported" msgid="8681268258599412706">"لا تتوفّر ميزة \"نافذة ضمن النافذة\" على هذا الجهاز."</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"\"إدارة مصادر التطبيقات غير المعروفة\" غير متاحة على هذا الجهاز."</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"\"إدارة الملفات\" غير متاحة على هذا الجهاز."</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"شاشات الاستراحة غير متاحة على هذا الجهاز"</string>
<string name="stub_name" msgid="3987164490218189006">"بدون"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-as/strings.xml b/FrameworkPackageStubs/res/values-as/strings.xml
index d0afde6..8511c2b 100644
--- a/FrameworkPackageStubs/res/values-as/strings.xml
+++ b/FrameworkPackageStubs/res/values-as/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"এই কাৰ্যটো কৰিবলৈ কোনো এপ্ নাই"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"এই ডিভাইচটোত চিত্ৰৰ ভিতৰত চিত্ৰ সুবিধাটো সমৰ্থিত নহয়"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"এই ডিভাইচটোত অজ্ঞাত এপৰ উৎস পৰিচালনা কৰাটো সমৰ্থিত নহয়"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"এই ডিভাইচটোত ফাইল পৰিচালনা কৰাটো সমৰ্থিত নহয়"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"এই ডিভাইচটোত স্ক্ৰীনছেভাৰ সমৰ্থিত নহয়"</string>
<string name="stub_name" msgid="3987164490218189006">"নাই"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-az/strings.xml b/FrameworkPackageStubs/res/values-az/strings.xml
index db102aa..75863a6 100644
--- a/FrameworkPackageStubs/res/values-az/strings.xml
+++ b/FrameworkPackageStubs/res/values-az/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Heç bir tətbiq bunu edə bilmir"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Şəkildə Şəkil bu cihazda dəstəklənmir"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Naməlum tətbiq mənbələrinin idarə edilməsi bu cihazda dəstəklənmir"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Fayl idarəetməsi bu cihazda dəstəklənmir"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Ekran qoruyucular bu cihazda dəstəklənmir"</string>
<string name="stub_name" msgid="3987164490218189006">"Yoxdur"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-b+sr+Latn/strings.xml b/FrameworkPackageStubs/res/values-b+sr+Latn/strings.xml
index 3150e65..8918871 100644
--- a/FrameworkPackageStubs/res/values-b+sr+Latn/strings.xml
+++ b/FrameworkPackageStubs/res/values-b+sr+Latn/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Nijedna apl. ne može da obradi ovu radnju"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Slika u slici nije podržana na ovom uređaju"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Upravljanje nepoznatim izvorima aplikacija nije podržano na ovom uređaju"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Upravljanje fajlovima nije podržano na ovom uređaju"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Čuvari ekrana nisu podržani na ovom uređaju"</string>
<string name="stub_name" msgid="3987164490218189006">"Nema"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-be/strings.xml b/FrameworkPackageStubs/res/values-be/strings.xml
index 71e3624..dd6a338 100644
--- a/FrameworkPackageStubs/res/values-be/strings.xml
+++ b/FrameworkPackageStubs/res/values-be/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Няма праграмы для апрацоўкі дзеяння"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Функцыя \"Відарыс у відарысе\" не падтрымліваецца на гэтай прыладзе"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Кіраванне невядомымі крыніцамі праграм не падтрымліваецца на гэтай прыладзе"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Кіраванне файламі не падтрымліваецца на гэтай прыладзе"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"На гэтай прыладзе застаўкі не падтрымліваюцца"</string>
<string name="stub_name" msgid="3987164490218189006">"Няма"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-bg/strings.xml b/FrameworkPackageStubs/res/values-bg/strings.xml
index a83330a..8a4ee9e 100644
--- a/FrameworkPackageStubs/res/values-bg/strings.xml
+++ b/FrameworkPackageStubs/res/values-bg/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Действ. не може да се обраб. от никое прил."</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Функцията „Картина в картината“ не се поддържа на това устройство"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Управлението на неизвестни източници на приложения не се поддържа на това устройство"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Управлението на файлове не се поддържа на това устройство"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Скрийнсейвърите не се поддържат на това устройство"</string>
<string name="stub_name" msgid="3987164490218189006">"Без"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-bn/strings.xml b/FrameworkPackageStubs/res/values-bn/strings.xml
index fc392ae..206ecc4 100644
--- a/FrameworkPackageStubs/res/values-bn/strings.xml
+++ b/FrameworkPackageStubs/res/values-bn/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"কোনও অ্যাপ্লিকেশন এই কাজটি করতে পারবে না"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"এই ডিভাইসে ছবির-মধ্যে-ছবি ফিচার ব্যবহার করা যাবে না"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"এই ডিভাইসে অজানা অ্যাপ সোর্স ম্যানেজ করার সুবিধা কাজ করবে না"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"এই ডিভাইসে ফাইল ম্যানেজমেন্ট কাজ করবে না"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"এই ডিভাইসে স্ক্রিন সেভার কাজ করে না"</string>
<string name="stub_name" msgid="3987164490218189006">"কিছুই না"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-bs/strings.xml b/FrameworkPackageStubs/res/values-bs/strings.xml
index dcf6e69..841b6b7 100644
--- a/FrameworkPackageStubs/res/values-bs/strings.xml
+++ b/FrameworkPackageStubs/res/values-bs/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Nijedna apl. ne može obraditi tu radnju"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Funkcija \"Slika u slici\" nije podržana na ovom uređaju"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Upravljanje nepoznatim izvorima aplikacija nije podržano na uređaju"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Upravljanje fajlovima nije podržano na uređaju"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Čuvari ekrana nisu podržani na uređaju"</string>
<string name="stub_name" msgid="3987164490218189006">"Nema"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-ca/strings.xml b/FrameworkPackageStubs/res/values-ca/strings.xml
index 861e4ef..17ba3f9 100644
--- a/FrameworkPackageStubs/res/values-ca/strings.xml
+++ b/FrameworkPackageStubs/res/values-ca/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"No hi ha cap app que pugui processar-ho"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"La funció d\'imatge sobre imatge no és compatible amb aquest dispositiu"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"La gestió de fonts d\'aplicacions desconegudes no és compatible amb aquest dispositiu"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"La gestió de fitxers no és compatible amb aquest dispositiu"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Els estalvis de pantalla no són compatibles amb aquest dispositiu"</string>
<string name="stub_name" msgid="3987164490218189006">"Cap"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-cs/strings.xml b/FrameworkPackageStubs/res/values-cs/strings.xml
index 8432070..b81970c 100644
--- a/FrameworkPackageStubs/res/values-cs/strings.xml
+++ b/FrameworkPackageStubs/res/values-cs/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Tuto akci nedokáže provést žádná aplikace"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Režim obraz v obraze v tomto zařízení není podporován"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Správa neznámých zdrojů aplikací na tomto zařízení není podporována"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Správa souborů na tomto zařízení není podporována"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Spořiče obrazovky na tomhle zařízení nejsou podporovány"</string>
<string name="stub_name" msgid="3987164490218189006">"Žádné"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-da/strings.xml b/FrameworkPackageStubs/res/values-da/strings.xml
index 33438b3..d7f3aac 100644
--- a/FrameworkPackageStubs/res/values-da/strings.xml
+++ b/FrameworkPackageStubs/res/values-da/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Ingen app kan håndtere denne handling"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Denne enhed understøtter ikke integreret billede"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Administrering af ukendte appkilder understøttes ikke på denne enhed"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Filhåndtering understøttes ikke på denne enhed"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Pauseskærme understøttes ikke på denne enhed"</string>
<string name="stub_name" msgid="3987164490218189006">"Ingen"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-de/strings.xml b/FrameworkPackageStubs/res/values-de/strings.xml
index 4a30432..3f58ab6 100644
--- a/FrameworkPackageStubs/res/values-de/strings.xml
+++ b/FrameworkPackageStubs/res/values-de/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Keine App kann diese Aktion ausführen"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Die Funktion \"Bild im Bild\" wird auf diesem Gerät nicht unterstützt"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Auf diesem Gerät wird die Verwaltung unbekannter App-Quellen nicht unterstützt"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Auf diesem Gerät wird die Dateiverwaltung nicht unterstützt"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Bildschirmschoner werden von diesem Gerät nicht unterstützt"</string>
<string name="stub_name" msgid="3987164490218189006">"–"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-el/strings.xml b/FrameworkPackageStubs/res/values-el/strings.xml
index b9466d1..cbaf042 100644
--- a/FrameworkPackageStubs/res/values-el/strings.xml
+++ b/FrameworkPackageStubs/res/values-el/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Καμία εφ. δεν μπορεί να διαχ. την ενέρ."</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Η λειτουργία Picture in Picture δεν υποστηρίζεται σε αυτήν τη συσκευή"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Η διαχείριση άγνωστων πηγών εφαρμογών δεν υποστηρίζεται σε αυτήν τη συσκευή"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Η διαχείριση αρχείων δεν υποστηρίζεται σε αυτήν τη συσκευή"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Οι προφυλάξεις οθόνης δεν υποστηρίζονται σε αυτή τη συσκευή"</string>
<string name="stub_name" msgid="3987164490218189006">"Καμία"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-en-rAU/strings.xml b/FrameworkPackageStubs/res/values-en-rAU/strings.xml
index 8916242..1841add 100644
--- a/FrameworkPackageStubs/res/values-en-rAU/strings.xml
+++ b/FrameworkPackageStubs/res/values-en-rAU/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"No application can handle this action"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Picture-in-picture is not supported on this device"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Manage unknown app sources is not supported on this device"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"File management is not supported on this device"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Screensavers are not supported on this device"</string>
<string name="stub_name" msgid="3987164490218189006">"None"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-en-rCA/strings.xml b/FrameworkPackageStubs/res/values-en-rCA/strings.xml
index 265ab9d..7284b69 100644
--- a/FrameworkPackageStubs/res/values-en-rCA/strings.xml
+++ b/FrameworkPackageStubs/res/values-en-rCA/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"No application can handle this action"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Picture in Picture is not supported on this device"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Manage Unknown App Sources is not supported on this device"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"File Management is not supported on this device"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Screensavers are not supported on this device"</string>
<string name="stub_name" msgid="3987164490218189006">"None"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-en-rGB/strings.xml b/FrameworkPackageStubs/res/values-en-rGB/strings.xml
index 8916242..1841add 100644
--- a/FrameworkPackageStubs/res/values-en-rGB/strings.xml
+++ b/FrameworkPackageStubs/res/values-en-rGB/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"No application can handle this action"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Picture-in-picture is not supported on this device"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Manage unknown app sources is not supported on this device"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"File management is not supported on this device"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Screensavers are not supported on this device"</string>
<string name="stub_name" msgid="3987164490218189006">"None"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-en-rIN/strings.xml b/FrameworkPackageStubs/res/values-en-rIN/strings.xml
index 8916242..1841add 100644
--- a/FrameworkPackageStubs/res/values-en-rIN/strings.xml
+++ b/FrameworkPackageStubs/res/values-en-rIN/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"No application can handle this action"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Picture-in-picture is not supported on this device"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Manage unknown app sources is not supported on this device"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"File management is not supported on this device"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Screensavers are not supported on this device"</string>
<string name="stub_name" msgid="3987164490218189006">"None"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-en-rXC/strings.xml b/FrameworkPackageStubs/res/values-en-rXC/strings.xml
index b951c7f..2fa01a7 100644
--- a/FrameworkPackageStubs/res/values-en-rXC/strings.xml
+++ b/FrameworkPackageStubs/res/values-en-rXC/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"No application can handle this action"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Picture in Picture is not supported on this device"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Manage Unknown App Sources is not supported on this device"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"File Management is not supported on this device"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Screensavers are not supported on this device"</string>
<string name="stub_name" msgid="3987164490218189006">"None"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-es-rUS/strings.xml b/FrameworkPackageStubs/res/values-es-rUS/strings.xml
index cc7863c..4417376 100644
--- a/FrameworkPackageStubs/res/values-es-rUS/strings.xml
+++ b/FrameworkPackageStubs/res/values-es-rUS/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Ninguna app puede procesar esta acción"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Este dispositivo no admite la función Pantalla en pantalla"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"No se admiten las fuentes desconocidas de administración de apps en este dispositivo"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"No se admite el administrador de archivos en este dispositivo"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Los protectores de pantalla no son compatibles con este dispositivo"</string>
<string name="stub_name" msgid="3987164490218189006">"Ninguno"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-es/strings.xml b/FrameworkPackageStubs/res/values-es/strings.xml
index 9a81d79..a56c142 100644
--- a/FrameworkPackageStubs/res/values-es/strings.xml
+++ b/FrameworkPackageStubs/res/values-es/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Ninguna app puede realizar esta acción"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"La función de imagen en imagen no es compatible con este dispositivo"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"La gestión de fuentes de aplicaciones desconocidas no se admite en este dispositivo"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"La gestión de archivos no está disponible en este dispositivo"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Los salvapantallas no se admiten en este dispositivo"</string>
<string name="stub_name" msgid="3987164490218189006">"Ninguno"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-et/strings.xml b/FrameworkPackageStubs/res/values-et/strings.xml
index dd71ec4..34d75bd 100644
--- a/FrameworkPackageStubs/res/values-et/strings.xml
+++ b/FrameworkPackageStubs/res/values-et/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Ükski rakendus ei saa seda teha"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Funktsiooni Pilt pildis selles seadmes ei toetata"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Tundmatute rakenduste allikate haldamist selles seadmes ei toetata"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"See seade ei toeta failihaldust"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"See seade ei toeta ekraanisäästjaid"</string>
<string name="stub_name" msgid="3987164490218189006">"Puudub"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-eu/strings.xml b/FrameworkPackageStubs/res/values-eu/strings.xml
index a51bba0..6ed1447 100644
--- a/FrameworkPackageStubs/res/values-eu/strings.xml
+++ b/FrameworkPackageStubs/res/values-eu/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Ez dago hori egin dezakeen aplikaziorik"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Gailu honek ez du onartzen pantaila txiki gainjarria"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"\"Kudeatu aplikazio-iturburu ezezagunak\" ez da onartzen gailuan"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Fitxategien kudeaketa ez da onartzen gailuan"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Gailu honetan ez dira onartzen pantaila-babesleak"</string>
<string name="stub_name" msgid="3987164490218189006">"Bat ere ez"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-fa/strings.xml b/FrameworkPackageStubs/res/values-fa/strings.xml
index e6171b7..ce0e90a 100644
--- a/FrameworkPackageStubs/res/values-fa/strings.xml
+++ b/FrameworkPackageStubs/res/values-fa/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"هیچ برنامهای نمیتواند این کنش را انجام دهد"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"تصویر در تصویر در این دستگاه پشتیبانی نمیشود"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"این دستگاه از «مدیریت منابع ناشناس برنامه» پشتیبانی نمیکند"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"این دستگاه از «مدیریت فایل» پشتیبانی نمیکند"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"از محافظ صفحهنمایش در این دستگاه پشتیبانی نمیشود"</string>
<string name="stub_name" msgid="3987164490218189006">"هیچکدام"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-fi/strings.xml b/FrameworkPackageStubs/res/values-fi/strings.xml
index ff09f0e..a95be5c 100644
--- a/FrameworkPackageStubs/res/values-fi/strings.xml
+++ b/FrameworkPackageStubs/res/values-fi/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Yksikään sovellus ei voi käsitellä tätä toimintoa."</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Pikkuruutua ei tueta tällä laitteella"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Tuntemattomien sovelluslähteiden hallinnointia ei tueta tällä laitteella"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Tiedostonhallintaa ei tueta tällä laitteella"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Näytönsäästäjiä ei tueta tällä laitteella"</string>
<string name="stub_name" msgid="3987164490218189006">"–"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-fr-rCA/strings.xml b/FrameworkPackageStubs/res/values-fr-rCA/strings.xml
index cce5be2..91fdb6b 100644
--- a/FrameworkPackageStubs/res/values-fr-rCA/strings.xml
+++ b/FrameworkPackageStubs/res/values-fr-rCA/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Aucune app ne peut gérer cette action"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Cet appareil ne prend pas en charge la fonctionnalité d\'incrustation d\'image"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"La gestion des applications provenant de sources inconnues n\'est pas prise en charge par cet appareil"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"La gestion des fichiers n\'est pas prise en charge par cet appareil"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Les écrans de veille ne sont pas pris en charge sur cet appareil"</string>
<string name="stub_name" msgid="3987164490218189006">"Aucun"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-fr/strings.xml b/FrameworkPackageStubs/res/values-fr/strings.xml
index 0496ce1..8b11897 100644
--- a/FrameworkPackageStubs/res/values-fr/strings.xml
+++ b/FrameworkPackageStubs/res/values-fr/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Aucune appli ne peut gérer cette action"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"La fonctionnalité Picture-in-picture n\'est pas compatible avec cet appareil"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"La gestion des sources d\'application inconnues n\'est pas prise en charge sur cet appareil."</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"La gestion des fichiers n\'est pas prise en charge sur cet appareil."</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Les économiseurs d\'écran ne sont pas pris en charge sur cet appareil"</string>
<string name="stub_name" msgid="3987164490218189006">"Aucun"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-gl/strings.xml b/FrameworkPackageStubs/res/values-gl/strings.xml
index 79a0562..2aedf46 100644
--- a/FrameworkPackageStubs/res/values-gl/strings.xml
+++ b/FrameworkPackageStubs/res/values-gl/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Ningunha app pode procesar esta acción"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"A función de pantalla superposta non se admite neste dispositivo"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Neste dispositivo non se admite a xestión de fontes de aplicacións descoñecidas"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Neste dispositivo non se admite a xestión de ficheiros"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Este dispositivo non admite protectores de pantalla"</string>
<string name="stub_name" msgid="3987164490218189006">"Ningún"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-gu/strings.xml b/FrameworkPackageStubs/res/values-gu/strings.xml
index 30353b4..2dc83c3 100644
--- a/FrameworkPackageStubs/res/values-gu/strings.xml
+++ b/FrameworkPackageStubs/res/values-gu/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"કોઈ ઍપ આ ક્રિયાને હૅન્ડલ કરી શકશે નહીં"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"આ ડિવાઇસ પર ચિત્ર-માં-ચિત્ર સુવિધા સપોર્ટ કરતી નથી"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"આ ડિવાઇસ પર \'ઍપના અજાણ્યા સૉર્સ મેનેજ કરો\' સપોર્ટેડ નથી"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"આ ડિવાઇસ પર ફાઇલ મેનેજમેન્ટ સપોર્ટેડ નથી"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"આ ડિવાઇસ પર સ્ક્રીનસેવરને સપોર્ટ કરવામાં આવતો નથી"</string>
<string name="stub_name" msgid="3987164490218189006">"કોઈ નહીં"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-hi/strings.xml b/FrameworkPackageStubs/res/values-hi/strings.xml
index dc1d6ec..fc315bc 100644
--- a/FrameworkPackageStubs/res/values-hi/strings.xml
+++ b/FrameworkPackageStubs/res/values-hi/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"कोई ऐप्लिकेशन यह कार्रवाई नहीं कर सकता"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"इस डिवाइस पर \'पिक्चर में पिक्चर\' सुविधा काम नहीं करती"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"अनजान ऐप्लिकेशन के सोर्स को मैनेज करने की सुविधा इस डिवाइस पर उपलब्ध नहीं है"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"फ़ाइल मैनेजमेंट की सुविधा इस डिवाइस पर उपलब्ध नहीं है"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"इस डिवाइस पर स्क्रीन सेवर की सुविधा उपलब्ध नहीं है"</string>
<string name="stub_name" msgid="3987164490218189006">"कोई नहीं"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-hr/strings.xml b/FrameworkPackageStubs/res/values-hr/strings.xml
index 68c109f..3da4888 100644
--- a/FrameworkPackageStubs/res/values-hr/strings.xml
+++ b/FrameworkPackageStubs/res/values-hr/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Nema aplikacije za obradu te radnje"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Slika u slici nije podržana na ovom uređaju"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Upravljanje nepoznatim izvorima aplikacije nije podržano na ovom uređaju"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Upravljanje datotekama nije podržano na ovom uređaju"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Na ovom uređaju nisu podržani čuvari zaslona"</string>
<string name="stub_name" msgid="3987164490218189006">"Nijedan"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-hu/strings.xml b/FrameworkPackageStubs/res/values-hu/strings.xml
index 56f9f6e..ce9e896 100644
--- a/FrameworkPackageStubs/res/values-hu/strings.xml
+++ b/FrameworkPackageStubs/res/values-hu/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Ezt nem tudják elvégezni az alkalmazások"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"A Kép a képben funkció nem támogatott ezen az eszközön"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Ezen az eszközön nem támogatott az ismeretlen alkalmazásforrások kezelése"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Ezen az eszközön nem támogatott a fájlkezelés"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Ez az eszköz nem támogatja a képernyőkímélőket"</string>
<string name="stub_name" msgid="3987164490218189006">"Nincs"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-hy/strings.xml b/FrameworkPackageStubs/res/values-hy/strings.xml
index 334ed55..b4e96ea 100644
--- a/FrameworkPackageStubs/res/values-hy/strings.xml
+++ b/FrameworkPackageStubs/res/values-hy/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Ոչ մի հավելված չի կարող կատարել սա"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"«Նկար նկարի մեջ գործառույթը» չի աջակցվում այս սարքում"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Հավելվածների անհայտ աղբյուրների կառավարումը չի աջակցվում այս սարքում"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Ֆայլերի կառավարումը չի աջակցվում այս սարքում"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Էկրանապահները չեն աջակցվում այս սարքում"</string>
<string name="stub_name" msgid="3987164490218189006">"Չկա"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-in/strings.xml b/FrameworkPackageStubs/res/values-in/strings.xml
index e65e5af..3fe38d9 100644
--- a/FrameworkPackageStubs/res/values-in/strings.xml
+++ b/FrameworkPackageStubs/res/values-in/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Tidak ada apl yang bisa menangani tindakan ini"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Picture-in-Picture tidak didukung di perangkat ini"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Kelola Sumber Aplikasi Tidak Dikenal tidak didukung di perangkat ini"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Pengelolaan File tidak didukung di perangkat ini"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Screensaver tidak didukung di perangkat ini"</string>
<string name="stub_name" msgid="3987164490218189006">"Tak Ada"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-is/strings.xml b/FrameworkPackageStubs/res/values-is/strings.xml
index 7930967..80567be 100644
--- a/FrameworkPackageStubs/res/values-is/strings.xml
+++ b/FrameworkPackageStubs/res/values-is/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Ekkert forrit getur meðhöndlað aðgerðina"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Mynd í mynd er ekki studd í þessu tæki"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Umsjón með forritum frá óþekktum uppruna er ekki studd í þessu tæki"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Umsjón með skrám er ekki studd í þessu tæki"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Skjávarar eru ekki studdir í þessu tæki"</string>
<string name="stub_name" msgid="3987164490218189006">"Engin"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-it/strings.xml b/FrameworkPackageStubs/res/values-it/strings.xml
index 96a56e5..a92a822 100644
--- a/FrameworkPackageStubs/res/values-it/strings.xml
+++ b/FrameworkPackageStubs/res/values-it/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Nessuna applicazione può gestire l\'azione"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"La funzionalità Picture in picture non è supportata su questo dispositivo"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"La gestione delle origini di app sconosciute non è supportata su questo dispositivo"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"La gestione dei file non è supportata su questo dispositivo"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"I salvaschermi non sono supportati su questo dispositivo"</string>
<string name="stub_name" msgid="3987164490218189006">"Nessuno"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-iw/strings.xml b/FrameworkPackageStubs/res/values-iw/strings.xml
index bf10da1..45b5493 100644
--- a/FrameworkPackageStubs/res/values-iw/strings.xml
+++ b/FrameworkPackageStubs/res/values-iw/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"אין אפליקציה שיכולה לבצע פעולה זו"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"המכשיר הזה לא תומך בתמונה בתוך תמונה"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"האפשרות \'ניהול מקורות לא ידועים של אפליקציות\' לא נתמכת במכשיר הזה"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"מנהל הקבצים לא נתמך במכשיר הזה"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"המכשיר הזה לא תומך בשומרי מסך"</string>
<string name="stub_name" msgid="3987164490218189006">"ללא"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-ja/strings.xml b/FrameworkPackageStubs/res/values-ja/strings.xml
index c440e3d..dfb2f5f 100644
--- a/FrameworkPackageStubs/res/values-ja/strings.xml
+++ b/FrameworkPackageStubs/res/values-ja/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"この操作を実行できるアプリはありません"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"ピクチャー イン ピクチャーはこのデバイスではご利用いただけません"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"このデバイスはソースが不明なアプリの管理に対応していません"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"このデバイスはファイル管理に対応していません"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"このデバイスではスクリーンセーバーはサポートされていません"</string>
<string name="stub_name" msgid="3987164490218189006">"なし"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-ka/strings.xml b/FrameworkPackageStubs/res/values-ka/strings.xml
index e37dc3e..3edd642 100644
--- a/FrameworkPackageStubs/res/values-ka/strings.xml
+++ b/FrameworkPackageStubs/res/values-ka/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"ამას ვერცერთი აპლიკაცია ვერ შეასრულებს"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"ეკრანი ეკრანში ამ მოწყობილობაზე მხარდაჭერილი არ არის"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"აპის უცნობი წყაროების მართვა არაა მხარდაჭერილი ამ მოწყობილობაზე"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"ფაილების მართვა არაა მხარდაჭერილი ამ მოწყობილობაზე"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"ამ მოწყობილობას არ აქვს ეკრანმზოგების მხარდაჭერა"</string>
<string name="stub_name" msgid="3987164490218189006">"არცერთი"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-kk/strings.xml b/FrameworkPackageStubs/res/values-kk/strings.xml
index 8971798..a3cf979 100644
--- a/FrameworkPackageStubs/res/values-kk/strings.xml
+++ b/FrameworkPackageStubs/res/values-kk/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Әрекетті ешбір қолданба орындай алмайды."</string>
<string name="pip_not_supported" msgid="8681268258599412706">"\"Суреттегі сурет\" режиміне бұл құрылғыда қолдау көрсетілмейді."</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Бұл құрылғыда белгісіз қолданба ресурстарын басқару мүмкіндігі жоқ."</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Бұл құрылғыда файлдарды басқару мүмкіндігі жоқ."</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Бұл құрылғыда скринсейверлерге қолдау көрсетілмейді."</string>
<string name="stub_name" msgid="3987164490218189006">"Жоқ"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-km/strings.xml b/FrameworkPackageStubs/res/values-km/strings.xml
index ddce2ca..f6af3c6 100644
--- a/FrameworkPackageStubs/res/values-km/strings.xml
+++ b/FrameworkPackageStubs/res/values-km/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"គ្មានកម្មវិធីអាចគ្រប់គ្រងសកម្មភាពនេះបានទេ"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"មិនអាចប្រើមុខងាររូបក្នុងរូបនៅលើឧបករណ៍នេះបានទេ"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"មិនអាចគ្រប់គ្រងកម្មវិធីដែលមិនស្គាល់នៅលើឧបករណ៍នេះបានទេ"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"មិនអាចប្រើការគ្រប់គ្រងឯកសារនៅលើឧបករណ៍នេះទេ"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"ធាតុរក្សាអេក្រង់មិនអាចប្រើបាននៅលើឧបករណ៍នេះទេ"</string>
<string name="stub_name" msgid="3987164490218189006">"គ្មាន"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-kn/strings.xml b/FrameworkPackageStubs/res/values-kn/strings.xml
index 08a0657..f65fb4d 100644
--- a/FrameworkPackageStubs/res/values-kn/strings.xml
+++ b/FrameworkPackageStubs/res/values-kn/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"ಯಾವುದೇ ಆ್ಯಪ್ ಈ ಕ್ರಿಯೆ ನಿರ್ವಹಿಸುವುದಿಲ್ಲ"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"ಈ ಸಾಧನದಲ್ಲಿ ಚಿತ್ರದಲ್ಲಿನ ಚಿತ್ರವನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"ಈ ಸಾಧನದಲ್ಲಿ ಅಪರಿಚಿತ ಆ್ಯಪ್ ಮೂಲಗಳ ನಿರ್ವಹಣೆಯನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"ಈ ಸಾಧನದಲ್ಲಿ ಫೈಲ್ ನಿರ್ವಹಣೆಯನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"ಈ ಸಾಧನದಲ್ಲಿ ಸ್ಕ್ರೀನ್ಸೇವರ್ಗಳು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"</string>
<string name="stub_name" msgid="3987164490218189006">"ಯಾವುದೂ ಬೇಡ"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-ko/strings.xml b/FrameworkPackageStubs/res/values-ko/strings.xml
index 20aab03..d2a4e4d 100644
--- a/FrameworkPackageStubs/res/values-ko/strings.xml
+++ b/FrameworkPackageStubs/res/values-ko/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"이 작업을 처리할 수 있는 애플리케이션이 없음"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"이 기기에서는 PIP 모드가 지원되지 않습니다."</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"알 수 없는 앱 소스 관리는 이 기기에서 지원되지 않습니다."</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"파일 관리는 이 기기에서 지원되지 않습니다."</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"화면 보호기는 이 기기에서 지원되지 않습니다"</string>
<string name="stub_name" msgid="3987164490218189006">"없음"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-ky/strings.xml b/FrameworkPackageStubs/res/values-ky/strings.xml
index cff5202..7e159f6 100644
--- a/FrameworkPackageStubs/res/values-ky/strings.xml
+++ b/FrameworkPackageStubs/res/values-ky/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Бул аракетти бир да колдонмо аткарбайт"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Бул түзмөктө сүрөттөгү сүрөт режими колдоого алынбайт"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Белгисиз колдонмо булактарын башкаруу бул түзмөктө колдоого алынбайт"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Файлдарды башкаруу бул түзмөктө колдоого алынбайт"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Бул түзмөктө көшөгөлөр колдоого алынбайт"</string>
<string name="stub_name" msgid="3987164490218189006">"Жок"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-lo/strings.xml b/FrameworkPackageStubs/res/values-lo/strings.xml
index 2bbcc5f..a8ed6ab 100644
--- a/FrameworkPackageStubs/res/values-lo/strings.xml
+++ b/FrameworkPackageStubs/res/values-lo/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"ບໍ່ມີແອັບພລິເຄຊັນໃດສາມາດຈັດການຄຳສັ່ງນີ້ໄດ້"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"ບໍ່ຮອງຮັບການສະແດງຜົນຊ້ອນໃນອຸປະກອນນີ້"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"ອຸປະກອນນີ້ບໍ່ຮອງຮັບການຈັດການແຫຼ່ງທີ່ມາຂອງແອັບທີ່ບໍ່ຮູ້ຈັກ"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"ອຸປະກອນນີ້ບໍ່ຮອງຮັບການຈັດການໄຟລ໌"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"ອຸປະກອນນີ້ບໍ່ຮອງຮັບພາບພັກໜ້າຈໍ"</string>
<string name="stub_name" msgid="3987164490218189006">"ບໍ່ມີ"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-lt/strings.xml b/FrameworkPackageStubs/res/values-lt/strings.xml
index 3aaa954..d703535 100644
--- a/FrameworkPackageStubs/res/values-lt/strings.xml
+++ b/FrameworkPackageStubs/res/values-lt/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Jokia progr. negali apdoroti šio veiksmo"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Vaizdo vaizde funkcija nepalaikoma šiame įrenginyje"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Nežinomų programų šaltinių tvarkymo funkcija nepalaikoma šiame įrenginyje"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Failų tvarkymo funkcija nepalaikoma šiame įrenginyje"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Ekrano užsklandos nepalaikomos šiame įrenginyje"</string>
<string name="stub_name" msgid="3987164490218189006">"Nėra"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-lv/strings.xml b/FrameworkPackageStubs/res/values-lv/strings.xml
index 66b3076..b65abad 100644
--- a/FrameworkPackageStubs/res/values-lv/strings.xml
+++ b/FrameworkPackageStubs/res/values-lv/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Šo darbību nevar veikt neviena lietotne."</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Attēls attēlā netiek atbalstīts šajā ierīcē."</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Šajā ierīcē netiek atbalstīta nezināmu lietotņu avotu pārvaldība."</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Šajā ierīcē netiek atbalstīta failu pārvaldība."</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Ekrānsaudzētāji netiek atbalstīti šajā ierīcē."</string>
<string name="stub_name" msgid="3987164490218189006">"Nav"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-mk/strings.xml b/FrameworkPackageStubs/res/values-mk/strings.xml
index 63bd43e..d47ddb2 100644
--- a/FrameworkPackageStubs/res/values-mk/strings.xml
+++ b/FrameworkPackageStubs/res/values-mk/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Нема апликација што ќе го изврши ова"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"„Слика во слика“ не е поддржана на уредов"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"„Управување со непознати извори на апликации“ не е поддржано на уредов"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"„Управување со датотеки“ не е поддржано на уредов"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Штедачите на екран не се поддржани на уредов"</string>
<string name="stub_name" msgid="3987164490218189006">"Нема"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-ml/strings.xml b/FrameworkPackageStubs/res/values-ml/strings.xml
index 876c39c..6955aad 100644
--- a/FrameworkPackageStubs/res/values-ml/strings.xml
+++ b/FrameworkPackageStubs/res/values-ml/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"ഒരു ആപ്പിനും ഈ പ്രവർത്തനം ഹാൻഡിൽ ചെയ്യാനാവില്ല"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"ചിത്രത്തിനുള്ളിലെ ചിത്രത്തിന് ഈ ഉപകരണത്തിൽ പിന്തുണയില്ല"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"ഈ ഉപകരണത്തിൽ \'അജ്ഞാത ആപ്പ് ഉറവിടങ്ങൾ മാനേജ് ചെയ്യുക\' പിന്തുണയ്ക്കുന്നില്ല"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"ഈ ഉപകരണത്തിൽ ഫയൽ മാനേജ്മെന്റ് പിന്തുണയ്ക്കുന്നില്ല"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"ഈ ഉപകരണത്തിൽ സ്ക്രീൻഷോട്ടുകൾക്ക് പിന്തുണയില്ല"</string>
<string name="stub_name" msgid="3987164490218189006">"ഒന്നുമില്ല"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-mn/strings.xml b/FrameworkPackageStubs/res/values-mn/strings.xml
index ff650bf..cc36ca8 100644
--- a/FrameworkPackageStubs/res/values-mn/strings.xml
+++ b/FrameworkPackageStubs/res/values-mn/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Энэ үйлдлийг хийх боломжтой апп алга"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Дэлгэц доторх дэлгэцийг энэ төхөөрөмж дээр дэмждэггүй"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Тодорхойгүй аппын эх сурвалжуудыг удирдахыг энэ төхөөрөмжид дэмждэггүй"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Файлын менежментийг энэ төхөөрөмжид дэмждэггүй"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Дэлгэц амраагчдыг энэ төхөөрөмж дээр дэмждэггүй"</string>
<string name="stub_name" msgid="3987164490218189006">"Байхгүй"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-mr/strings.xml b/FrameworkPackageStubs/res/values-mr/strings.xml
index fb64af6..a5cd0b1 100644
--- a/FrameworkPackageStubs/res/values-mr/strings.xml
+++ b/FrameworkPackageStubs/res/values-mr/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"कोणतेही अॅप ही क्रिया हाताळू शकत नाही"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"या डिव्हाइसवर चित्रात-चित्र याला सपोर्ट नाही"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"अज्ञात अॅप स्रोत व्यवस्थापित करणे याला या डिव्हाइसवर सपोर्ट नाही"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"फाइल व्यवस्थापन करणे याला डिव्हाइसवर सपोर्ट नाही"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"या डिव्हाइसवर स्क्रीनसेव्हरना सपोर्ट नाही"</string>
<string name="stub_name" msgid="3987164490218189006">"काहीही नाही"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-ms/strings.xml b/FrameworkPackageStubs/res/values-ms/strings.xml
index f2c27e1..0b54585 100644
--- a/FrameworkPackageStubs/res/values-ms/strings.xml
+++ b/FrameworkPackageStubs/res/values-ms/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Tiada apl dapat kendalikan tindakan ini"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Gambar dalam Gambar tidak disokong pada peranti ini"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Urus Sumber Apl Tidak Diketahui tidak disokong pada peranti ini"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Pengurusan Fail tidak disokong pada peranti ini"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Penyelamat skrin tidak disokong pada peranti ini"</string>
<string name="stub_name" msgid="3987164490218189006">"Tiada"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-my/strings.xml b/FrameworkPackageStubs/res/values-my/strings.xml
index 60837ca..1882ccf 100644
--- a/FrameworkPackageStubs/res/values-my/strings.xml
+++ b/FrameworkPackageStubs/res/values-my/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"ဤအလုပ်ကို မည်သည့်အက်ပ်မျှ မစီမံနိုင်ပါ"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"ဤစက်တွင် \'နှစ်ခုထပ်၍ကြည့်ခြင်း\' သုံး၍မရပါ"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"‘မူရင်းမသိရှိသော အက်ပ်ရင်းမြစ်များ စီမံခြင်း’ ကို ဤစက်တွင် ပံ့ပိုးမထားပါ"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"‘ဖိုင်စီမံခန့်ခွဲမှု’ ကို ဤစက်တွင် ပံ့ပိုးမထားပါ"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"စခရင်နားချိန်ပုံများကို ဤစက်ပစ္စည်းတွင် ပံ့ပိုးမထားပါ"</string>
<string name="stub_name" msgid="3987164490218189006">"မရှိ"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-nb/strings.xml b/FrameworkPackageStubs/res/values-nb/strings.xml
index 9291575..6c6745e 100644
--- a/FrameworkPackageStubs/res/values-nb/strings.xml
+++ b/FrameworkPackageStubs/res/values-nb/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Ingen apper kan gjøre dette"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Bilde-i-bilde støttes ikke på denne enheten"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Administrering av ukjente appkilder støttes ikke på denne enheten"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Filbehandling støttes ikke på denne enheten"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Skjermsparere støttes ikke på denne enheten"</string>
<string name="stub_name" msgid="3987164490218189006">"Ingen"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-ne/strings.xml b/FrameworkPackageStubs/res/values-ne/strings.xml
index b5ab5f3..565f5a0 100644
--- a/FrameworkPackageStubs/res/values-ne/strings.xml
+++ b/FrameworkPackageStubs/res/values-ne/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"कुनै पनि एपले यो कारबाही गर्न सक्दैन"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"यो डिभाइसमा Picture in Picture सुविधा प्रयोग गर्न मिल्दैन"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"यो डिभाइसमा अज्ञात एपका स्रोतहरू व्यवस्थापन गर्ने सुविधा प्रयोग गर्न मिल्दैन"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"यो डिभाइसमा फाइल व्यवस्थापन गर्ने सुविधा प्रयोग गर्न मिल्दैन"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"यो डिभाइसमा स्क्रिनसेभरहरू प्रयोग गर्न मिल्दैनन्"</string>
<string name="stub_name" msgid="3987164490218189006">"कुनै पनि होइन"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-nl/strings.xml b/FrameworkPackageStubs/res/values-nl/strings.xml
index 1c6f3c3..10082eb 100644
--- a/FrameworkPackageStubs/res/values-nl/strings.xml
+++ b/FrameworkPackageStubs/res/values-nl/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Geen enkele app kan deze actie verwerken"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Scherm-in-scherm wordt niet ondersteund op dit apparaat"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Onbekende app-bronnen beheren wordt niet ondersteund op dit apparaat"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Bestandsbeheer wordt niet ondersteund op dit apparaat"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Screensavers worden niet ondersteund op dit apparaat"</string>
<string name="stub_name" msgid="3987164490218189006">"Geen"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-or/strings.xml b/FrameworkPackageStubs/res/values-or/strings.xml
index e8cdc50..c54ce8d 100644
--- a/FrameworkPackageStubs/res/values-or/strings.xml
+++ b/FrameworkPackageStubs/res/values-or/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"କୌଣସି ଆପ୍ ଏହାକୁ ନିୟନ୍ତ୍ରଣ କରିପାରିବେ ନାହିଁ।"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"ଏହି ଡିଭାଇସରେ ପିକଚର୍ ଇନ୍ ପିକଚର୍ ସମର୍ଥିତ ନୁହେଁ"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"ଏହି ଡିଭାଇସରେ ଅଜଣା ଆପ ସୋର୍ସ ପରିଚାଳନା କରିବା ସମର୍ଥିତ ନୁହେଁ"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"ଏହି ଡିଭାଇସରେ ଫାଇଲ ମେନେଜମେଣ୍ଟ ସମର୍ଥିତ ନୁହେଁ"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"ଏହି ଡିଭାଇସରେ ସ୍କ୍ରିନସେଭରଗୁଡ଼ିକ ସମର୍ଥିତ ନୁହେଁ"</string>
<string name="stub_name" msgid="3987164490218189006">"କିଛିନାହିଁ"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-pa/strings.xml b/FrameworkPackageStubs/res/values-pa/strings.xml
index adb681d..0c02f93 100644
--- a/FrameworkPackageStubs/res/values-pa/strings.xml
+++ b/FrameworkPackageStubs/res/values-pa/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"ਕਾਰਵਾਈ ਨੂੰ ਐਪਲੀਕੇਸ਼ਨ ਹੈਂਡਲ ਨਹੀਂ ਕਰ ਸਕਦੀ"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਤਸਵੀਰ-ਵਿੱਚ-ਤਸਵੀਰ ਦੀ ਸੁਵਿਧਾ ਨਹੀਂ ਹੈ"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਅਗਿਆਤ ਐਪ ਸਰੋਤਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਫ਼ਾਈਲ ਪ੍ਰਬੰਧਨ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"ਸਕ੍ਰੀਨ-ਸੇਵਰ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਮਰਥਿਤ ਨਹੀਂ ਹਨ"</string>
<string name="stub_name" msgid="3987164490218189006">"ਕੋਈ ਨਹੀਂ"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-pl/strings.xml b/FrameworkPackageStubs/res/values-pl/strings.xml
index 49bf0e5..30e901e 100644
--- a/FrameworkPackageStubs/res/values-pl/strings.xml
+++ b/FrameworkPackageStubs/res/values-pl/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Aplikacje nie mogą obsłużyć działania"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Obraz w obrazie nie jest obsługiwany na tym urządzeniu"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"To urządzenie nie obsługuje Manage Unknown App Sources"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"To urządzenie nie obsługuje File Management"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Wygaszacze ekranu nie są obsługiwane na tym urządzeniu"</string>
<string name="stub_name" msgid="3987164490218189006">"Brak"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-pt-rPT/strings.xml b/FrameworkPackageStubs/res/values-pt-rPT/strings.xml
index 5444619..ec24f35 100644
--- a/FrameworkPackageStubs/res/values-pt-rPT/strings.xml
+++ b/FrameworkPackageStubs/res/values-pt-rPT/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Nenhuma aplicação pode efetuar esta ação."</string>
<string name="pip_not_supported" msgid="8681268258599412706">"A funcionalidade ecrã no ecrã não é suportada neste dispositivo."</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"A gestão de origens de apps desconhecidas não é suportada neste dispositivo"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"A gestão de ficheiros não é suportada neste dispositivo"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"As proteções de ecrã não são suportadas neste dispositivo"</string>
<string name="stub_name" msgid="3987164490218189006">"Nenhum"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-pt/strings.xml b/FrameworkPackageStubs/res/values-pt/strings.xml
index 2875484..e18a6dc 100644
--- a/FrameworkPackageStubs/res/values-pt/strings.xml
+++ b/FrameworkPackageStubs/res/values-pt/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Nenhum aplicativo pode executar a ação"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"O recurso picture-in-picture não é compatível com este dispositivo."</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"A opção \"Gerenciar fontes de apps desconhecidas\" não é compatível com este dispositivo"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"A opção \"Gerenciamento de arquivos\" não é compatível com este dispositivo"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Os protetores de tela não funcionam neste dispositivo"</string>
<string name="stub_name" msgid="3987164490218189006">"Nenhum"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-ro/strings.xml b/FrameworkPackageStubs/res/values-ro/strings.xml
index e94244a..8a8f781 100644
--- a/FrameworkPackageStubs/res/values-ro/strings.xml
+++ b/FrameworkPackageStubs/res/values-ro/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Aplicațiile nu pot gestiona acțiunea"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Funcția picture-in-picture nu este acceptată pe acest dispozitiv"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Administrarea surselor de aplicații necunoscute nu este acceptată pe acest dispozitiv"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Administrarea fișierelor nu este acceptată pe acest dispozitiv"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Screensaverele nu sunt acceptate pe acest dispozitiv"</string>
<string name="stub_name" msgid="3987164490218189006">"Fără"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-ru/strings.xml b/FrameworkPackageStubs/res/values-ru/strings.xml
index ea524bd..c8bcb38 100644
--- a/FrameworkPackageStubs/res/values-ru/strings.xml
+++ b/FrameworkPackageStubs/res/values-ru/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Нет приложений для этого действия"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Функция \"Картинка в картинке\" не поддерживается на этом устройстве."</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Управление неизвестными источниками приложений не поддерживается на этом устройстве."</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Управление файлами не поддерживается на этом устройстве."</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"На этом устройстве не поддерживаются заставки."</string>
<string name="stub_name" msgid="3987164490218189006">"Нет"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-si/strings.xml b/FrameworkPackageStubs/res/values-si/strings.xml
index 8c00330..7d77b2d 100644
--- a/FrameworkPackageStubs/res/values-si/strings.xml
+++ b/FrameworkPackageStubs/res/values-si/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"කිසි යෙදුමකට මෙම ක්රියාව හැසිරවිය නොහැක"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"මෙම උපාංගයෙහි පින්තූරය තුළ පින්තූරය සහාය නොදක්වයි"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"නොදන්නා යෙදුම් මූලාශ්ර කළමනාකරණය කිරීම මෙම උපාංගයෙහි සහාය නොදක්වයි"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"ගොනු කළමනාකරණය මෙම උපාංගයෙහි සහාය නොදක්වයි"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"මෙම උපාංගයෙහි තිර සුරැකුම් සඳහා සහය නොදක්වයි"</string>
<string name="stub_name" msgid="3987164490218189006">"කිසිවක් නැත"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-sk/strings.xml b/FrameworkPackageStubs/res/values-sk/strings.xml
index a9000fb..07269bc 100644
--- a/FrameworkPackageStubs/res/values-sk/strings.xml
+++ b/FrameworkPackageStubs/res/values-sk/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Túto akciu nedokáže spracovať žiadna aplikácia"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Obraz v obraze nie je v tomto zariadení podporovaný"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Správa neznámych zdrojov aplikácií v tomto zariadení nie je podporovaná"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Správa súborov v tomto zariadení nie je podporovaná"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Šetriče obrazovky nie sú v tomto zariadení podporované"</string>
<string name="stub_name" msgid="3987164490218189006">"Žiadne"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-sl/strings.xml b/FrameworkPackageStubs/res/values-sl/strings.xml
index da2f7ee..b615c6b 100644
--- a/FrameworkPackageStubs/res/values-sl/strings.xml
+++ b/FrameworkPackageStubs/res/values-sl/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Ni aplikacij za obravnavo tega dejanja"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Način slike v sliki v tej napravi ni podprt."</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Upravljanje neznanih virov aplikacij ni podprto v tej napravi"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Upravljanje datotek ni podprto v tej napravi"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Ohranjevalniki zaslona niso podprti v tej napravi"</string>
<string name="stub_name" msgid="3987164490218189006">"Brez"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-sq/strings.xml b/FrameworkPackageStubs/res/values-sq/strings.xml
index e4d84ec..36493b5 100644
--- a/FrameworkPackageStubs/res/values-sq/strings.xml
+++ b/FrameworkPackageStubs/res/values-sq/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Asnjë aplikacion s\'e menaxhon këtë veprim"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"\"Figura brenda figurës\" nuk mbështetet në këtë pajisje."</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"\"Menaxhimi i burimeve të panjohura të aplikacioneve\" nuk mbështetet në këtë pajisje"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"\"Menaxhimi i skedarëve\" nuk mbështetet në këtë pajisje"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Mbrojtësit e ekranit nuk mbështeten në këtë pajisje"</string>
<string name="stub_name" msgid="3987164490218189006">"Asnjë"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-sr/strings.xml b/FrameworkPackageStubs/res/values-sr/strings.xml
index 91d3af6..0264ee0 100644
--- a/FrameworkPackageStubs/res/values-sr/strings.xml
+++ b/FrameworkPackageStubs/res/values-sr/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Ниједна апл. не може да обради ову радњу"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Слика у слици није подржана на овом уређају"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Управљање непознатим изворима апликација није подржано на овом уређају"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Управљање фајловима није подржано на овом уређају"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Чувари екрана нису подржани на овом уређају"</string>
<string name="stub_name" msgid="3987164490218189006">"Нема"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-sv/strings.xml b/FrameworkPackageStubs/res/values-sv/strings.xml
index 79b2e5c..685802f 100644
--- a/FrameworkPackageStubs/res/values-sv/strings.xml
+++ b/FrameworkPackageStubs/res/values-sv/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Ingen app kan hantera den här åtgärden"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Bild-i-bild stöds inte på enheten"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Det finns inte stöd för att hantera appar med okända källor på den här enheten"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Det finns inte stöd för filhantering på den här enheten"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Skärmsläckare stöds inte på enheten"</string>
<string name="stub_name" msgid="3987164490218189006">"Inga"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-sw/strings.xml b/FrameworkPackageStubs/res/values-sw/strings.xml
index 156246e..5e1be39 100644
--- a/FrameworkPackageStubs/res/values-sw/strings.xml
+++ b/FrameworkPackageStubs/res/values-sw/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Hamna programu ya kufanya kitendo hiki"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Mipangilio ya Kupachika Picha Ndani ya Picha Nyingine haitumiki kwenye kifaa hiki"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Programu ya Kudhibiti Vyanzo vya Programu Visivyojulikana haiwezi kutumika kwenye kifaa hiki"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Programu ya Udhibiti wa Faili haiwezi kutumika kwenye kifaa hiki"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Taswira za skrini hazitumiki kwenye kifaa hiki"</string>
<string name="stub_name" msgid="3987164490218189006">"Hamna"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-ta/strings.xml b/FrameworkPackageStubs/res/values-ta/strings.xml
index 03204f4..c92a3e9 100644
--- a/FrameworkPackageStubs/res/values-ta/strings.xml
+++ b/FrameworkPackageStubs/res/values-ta/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"இந்தச் செயல்பாட்டை செய்யும் ஆப்ஸ் இல்லை"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"இந்தச் சாதனத்தில் \'பிக்ச்சர்-இன்-பிக்ச்சர்\' அம்சம் ஆதரிக்கப்படவில்லை"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"இந்தச் சாதனத்தில் அறியப்படாத ஆப்ஸ் ஆதாரங்களை நிர்வகித்தல் ஆதரிக்கப்படாது"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"இந்தச் சாதனத்தில் ஃபைல் நிர்வாகம் ஆதரிக்கப்படாது"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"இந்தச் சாதனத்தில் ஸ்கிரீன் சேவர்கள் ஆதரிக்கப்படவில்லை"</string>
<string name="stub_name" msgid="3987164490218189006">"எதுவுமில்லை"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-te/strings.xml b/FrameworkPackageStubs/res/values-te/strings.xml
index 7439976..639732a 100644
--- a/FrameworkPackageStubs/res/values-te/strings.xml
+++ b/FrameworkPackageStubs/res/values-te/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"ఈ చర్యను ఏ అప్లికేషన్ నిర్వహించలేదు"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"ఈ పరికరంలో పిక్చర్-ఇన్-పిక్చర్ ఫీచర్కు సపోర్ట్ లేదు"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"ఈ పరికరం తెలియని యాప్ సోర్స్ల మేనేజ్మెంట్కు సపోర్ట్ ఇవ్వదు"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"ఈ పరికరంలో ఫైల్ మేనేజ్మెంట్ సపోర్ట్ చేయదు"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"ఈ పరికరంలో స్క్రీన్ సేవర్లకు సపోర్ట్ లేదు"</string>
<string name="stub_name" msgid="3987164490218189006">"ఏదీ లేదు"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-th/strings.xml b/FrameworkPackageStubs/res/values-th/strings.xml
index 81d21da..6ea15bf 100644
--- a/FrameworkPackageStubs/res/values-th/strings.xml
+++ b/FrameworkPackageStubs/res/values-th/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"ไม่มีแอปพลิเคชันใดทำงานนี้ได้"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"อุปกรณ์นี้ไม่รองรับการแสดงภาพซ้อนภาพ"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"อุปกรณ์นี้ไม่รองรับฟังก์ชันจัดการแหล่งที่มาแอปที่ไม่รู้จัก"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"อุปกรณ์นี้ไม่รองรับการจัดการไฟล์"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"อุปกรณ์นี้ไม่รองรับการพักหน้าจอ"</string>
<string name="stub_name" msgid="3987164490218189006">"ไม่มี"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-tl/strings.xml b/FrameworkPackageStubs/res/values-tl/strings.xml
index b11d701..ee2c230 100644
--- a/FrameworkPackageStubs/res/values-tl/strings.xml
+++ b/FrameworkPackageStubs/res/values-tl/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Walang app ang makakapangasiwa sa action"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Hindi sinusuportahan sa device na ito ang Picture in Picture"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Hindi sinusuportahan sa device na ito ang Pamahalaan ang Mga Hindi Kilalang Source ng App"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Hindi sinusuportahan sa device na ito ang Pamamahala ng File"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Hindi sinusuportahan ang mga screensaver sa device na ito"</string>
<string name="stub_name" msgid="3987164490218189006">"Wala"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-tr/strings.xml b/FrameworkPackageStubs/res/values-tr/strings.xml
index 8b3d0e7..6be37f7 100644
--- a/FrameworkPackageStubs/res/values-tr/strings.xml
+++ b/FrameworkPackageStubs/res/values-tr/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"İşlemi gerçekleştirebilecek uygulama yok"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Pencere içinde pencere işlevi bu cihazda desteklenmiyor"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Bilinmeyen Uygulama Kaynaklarını Yönetme Özelliği bu cihazda desteklenmiyor"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Dosya Yöneticisi bu cihazda desteklenmiyor"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Ekran koruyucular bu cihazda desteklenmiyor"</string>
<string name="stub_name" msgid="3987164490218189006">"Yok"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-uk/strings.xml b/FrameworkPackageStubs/res/values-uk/strings.xml
index 2f89124..d0366e5 100644
--- a/FrameworkPackageStubs/res/values-uk/strings.xml
+++ b/FrameworkPackageStubs/res/values-uk/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Немає додатка, який може виконати цю дію"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Функція \"Картинка в картинці\" не підтримується на цьому пристрої"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Керування невідомими додатками-джерелами не підтримується на цьому пристрої"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Керування файлами не підтримується на цьому пристрої"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"На цьому пристрої не підтримуються заставки"</string>
<string name="stub_name" msgid="3987164490218189006">"Немає"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-ur/strings.xml b/FrameworkPackageStubs/res/values-ur/strings.xml
index bf5e8df..ef99195 100644
--- a/FrameworkPackageStubs/res/values-ur/strings.xml
+++ b/FrameworkPackageStubs/res/values-ur/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"یہ عمل کوئی ایپلیکیشن ہینڈل نہیں کر سکتی"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"تصویر میں تصویر اس آلے پر تعاون یافتہ نہیں ہے"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"\'نامعلوم ایپ کے ذرائع کا نظم کریں\' اس آلے پر تعاون یافتہ نہیں ہے"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"فائل مینجمنٹ اس آلے پر تعاون یافتہ نہیں ہے"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"اسکرین سیورز اس آلہ پر تعاون یافتہ نہیں ہیں"</string>
<string name="stub_name" msgid="3987164490218189006">"کوئی نہیں"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-uz/strings.xml b/FrameworkPackageStubs/res/values-uz/strings.xml
index 3691fd2..883438d 100644
--- a/FrameworkPackageStubs/res/values-uz/strings.xml
+++ b/FrameworkPackageStubs/res/values-uz/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Bu amalni bajaradigan ilova mavjud emas"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Tasvir ustida tasvir bu qurilmada ishlamaydi"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Bu qurilmada Notanish ilova manbalarini boshqarish ishlamaydi"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Bu qurilmada fayl boshqaruvi ishlamaydi"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Bu qurilmada ekran lavhasi ishlamaydi"</string>
<string name="stub_name" msgid="3987164490218189006">"Hech qanday"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-vi/strings.xml b/FrameworkPackageStubs/res/values-vi/strings.xml
index 1260811..cd0020f 100644
--- a/FrameworkPackageStubs/res/values-vi/strings.xml
+++ b/FrameworkPackageStubs/res/values-vi/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Không ứng dụng nào xử lý được thao tác này"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Thiết bị này không hỗ trợ tính năng Hình trong hình"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Tính năng Quản lý nguồn dữ liệu ứng dụng không xác định không được hỗ trợ trên thiết bị này"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Tính năng Quản lý tệp không được hỗ trợ trên thiết bị này"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Thiết bị này không hỗ trợ trình bảo vệ màn hình"</string>
<string name="stub_name" msgid="3987164490218189006">"Không"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-zh-rCN/strings.xml b/FrameworkPackageStubs/res/values-zh-rCN/strings.xml
index 7675548..3d836ab 100644
--- a/FrameworkPackageStubs/res/values-zh-rCN/strings.xml
+++ b/FrameworkPackageStubs/res/values-zh-rCN/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"没有可处理此操作的应用"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"此设备不支持画中画功能"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"此设备不支持“管理未知应用来源”"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"此设备不支持“文件管理”"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"此设备不支持屏保"</string>
<string name="stub_name" msgid="3987164490218189006">"无"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-zh-rHK/strings.xml b/FrameworkPackageStubs/res/values-zh-rHK/strings.xml
index e904a0c..e73e9d7 100644
--- a/FrameworkPackageStubs/res/values-zh-rHK/strings.xml
+++ b/FrameworkPackageStubs/res/values-zh-rHK/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"沒有可處理此操作的應用程式"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"此裝置不支援「畫中畫」功能"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"此裝置不支援「管理不明的應用程式來源」"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"此裝置不支援「檔案管理」"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"此裝置不支援螢幕保護程式"</string>
<string name="stub_name" msgid="3987164490218189006">"無"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-zh-rTW/strings.xml b/FrameworkPackageStubs/res/values-zh-rTW/strings.xml
index d127169..d982833 100644
--- a/FrameworkPackageStubs/res/values-zh-rTW/strings.xml
+++ b/FrameworkPackageStubs/res/values-zh-rTW/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"沒有應用程式能夠處理這項操作"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"這部裝置不支援子母畫面功能"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"這部裝置不支援不明應用程式來源管理功能"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"這部裝置不支援檔案管理功能"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"這部裝置不支援螢幕保護程式"</string>
<string name="stub_name" msgid="3987164490218189006">"無"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values-zu/strings.xml b/FrameworkPackageStubs/res/values-zu/strings.xml
index a4ddf3e..16a5f3b 100644
--- a/FrameworkPackageStubs/res/values-zu/strings.xml
+++ b/FrameworkPackageStubs/res/values-zu/strings.xml
@@ -5,6 +5,6 @@
<string name="message_not_supported" msgid="133939962837892495">"Alukho uhlelo lokusebenza olungaphatha lesi senzo"</string>
<string name="pip_not_supported" msgid="8681268258599412706">"Isithombe-esithombeni asisekelwa kule divayisi"</string>
<string name="manage_unknown_app_sources_not_supported" msgid="7368611099531260439">"Ukuphatha Imithombo ye-App Engaziwa akusekelwa kule divayisi"</string>
- <string name="documentsui_not_supported" msgid="2786636475647817501">"Ukuphathwa Kwefayela akusekelwe kule divayisi"</string>
+ <string name="dream_not_supported" msgid="2274365800432553936">"Izigcini zesikrini azisekelwa kule divayisi"</string>
<string name="stub_name" msgid="3987164490218189006">"Lutho"</string>
</resources>
diff --git a/FrameworkPackageStubs/res/values/strings.xml b/FrameworkPackageStubs/res/values/strings.xml
index af92311..35e9ced 100644
--- a/FrameworkPackageStubs/res/values/strings.xml
+++ b/FrameworkPackageStubs/res/values/strings.xml
@@ -13,8 +13,8 @@
<!-- Toast message displayed when MANAGE_UNKNOWN_APP_SOURCES isn't supported. [CHAR LIMIT=NONE] -->
<string name="manage_unknown_app_sources_not_supported">Manage Unknown App Sources is not supported on this device</string>
- <!-- Toast message displayed when documentsui isn't supported. [CHAR LIMIT=NONE] -->
- <string name="documentsui_not_supported">File Management is not supported on this device</string>
+ <!-- Toast message displayed when android.settings.DREAM_SETTINGS isn't supported. [CHAR LIMIT=NONE] -->
+ <string name="dream_not_supported">Screensavers are not supported on this device</string>
<!-- Stub name [CHAR LIMIT=NONE]-->
<string name="stub_name">None</string>
diff --git a/FrameworkPackageStubs/src/com/android/car/frameworkpackagestubs/Stubs.java b/FrameworkPackageStubs/src/com/android/car/frameworkpackagestubs/Stubs.java
index 470579d..c380e64 100644
--- a/FrameworkPackageStubs/src/com/android/car/frameworkpackagestubs/Stubs.java
+++ b/FrameworkPackageStubs/src/com/android/car/frameworkpackagestubs/Stubs.java
@@ -156,19 +156,11 @@
}
}
- /** Stub activity for DocumentsUI intents. */
- public static class DocumentsUIStub extends BaseActivity {
+ /** Stub activity for Dream Settings. */
+ public static class DreamSettingsStub extends BaseActivity {
@Override
protected CharSequence getMessage() {
- return getResources().getString(R.string.documentsui_not_supported);
- }
- }
-
- /** Stub activity for DocumentsUI intents expecting the result. */
- public static class DocumentsUIStubWithResult extends DocumentsUIStub {
- @Override
- protected void setResultImp() {
- setResult(RESULT_CANCELED);
+ return getResources().getString(R.string.dream_not_supported);
}
}
}
diff --git a/OWNERS b/OWNERS
index c52ef49..25dc602 100644
--- a/OWNERS
+++ b/OWNERS
@@ -2,8 +2,7 @@
# Owned by Android Automotive OS team.
ericjeong@google.com
lesliewatkins@google.com
-ralphthomas@google.com
sgurun@google.com
+stenning@google.com
xiangw@google.com
-ycheo@google.com
-goodsons@google.com #{LAST_RESORT_SUGGESTION}
+ralphthomas@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 19b04e0..f8b1644 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -23,6 +23,9 @@
"auto-presubmit": [
{
"name": "hal_implementation_test"
+ },
+ {
+ "name": "CarLibDeviceUnitTest"
}
],
"ravenwood-presubmit": [
diff --git a/aconfig/Android.bp b/aconfig/Android.bp
index d474721..0a655f9 100644
--- a/aconfig/Android.bp
+++ b/aconfig/Android.bp
@@ -39,6 +39,7 @@
aconfig_declarations {
name: "android.car.feature-aconfig",
package: "android.car.feature",
+ container: "system",
srcs: ["flags.aconfig"],
}
diff --git a/aconfig/flags.aconfig b/aconfig/flags.aconfig
index 2180092..09df2f0 100644
--- a/aconfig/flags.aconfig
+++ b/aconfig/flags.aconfig
@@ -1,8 +1,10 @@
package: "android.car.feature"
+container: "system"
# CarUserManager
flag {
name: "switch_user_ignoring_uxr"
+ is_exported: true
namespace: "car_framework"
description: "Allow user switching ignoring the Ux Restrictions."
bug: "320545306"
@@ -11,14 +13,16 @@
# CarPackageManager
flag {
name: "display_compatibility"
+ is_exported: true
namespace: "car_framework"
- description: "This flag controls if apps can query packages that need car display compatibility treatment"
+ description: "This flag controls the display compatibility system feature."
bug: "291638685"
}
# CarProjectionManager
flag {
name: "projection_query_bt_profile_inhibit"
+ is_exported: true
namespace: "car_framework"
description: "This flag controls if apps can query the inhibit state or bluetooth profiles."
bug: "281791498"
@@ -27,6 +31,7 @@
# CarRemoteAccessManager
flag {
name: "serverless_remote_access"
+ is_exported: true
namespace: "car_framework"
description: "Enables serverless remote access"
bug: "302339402"
@@ -40,9 +45,35 @@
bug: "286303350"
}
+flag {
+ name: "per_display_max_brightness"
+ namespace: "car_framework"
+ description: "Use per display max brightness defined by VHAL"
+ bug: "336831738"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "car_power_cancel_shell_command"
+ is_exported: true
+ namespace: "car_framework"
+ description: "Controls whether --cancel-after flag can be used in suspend command"
+ bug: "337063842"
+}
+
+flag {
+ name: "stop_process_before_suspend_to_disk"
+ namespace: "car_framework"
+ description: "Controls whether processes need to be stopped before suspend to disk"
+ bug: "324593655"
+}
+
# CarAudioManager
flag {
name: "car_audio_dynamic_devices"
+ is_exported: true
namespace: "car_framework"
description: "Controls whether car audio system can be configured with dynamic output devices"
bug: "305301155"
@@ -50,6 +81,7 @@
flag {
name: "car_audio_min_max_activation_volume"
+ is_exported: true
namespace: "car_framework"
description: "Controls whether car audio can be configured with min/max activation volume"
bug: "310667172"
@@ -57,6 +89,7 @@
flag {
name: "car_audio_mute_ambiguity"
+ is_exported: true
namespace: "car_framework"
description: "Controls whether mute ambiguity API works on car audio system"
bug: "321063661"
@@ -64,6 +97,7 @@
flag {
name: "car_audio_fade_manager_configuration"
+ is_exported: true
namespace: "car_framework"
description: "Controls whether car audio can be configured with fade manager configurations"
bug: "315695829"
@@ -72,6 +106,7 @@
# CarPropertyManager
flag {
name: "batched_subscriptions"
+ is_exported: true
namespace: "car_framework"
description: "Controls whether to enable batched subscriptions for multiple [propId, areaId]s"
bug: "298257574"
@@ -79,6 +114,7 @@
flag {
name: "variable_update_rate"
+ is_exported: true
namespace: "car_framework"
description: "Controls whether to enable variable update rate for subscription"
bug: "300700719"
@@ -86,6 +122,7 @@
flag {
name: "area_id_config_access"
+ is_exported: true
namespace: "car_framework"
description: "Controls whether apps can use the new access level APIs added to AreaIdConfig"
bug: "290801790"
@@ -93,6 +130,7 @@
flag {
name: "subscription_with_resolution"
+ is_exported: true
namespace: "car_framework"
description: "Controls whether to round incoming property events to the resolution requested"
bug: "276124296"
@@ -106,9 +144,17 @@
bug: "302596493"
}
+flag {
+ name: "create_car_use_notifications"
+ namespace: "car_framework"
+ description: "Optimized Car.createCar implementation using ServiceManagerHelper.registerForNotifications and binder death recipient"
+ bug: "343489611"
+}
+
# ClusterHomeManager
flag {
name: "cluster_health_monitoring"
+ is_exported: true
namespace: "car_framework"
description: "Controls whether to enable the health monitoring for Cluster"
bug: "285415531"
@@ -117,6 +163,7 @@
# VehiclePropertyIds
flag {
name: "android_vic_vehicle_properties"
+ is_exported: true
namespace: "car_framework"
description: "Enables use of vehicle properties introduced in Android V"
bug: "309529020"
@@ -125,6 +172,7 @@
# CarNightService
flag {
name: "car_night_global_setting"
+ is_exported: true
namespace: "car_framework"
description: "Controls whether to use global settings to control the day/night mode configuration"
bug: "260762437"
@@ -133,6 +181,7 @@
# CarWifiService
flag {
name: "persist_ap_settings"
+ is_exported: true
namespace: "car_framework"
description: "Controls whether car persist ap settings can be configured"
bug: "301660611"
@@ -149,6 +198,7 @@
# CarEvsService
flag {
name: "car_evs_stream_management"
+ is_exported: true
namespace: "car_framework"
description: "Allows clients to stop stream individually and identify the origin of delivered framebuffers from CarEvsBufferDescriptor"
bug: "293810167"
@@ -156,6 +206,7 @@
flag {
name: "car_evs_query_service_status"
+ is_exported: true
namespace: "car_framework"
description: "Retrieves current status of each CarEvsService type individually"
bug: "320766863"
@@ -164,6 +215,7 @@
# CarPropertyErrorCodes
flag {
name: "car_property_detailed_error_codes"
+ is_exported: true
namespace: "car_framework"
description: "Enables use of detailed error codes for CarPropertyManager async APIs"
bug: "274165552"
@@ -172,7 +224,17 @@
# CarPropertyValue
flag {
name: "car_property_value_property_status"
+ is_exported: true
namespace: "car_framework"
description: "Enables use of new property status API"
bug: "326109669"
}
+
+# CarAppCard
+flag {
+ name: "car_app_card"
+ is_exported: true
+ namespace: "car_framework"
+ description: "Enables use of Car App Card APIs"
+ bug: "331282711"
+}
diff --git a/apex_car_framework/Android.bp b/apex_car_framework/Android.bp
index 30dfd71..6e621ed 100644
--- a/apex_car_framework/Android.bp
+++ b/apex_car_framework/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/car-builtin-lib/Android.bp b/car-builtin-lib/Android.bp
index 1f72df5..4057936 100644
--- a/car-builtin-lib/Android.bp
+++ b/car-builtin-lib/Android.bp
@@ -13,12 +13,13 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
-// This library is internal only and should not be used outside core car stack.
-java_sdk_library {
- name: "android.car.builtin",
+java_defaults {
+ name: "android.car.builtin-defaults",
+
srcs: [
"src/**/*.java",
"src/**/*.aidl",
@@ -49,6 +50,13 @@
"framework-bluetooth",
"framework-annotations-lib",
],
+}
+
+// This library is internal only and should not be used outside core car stack.
+java_sdk_library {
+ name: "android.car.builtin",
+
+ defaults: ["android.car.builtin-defaults"],
test: {
enabled: false,
@@ -67,11 +75,11 @@
"//external/robolectric",
"//packages/services/Car/car-lib",
"//packages/services/Car/car-lib-module",
- "//packages/services/Car/car-test-lib",
- "//packages/services/Car/procfs-inspector/client",
+ "//packages/services/Car/libs/car-test-lib",
+ "//packages/services/Car/libs/procfs-inspector/client",
"//packages/services/Car/service-builtin",
"//packages/services/Car/service",
- "//packages/services/Car/tests/CarLibHostUnitTest",
+ "//packages/services/Car/tests/CarLibUnitTest:__subpackages__",
"//packages/services/Car/tests/carservice_test",
"//packages/services/Car/tests/carservice_unit_test",
"//packages/services/Car/cpp/watchdog/car-watchdog-lib",
@@ -94,3 +102,14 @@
installable: true,
}
+
+java_library {
+ name: "android.car.builtin.testonly",
+
+ defaults: ["android.car.builtin-defaults"],
+
+ visibility: [
+ "//packages/services/Car/service",
+ "//packages/services/Car/tests/CarLibUnitTest",
+ ],
+}
diff --git a/car-builtin-lib/api/module-lib-current.txt b/car-builtin-lib/api/module-lib-current.txt
index 3cb4541..94c36e4 100644
--- a/car-builtin-lib/api/module-lib-current.txt
+++ b/car-builtin-lib/api/module-lib-current.txt
@@ -31,6 +31,8 @@
method public static int checkComponentPermission(@NonNull String, int, int, boolean);
method @NonNull public static android.app.ActivityOptions createActivityOptions(@NonNull android.os.Bundle);
method public static android.os.IBinder getActivityToken(android.app.Activity);
+ method public static int getFlagsForRunningAppProcessInfo(@NonNull android.app.ActivityManager.RunningAppProcessInfo);
+ method public static java.util.List<android.app.ActivityManager.RunningAppProcessInfo> getRunningAppProcesses();
method public static java.util.List<android.app.ActivityManager.RunningTaskInfo> getTasks(int, boolean, boolean, int);
method public static boolean isVisible(android.app.Activity);
method public static void killAllBackgroundProcesses();
@@ -38,7 +40,6 @@
method @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW) public static void moveRootTaskToDisplay(int, int);
method public static void registerProcessObserverCallback(android.car.builtin.app.ActivityManagerHelper.ProcessObserverCallback);
method public static boolean removeTask(int);
- method public static void setFocusedRootTask(int);
method public static void setFocusedTask(int);
method public static boolean startUserInBackground(int);
method public static boolean startUserInBackgroundVisibleOnDisplay(int, int);
@@ -49,6 +50,7 @@
method public static boolean unlockUser(int);
method public static void unregisterProcessObserverCallback(android.car.builtin.app.ActivityManagerHelper.ProcessObserverCallback);
field public static final int INVALID_TASK_ID = -1; // 0xffffffff
+ field public static final int PROCESS_INFO_PERSISTENT_FLAG = 2; // 0x2
}
public abstract static class ActivityManagerHelper.ProcessObserverCallback {
@@ -157,6 +159,7 @@
public final class DisplayManagerHelper {
method public static float getBrightness(android.content.Context, int);
+ method @Nullable public static String getUniqueId(@NonNull android.view.Display);
method public static void registerDisplayListener(android.content.Context, android.hardware.display.DisplayManager.DisplayListener, android.os.Handler, long);
method public static void setBrightness(android.content.Context, int, @FloatRange(from=0.0f, to=1.0f) float);
field public static final long EVENT_FLAG_DISPLAY_ADDED = 1L; // 0x1L
@@ -170,8 +173,10 @@
package android.car.builtin.input {
public class InputManagerHelper {
+ method public static void addUniqueIdAssociationByDescriptor(@NonNull android.hardware.input.InputManager, @NonNull String, @NonNull String);
method public static boolean injectInputEvent(@NonNull android.hardware.input.InputManager, @NonNull android.view.InputEvent);
method public static void pilferPointers(@NonNull android.hardware.input.InputManager, @NonNull android.view.View);
+ method public static void removeUniqueIdAssociationByDescriptor(@NonNull android.hardware.input.InputManager, @NonNull String);
}
}
@@ -297,9 +302,14 @@
method @Nullable public static android.os.IBinder checkService(@NonNull String);
method @Nullable public static String[] getDeclaredInstances(@NonNull String);
method @Nullable public static android.os.IBinder getService(@NonNull String);
+ method public static void registerForNotifications(@NonNull String, @NonNull android.car.builtin.os.ServiceManagerHelper.IServiceRegistrationCallback) throws android.os.RemoteException;
method @Nullable public static android.os.IBinder waitForDeclaredService(@NonNull String);
}
+ public static interface ServiceManagerHelper.IServiceRegistrationCallback {
+ method public void onRegistration(@NonNull String, android.os.IBinder);
+ }
+
public final class SharedMemoryHelper {
method @NonNull public static android.os.ParcelFileDescriptor createParcelFileDescriptor(@NonNull android.os.SharedMemory) throws java.io.IOException;
}
@@ -309,6 +319,7 @@
}
public final class TraceHelper {
+ field public static final long TRACE_TAG_CAR_EVS_SERVICE = 1024L; // 0x400L
field public static final long TRACE_TAG_CAR_SERVICE = 524288L; // 0x80000L
}
@@ -342,6 +353,7 @@
field public static final int FLAG_QUIET_MODE = 128; // 0x80
field public static final int FLAG_RESTRICTED = 8; // 0x8
field public static final int FLAG_SYSTEM = 2048; // 0x800
+ field public static final int USER_ALL = -1; // 0xffffffff
field public static final int USER_NULL = -10000; // 0xffffd8f0
field public static final int USER_SYSTEM = 0; // 0x0
}
@@ -544,13 +556,14 @@
public class TimingsTraceLog {
ctor public TimingsTraceLog(@NonNull String, long);
+ ctor public TimingsTraceLog(@NonNull String, long, int);
method public void logDuration(@NonNull String, long);
method public void traceBegin(@NonNull String);
method public void traceEnd();
}
public final class UsageStatsManagerHelper {
- method public static void reportUserInteraction(@NonNull android.app.usage.UsageStatsManager, @NonNull String, int);
+ method public static void reportUserInteraction(@NonNull android.app.usage.UsageStatsManager, @NonNull String, int, @NonNull android.os.PersistableBundle);
}
public final class ValidationHelper {
diff --git a/car-builtin-lib/src/android/car/builtin/app/ActivityManagerHelper.java b/car-builtin-lib/src/android/car/builtin/app/ActivityManagerHelper.java
index dafafc1..3cfea4f 100644
--- a/car-builtin-lib/src/android/car/builtin/app/ActivityManagerHelper.java
+++ b/car-builtin-lib/src/android/car/builtin/app/ActivityManagerHelper.java
@@ -46,6 +46,10 @@
/** Invalid task ID. */
public static final int INVALID_TASK_ID = ActivityTaskManager.INVALID_TASK_ID;
+ /** Persistent process flag */
+ public static final int PROCESS_INFO_PERSISTENT_FLAG =
+ ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT;
+
private static final String TAG = "CAR.AM"; // CarLog.TAG_AM
// Lazy initialization holder class idiom for static fields; See go/ej3e-83 for the detail.
@@ -102,8 +106,10 @@
* @throws IllegalStateException if ActivityManager binder throws RemoteException
*/
public static int stopUser(@UserIdInt int userId, boolean force) {
+ // Note that the value of force is irrelevant. Even historically, it never had any effect
+ // in this case, since it only even applied to profiles (which Car didn't support).
return runRemotely(
- () -> getActivityManager().stopUser(userId, force, /* callback= */ null),
+ () -> getActivityManager().stopUserWithCallback(userId, /* callback= */ null),
"error while stopUser userId:%d force:%b", userId, force);
}
@@ -113,9 +119,11 @@
* @throws IllegalStateException if ActivityManager binder throws RemoteException
*/
public static int stopUserWithDelayedLocking(@UserIdInt int userId, boolean force) {
+ // Note that the value of force is irrelevant. Even historically, it never had any effect
+ // in this case, since it only even applied to profiles (which Car didn't support).
return runRemotely(
() -> getActivityManager().stopUserWithDelayedLocking(
- userId, force, /* callback= */ null),
+ userId, /* callback= */ null),
"error while stopUserWithDelayedLocking userId:%d force:%b", userId, force);
}
@@ -176,17 +184,6 @@
}
/**
- * Makes the root task of the given taskId focused.
- */
- public static void setFocusedRootTask(int taskId) {
- try {
- getActivityManager().setFocusedRootTask(taskId);
- } catch (RemoteException e) {
- Slogf.e(TAG, "Failed to setFocusedRootTask", e);
- }
- }
-
- /**
* Makes the task of the given taskId focused.
*/
public static void setFocusedTask(int taskId) {
@@ -210,6 +207,31 @@
}
/**
+ * Gets the flag values for the given {@link ActivityManager.RunningAppProcessInfo}
+ *
+ * @param appProcessInfo The {@link ActivityManager.RunningAppProcessInfo}
+ * @return The flags for the appProcessInfo
+ */
+ public static int getFlagsForRunningAppProcessInfo(
+ @NonNull ActivityManager.RunningAppProcessInfo appProcessInfo) {
+ return appProcessInfo.flags;
+ }
+
+ /**
+ * Gets all the running app process
+ *
+ * @return List of all the RunningAppProcessInfo
+ */
+ public static List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() {
+ try {
+ return getActivityManager().getRunningAppProcesses();
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "Failed to removeTask", e);
+ }
+ return List.of();
+ }
+
+ /**
* Callback to monitor Processes in the system
*/
public abstract static class ProcessObserverCallback {
diff --git a/car-builtin-lib/src/android/car/builtin/display/DisplayManagerHelper.java b/car-builtin-lib/src/android/car/builtin/display/DisplayManagerHelper.java
index cbe8a96..a1b5025 100644
--- a/car-builtin-lib/src/android/car/builtin/display/DisplayManagerHelper.java
+++ b/car-builtin-lib/src/android/car/builtin/display/DisplayManagerHelper.java
@@ -17,12 +17,15 @@
package android.car.builtin.display;
import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.hardware.display.DisplayManager.EventsMask;
import android.os.Handler;
+import android.view.Display;
/**
* Helper for DisplayManager related operations.
@@ -112,4 +115,12 @@
DisplayManager displayManager = context.getSystemService(DisplayManager.class);
displayManager.setBrightness(displayId, brightness);
}
+
+ /**
+ * See {@link Display#getUniqueId()}.
+ */
+ @Nullable
+ public static String getUniqueId(@NonNull Display display) {
+ return display.getUniqueId();
+ }
}
diff --git a/car-builtin-lib/src/android/car/builtin/input/InputManagerHelper.java b/car-builtin-lib/src/android/car/builtin/input/InputManagerHelper.java
index 22a270a..a564d87 100644
--- a/car-builtin-lib/src/android/car/builtin/input/InputManagerHelper.java
+++ b/car-builtin-lib/src/android/car/builtin/input/InputManagerHelper.java
@@ -52,4 +52,25 @@
public static void pilferPointers(@NonNull InputManager inputManager, @NonNull View v) {
inputManager.pilferPointers(v.getViewRootImpl().getInputToken());
}
+
+ /**
+ * See {@link InputManager#addUniqueIdAssociationByDescriptor(String, String)}.
+ */
+ public static void addUniqueIdAssociationByDescriptor(@NonNull InputManager inputManager,
+ @NonNull String inputDeviceDescriptor,
+ @NonNull String displayUniqueId) {
+ // TODO(b/341949977): Improve addUniqueIdAssociationByDescriptor to handle incorrect
+ // input
+ inputManager.addUniqueIdAssociationByDescriptor(inputDeviceDescriptor, displayUniqueId);
+ }
+
+ /**
+ * See {@link InputManager#removeUniqueIdAssociationByDescriptor(String)}.
+ */
+ public static void removeUniqueIdAssociationByDescriptor(@NonNull InputManager inputManager,
+ @NonNull String inputDeviceDescriptor) {
+ // TODO(b/341949977): Improve removeUniqueIdAssociationByDescriptor to handle incorrect
+ // input
+ inputManager.removeUniqueIdAssociationByDescriptor(inputDeviceDescriptor);
+ }
}
diff --git a/car-builtin-lib/src/android/car/builtin/os/ServiceManagerHelper.java b/car-builtin-lib/src/android/car/builtin/os/ServiceManagerHelper.java
index b3a1e99..abac3be 100644
--- a/car-builtin-lib/src/android/car/builtin/os/ServiceManagerHelper.java
+++ b/car-builtin-lib/src/android/car/builtin/os/ServiceManagerHelper.java
@@ -20,6 +20,8 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.IBinder;
+import android.os.IServiceCallback;
+import android.os.RemoteException;
import android.os.ServiceManager;
/**
@@ -62,4 +64,41 @@
public static String[] getDeclaredInstances(@NonNull String iface) {
return ServiceManager.getDeclaredInstances(iface);
}
+
+ /**
+ * The callback interface for {@link registerForNotifications}.
+ */
+ public interface IServiceRegistrationCallback {
+ /**
+ * Called when a service is registered.
+ *
+ * @param name the service name that has been registered with
+ * @param binder the binder that is registered
+ */
+ void onRegistration(@NonNull String name, IBinder binder);
+ }
+
+ private static final class ServiceCallbackImpl extends IServiceCallback.Stub {
+ private final IServiceRegistrationCallback mClientCallback;
+
+ ServiceCallbackImpl(IServiceRegistrationCallback clientCallback) {
+ mClientCallback = clientCallback;
+ }
+
+ @Override
+ public void onRegistration(String name, IBinder binder) {
+ mClientCallback.onRegistration(name, binder);
+ }
+ }
+
+ /**
+ * Register callback for service registration notifications.
+ *
+ * @throws RemoteException for underlying error.
+ */
+ public static void registerForNotifications(
+ @NonNull String name, @NonNull IServiceRegistrationCallback callback)
+ throws RemoteException {
+ ServiceManager.registerForNotifications(name, new ServiceCallbackImpl(callback));
+ }
}
diff --git a/car-builtin-lib/src/android/car/builtin/os/TraceHelper.java b/car-builtin-lib/src/android/car/builtin/os/TraceHelper.java
index 35cc4c9..301a902 100644
--- a/car-builtin-lib/src/android/car/builtin/os/TraceHelper.java
+++ b/car-builtin-lib/src/android/car/builtin/os/TraceHelper.java
@@ -36,4 +36,11 @@
* trace tools to access these traces.
*/
public static final long TRACE_TAG_CAR_SERVICE = Trace.TRACE_TAG_SYSTEM_SERVER;
+
+ /**
+ * {@code Trace tag} that should be used by car evs service. This is same with
+ * {@link Trace#TRACE_TAG_CAMERA}, so {@code Camera} tracing should be enabled from
+ * trace tools to access these traces.
+ */
+ public static final long TRACE_TAG_CAR_EVS_SERVICE = Trace.TRACE_TAG_CAMERA;
}
diff --git a/car-builtin-lib/src/android/car/builtin/os/UserManagerHelper.java b/car-builtin-lib/src/android/car/builtin/os/UserManagerHelper.java
index e80820b..f72b090 100644
--- a/car-builtin-lib/src/android/car/builtin/os/UserManagerHelper.java
+++ b/car-builtin-lib/src/android/car/builtin/os/UserManagerHelper.java
@@ -45,6 +45,9 @@
/** A user id constant to indicate the "system" user of the device */
public static final @UserIdInt int USER_SYSTEM = UserHandle.USER_SYSTEM;
+ /** A user id constant to indicate "all" users of the device */
+ public static final @UserIdInt int USER_ALL = UserHandle.USER_ALL;
+
// Flags copied from UserInfo.
public static final int FLAG_PRIMARY = UserInfo.FLAG_PRIMARY;
public static final int FLAG_ADMIN = UserInfo.FLAG_ADMIN;
diff --git a/car-builtin-lib/src/android/car/builtin/util/TimingsTraceLog.java b/car-builtin-lib/src/android/car/builtin/util/TimingsTraceLog.java
index d159667..e19354e 100644
--- a/car-builtin-lib/src/android/car/builtin/util/TimingsTraceLog.java
+++ b/car-builtin-lib/src/android/car/builtin/util/TimingsTraceLog.java
@@ -26,10 +26,34 @@
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public class TimingsTraceLog {
- private final android.util.TimingsTraceLog mTimingsTraceLog;
+ private static final class TimingsTraceLogInternal extends android.util.TimingsTraceLog {
+ private final int mMinDurationMs;
+
+ /**
+ * Same as {@link TimingsTraceLog} except last argument {@code minDurationMs} which
+ * specifies the minimum duration to log the duration.
+ */
+ TimingsTraceLogInternal(String tag, long traceTag, int minDurationMs) {
+ super(tag, traceTag);
+ mMinDurationMs = minDurationMs;
+ }
+
+ @Override
+ public void logDuration(String name, long timeMs) {
+ if (timeMs >= mMinDurationMs) {
+ super.logDuration(name, timeMs);
+ }
+ }
+ }
+
+ private final TimingsTraceLogInternal mTimingsTraceLog;
public TimingsTraceLog(@NonNull String tag, long traceTag) {
- mTimingsTraceLog = new android.util.TimingsTraceLog(tag, traceTag);
+ mTimingsTraceLog = new TimingsTraceLogInternal(tag, traceTag, /* minDurationMs= */ 0);
+ }
+
+ public TimingsTraceLog(@NonNull String tag, long traceTag, int minDurationMs) {
+ mTimingsTraceLog = new TimingsTraceLogInternal(tag, traceTag, minDurationMs);
}
/** Check {@code android.util.Slog}. */
diff --git a/car-builtin-lib/src/android/car/builtin/util/UsageStatsManagerHelper.java b/car-builtin-lib/src/android/car/builtin/util/UsageStatsManagerHelper.java
index ebe3a8f..5d02f84 100644
--- a/car-builtin-lib/src/android/car/builtin/util/UsageStatsManagerHelper.java
+++ b/car-builtin-lib/src/android/car/builtin/util/UsageStatsManagerHelper.java
@@ -19,7 +19,9 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
+import android.app.usage.Flags;
import android.app.usage.UsageStatsManager;
+import android.os.PersistableBundle;
import android.util.Log;
import java.util.Objects;
@@ -40,23 +42,44 @@
}
/**
- * Reports user interaction with a given package in the given user.
- * Check {@link UsageStatsManager#reportUserInteraction(String, int)}
+ * Reports user interaction with a given package in the given user with {@code extras}.
+ * Check {@link UsageStatsManager#reportUserInteraction(String, int, PersistableBundle)}.
+ * If {@code Flags.userInteractionTypeApi()} is {@code false}, fall back to
+ * {@link UsageStatsManager#reportUserInteraction(String, int)}.
+ *
+ * <p>
+ * Note: The structure of {@code extras} is a {@link PersistableBundle} with the
+ * category {@link UsageStatsManager#EXTRA_EVENT_CATEGORY} and the action
+ * {@link UsageStatsManager#EXTRA_EVENT_ACTION}.
+ * Category provides additional detail about the user interaction, the value
+ * is defined in namespace based. Example: android.app.notification could be used to
+ * indicate that the reported user interaction is related to notification. Action
+ * indicates the general action that performed.
+ * </p>
*
* @param usageStatsManager {@link UsageStatsManager} instance used to report the user
* interaction
- * @param packageName package name reported for the user interaction
- * @param userId user id of the process
+ * @param packageName The package name of the app
+ * @param userId The user id who triggers the user interaction
+ * @param extras The {@link PersistableBundle} that will be used to specify the
+ * extra details for the user interaction event. The {@link PersistableBundle}
+ * must contain the extras {@link UsageStatsManager#EXTRA_EVENT_CATEGORY},
+ * {@link UsageStatsManager#EXTRA_EVENT_ACTION}. Cannot be empty.
*/
public static void reportUserInteraction(@NonNull UsageStatsManager usageStatsManager,
- @NonNull String packageName, @UserIdInt int userId) {
+ @NonNull String packageName, @UserIdInt int userId, @NonNull PersistableBundle extras) {
if (DEBUG) {
Log.d(TAG, "usageStatsManager#reportUserInteraction package=" + packageName
- + " userId=" + userId);
+ + " userId=" + userId + " extras=" + extras);
}
Objects.requireNonNull(usageStatsManager);
Objects.requireNonNull(packageName);
- usageStatsManager.reportUserInteraction(packageName, userId);
+ if (Flags.userInteractionTypeApi()) {
+ Objects.requireNonNull(extras);
+ usageStatsManager.reportUserInteraction(packageName, userId, extras);
+ } else {
+ usageStatsManager.reportUserInteraction(packageName, userId);
+ }
}
}
diff --git a/car-builtin-lib/src/android/car/builtin/view/TouchableInsetsProvider.java b/car-builtin-lib/src/android/car/builtin/view/TouchableInsetsProvider.java
index fcbc2ec..a11b22b 100644
--- a/car-builtin-lib/src/android/car/builtin/view/TouchableInsetsProvider.java
+++ b/car-builtin-lib/src/android/car/builtin/view/TouchableInsetsProvider.java
@@ -20,7 +20,6 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UiThread;
-import android.car.builtin.util.Slogf;
import android.graphics.Rect;
import android.graphics.Region;
import android.view.View;
@@ -62,7 +61,6 @@
private void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
if (!mView.isVisibleToUser()) {
- Slogf.d(TAG, "Skip onComputeInternalInsets since the view is invisible");
return;
}
if (inoutInfo.touchableRegion.isEmpty()) {
diff --git a/car-lib-module/Android.bp b/car-lib-module/Android.bp
index 465ac27..b2d5e61 100644
--- a/car-lib-module/Android.bp
+++ b/car-lib-module/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -98,4 +99,7 @@
compile_dex: true,
installable: true,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/car-lib-module/api/current.txt b/car-lib-module/api/current.txt
index 89fe0ed..d96c5f1 100644
--- a/car-lib-module/api/current.txt
+++ b/car-lib-module/api/current.txt
@@ -19,8 +19,8 @@
method @Deprecated public int getCarConnectionType();
method @Nullable public Object getCarManager(String);
method @Nullable public <T> T getCarManager(@NonNull Class<T>);
- method @NonNull public static android.car.CarVersion getCarVersion();
- method @NonNull public static android.car.PlatformVersion getPlatformVersion();
+ method @Deprecated @NonNull public static android.car.CarVersion getCarVersion();
+ method @Deprecated @NonNull public static android.car.PlatformVersion getPlatformVersion();
method @Deprecated public static boolean isApiAndPlatformVersionAtLeast(int, int);
method @Deprecated public static boolean isApiAndPlatformVersionAtLeast(int, int, int);
method @Deprecated public static boolean isApiVersionAtLeast(int);
@@ -317,14 +317,14 @@
ctor @Deprecated public VehiclePropertyIds();
method public static String toString(int);
field @RequiresPermission("android.car.permission.CAR_DYNAMICS_STATE") public static final int ABS_ACTIVE = 287310858; // 0x1120040a
- field @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_BOOTUP_REASON = 289409538; // 0x11400a02
- field @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_STATE_REPORT = 289475073; // 0x11410a01
- field @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_STATE_REQ = 289475072; // 0x11410a00
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_BOOTUP_REASON = 289409538; // 0x11400a02
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_STATE_REPORT = 289475073; // 0x11410a01
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_STATE_REQ = 289475072; // 0x11410a00
field @RequiresPermission(android.car.Car.PERMISSION_READ_INTERIOR_LIGHTS) public static final int CABIN_LIGHTS_STATE = 289410817; // 0x11400f01
field @RequiresPermission(android.car.Car.PERMISSION_CONTROL_INTERIOR_LIGHTS) public static final int CABIN_LIGHTS_SWITCH = 289410818; // 0x11400f02
field @RequiresPermission("android.car.permission.CAR_TIRES") public static final int CRITICALLY_LOW_TIRE_PRESSURE = 392168202; // 0x1760030a
field @RequiresPermission(android.car.Car.PERMISSION_POWERTRAIN) public static final int CURRENT_GEAR = 289408001; // 0x11400401
- field @RequiresPermission("android.car.permission.CAR_POWER") public static final int DISPLAY_BRIGHTNESS = 289409539; // 0x11400a03
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_POWER") public static final int DISPLAY_BRIGHTNESS = 289409539; // 0x11400a03
field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_READ_DISPLAY_UNITS)) @RequiresPermission.Write(@androidx.annotation.RequiresPermission(allOf={android.car.Car.PERMISSION_CONTROL_DISPLAY_UNITS, "android.car.permission.CAR_VENDOR_EXTENSION"})) public static final int DISTANCE_DISPLAY_UNITS = 289408512; // 0x11400600
field @RequiresPermission("android.car.permission.CONTROL_CAR_DOORS") public static final int DOOR_LOCK = 371198722; // 0x16200b02
field @RequiresPermission("android.car.permission.CONTROL_CAR_DOORS") public static final int DOOR_MOVE = 373295873; // 0x16400b01
@@ -390,7 +390,7 @@
field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(anyOf={android.car.Car.PERMISSION_READ_DISPLAY_UNITS, "android.car.permission.CONTROL_CAR_CLIMATE"})) @RequiresPermission.Write(@androidx.annotation.RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE")) public static final int HVAC_TEMPERATURE_DISPLAY_UNITS = 289408270; // 0x1140050e
field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_TEMPERATURE_SET = 358614275; // 0x15600503
field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_TEMPERATURE_VALUE_SUGGESTION = 291570965; // 0x11610515
- field public static final int HW_KEY_INPUT = 289475088; // 0x11410a10
+ field @Deprecated public static final int HW_KEY_INPUT = 289475088; // 0x11410a10
field @RequiresPermission(android.car.Car.PERMISSION_POWERTRAIN) public static final int IGNITION_STATE = 289408009; // 0x11400409
field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_DRIVER_SEAT = 356516106; // 0x1540010a
field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_EV_BATTERY_CAPACITY = 291504390; // 0x11600106
@@ -414,10 +414,10 @@
field @RequiresPermission("android.car.permission.CONTROL_CAR_MIRRORS") public static final int MIRROR_Z_MOVE = 339741505; // 0x14400b41
field @RequiresPermission("android.car.permission.CONTROL_CAR_MIRRORS") public static final int MIRROR_Z_POS = 339741504; // 0x14400b40
field @RequiresPermission(android.car.Car.PERMISSION_EXTERIOR_ENVIRONMENT) public static final int NIGHT_MODE = 287310855; // 0x11200407
- field @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME = 299896065; // 0x11e00d01
- field @RequiresPermission("android.car.permission.CLEAR_CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME_CLEAR = 299896067; // 0x11e00d03
- field @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME_INFO = 299896066; // 0x11e00d02
- field @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_LIVE_FRAME = 299896064; // 0x11e00d00
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME = 299896065; // 0x11e00d01
+ field @Deprecated @RequiresPermission("android.car.permission.CLEAR_CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME_CLEAR = 299896067; // 0x11e00d03
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME_INFO = 299896066; // 0x11e00d02
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_LIVE_FRAME = 299896064; // 0x11e00d00
field @RequiresPermission(android.car.Car.PERMISSION_POWERTRAIN) public static final int PARKING_BRAKE_AUTO_APPLY = 287310851; // 0x11200403
field @RequiresPermission(android.car.Car.PERMISSION_POWERTRAIN) public static final int PARKING_BRAKE_ON = 287310850; // 0x11200402
field @RequiresPermission("android.car.permission.CAR_MILEAGE") public static final int PERF_ODOMETER = 291504644; // 0x11600204
@@ -425,7 +425,7 @@
field @RequiresPermission(android.car.Car.PERMISSION_READ_STEERING_STATE) public static final int PERF_STEERING_ANGLE = 291504649; // 0x11600209
field @RequiresPermission(android.car.Car.PERMISSION_SPEED) public static final int PERF_VEHICLE_SPEED = 291504647; // 0x11600207
field @RequiresPermission(android.car.Car.PERMISSION_SPEED) public static final int PERF_VEHICLE_SPEED_DISPLAY = 291504648; // 0x11600208
- field @RequiresPermission("android.car.permission.CAR_POWER") public static final int PER_DISPLAY_BRIGHTNESS = 289475076; // 0x11410a04
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_POWER") public static final int PER_DISPLAY_BRIGHTNESS = 289475076; // 0x11410a04
field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(anyOf={android.car.Car.PERMISSION_ENERGY, "android.car.permission.ADJUST_RANGE_REMAINING"})) @RequiresPermission.Write(@androidx.annotation.RequiresPermission("android.car.permission.ADJUST_RANGE_REMAINING")) public static final int RANGE_REMAINING = 291504904; // 0x11600308
field @RequiresPermission(android.car.Car.PERMISSION_READ_INTERIOR_LIGHTS) public static final int READING_LIGHTS_STATE = 356519683; // 0x15400f03
field @RequiresPermission(android.car.Car.PERMISSION_CONTROL_INTERIOR_LIGHTS) public static final int READING_LIGHTS_SWITCH = 356519684; // 0x15400f04
@@ -470,7 +470,7 @@
field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_PRIVILEGED_CAR_INFO)) public static final int TRAILER_PRESENT = 289410885; // 0x11400f45
field @RequiresPermission("android.car.permission.CAR_EXTERIOR_LIGHTS") public static final int TURN_SIGNAL_STATE = 289408008; // 0x11400408
field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_PRIVILEGED_CAR_INFO)) public static final int VEHICLE_CURB_WEIGHT = 289410886; // 0x11400f46
- field @RequiresPermission(anyOf={"android.car.permission.VMS_PUBLISHER", "android.car.permission.VMS_SUBSCRIBER"}) public static final int VEHICLE_MAP_SERVICE = 299895808; // 0x11e00c00
+ field @Deprecated @RequiresPermission(anyOf={"android.car.permission.VMS_PUBLISHER", "android.car.permission.VMS_SUBSCRIBER"}) public static final int VEHICLE_MAP_SERVICE = 299895808; // 0x11e00c00
field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_READ_DISPLAY_UNITS)) @RequiresPermission.Write(@androidx.annotation.RequiresPermission(allOf={android.car.Car.PERMISSION_CONTROL_DISPLAY_UNITS, "android.car.permission.CAR_VENDOR_EXTENSION"})) public static final int VEHICLE_SPEED_DISPLAY_UNITS = 289408516; // 0x11400604
field @RequiresPermission(android.car.Car.PERMISSION_SPEED) public static final int WHEEL_TICK = 290521862; // 0x11510306
field @RequiresPermission("android.car.permission.CONTROL_CAR_WINDOWS") public static final int WINDOW_LOCK = 320867268; // 0x13200bc4
@@ -506,11 +506,11 @@
package android.car.content.pm {
public final class CarPackageManager {
- method @NonNull public android.car.CarVersion getTargetCarVersion();
+ method @Deprecated @NonNull public android.car.CarVersion getTargetCarVersion();
method public boolean isActivityDistractionOptimized(String, String);
method public boolean isPendingIntentDistractionOptimized(@NonNull android.app.PendingIntent);
method public boolean isServiceDistractionOptimized(String, String);
- field public static final String MANIFEST_METADATA_TARGET_CAR_VERSION = "android.car.targetCarVersion";
+ field @Deprecated public static final String MANIFEST_METADATA_TARGET_CAR_VERSION = "android.car.targetCarVersion";
}
}
@@ -778,7 +778,7 @@
method @NonNull public java.util.List<android.car.hardware.CarPropertyConfig> getPropertyList();
method @NonNull public java.util.List<android.car.hardware.CarPropertyConfig> getPropertyList(@NonNull android.util.ArraySet<java.lang.Integer>);
method public boolean isPropertyAvailable(int, int);
- method public boolean registerCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback, int, @FloatRange(from=0.0, to=100.0) float);
+ method @Deprecated public boolean registerCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback, int, @FloatRange(from=0.0, to=100.0) float);
method public void setBooleanProperty(int, int, boolean);
method public void setFloatProperty(int, int, float);
method public void setIntProperty(int, int, int);
@@ -790,8 +790,8 @@
method @FlaggedApi("android.car.feature.variable_update_rate") public boolean subscribePropertyEvents(int, int, @NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
method @FlaggedApi("android.car.feature.variable_update_rate") public boolean subscribePropertyEvents(int, int, @FloatRange(from=0.0, to=100.0) float, @NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
method @FlaggedApi("android.car.feature.batched_subscriptions") public boolean subscribePropertyEvents(@NonNull java.util.List<android.car.hardware.property.Subscription>, @Nullable java.util.concurrent.Executor, @NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
- method public void unregisterCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
- method public void unregisterCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback, int);
+ method @Deprecated public void unregisterCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
+ method @Deprecated public void unregisterCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback, int);
method @FlaggedApi("android.car.feature.batched_subscriptions") public void unsubscribePropertyEvents(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
method @FlaggedApi("android.car.feature.batched_subscriptions") public void unsubscribePropertyEvents(int, @NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
field public static final long ASYNC_GET_DEFAULT_TIMEOUT_MS = 10000L; // 0x2710L
diff --git a/car-lib-module/api/system-current.txt b/car-lib-module/api/system-current.txt
index 53c16e6..cdd2d5a 100644
--- a/car-lib-module/api/system-current.txt
+++ b/car-lib-module/api/system-current.txt
@@ -45,6 +45,7 @@
field public static final String OCCUPANT_AWARENESS_SERVICE = "occupant_awareness";
field public static final String PERMISSION_ACCESS_MIRRORRED_SURFACE = "android.car.permission.ACCESS_MIRRORED_SURFACE";
field public static final String PERMISSION_ADJUST_RANGE_REMAINING = "android.car.permission.ADJUST_RANGE_REMAINING";
+ field @FlaggedApi("android.car.feature.car_app_card") public static final String PERMISSION_BIND_APP_CARD_PROVIDER = "android.car.permission.BIND_APP_CARD_PROVIDER";
field public static final String PERMISSION_CAR_DIAGNOSTIC_CLEAR = "android.car.permission.CLEAR_CAR_DIAGNOSTICS";
field public static final String PERMISSION_CAR_DIAGNOSTIC_READ_ALL = "android.car.permission.CAR_DIAGNOSTICS";
field public static final String PERMISSION_CAR_DRIVING_STATE = "android.car.permission.CAR_DRIVING_STATE";
@@ -89,6 +90,7 @@
field public static final String PERMISSION_CONTROL_WINDSHIELD_WIPERS = "android.car.permission.CONTROL_WINDSHIELD_WIPERS";
field public static final String PERMISSION_EXTERIOR_LIGHTS = "android.car.permission.CAR_EXTERIOR_LIGHTS";
field public static final String PERMISSION_MANAGE_CAR_SYSTEM_UI = "android.car.permission.MANAGE_CAR_SYSTEM_UI";
+ field @FlaggedApi("android.car.feature.display_compatibility") public static final String PERMISSION_MANAGE_DISPLAY_COMPATIBILITY = "android.car.permission.MANAGE_DISPLAY_COMPATIBILITY";
field public static final String PERMISSION_MANAGE_OCCUPANT_CONNECTION = "android.car.permission.MANAGE_OCCUPANT_CONNECTION";
field public static final String PERMISSION_MANAGE_OCCUPANT_ZONE = "android.car.permission.MANAGE_OCCUPANT_ZONE";
field public static final String PERMISSION_MANAGE_REMOTE_DEVICE = "android.car.permission.MANAGE_REMOTE_DEVICE";
@@ -97,7 +99,6 @@
field public static final String PERMISSION_MIRROR_DISPLAY = "android.car.permission.MIRROR_DISPLAY";
field @Deprecated public static final String PERMISSION_MOCK_VEHICLE_HAL = "android.car.permission.CAR_MOCK_VEHICLE_HAL";
field public static final String PERMISSION_MONITOR_CAR_EVS_STATUS = "android.car.permission.MONITOR_CAR_EVS_STATUS";
- field @FlaggedApi("android.car.feature.display_compatibility") public static final String PERMISSION_QUERY_DISPLAY_COMPATIBILITY = "android.car.permission.QUERY_DISPLAY_COMPATIBILITY";
field public static final String PERMISSION_READ_ADAS_SETTINGS = "android.car.permission.READ_ADAS_SETTINGS";
field public static final String PERMISSION_READ_ADAS_STATES = "android.car.permission.READ_ADAS_STATES";
field @FlaggedApi("android.car.feature.android_vic_vehicle_properties") public static final String PERMISSION_READ_CAR_AIRBAGS = "android.car.permission.READ_CAR_AIRBAGS";
@@ -439,23 +440,19 @@
}
@FlaggedApi("android.car.feature.display_compatibility") public final class CarDisplayCompatContainer {
- method @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public void onBackPressed();
- method @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public void setDensity(int);
- method @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public void setVisibility(int);
- method @NonNull @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public android.graphics.Rect setWindowBounds(@NonNull android.graphics.Rect);
- method @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public void startActivity(@NonNull android.content.Intent, @Nullable android.os.Bundle);
+ method @RequiresPermission(android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY) @UiThread public void notifyBackPressed();
+ method @RequiresPermission(android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY) @UiThread public void setVisibility(int);
+ method @NonNull @RequiresPermission(android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY) @UiThread public android.graphics.Rect setWindowBounds(@NonNull android.graphics.Rect);
+ method @RequiresPermission(allOf={android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY, android.car.Car.PERMISSION_MANAGE_CAR_SYSTEM_UI, android.Manifest.permission.INTERACT_ACROSS_USERS}) @UiThread public void startActivity(@NonNull android.content.Intent, @Nullable android.os.Bundle);
}
- public static final class CarDisplayCompatContainer.Builder {
+ @FlaggedApi("android.car.feature.display_compatibility") public static final class CarDisplayCompatContainer.Builder {
ctor public CarDisplayCompatContainer.Builder(@NonNull android.app.Activity);
- method @NonNull public android.car.app.CarDisplayCompatContainer.Builder setDensity(int);
- method @NonNull public android.car.app.CarDisplayCompatContainer.Builder setHeight(int);
method @NonNull public android.car.app.CarDisplayCompatContainer.Builder setSurfaceViewCallback(@Nullable java.util.function.Consumer<android.view.SurfaceView>);
- method @NonNull public android.car.app.CarDisplayCompatContainer.Builder setWidth(int);
}
@FlaggedApi("android.car.feature.display_compatibility") public final class CarDisplayCompatManager {
- method @Nullable @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public android.car.app.CarDisplayCompatContainer initializeDisplayCompatContainer(@NonNull android.car.app.CarDisplayCompatContainer.Builder);
+ method @Nullable @RequiresPermission(android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY) public android.car.app.CarDisplayCompatContainer initializeDisplayCompatContainer(@NonNull android.car.app.CarDisplayCompatContainer.Builder);
}
public interface CarSystemUIProxy {
@@ -646,9 +643,9 @@
}
public final class CarPackageManager {
- method @NonNull @RequiresPermission(android.Manifest.permission.QUERY_ALL_PACKAGES) public android.car.CarVersion getTargetCarVersion(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.QUERY_ALL_PACKAGES) public android.car.CarVersion getTargetCarVersion(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isActivityBackedBySafeActivity(android.content.ComponentName);
- method @FlaggedApi("android.car.feature.display_compatibility") @RequiresPermission(allOf={android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY, android.Manifest.permission.QUERY_ALL_PACKAGES}) public boolean requiresDisplayCompat(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @FlaggedApi("android.car.feature.display_compatibility") @RequiresPermission(allOf={android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY, android.Manifest.permission.QUERY_ALL_PACKAGES}) public boolean requiresDisplayCompat(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public void setAppBlockingPolicy(String, android.car.content.pm.CarAppBlockingPolicy, int);
field @Deprecated public static final int FLAG_SET_POLICY_ADD = 2; // 0x2
field @Deprecated public static final int FLAG_SET_POLICY_REMOVE = 4; // 0x4
@@ -965,7 +962,7 @@
method public void onCarDisconnected();
method @RequiresPermission(android.car.Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) public boolean saveUxRestrictionsConfigurationForNextBoot(@NonNull java.util.List<android.car.drivingstate.CarUxRestrictionsConfiguration>);
method @RequiresPermission(android.car.Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) public boolean saveUxRestrictionsConfigurationForNextBoot(@NonNull android.car.drivingstate.CarUxRestrictionsConfiguration);
- method public void setListener(int, @NonNull android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener);
+ method @Deprecated public void setListener(int, @NonNull android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener);
method @RequiresPermission(android.car.Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) public boolean setRestrictionMode(@NonNull String);
field public static final String UX_RESTRICTION_MODE_BASELINE = "baseline";
}
@@ -1033,7 +1030,8 @@
public static interface CarEvsManager.CarEvsStreamCallback {
method public default void onNewFrame(@NonNull android.car.evs.CarEvsBufferDescriptor);
- method public default void onStreamEvent(int);
+ method @Deprecated public default void onStreamEvent(int);
+ method @FlaggedApi("android.car.feature.car_evs_stream_management") public default void onStreamEvent(int, int);
}
public final class CarEvsStatus implements android.os.Parcelable {
@@ -1061,21 +1059,18 @@
}
public final class CarPropertyConfig<T> implements android.os.Parcelable {
- method public static <T> android.car.hardware.CarPropertyConfig.Builder<T> newBuilder(Class<T>, int, int, int);
+ method @Deprecated public static <T> android.car.hardware.CarPropertyConfig.Builder<T> newBuilder(Class<T>, int, int, int);
}
- public static class CarPropertyConfig.Builder<T> {
- method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addArea(int);
- method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addAreaConfig(int, T, T);
- method @NonNull public android.car.hardware.CarPropertyConfig.Builder<T> addAreaIdConfig(@NonNull android.car.hardware.property.AreaIdConfig<T>);
- method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addAreas(int[]);
- method public android.car.hardware.CarPropertyConfig<T> build();
- method public android.car.hardware.CarPropertyConfig.Builder<T> setAccess(int);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setChangeMode(int);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setConfigArray(java.util.ArrayList<java.lang.Integer>);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setConfigString(String);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setMaxSampleRate(float);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setMinSampleRate(float);
+ @Deprecated public static class CarPropertyConfig.Builder<T> {
+ method @Deprecated @NonNull public android.car.hardware.CarPropertyConfig.Builder<T> addAreaIdConfig(@NonNull android.car.hardware.property.AreaIdConfig<T>);
+ method @Deprecated public android.car.hardware.CarPropertyConfig<T> build();
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setAccess(int);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setChangeMode(int);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setConfigArray(java.util.ArrayList<java.lang.Integer>);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setConfigString(String);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setMaxSampleRate(float);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setMinSampleRate(float);
}
@Deprecated public final class CarVendorExtensionManager {
@@ -1245,13 +1240,13 @@
package android.car.hardware.property {
- public static final class AreaIdConfig.Builder<T> {
- ctor public AreaIdConfig.Builder(int);
- ctor @FlaggedApi("android.car.feature.area_id_config_access") public AreaIdConfig.Builder(int, int);
- method @NonNull public android.car.hardware.property.AreaIdConfig<T> build();
- method @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setMaxValue(T);
- method @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setMinValue(T);
- method @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setSupportedEnumValues(@NonNull java.util.List<T>);
+ @Deprecated public static final class AreaIdConfig.Builder<T> {
+ ctor @Deprecated public AreaIdConfig.Builder(int);
+ ctor @Deprecated @FlaggedApi("android.car.feature.area_id_config_access") public AreaIdConfig.Builder(int, int);
+ method @Deprecated @NonNull public android.car.hardware.property.AreaIdConfig<T> build();
+ method @Deprecated @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setMaxValue(T);
+ method @Deprecated @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setMinValue(T);
+ method @Deprecated @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setSupportedEnumValues(@NonNull java.util.List<T>);
}
public final class AutomaticEmergencyBrakingState {
diff --git a/car-lib-module/api/system-removed.txt b/car-lib-module/api/system-removed.txt
index bca7216..762ae83 100644
--- a/car-lib-module/api/system-removed.txt
+++ b/car-lib-module/api/system-removed.txt
@@ -7,6 +7,16 @@
}
+package android.car.hardware {
+
+ @Deprecated public static class CarPropertyConfig.Builder<T> {
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addArea(int);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addAreaConfig(int, T, T);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addAreas(int[]);
+ }
+
+}
+
package android.car.input {
@Deprecated public abstract class CarInputHandlingService extends android.app.Service {
diff --git a/car-lib-module/api/test-current.txt b/car-lib-module/api/test-current.txt
index 2d6b46a..205f9d5 100644
--- a/car-lib-module/api/test-current.txt
+++ b/car-lib-module/api/test-current.txt
@@ -128,6 +128,8 @@
public final class CarRemoteAccessManager {
method @FlaggedApi("android.car.feature.serverless_remote_access") @RequiresPermission(android.car.Car.PERMISSION_CONTROL_REMOTE_ACCESS) public void addServerlessRemoteTaskClient(@NonNull String, @NonNull String);
+ method @FlaggedApi("android.car.feature.serverless_remote_access") @RequiresPermission(android.car.Car.PERMISSION_CONTROL_REMOTE_ACCESS) public boolean isShutdownRequestSupported();
+ method @FlaggedApi("android.car.feature.serverless_remote_access") @RequiresPermission(android.car.Car.PERMISSION_CONTROL_REMOTE_ACCESS) public boolean isVehicleInUseSupported();
method @FlaggedApi("android.car.feature.serverless_remote_access") @RequiresPermission(android.car.Car.PERMISSION_CONTROL_REMOTE_ACCESS) public void removeServerlessRemoteTaskClient(@NonNull String);
}
diff --git a/car-lib-module/lint-baseline.xml b/car-lib-module/lint-baseline.xml
new file mode 100644
index 0000000..0d1f57a
--- /dev/null
+++ b/car-lib-module/lint-baseline.xml
@@ -0,0 +1,697 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="Class `ClusterHomeManager` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" CAR_SERVICE_NAMES.put(ClusterHomeManager.class, CLUSTER_HOME_SERVICE);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="1741"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `ClusterHomeManager()` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `createCarManagerLocked` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" manager = new ClusterHomeManager(this, binder);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="2713"
+ column="27"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onAudioZoneConfigurationsChanged()` is a flagged API and should be inside an `if (Flags.carAudioDynamicDevices())` check (or annotate the surrounding method `onAudioZoneConfigurationsChanged` with `@FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) to transfer requirement to caller`)"
+ errorLine1=" mExecutor.execute(() -> mCallback.onAudioZoneConfigurationsChanged(configs,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/media/CarAudioManager.java"
+ line="2252"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `CarEvsBufferDescriptor()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `CarEvsBufferDescriptor` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" this(id, Flags.carEvsStreamManagement() ?"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsBufferDescriptor.java"
+ line="79"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onStreamEvent()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `handleStreamEventLocked` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" executor.execute(() -> cb.onStreamEvent(origin, event));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsManager.java"
+ line="595"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getType()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `handleNewFrame` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" buffer.getType() : CarEvsUtils.getTag(buffer.getId());"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsManager.java"
+ line="610"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getType()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `returnFrameBuffer` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" buffer.getType() : CarEvsUtils.getTag(buffer.getId());"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsManager.java"
+ line="719"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1243"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1244"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1244"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1244"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1274"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1275"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1275"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1275"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1305"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1306"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1306"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1306"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1306"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1361"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1361"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1361"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1361"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of(subscription), /* callbackExecutor= */ null,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1363"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getPropertyId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.propertyId = clientOption.getPropertyId();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1499"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getAreaIds()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.areaIds = clientOption.getAreaIds();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1500"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.updateRateHz = clientOption.getUpdateRateHz();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1501"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateEnabled()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" internalOption.enableVariableUpdateRate = clientOption.isVariableUpdateRateEnabled();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1502"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getResolution()` is a flagged API and should be inside an `if (Flags.subscriptionWithResolution())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_SUBSCRIPTION_WITH_RESOLUTION) to transfer requirement to caller`)"
+ errorLine1=" internalOption.resolution = clientOption.getResolution();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1503"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `InVehicleTaskScheduler()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" private final InVehicleTaskScheduler mInVehicleTaskScheduler = new InVehicleTaskScheduler();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="102"
+ column="68"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onServerlessClientRegistered()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `onServerlessClientRegistered` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" executor.execute(() -> callback.onServerlessClientRegistered());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="224"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateSupported()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `sanitizeEnableVariableUpdateRate` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" if (carPropertyConfig.getAreaIdConfig(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/com/android/car/internal/property/InputSanitizationUtils.java"
+ line="160"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Class `ClusterHomeManager` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" CAR_SERVICE_NAMES.put(ClusterHomeManager.class, CLUSTER_HOME_SERVICE);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="1724"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `ClusterHomeManager()` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `createCarManagerLocked` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" manager = new ClusterHomeManager(this, binder);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="2633"
+ column="27"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onAudioZoneConfigurationsChanged()` is a flagged API and should be inside an `if (Flags.carAudioDynamicDevices())` check (or annotate the surrounding method `onAudioZoneConfigurationsChanged` with `@FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) to transfer requirement to caller`)"
+ errorLine1=" mExecutor.execute(() -> mCallback.onAudioZoneConfigurationsChanged(configs,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/media/CarAudioManager.java"
+ line="2249"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `CarEvsBufferDescriptor()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `CarEvsBufferDescriptor` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" this(id, Flags.carEvsStreamManagement() ?"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsBufferDescriptor.java"
+ line="79"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1228"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1229"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1229"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1229"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1259"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1260"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1260"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1260"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1290"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of(subscription), /* callbackExecutor= */ null,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1348"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getPropertyId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.propertyId = clientOption.getPropertyId();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1484"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getAreaIds()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.areaIds = clientOption.getAreaIds();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1485"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.updateRateHz = clientOption.getUpdateRateHz();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1486"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateEnabled()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" internalOption.enableVariableUpdateRate = clientOption.isVariableUpdateRateEnabled();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1487"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getResolution()` is a flagged API and should be inside an `if (Flags.subscriptionWithResolution())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_SUBSCRIPTION_WITH_RESOLUTION) to transfer requirement to caller`)"
+ errorLine1=" internalOption.resolution = clientOption.getResolution();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1488"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `InVehicleTaskScheduler()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" private final InVehicleTaskScheduler mInVehicleTaskScheduler = new InVehicleTaskScheduler();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="102"
+ column="68"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onServerlessClientRegistered()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `onServerlessClientRegistered` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" executor.execute(() -> callback.onServerlessClientRegistered());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="224"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateSupported()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `sanitizeEnableVariableUpdateRate` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" if (carPropertyConfig.getAreaIdConfig(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/com/android/car/internal/property/InputSanitizationUtils.java"
+ line="159"
+ column="21"/>
+ </issue>
+
+</issues>
diff --git a/car-lib/Android.bp b/car-lib/Android.bp
index ffdfd2d..16a49ec 100644
--- a/car-lib/Android.bp
+++ b/car-lib/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -57,7 +58,7 @@
visibility: [
"//packages/services/Car/car-lib",
"//packages/services/Car/car-lib-module",
- "//packages/services/Car/tests/CarLibHostUnitTest",
+ "//packages/services/Car/tests/CarLibUnitTest:__subpackages__",
],
}
@@ -86,6 +87,9 @@
// TODO(b/288271411): enable it when car mainline module is supported
// min_sdk_version: "33",
sdk_version: "module_current",
+ dist: {
+ targets: ["dist_files"],
+ },
}
java_library {
@@ -96,6 +100,9 @@
static_libs: [
"com.android.car.internal.dep",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// TODO(b/248635421): these annotations are part of android.car, but
@@ -120,28 +127,6 @@
],
}
-genrule {
- name: "android-car-last-released-api",
- srcs: [
- "api/released/*.txt",
- ],
- cmd: "cp -f $$(echo $(in) | tr \" \" \"\\n\" | sort -n | tail -1) $(genDir)/last-released-api.txt",
- out: [
- "last-released-api.txt",
- ],
-}
-
-genrule {
- name: "android-car-last-released-system-api",
- srcs: [
- "api/system-released/*.txt",
- ],
- cmd: "cp -f $$(echo $(in) | tr \" \" \"\\n\" | sort -n | tail -1) $(genDir)/last-released-system-api.txt",
- out: [
- "last-released-system-api.txt",
- ],
-}
-
droidstubs {
name: "android.car-stubs-docs",
defaults: ["android.car-docs-default"],
@@ -151,21 +136,16 @@
installable: false,
check_api: {
last_released: {
- api_file: ":android-car-last-released-api",
- removed_api_file: "api/removed.txt",
- args: " -hide 2 -hide 3 -hide 4 -hide 5 -hide 6 -hide 24 -hide 25 -hide 26 -hide 27 " +
- " -warning 7 -warning 8 -warning 9 -warning 10 -warning 11 -warning 12 " +
- " -warning 13 -warning 14 -warning 15 -warning 16 -warning 17 -warning 18 -hide 113 ",
+ api_file: ":android.car.api.combined.public.latest",
+ removed_api_file: ":android.car-removed.api.combined.public.latest",
},
current: {
api_file: "api/current.txt",
removed_api_file: "api/removed.txt",
- args: " -error 2 -error 3 -error 4 -error 5 -error 6 -error 7 -error 8 -error 9 -error 10 -error 11 " +
- " -error 12 -error 13 -error 14 -error 15 -error 16 -error 17 -error 18 -error 19 -error 20 " +
- " -error 21 -error 23 -error 24 -error 25 -hide 113 ",
},
api_lint: {
enabled: true,
+ new_since: ":android.car.api.combined.public.latest",
baseline_file: "api/lint-baseline.txt",
},
},
@@ -182,21 +162,16 @@
installable: false,
check_api: {
last_released: {
- api_file: ":android-car-last-released-system-api",
- removed_api_file: "api/system-removed.txt",
- args: " -hide 2 -hide 3 -hide 4 -hide 5 -hide 6 -hide 24 -hide 25 -hide 26 -hide 27 " +
- " -warning 7 -warning 8 -warning 9 -warning 10 -warning 11 -warning 12 " +
- " -warning 13 -warning 14 -warning 15 -warning 16 -warning 17 -warning 18 -hide 113 ",
+ api_file: ":android.car.api.combined.system.latest",
+ removed_api_file: ":android.car-removed.api.combined.system.latest",
},
current: {
api_file: "api/system-current.txt",
removed_api_file: "api/system-removed.txt",
- args: " -error 2 -error 3 -error 4 -error 5 -error 6 -error 7 -error 8 -error 9 -error 10 -error 11 " +
- " -error 12 -error 13 -error 14 -error 15 -error 16 -error 17 -error 18 -error 19 -error 20 " +
- " -error 21 -error 23 -error 24 -error 25 -hide 113 ",
},
api_lint: {
enabled: true,
+ new_since: ":android.car.api.combined.system.latest",
baseline_file: "api/system-lint-baseline.txt",
},
},
@@ -213,9 +188,6 @@
current: {
api_file: "api/test-current.txt",
removed_api_file: "api/test-removed.txt",
- args: " -error 2 -error 3 -error 4 -error 5 -error 6 -error 7 -error 8 -error 9 -error 10 -error 11 " +
- " -error 12 -error 13 -error 14 -error 15 -error 16 -error 17 -error 18 -error 19 -error 20 " +
- " -error 21 -error 23 -error 24 -error 25 -hide 113 ",
},
},
}
@@ -247,7 +219,7 @@
"sdk-dir",
"api-versions-jars-dir",
],
- previous_api: ":android-car-last-released-api",
+ previous_api: ":android.car.api.combined.public.latest",
merge_annotations_dirs: [
"metalava-manual",
],
@@ -393,3 +365,33 @@
"api/system-removed.txt",
],
}
+
+genrule_defaults {
+ name: "car_signature_to_jdiff_defaults",
+ tools: ["metalava"],
+ cmd: "$(location metalava) signature-to-jdiff --strip $(in) $(out)",
+ dist: {
+ targets: ["dist_files"],
+ },
+}
+
+genrule {
+ name: "android.car-test-stubs-jdiff",
+ defaults: ["car_signature_to_jdiff_defaults"],
+ srcs: ["api/test-current.txt"],
+ out: ["car-test-api.xml"],
+}
+
+genrule {
+ name: "android.car-system-stubs-jdiff",
+ defaults: ["car_signature_to_jdiff_defaults"],
+ srcs: ["api/system-current.txt"],
+ out: ["car-system-api.xml"],
+}
+
+genrule {
+ name: "android.car-stubs-jdiff",
+ defaults: ["car_signature_to_jdiff_defaults"],
+ srcs: ["api/current.txt"],
+ out: ["car-api.xml"],
+}
diff --git a/car-lib/Android.mk b/car-lib/Android.mk
deleted file mode 100644
index daeaad0..0000000
--- a/car-lib/Android.mk
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-#
-
-#disble build in PDK, missing aidl import breaks build
-ifneq ($(TARGET_BUILD_PDK),true)
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-ifeq ($(BOARD_IS_AUTOMOTIVE), true)
-full_classes_jar := $(call intermediates-dir-for,JAVA_LIBRARIES,android.car,,COMMON)/classes.jar
-$(call dist-for-goals,dist_files,$(full_classes_jar):android.car.jar)
-
-ifeq ($(EMMA_INSTRUMENT),true)
-# Put XML formatted API files in the dist dir.
-car_api_xmls := $(addprefix $(TARGET_OUT_COMMON_INTERMEDIATES)/,car-api.xml car-system-api.xml car-test-api.xml)
-$(car_api_xmls): $(TARGET_OUT_COMMON_INTERMEDIATES)/car-%api.xml : packages/services/Car/car-lib/api/%current.txt $(APICHECK)
- $(hide) echo "Converting API file to XML: $@"
- $(hide) mkdir -p $(dir $@)
- $(hide) $(APICHECK_COMMAND) signature-to-jdiff --strip $< $@
-
-$(call dist-for-goals, dist_files, $(car_api_xmls))
-car_api_xmls :=
-endif #EMMA_INSTRUMENT
-
-endif #BOARD_IS_AUTOMOTIVE
-
-endif #TARGET_BUILD_PDK
diff --git a/car-lib/OWNERS b/car-lib/OWNERS
index 27ee922..50f29fa 100644
--- a/car-lib/OWNERS
+++ b/car-lib/OWNERS
@@ -1,8 +1,8 @@
# App
-per-file src/android/car/app/* = ycheo@google.com
+per-file src/android/car/app/* = gauravbhola@google.com
# AppFocus
-per-file src/android/car/app/IAppFocus* = ycheo@google.com
+per-file src/android/car/app/IAppFocus* = bkchoi@google.com
# Audio
per-file src/android/car/media/CarAudio*.java = oscarazu@google.com, ericjeong@google.com
@@ -10,20 +10,20 @@
per-file src/android/car/media/ICarVolumeCallback.aidl = oscarazu@google.com, ericjeong@google.com
# Cluster
-per-file src/android/car/cluster/* = ycheo@google.com
+per-file src/android/car/cluster/* = bkchoi@google.com
# Input
-per-file src/android/car/input/* = ycheo@google.com, kanant@google.com
+per-file src/android/car/input/* = kanant@google.com
# Navigation
-per-file src/android/car/navigation/* = ycheo@google.com
+per-file src/android/car/navigation/* = bkchoi@google.com
# OccupantZone
-per-file src/android/car/CarOccupantZoneManager.* = ycheo@google.com, oscarazu@google.com, ericjeong@google.com
-per-file src/android/car/ICarOccupantZone* = ycheo@google.com, oscarazu@google.com, ericjeong@google.com
+per-file src/android/car/CarOccupantZoneManager.* = xiangw@google.com, oscarazu@google.com, ericjeong@google.com
+per-file src/android/car/ICarOccupantZone* = xiangw@google.com, oscarazu@google.com, ericjeong@google.com
# PackageManager
-per-file src/android/car/content/pm/* = ycheo@google.com
+per-file src/android/car/content/pm/* = gauravbhola@google.com, gargmayank@google.com
# Power
per-file src/android/car/hardware/power/* = ericjeong@google.com
diff --git a/car-lib/api/current.txt b/car-lib/api/current.txt
index 89fe0ed..d96c5f1 100644
--- a/car-lib/api/current.txt
+++ b/car-lib/api/current.txt
@@ -19,8 +19,8 @@
method @Deprecated public int getCarConnectionType();
method @Nullable public Object getCarManager(String);
method @Nullable public <T> T getCarManager(@NonNull Class<T>);
- method @NonNull public static android.car.CarVersion getCarVersion();
- method @NonNull public static android.car.PlatformVersion getPlatformVersion();
+ method @Deprecated @NonNull public static android.car.CarVersion getCarVersion();
+ method @Deprecated @NonNull public static android.car.PlatformVersion getPlatformVersion();
method @Deprecated public static boolean isApiAndPlatformVersionAtLeast(int, int);
method @Deprecated public static boolean isApiAndPlatformVersionAtLeast(int, int, int);
method @Deprecated public static boolean isApiVersionAtLeast(int);
@@ -317,14 +317,14 @@
ctor @Deprecated public VehiclePropertyIds();
method public static String toString(int);
field @RequiresPermission("android.car.permission.CAR_DYNAMICS_STATE") public static final int ABS_ACTIVE = 287310858; // 0x1120040a
- field @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_BOOTUP_REASON = 289409538; // 0x11400a02
- field @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_STATE_REPORT = 289475073; // 0x11410a01
- field @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_STATE_REQ = 289475072; // 0x11410a00
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_BOOTUP_REASON = 289409538; // 0x11400a02
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_STATE_REPORT = 289475073; // 0x11410a01
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_POWER") public static final int AP_POWER_STATE_REQ = 289475072; // 0x11410a00
field @RequiresPermission(android.car.Car.PERMISSION_READ_INTERIOR_LIGHTS) public static final int CABIN_LIGHTS_STATE = 289410817; // 0x11400f01
field @RequiresPermission(android.car.Car.PERMISSION_CONTROL_INTERIOR_LIGHTS) public static final int CABIN_LIGHTS_SWITCH = 289410818; // 0x11400f02
field @RequiresPermission("android.car.permission.CAR_TIRES") public static final int CRITICALLY_LOW_TIRE_PRESSURE = 392168202; // 0x1760030a
field @RequiresPermission(android.car.Car.PERMISSION_POWERTRAIN) public static final int CURRENT_GEAR = 289408001; // 0x11400401
- field @RequiresPermission("android.car.permission.CAR_POWER") public static final int DISPLAY_BRIGHTNESS = 289409539; // 0x11400a03
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_POWER") public static final int DISPLAY_BRIGHTNESS = 289409539; // 0x11400a03
field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_READ_DISPLAY_UNITS)) @RequiresPermission.Write(@androidx.annotation.RequiresPermission(allOf={android.car.Car.PERMISSION_CONTROL_DISPLAY_UNITS, "android.car.permission.CAR_VENDOR_EXTENSION"})) public static final int DISTANCE_DISPLAY_UNITS = 289408512; // 0x11400600
field @RequiresPermission("android.car.permission.CONTROL_CAR_DOORS") public static final int DOOR_LOCK = 371198722; // 0x16200b02
field @RequiresPermission("android.car.permission.CONTROL_CAR_DOORS") public static final int DOOR_MOVE = 373295873; // 0x16400b01
@@ -390,7 +390,7 @@
field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(anyOf={android.car.Car.PERMISSION_READ_DISPLAY_UNITS, "android.car.permission.CONTROL_CAR_CLIMATE"})) @RequiresPermission.Write(@androidx.annotation.RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE")) public static final int HVAC_TEMPERATURE_DISPLAY_UNITS = 289408270; // 0x1140050e
field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_TEMPERATURE_SET = 358614275; // 0x15600503
field @RequiresPermission("android.car.permission.CONTROL_CAR_CLIMATE") public static final int HVAC_TEMPERATURE_VALUE_SUGGESTION = 291570965; // 0x11610515
- field public static final int HW_KEY_INPUT = 289475088; // 0x11410a10
+ field @Deprecated public static final int HW_KEY_INPUT = 289475088; // 0x11410a10
field @RequiresPermission(android.car.Car.PERMISSION_POWERTRAIN) public static final int IGNITION_STATE = 289408009; // 0x11400409
field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_DRIVER_SEAT = 356516106; // 0x1540010a
field @RequiresPermission(android.car.Car.PERMISSION_CAR_INFO) public static final int INFO_EV_BATTERY_CAPACITY = 291504390; // 0x11600106
@@ -414,10 +414,10 @@
field @RequiresPermission("android.car.permission.CONTROL_CAR_MIRRORS") public static final int MIRROR_Z_MOVE = 339741505; // 0x14400b41
field @RequiresPermission("android.car.permission.CONTROL_CAR_MIRRORS") public static final int MIRROR_Z_POS = 339741504; // 0x14400b40
field @RequiresPermission(android.car.Car.PERMISSION_EXTERIOR_ENVIRONMENT) public static final int NIGHT_MODE = 287310855; // 0x11200407
- field @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME = 299896065; // 0x11e00d01
- field @RequiresPermission("android.car.permission.CLEAR_CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME_CLEAR = 299896067; // 0x11e00d03
- field @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME_INFO = 299896066; // 0x11e00d02
- field @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_LIVE_FRAME = 299896064; // 0x11e00d00
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME = 299896065; // 0x11e00d01
+ field @Deprecated @RequiresPermission("android.car.permission.CLEAR_CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME_CLEAR = 299896067; // 0x11e00d03
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_FREEZE_FRAME_INFO = 299896066; // 0x11e00d02
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_DIAGNOSTICS") public static final int OBD2_LIVE_FRAME = 299896064; // 0x11e00d00
field @RequiresPermission(android.car.Car.PERMISSION_POWERTRAIN) public static final int PARKING_BRAKE_AUTO_APPLY = 287310851; // 0x11200403
field @RequiresPermission(android.car.Car.PERMISSION_POWERTRAIN) public static final int PARKING_BRAKE_ON = 287310850; // 0x11200402
field @RequiresPermission("android.car.permission.CAR_MILEAGE") public static final int PERF_ODOMETER = 291504644; // 0x11600204
@@ -425,7 +425,7 @@
field @RequiresPermission(android.car.Car.PERMISSION_READ_STEERING_STATE) public static final int PERF_STEERING_ANGLE = 291504649; // 0x11600209
field @RequiresPermission(android.car.Car.PERMISSION_SPEED) public static final int PERF_VEHICLE_SPEED = 291504647; // 0x11600207
field @RequiresPermission(android.car.Car.PERMISSION_SPEED) public static final int PERF_VEHICLE_SPEED_DISPLAY = 291504648; // 0x11600208
- field @RequiresPermission("android.car.permission.CAR_POWER") public static final int PER_DISPLAY_BRIGHTNESS = 289475076; // 0x11410a04
+ field @Deprecated @RequiresPermission("android.car.permission.CAR_POWER") public static final int PER_DISPLAY_BRIGHTNESS = 289475076; // 0x11410a04
field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(anyOf={android.car.Car.PERMISSION_ENERGY, "android.car.permission.ADJUST_RANGE_REMAINING"})) @RequiresPermission.Write(@androidx.annotation.RequiresPermission("android.car.permission.ADJUST_RANGE_REMAINING")) public static final int RANGE_REMAINING = 291504904; // 0x11600308
field @RequiresPermission(android.car.Car.PERMISSION_READ_INTERIOR_LIGHTS) public static final int READING_LIGHTS_STATE = 356519683; // 0x15400f03
field @RequiresPermission(android.car.Car.PERMISSION_CONTROL_INTERIOR_LIGHTS) public static final int READING_LIGHTS_SWITCH = 356519684; // 0x15400f04
@@ -470,7 +470,7 @@
field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_PRIVILEGED_CAR_INFO)) public static final int TRAILER_PRESENT = 289410885; // 0x11400f45
field @RequiresPermission("android.car.permission.CAR_EXTERIOR_LIGHTS") public static final int TURN_SIGNAL_STATE = 289408008; // 0x11400408
field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_PRIVILEGED_CAR_INFO)) public static final int VEHICLE_CURB_WEIGHT = 289410886; // 0x11400f46
- field @RequiresPermission(anyOf={"android.car.permission.VMS_PUBLISHER", "android.car.permission.VMS_SUBSCRIBER"}) public static final int VEHICLE_MAP_SERVICE = 299895808; // 0x11e00c00
+ field @Deprecated @RequiresPermission(anyOf={"android.car.permission.VMS_PUBLISHER", "android.car.permission.VMS_SUBSCRIBER"}) public static final int VEHICLE_MAP_SERVICE = 299895808; // 0x11e00c00
field @RequiresPermission.Read(@androidx.annotation.RequiresPermission(android.car.Car.PERMISSION_READ_DISPLAY_UNITS)) @RequiresPermission.Write(@androidx.annotation.RequiresPermission(allOf={android.car.Car.PERMISSION_CONTROL_DISPLAY_UNITS, "android.car.permission.CAR_VENDOR_EXTENSION"})) public static final int VEHICLE_SPEED_DISPLAY_UNITS = 289408516; // 0x11400604
field @RequiresPermission(android.car.Car.PERMISSION_SPEED) public static final int WHEEL_TICK = 290521862; // 0x11510306
field @RequiresPermission("android.car.permission.CONTROL_CAR_WINDOWS") public static final int WINDOW_LOCK = 320867268; // 0x13200bc4
@@ -506,11 +506,11 @@
package android.car.content.pm {
public final class CarPackageManager {
- method @NonNull public android.car.CarVersion getTargetCarVersion();
+ method @Deprecated @NonNull public android.car.CarVersion getTargetCarVersion();
method public boolean isActivityDistractionOptimized(String, String);
method public boolean isPendingIntentDistractionOptimized(@NonNull android.app.PendingIntent);
method public boolean isServiceDistractionOptimized(String, String);
- field public static final String MANIFEST_METADATA_TARGET_CAR_VERSION = "android.car.targetCarVersion";
+ field @Deprecated public static final String MANIFEST_METADATA_TARGET_CAR_VERSION = "android.car.targetCarVersion";
}
}
@@ -778,7 +778,7 @@
method @NonNull public java.util.List<android.car.hardware.CarPropertyConfig> getPropertyList();
method @NonNull public java.util.List<android.car.hardware.CarPropertyConfig> getPropertyList(@NonNull android.util.ArraySet<java.lang.Integer>);
method public boolean isPropertyAvailable(int, int);
- method public boolean registerCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback, int, @FloatRange(from=0.0, to=100.0) float);
+ method @Deprecated public boolean registerCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback, int, @FloatRange(from=0.0, to=100.0) float);
method public void setBooleanProperty(int, int, boolean);
method public void setFloatProperty(int, int, float);
method public void setIntProperty(int, int, int);
@@ -790,8 +790,8 @@
method @FlaggedApi("android.car.feature.variable_update_rate") public boolean subscribePropertyEvents(int, int, @NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
method @FlaggedApi("android.car.feature.variable_update_rate") public boolean subscribePropertyEvents(int, int, @FloatRange(from=0.0, to=100.0) float, @NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
method @FlaggedApi("android.car.feature.batched_subscriptions") public boolean subscribePropertyEvents(@NonNull java.util.List<android.car.hardware.property.Subscription>, @Nullable java.util.concurrent.Executor, @NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
- method public void unregisterCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
- method public void unregisterCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback, int);
+ method @Deprecated public void unregisterCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
+ method @Deprecated public void unregisterCallback(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback, int);
method @FlaggedApi("android.car.feature.batched_subscriptions") public void unsubscribePropertyEvents(@NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
method @FlaggedApi("android.car.feature.batched_subscriptions") public void unsubscribePropertyEvents(int, @NonNull android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback);
field public static final long ASYNC_GET_DEFAULT_TIMEOUT_MS = 10000L; // 0x2710L
diff --git a/car-lib/api/lint-baseline.txt b/car-lib/api/lint-baseline.txt
index 9f6c377..8396e80 100644
--- a/car-lib/api/lint-baseline.txt
+++ b/car-lib/api/lint-baseline.txt
@@ -37,8 +37,6 @@
Registration methods should have overload that accepts delivery Executor: `registerListener`
ExecutorRegistration: android.car.hardware.property.CarPropertyManager#registerCallback(android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback, int, float):
Registration methods should have overload that accepts delivery Executor: `registerCallback`
-ExecutorRegistration: android.car.hardware.property.CarPropertyManager#unsubscribePropertyEvents(android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback, int):
- Registration methods should have overload that accepts delivery Executor: `unsubscribePropertyEvents`
ExecutorRegistration: android.car.media.CarAudioManager#registerCarVolumeCallback(android.car.media.CarAudioManager.CarVolumeCallback):
Registration methods should have overload that accepts delivery Executor: `registerCarVolumeCallback`
@@ -53,8 +51,6 @@
Listeners should always be at end of argument list (method `abandonAppFocus`)
ListenerLast: android.car.CarAppFocusManager#isOwningFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback, int) parameter #1:
Listeners should always be at end of argument list (method `isOwningFocus`)
-ListenerLast: android.car.hardware.property.CarPropertyManager#unsubscribePropertyEvents(android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback, int) parameter #1:
- Listeners should always be at end of argument list (method `unsubscribePropertyEvents`)
MissingGetterMatchingBuilder: android.car.drivingstate.CarUxRestrictions.Builder#setMaxStringLength(int):
@@ -317,10 +313,6 @@
New API must be flagged with @FlaggedApi: method android.car.Car.getCarManager(Class<T>)
UnflaggedApi: android.car.Car#getCarManager(String):
New API must be flagged with @FlaggedApi: method android.car.Car.getCarManager(String)
-UnflaggedApi: android.car.Car#getCarVersion():
- New API must be flagged with @FlaggedApi: method android.car.Car.getCarVersion()
-UnflaggedApi: android.car.Car#getPlatformVersion():
- New API must be flagged with @FlaggedApi: method android.car.Car.getPlatformVersion()
UnflaggedApi: android.car.Car#isConnected():
New API must be flagged with @FlaggedApi: method android.car.Car.isConnected()
UnflaggedApi: android.car.Car#isConnecting():
@@ -1061,10 +1053,6 @@
New API must be flagged with @FlaggedApi: field android.car.VehicleUnit.WATT_HOUR
UnflaggedApi: android.car.content.pm.CarPackageManager:
New API must be flagged with @FlaggedApi: class android.car.content.pm.CarPackageManager
-UnflaggedApi: android.car.content.pm.CarPackageManager#MANIFEST_METADATA_TARGET_CAR_VERSION:
- New API must be flagged with @FlaggedApi: field android.car.content.pm.CarPackageManager.MANIFEST_METADATA_TARGET_CAR_VERSION
-UnflaggedApi: android.car.content.pm.CarPackageManager#getTargetCarVersion():
- New API must be flagged with @FlaggedApi: method android.car.content.pm.CarPackageManager.getTargetCarVersion()
UnflaggedApi: android.car.content.pm.CarPackageManager#isActivityDistractionOptimized(String, String):
New API must be flagged with @FlaggedApi: method android.car.content.pm.CarPackageManager.isActivityDistractionOptimized(String,String)
UnflaggedApi: android.car.content.pm.CarPackageManager#isPendingIntentDistractionOptimized(android.app.PendingIntent):
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 53c16e6..cdd2d5a 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -45,6 +45,7 @@
field public static final String OCCUPANT_AWARENESS_SERVICE = "occupant_awareness";
field public static final String PERMISSION_ACCESS_MIRRORRED_SURFACE = "android.car.permission.ACCESS_MIRRORED_SURFACE";
field public static final String PERMISSION_ADJUST_RANGE_REMAINING = "android.car.permission.ADJUST_RANGE_REMAINING";
+ field @FlaggedApi("android.car.feature.car_app_card") public static final String PERMISSION_BIND_APP_CARD_PROVIDER = "android.car.permission.BIND_APP_CARD_PROVIDER";
field public static final String PERMISSION_CAR_DIAGNOSTIC_CLEAR = "android.car.permission.CLEAR_CAR_DIAGNOSTICS";
field public static final String PERMISSION_CAR_DIAGNOSTIC_READ_ALL = "android.car.permission.CAR_DIAGNOSTICS";
field public static final String PERMISSION_CAR_DRIVING_STATE = "android.car.permission.CAR_DRIVING_STATE";
@@ -89,6 +90,7 @@
field public static final String PERMISSION_CONTROL_WINDSHIELD_WIPERS = "android.car.permission.CONTROL_WINDSHIELD_WIPERS";
field public static final String PERMISSION_EXTERIOR_LIGHTS = "android.car.permission.CAR_EXTERIOR_LIGHTS";
field public static final String PERMISSION_MANAGE_CAR_SYSTEM_UI = "android.car.permission.MANAGE_CAR_SYSTEM_UI";
+ field @FlaggedApi("android.car.feature.display_compatibility") public static final String PERMISSION_MANAGE_DISPLAY_COMPATIBILITY = "android.car.permission.MANAGE_DISPLAY_COMPATIBILITY";
field public static final String PERMISSION_MANAGE_OCCUPANT_CONNECTION = "android.car.permission.MANAGE_OCCUPANT_CONNECTION";
field public static final String PERMISSION_MANAGE_OCCUPANT_ZONE = "android.car.permission.MANAGE_OCCUPANT_ZONE";
field public static final String PERMISSION_MANAGE_REMOTE_DEVICE = "android.car.permission.MANAGE_REMOTE_DEVICE";
@@ -97,7 +99,6 @@
field public static final String PERMISSION_MIRROR_DISPLAY = "android.car.permission.MIRROR_DISPLAY";
field @Deprecated public static final String PERMISSION_MOCK_VEHICLE_HAL = "android.car.permission.CAR_MOCK_VEHICLE_HAL";
field public static final String PERMISSION_MONITOR_CAR_EVS_STATUS = "android.car.permission.MONITOR_CAR_EVS_STATUS";
- field @FlaggedApi("android.car.feature.display_compatibility") public static final String PERMISSION_QUERY_DISPLAY_COMPATIBILITY = "android.car.permission.QUERY_DISPLAY_COMPATIBILITY";
field public static final String PERMISSION_READ_ADAS_SETTINGS = "android.car.permission.READ_ADAS_SETTINGS";
field public static final String PERMISSION_READ_ADAS_STATES = "android.car.permission.READ_ADAS_STATES";
field @FlaggedApi("android.car.feature.android_vic_vehicle_properties") public static final String PERMISSION_READ_CAR_AIRBAGS = "android.car.permission.READ_CAR_AIRBAGS";
@@ -439,23 +440,19 @@
}
@FlaggedApi("android.car.feature.display_compatibility") public final class CarDisplayCompatContainer {
- method @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public void onBackPressed();
- method @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public void setDensity(int);
- method @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public void setVisibility(int);
- method @NonNull @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public android.graphics.Rect setWindowBounds(@NonNull android.graphics.Rect);
- method @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public void startActivity(@NonNull android.content.Intent, @Nullable android.os.Bundle);
+ method @RequiresPermission(android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY) @UiThread public void notifyBackPressed();
+ method @RequiresPermission(android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY) @UiThread public void setVisibility(int);
+ method @NonNull @RequiresPermission(android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY) @UiThread public android.graphics.Rect setWindowBounds(@NonNull android.graphics.Rect);
+ method @RequiresPermission(allOf={android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY, android.car.Car.PERMISSION_MANAGE_CAR_SYSTEM_UI, android.Manifest.permission.INTERACT_ACROSS_USERS}) @UiThread public void startActivity(@NonNull android.content.Intent, @Nullable android.os.Bundle);
}
- public static final class CarDisplayCompatContainer.Builder {
+ @FlaggedApi("android.car.feature.display_compatibility") public static final class CarDisplayCompatContainer.Builder {
ctor public CarDisplayCompatContainer.Builder(@NonNull android.app.Activity);
- method @NonNull public android.car.app.CarDisplayCompatContainer.Builder setDensity(int);
- method @NonNull public android.car.app.CarDisplayCompatContainer.Builder setHeight(int);
method @NonNull public android.car.app.CarDisplayCompatContainer.Builder setSurfaceViewCallback(@Nullable java.util.function.Consumer<android.view.SurfaceView>);
- method @NonNull public android.car.app.CarDisplayCompatContainer.Builder setWidth(int);
}
@FlaggedApi("android.car.feature.display_compatibility") public final class CarDisplayCompatManager {
- method @Nullable @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public android.car.app.CarDisplayCompatContainer initializeDisplayCompatContainer(@NonNull android.car.app.CarDisplayCompatContainer.Builder);
+ method @Nullable @RequiresPermission(android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY) public android.car.app.CarDisplayCompatContainer initializeDisplayCompatContainer(@NonNull android.car.app.CarDisplayCompatContainer.Builder);
}
public interface CarSystemUIProxy {
@@ -646,9 +643,9 @@
}
public final class CarPackageManager {
- method @NonNull @RequiresPermission(android.Manifest.permission.QUERY_ALL_PACKAGES) public android.car.CarVersion getTargetCarVersion(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.QUERY_ALL_PACKAGES) public android.car.CarVersion getTargetCarVersion(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isActivityBackedBySafeActivity(android.content.ComponentName);
- method @FlaggedApi("android.car.feature.display_compatibility") @RequiresPermission(allOf={android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY, android.Manifest.permission.QUERY_ALL_PACKAGES}) public boolean requiresDisplayCompat(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @FlaggedApi("android.car.feature.display_compatibility") @RequiresPermission(allOf={android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY, android.Manifest.permission.QUERY_ALL_PACKAGES}) public boolean requiresDisplayCompat(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public void setAppBlockingPolicy(String, android.car.content.pm.CarAppBlockingPolicy, int);
field @Deprecated public static final int FLAG_SET_POLICY_ADD = 2; // 0x2
field @Deprecated public static final int FLAG_SET_POLICY_REMOVE = 4; // 0x4
@@ -965,7 +962,7 @@
method public void onCarDisconnected();
method @RequiresPermission(android.car.Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) public boolean saveUxRestrictionsConfigurationForNextBoot(@NonNull java.util.List<android.car.drivingstate.CarUxRestrictionsConfiguration>);
method @RequiresPermission(android.car.Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) public boolean saveUxRestrictionsConfigurationForNextBoot(@NonNull android.car.drivingstate.CarUxRestrictionsConfiguration);
- method public void setListener(int, @NonNull android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener);
+ method @Deprecated public void setListener(int, @NonNull android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener);
method @RequiresPermission(android.car.Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) public boolean setRestrictionMode(@NonNull String);
field public static final String UX_RESTRICTION_MODE_BASELINE = "baseline";
}
@@ -1033,7 +1030,8 @@
public static interface CarEvsManager.CarEvsStreamCallback {
method public default void onNewFrame(@NonNull android.car.evs.CarEvsBufferDescriptor);
- method public default void onStreamEvent(int);
+ method @Deprecated public default void onStreamEvent(int);
+ method @FlaggedApi("android.car.feature.car_evs_stream_management") public default void onStreamEvent(int, int);
}
public final class CarEvsStatus implements android.os.Parcelable {
@@ -1061,21 +1059,18 @@
}
public final class CarPropertyConfig<T> implements android.os.Parcelable {
- method public static <T> android.car.hardware.CarPropertyConfig.Builder<T> newBuilder(Class<T>, int, int, int);
+ method @Deprecated public static <T> android.car.hardware.CarPropertyConfig.Builder<T> newBuilder(Class<T>, int, int, int);
}
- public static class CarPropertyConfig.Builder<T> {
- method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addArea(int);
- method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addAreaConfig(int, T, T);
- method @NonNull public android.car.hardware.CarPropertyConfig.Builder<T> addAreaIdConfig(@NonNull android.car.hardware.property.AreaIdConfig<T>);
- method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addAreas(int[]);
- method public android.car.hardware.CarPropertyConfig<T> build();
- method public android.car.hardware.CarPropertyConfig.Builder<T> setAccess(int);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setChangeMode(int);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setConfigArray(java.util.ArrayList<java.lang.Integer>);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setConfigString(String);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setMaxSampleRate(float);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setMinSampleRate(float);
+ @Deprecated public static class CarPropertyConfig.Builder<T> {
+ method @Deprecated @NonNull public android.car.hardware.CarPropertyConfig.Builder<T> addAreaIdConfig(@NonNull android.car.hardware.property.AreaIdConfig<T>);
+ method @Deprecated public android.car.hardware.CarPropertyConfig<T> build();
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setAccess(int);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setChangeMode(int);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setConfigArray(java.util.ArrayList<java.lang.Integer>);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setConfigString(String);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setMaxSampleRate(float);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setMinSampleRate(float);
}
@Deprecated public final class CarVendorExtensionManager {
@@ -1245,13 +1240,13 @@
package android.car.hardware.property {
- public static final class AreaIdConfig.Builder<T> {
- ctor public AreaIdConfig.Builder(int);
- ctor @FlaggedApi("android.car.feature.area_id_config_access") public AreaIdConfig.Builder(int, int);
- method @NonNull public android.car.hardware.property.AreaIdConfig<T> build();
- method @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setMaxValue(T);
- method @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setMinValue(T);
- method @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setSupportedEnumValues(@NonNull java.util.List<T>);
+ @Deprecated public static final class AreaIdConfig.Builder<T> {
+ ctor @Deprecated public AreaIdConfig.Builder(int);
+ ctor @Deprecated @FlaggedApi("android.car.feature.area_id_config_access") public AreaIdConfig.Builder(int, int);
+ method @Deprecated @NonNull public android.car.hardware.property.AreaIdConfig<T> build();
+ method @Deprecated @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setMaxValue(T);
+ method @Deprecated @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setMinValue(T);
+ method @Deprecated @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setSupportedEnumValues(@NonNull java.util.List<T>);
}
public final class AutomaticEmergencyBrakingState {
diff --git a/car-lib/api/system-lint-baseline.txt b/car-lib/api/system-lint-baseline.txt
index 8841bb9..13b6b6c 100644
--- a/car-lib/api/system-lint-baseline.txt
+++ b/car-lib/api/system-lint-baseline.txt
@@ -893,10 +893,6 @@
Listener methods should be named add/remove; was unregisterListener
-RequiresPermission: android.car.view.WindowManagerHelper#setInputFeatureSpy(android.view.WindowManager.LayoutParams):
- Method 'setInputFeatureSpy' documentation mentions permissions without declaring @RequiresPermission
-
-
RethrowRemoteException: android.car.occupantconnection.AbstractReceiverService#forwardPayload(android.car.CarOccupantZoneManager.OccupantZoneInfo, String, android.car.occupantconnection.Payload):
Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
@@ -945,34 +941,6 @@
Builder must be final: android.car.hardware.CarPropertyConfig.Builder
-Todo: android.car.VehiclePropertyIds#AP_POWER_BOOTUP_REASON:
- Documentation mentions 'TODO'
-Todo: android.car.VehiclePropertyIds#AP_POWER_STATE_REPORT:
- Documentation mentions 'TODO'
-Todo: android.car.VehiclePropertyIds#AP_POWER_STATE_REQ:
- Documentation mentions 'TODO'
-Todo: android.car.VehiclePropertyIds#DISPLAY_BRIGHTNESS:
- Documentation mentions 'TODO'
-Todo: android.car.VehiclePropertyIds#HW_KEY_INPUT:
- Documentation mentions 'TODO'
-Todo: android.car.VehiclePropertyIds#OBD2_FREEZE_FRAME:
- Documentation mentions 'TODO'
-Todo: android.car.VehiclePropertyIds#OBD2_FREEZE_FRAME_CLEAR:
- Documentation mentions 'TODO'
-Todo: android.car.VehiclePropertyIds#OBD2_FREEZE_FRAME_INFO:
- Documentation mentions 'TODO'
-Todo: android.car.VehiclePropertyIds#OBD2_LIVE_FRAME:
- Documentation mentions 'TODO'
-Todo: android.car.VehiclePropertyIds#PER_DISPLAY_BRIGHTNESS:
- Documentation mentions 'TODO'
-Todo: android.car.VehiclePropertyIds#VEHICLE_MAP_SERVICE:
- Documentation mentions 'TODO'
-Todo: android.car.evs.CarEvsManager#startVideoStream(int, android.os.IBinder, java.util.concurrent.Executor, android.car.evs.CarEvsManager.CarEvsStreamCallback):
- Documentation mentions 'TODO'
-Todo: android.car.telemetry.CarTelemetryManager.MetricsReportCallback#onResult(String, android.os.PersistableBundle, byte[], int):
- Documentation mentions 'TODO'
-
-
UnflaggedApi: android.car.AoapService:
New API must be flagged with @FlaggedApi: class android.car.AoapService
UnflaggedApi: android.car.AoapService#AoapService():
@@ -1855,8 +1823,6 @@
New API must be flagged with @FlaggedApi: method android.car.content.pm.CarAppBlockingPolicyService.getAppBlockingPolicy()
UnflaggedApi: android.car.content.pm.CarAppBlockingPolicyService#onBind(android.content.Intent):
New API must be flagged with @FlaggedApi: method android.car.content.pm.CarAppBlockingPolicyService.onBind(android.content.Intent)
-UnflaggedApi: android.car.content.pm.CarPackageManager#getTargetCarVersion(String):
- New API must be flagged with @FlaggedApi: method android.car.content.pm.CarPackageManager.getTargetCarVersion(String)
UnflaggedApi: android.car.content.pm.CarPackageManager#isActivityBackedBySafeActivity(android.content.ComponentName):
New API must be flagged with @FlaggedApi: method android.car.content.pm.CarPackageManager.isActivityBackedBySafeActivity(android.content.ComponentName)
UnflaggedApi: android.car.diagnostic.CarDiagnosticEvent:
diff --git a/car-lib/api/system-released/system-2.txt b/car-lib/api/system-released/system-2.txt
deleted file mode 100644
index e69de29..0000000
--- a/car-lib/api/system-released/system-2.txt
+++ /dev/null
diff --git a/car-lib/api/system-removed.txt b/car-lib/api/system-removed.txt
index bca7216..762ae83 100644
--- a/car-lib/api/system-removed.txt
+++ b/car-lib/api/system-removed.txt
@@ -7,6 +7,16 @@
}
+package android.car.hardware {
+
+ @Deprecated public static class CarPropertyConfig.Builder<T> {
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addArea(int);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addAreaConfig(int, T, T);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addAreas(int[]);
+ }
+
+}
+
package android.car.input {
@Deprecated public abstract class CarInputHandlingService extends android.app.Service {
diff --git a/car-lib/api/test-current.txt b/car-lib/api/test-current.txt
index 5b2d762..34044ef 100644
--- a/car-lib/api/test-current.txt
+++ b/car-lib/api/test-current.txt
@@ -45,6 +45,7 @@
field public static final String OCCUPANT_AWARENESS_SERVICE = "occupant_awareness";
field public static final String PERMISSION_ACCESS_MIRRORRED_SURFACE = "android.car.permission.ACCESS_MIRRORED_SURFACE";
field public static final String PERMISSION_ADJUST_RANGE_REMAINING = "android.car.permission.ADJUST_RANGE_REMAINING";
+ field @FlaggedApi("android.car.feature.car_app_card") public static final String PERMISSION_BIND_APP_CARD_PROVIDER = "android.car.permission.BIND_APP_CARD_PROVIDER";
field public static final String PERMISSION_CAR_DIAGNOSTIC_CLEAR = "android.car.permission.CLEAR_CAR_DIAGNOSTICS";
field public static final String PERMISSION_CAR_DIAGNOSTIC_READ_ALL = "android.car.permission.CAR_DIAGNOSTICS";
field public static final String PERMISSION_CAR_DRIVING_STATE = "android.car.permission.CAR_DRIVING_STATE";
@@ -89,6 +90,7 @@
field public static final String PERMISSION_CONTROL_WINDSHIELD_WIPERS = "android.car.permission.CONTROL_WINDSHIELD_WIPERS";
field public static final String PERMISSION_EXTERIOR_LIGHTS = "android.car.permission.CAR_EXTERIOR_LIGHTS";
field public static final String PERMISSION_MANAGE_CAR_SYSTEM_UI = "android.car.permission.MANAGE_CAR_SYSTEM_UI";
+ field @FlaggedApi("android.car.feature.display_compatibility") public static final String PERMISSION_MANAGE_DISPLAY_COMPATIBILITY = "android.car.permission.MANAGE_DISPLAY_COMPATIBILITY";
field public static final String PERMISSION_MANAGE_OCCUPANT_CONNECTION = "android.car.permission.MANAGE_OCCUPANT_CONNECTION";
field public static final String PERMISSION_MANAGE_OCCUPANT_ZONE = "android.car.permission.MANAGE_OCCUPANT_ZONE";
field public static final String PERMISSION_MANAGE_REMOTE_DEVICE = "android.car.permission.MANAGE_REMOTE_DEVICE";
@@ -97,7 +99,6 @@
field public static final String PERMISSION_MIRROR_DISPLAY = "android.car.permission.MIRROR_DISPLAY";
field @Deprecated public static final String PERMISSION_MOCK_VEHICLE_HAL = "android.car.permission.CAR_MOCK_VEHICLE_HAL";
field public static final String PERMISSION_MONITOR_CAR_EVS_STATUS = "android.car.permission.MONITOR_CAR_EVS_STATUS";
- field @FlaggedApi("android.car.feature.display_compatibility") public static final String PERMISSION_QUERY_DISPLAY_COMPATIBILITY = "android.car.permission.QUERY_DISPLAY_COMPATIBILITY";
field public static final String PERMISSION_READ_ADAS_SETTINGS = "android.car.permission.READ_ADAS_SETTINGS";
field public static final String PERMISSION_READ_ADAS_STATES = "android.car.permission.READ_ADAS_STATES";
field @FlaggedApi("android.car.feature.android_vic_vehicle_properties") public static final String PERMISSION_READ_CAR_AIRBAGS = "android.car.permission.READ_CAR_AIRBAGS";
@@ -510,23 +511,19 @@
}
@FlaggedApi("android.car.feature.display_compatibility") public final class CarDisplayCompatContainer {
- method @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public void onBackPressed();
- method @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public void setDensity(int);
- method @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public void setVisibility(int);
- method @NonNull @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public android.graphics.Rect setWindowBounds(@NonNull android.graphics.Rect);
- method @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public void startActivity(@NonNull android.content.Intent, @Nullable android.os.Bundle);
+ method @RequiresPermission(android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY) @UiThread public void notifyBackPressed();
+ method @RequiresPermission(android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY) @UiThread public void setVisibility(int);
+ method @NonNull @RequiresPermission(android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY) @UiThread public android.graphics.Rect setWindowBounds(@NonNull android.graphics.Rect);
+ method @RequiresPermission(allOf={android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY, android.car.Car.PERMISSION_MANAGE_CAR_SYSTEM_UI, android.Manifest.permission.INTERACT_ACROSS_USERS}) @UiThread public void startActivity(@NonNull android.content.Intent, @Nullable android.os.Bundle);
}
- public static final class CarDisplayCompatContainer.Builder {
+ @FlaggedApi("android.car.feature.display_compatibility") public static final class CarDisplayCompatContainer.Builder {
ctor public CarDisplayCompatContainer.Builder(@NonNull android.app.Activity);
- method @NonNull public android.car.app.CarDisplayCompatContainer.Builder setDensity(int);
- method @NonNull public android.car.app.CarDisplayCompatContainer.Builder setHeight(int);
method @NonNull public android.car.app.CarDisplayCompatContainer.Builder setSurfaceViewCallback(@Nullable java.util.function.Consumer<android.view.SurfaceView>);
- method @NonNull public android.car.app.CarDisplayCompatContainer.Builder setWidth(int);
}
@FlaggedApi("android.car.feature.display_compatibility") public final class CarDisplayCompatManager {
- method @Nullable @RequiresPermission(android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY) public android.car.app.CarDisplayCompatContainer initializeDisplayCompatContainer(@NonNull android.car.app.CarDisplayCompatContainer.Builder);
+ method @Nullable @RequiresPermission(android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY) public android.car.app.CarDisplayCompatContainer initializeDisplayCompatContainer(@NonNull android.car.app.CarDisplayCompatContainer.Builder);
}
public interface CarSystemUIProxy {
@@ -717,9 +714,9 @@
}
public final class CarPackageManager {
- method @NonNull @RequiresPermission(android.Manifest.permission.QUERY_ALL_PACKAGES) public android.car.CarVersion getTargetCarVersion(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.QUERY_ALL_PACKAGES) public android.car.CarVersion getTargetCarVersion(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isActivityBackedBySafeActivity(android.content.ComponentName);
- method @FlaggedApi("android.car.feature.display_compatibility") @RequiresPermission(allOf={android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY, android.Manifest.permission.QUERY_ALL_PACKAGES}) public boolean requiresDisplayCompat(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @FlaggedApi("android.car.feature.display_compatibility") @RequiresPermission(allOf={android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY, android.Manifest.permission.QUERY_ALL_PACKAGES}) public boolean requiresDisplayCompat(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public void setAppBlockingPolicy(String, android.car.content.pm.CarAppBlockingPolicy, int);
method public void setEnableActivityBlocking(boolean);
field @Deprecated public static final int FLAG_SET_POLICY_ADD = 2; // 0x2
@@ -1038,7 +1035,7 @@
method public void onCarDisconnected();
method @RequiresPermission(android.car.Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) public boolean saveUxRestrictionsConfigurationForNextBoot(@NonNull java.util.List<android.car.drivingstate.CarUxRestrictionsConfiguration>);
method @RequiresPermission(android.car.Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) public boolean saveUxRestrictionsConfigurationForNextBoot(@NonNull android.car.drivingstate.CarUxRestrictionsConfiguration);
- method public void setListener(int, @NonNull android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener);
+ method @Deprecated public void setListener(int, @NonNull android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener);
method @RequiresPermission(android.car.Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) public boolean setRestrictionMode(@NonNull String);
field public static final String UX_RESTRICTION_MODE_BASELINE = "baseline";
}
@@ -1106,7 +1103,8 @@
public static interface CarEvsManager.CarEvsStreamCallback {
method public default void onNewFrame(@NonNull android.car.evs.CarEvsBufferDescriptor);
- method public default void onStreamEvent(int);
+ method @Deprecated public default void onStreamEvent(int);
+ method @FlaggedApi("android.car.feature.car_evs_stream_management") public default void onStreamEvent(int, int);
}
public final class CarEvsStatus implements android.os.Parcelable {
@@ -1134,21 +1132,18 @@
}
public final class CarPropertyConfig<T> implements android.os.Parcelable {
- method public static <T> android.car.hardware.CarPropertyConfig.Builder<T> newBuilder(Class<T>, int, int, int);
+ method @Deprecated public static <T> android.car.hardware.CarPropertyConfig.Builder<T> newBuilder(Class<T>, int, int, int);
}
- public static class CarPropertyConfig.Builder<T> {
- method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addArea(int);
- method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addAreaConfig(int, T, T);
- method @NonNull public android.car.hardware.CarPropertyConfig.Builder<T> addAreaIdConfig(@NonNull android.car.hardware.property.AreaIdConfig<T>);
- method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addAreas(int[]);
- method public android.car.hardware.CarPropertyConfig<T> build();
- method public android.car.hardware.CarPropertyConfig.Builder<T> setAccess(int);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setChangeMode(int);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setConfigArray(java.util.ArrayList<java.lang.Integer>);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setConfigString(String);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setMaxSampleRate(float);
- method public android.car.hardware.CarPropertyConfig.Builder<T> setMinSampleRate(float);
+ @Deprecated public static class CarPropertyConfig.Builder<T> {
+ method @Deprecated @NonNull public android.car.hardware.CarPropertyConfig.Builder<T> addAreaIdConfig(@NonNull android.car.hardware.property.AreaIdConfig<T>);
+ method @Deprecated public android.car.hardware.CarPropertyConfig<T> build();
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setAccess(int);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setChangeMode(int);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setConfigArray(java.util.ArrayList<java.lang.Integer>);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setConfigString(String);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setMaxSampleRate(float);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> setMinSampleRate(float);
}
@Deprecated public final class CarVendorExtensionManager {
@@ -1319,13 +1314,13 @@
package android.car.hardware.property {
- public static final class AreaIdConfig.Builder<T> {
- ctor public AreaIdConfig.Builder(int);
- ctor @FlaggedApi("android.car.feature.area_id_config_access") public AreaIdConfig.Builder(int, int);
- method @NonNull public android.car.hardware.property.AreaIdConfig<T> build();
- method @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setMaxValue(T);
- method @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setMinValue(T);
- method @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setSupportedEnumValues(@NonNull java.util.List<T>);
+ @Deprecated public static final class AreaIdConfig.Builder<T> {
+ ctor @Deprecated public AreaIdConfig.Builder(int);
+ ctor @Deprecated @FlaggedApi("android.car.feature.area_id_config_access") public AreaIdConfig.Builder(int, int);
+ method @Deprecated @NonNull public android.car.hardware.property.AreaIdConfig<T> build();
+ method @Deprecated @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setMaxValue(T);
+ method @Deprecated @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setMinValue(T);
+ method @Deprecated @NonNull public android.car.hardware.property.AreaIdConfig.Builder<T> setSupportedEnumValues(@NonNull java.util.List<T>);
}
public final class AutomaticEmergencyBrakingState {
@@ -2308,7 +2303,9 @@
public final class CarRemoteAccessManager {
method @FlaggedApi("android.car.feature.serverless_remote_access") @RequiresPermission(android.car.Car.PERMISSION_CONTROL_REMOTE_ACCESS) public void addServerlessRemoteTaskClient(@NonNull String, @NonNull String);
method @FlaggedApi("android.car.feature.serverless_remote_access") @Nullable @RequiresPermission(android.car.Car.PERMISSION_CONTROL_REMOTE_ACCESS) public android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler getInVehicleTaskScheduler();
+ method @FlaggedApi("android.car.feature.serverless_remote_access") @RequiresPermission(android.car.Car.PERMISSION_CONTROL_REMOTE_ACCESS) public boolean isShutdownRequestSupported();
method @FlaggedApi("android.car.feature.serverless_remote_access") @RequiresPermission(android.car.Car.PERMISSION_CONTROL_REMOTE_ACCESS) public boolean isTaskScheduleSupported();
+ method @FlaggedApi("android.car.feature.serverless_remote_access") @RequiresPermission(android.car.Car.PERMISSION_CONTROL_REMOTE_ACCESS) public boolean isVehicleInUseSupported();
method @FlaggedApi("android.car.feature.serverless_remote_access") @RequiresPermission(android.car.Car.PERMISSION_CONTROL_REMOTE_ACCESS) public void removeServerlessRemoteTaskClient(@NonNull String);
method @RequiresPermission(android.car.Car.PERMISSION_CONTROL_REMOTE_ACCESS) public void setPowerStatePostTaskExecution(int, boolean);
field public static final int NEXT_POWER_STATE_OFF = 2; // 0x2
diff --git a/car-lib/api/test-removed.txt b/car-lib/api/test-removed.txt
index bca7216..762ae83 100644
--- a/car-lib/api/test-removed.txt
+++ b/car-lib/api/test-removed.txt
@@ -7,6 +7,16 @@
}
+package android.car.hardware {
+
+ @Deprecated public static class CarPropertyConfig.Builder<T> {
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addArea(int);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addAreaConfig(int, T, T);
+ method @Deprecated public android.car.hardware.CarPropertyConfig.Builder<T> addAreas(int[]);
+ }
+
+}
+
package android.car.input {
@Deprecated public abstract class CarInputHandlingService extends android.app.Service {
diff --git a/car-lib/generated-prop-config/Android.bp b/car-lib/generated-prop-config/Android.bp
index 6672f1c..8c58a15 100644
--- a/car-lib/generated-prop-config/Android.bp
+++ b/car-lib/generated-prop-config/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/car-lib/generated-prop-config/CarSvcProps.json b/car-lib/generated-prop-config/CarSvcProps.json
index 5fb0a5a..bacaa05 100644
--- a/car-lib/generated-prop-config/CarSvcProps.json
+++ b/car-lib/generated-prop-config/CarSvcProps.json
@@ -73,7 +73,7 @@
"INFO_EV_BATTERY_CAPACITY": {
"propertyName": "INFO_EV_BATTERY_CAPACITY",
"propertyId": 291504390,
- "description": "Nominal battery capacity for EV or hybrid vehicle.",
+ "description": "Nominal usable battery capacity for EV or hybrid vehicle.",
"readPermission": {
"type": "single",
"value": "android.car.permission.CAR_INFO"
@@ -321,7 +321,7 @@
"FUEL_LEVEL": {
"propertyName": "FUEL_LEVEL",
"propertyId": 291504903,
- "description": "Fuel remaining in the vehicle in milliliters.",
+ "description": "Fuel level in milliliters.",
"readPermission": {
"type": "single",
"value": "android.car.permission.CAR_ENERGY"
@@ -361,7 +361,7 @@
"EV_CURRENT_BATTERY_CAPACITY": {
"propertyName": "EV_CURRENT_BATTERY_CAPACITY",
"propertyId": 291504909,
- "description": "Current battery capacity for EV or hybrid vehicle.",
+ "description": "Current usable battery capacity for EV or hybrid vehicle.",
"readPermission": {
"type": "single",
"value": "android.car.permission.CAR_ENERGY"
@@ -528,7 +528,7 @@
"EV_BRAKE_REGENERATION_LEVEL": {
"propertyName": "EV_BRAKE_REGENERATION_LEVEL",
"propertyId": 289408012,
- "description": "Regenerative braking level of a electronic vehicle.",
+ "description": "Regenerative braking level of an electronic vehicle.",
"readPermission": {
"type": "anyOf",
"value": [
@@ -736,7 +736,7 @@
"HVAC_TEMPERATURE_SET": {
"propertyName": "HVAC_TEMPERATURE_SET",
"propertyId": 358614275,
- "description": "HVAC, target temperature set.",
+ "description": "HVAC target temperature set in Celsius.",
"readPermission": {
"type": "single",
"value": "android.car.permission.CONTROL_CAR_CLIMATE"
@@ -2162,7 +2162,7 @@
"WINDOW_LOCK": {
"propertyName": "WINDOW_LOCK",
"propertyId": 320867268,
- "description": "Window Lock.",
+ "description": "Window Child Lock.",
"readPermission": {
"type": "single",
"value": "android.car.permission.CONTROL_CAR_WINDOWS"
@@ -2881,7 +2881,7 @@
"EV_REGENERATIVE_BRAKING_STATE": {
"propertyName": "EV_REGENERATIVE_BRAKING_STATE",
"propertyId": 289410884,
- "description": "Regenerative braking or one-pedal drive state of the car.",
+ "description": "Regenerative braking or one-pedal drive setting on the car.",
"readPermission": {
"type": "single",
"value": "android.car.permission.CAR_ENERGY"
@@ -2896,7 +2896,7 @@
"VEHICLE_CURB_WEIGHT": {
"propertyName": "VEHICLE_CURB_WEIGHT",
"propertyId": 289410886,
- "description": "Vehicle’s curb weight.",
+ "description": "Vehicle’s curb weight in kilograms.",
"readPermission": {
"type": "single",
"value": "android.car.permission.PRIVILEGED_CAR_INFO"
diff --git a/car-lib/lint-baseline.xml b/car-lib/lint-baseline.xml
new file mode 100644
index 0000000..0d1f57a
--- /dev/null
+++ b/car-lib/lint-baseline.xml
@@ -0,0 +1,697 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="Class `ClusterHomeManager` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" CAR_SERVICE_NAMES.put(ClusterHomeManager.class, CLUSTER_HOME_SERVICE);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="1741"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `ClusterHomeManager()` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `createCarManagerLocked` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" manager = new ClusterHomeManager(this, binder);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="2713"
+ column="27"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onAudioZoneConfigurationsChanged()` is a flagged API and should be inside an `if (Flags.carAudioDynamicDevices())` check (or annotate the surrounding method `onAudioZoneConfigurationsChanged` with `@FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) to transfer requirement to caller`)"
+ errorLine1=" mExecutor.execute(() -> mCallback.onAudioZoneConfigurationsChanged(configs,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/media/CarAudioManager.java"
+ line="2252"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `CarEvsBufferDescriptor()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `CarEvsBufferDescriptor` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" this(id, Flags.carEvsStreamManagement() ?"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsBufferDescriptor.java"
+ line="79"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onStreamEvent()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `handleStreamEventLocked` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" executor.execute(() -> cb.onStreamEvent(origin, event));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsManager.java"
+ line="595"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getType()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `handleNewFrame` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" buffer.getType() : CarEvsUtils.getTag(buffer.getId());"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsManager.java"
+ line="610"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getType()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `returnFrameBuffer` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" buffer.getType() : CarEvsUtils.getTag(buffer.getId());"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsManager.java"
+ line="719"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1243"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1244"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1244"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1244"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1274"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1275"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1275"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1275"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1305"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1306"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1306"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1306"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1306"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1361"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1361"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1361"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1361"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of(subscription), /* callbackExecutor= */ null,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1363"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getPropertyId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.propertyId = clientOption.getPropertyId();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1499"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getAreaIds()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.areaIds = clientOption.getAreaIds();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1500"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.updateRateHz = clientOption.getUpdateRateHz();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1501"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateEnabled()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" internalOption.enableVariableUpdateRate = clientOption.isVariableUpdateRateEnabled();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1502"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getResolution()` is a flagged API and should be inside an `if (Flags.subscriptionWithResolution())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_SUBSCRIPTION_WITH_RESOLUTION) to transfer requirement to caller`)"
+ errorLine1=" internalOption.resolution = clientOption.getResolution();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1503"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `InVehicleTaskScheduler()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" private final InVehicleTaskScheduler mInVehicleTaskScheduler = new InVehicleTaskScheduler();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="102"
+ column="68"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onServerlessClientRegistered()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `onServerlessClientRegistered` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" executor.execute(() -> callback.onServerlessClientRegistered());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="224"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateSupported()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `sanitizeEnableVariableUpdateRate` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" if (carPropertyConfig.getAreaIdConfig(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/com/android/car/internal/property/InputSanitizationUtils.java"
+ line="160"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Class `ClusterHomeManager` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" CAR_SERVICE_NAMES.put(ClusterHomeManager.class, CLUSTER_HOME_SERVICE);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="1724"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `ClusterHomeManager()` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `createCarManagerLocked` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" manager = new ClusterHomeManager(this, binder);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="2633"
+ column="27"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onAudioZoneConfigurationsChanged()` is a flagged API and should be inside an `if (Flags.carAudioDynamicDevices())` check (or annotate the surrounding method `onAudioZoneConfigurationsChanged` with `@FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) to transfer requirement to caller`)"
+ errorLine1=" mExecutor.execute(() -> mCallback.onAudioZoneConfigurationsChanged(configs,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/media/CarAudioManager.java"
+ line="2249"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `CarEvsBufferDescriptor()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `CarEvsBufferDescriptor` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" this(id, Flags.carEvsStreamManagement() ?"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsBufferDescriptor.java"
+ line="79"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1228"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1229"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1229"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1229"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1259"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1260"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1260"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1260"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1290"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of(subscription), /* callbackExecutor= */ null,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1348"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getPropertyId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.propertyId = clientOption.getPropertyId();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1484"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getAreaIds()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.areaIds = clientOption.getAreaIds();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1485"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.updateRateHz = clientOption.getUpdateRateHz();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1486"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateEnabled()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" internalOption.enableVariableUpdateRate = clientOption.isVariableUpdateRateEnabled();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1487"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getResolution()` is a flagged API and should be inside an `if (Flags.subscriptionWithResolution())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_SUBSCRIPTION_WITH_RESOLUTION) to transfer requirement to caller`)"
+ errorLine1=" internalOption.resolution = clientOption.getResolution();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1488"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `InVehicleTaskScheduler()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" private final InVehicleTaskScheduler mInVehicleTaskScheduler = new InVehicleTaskScheduler();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="102"
+ column="68"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onServerlessClientRegistered()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `onServerlessClientRegistered` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" executor.execute(() -> callback.onServerlessClientRegistered());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="224"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateSupported()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `sanitizeEnableVariableUpdateRate` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" if (carPropertyConfig.getAreaIdConfig(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/com/android/car/internal/property/InputSanitizationUtils.java"
+ line="159"
+ column="21"/>
+ </issue>
+
+</issues>
diff --git a/car-lib/src/android/car/AoapService.java b/car-lib/src/android/car/AoapService.java
index 491ac6d..e2694a8 100644
--- a/car-lib/src/android/car/AoapService.java
+++ b/car-lib/src/android/car/AoapService.java
@@ -32,7 +32,7 @@
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
-import android.util.Log;
+import android.util.Slog;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
@@ -164,7 +164,7 @@
@Override
public IBinder onBind(Intent intent) {
if (mBound) {
- Log.w(TAG, "Received onBind event when the service was already bound");
+ Slog.w(TAG, "Received onBind event when the service was already bound");
}
mBound = true;
return mMessenger.getBinder();
@@ -198,11 +198,11 @@
}
Bundle data = msg.getData();
if (data == null) {
- Log.e(TAG, "Ignoring message " + msg.what + " without data");
+ Slog.e(TAG, "Ignoring message " + msg.what + " without data");
return;
}
- Log.i(TAG, "Message received: " + msg.what);
+ Slog.i(TAG, "Message received: " + msg.what);
switch (msg.what) {
case MSG_NEW_DEVICE_ATTACHED: {
@@ -227,7 +227,7 @@
}
default:
- Log.e(TAG, "Unknown message received: " + msg.what);
+ Slog.e(TAG, "Unknown message received: " + msg.what);
break;
}
}
@@ -236,7 +236,7 @@
try {
messenger.send(createResponseMessage(msg, result));
} catch (RemoteException e) {
- Log.e(TAG, "Failed to send message", e);
+ Slog.e(TAG, "Failed to send message", e);
}
}
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index dc9a3b7..ba53f5d 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -39,6 +39,7 @@
import android.car.app.CarDisplayCompatManager;
import android.car.builtin.os.BuildHelper;
import android.car.builtin.os.ServiceManagerHelper;
+import android.car.builtin.util.Slogf;
import android.car.cluster.CarInstrumentClusterManager;
import android.car.cluster.ClusterActivityState;
import android.car.cluster.ClusterHomeManager;
@@ -79,19 +80,24 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.TransactionTooLargeException;
import android.util.ArrayMap;
-import android.util.Log;
+import android.util.Slog;
import com.android.car.internal.ICarBase;
import com.android.car.internal.VisibleForHiddenApiCheck;
import com.android.car.internal.common.CommonConstants;
import com.android.car.internal.dep.SystemProperties;
+import com.android.car.internal.os.Process;
+import com.android.car.internal.os.ServiceManager;
+import com.android.car.internal.os.SystemProcess;
+import com.android.car.internal.os.SystemServiceManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -108,7 +114,7 @@
import java.util.Objects;
/**
- * Top level car API for embedded Android Auto deployments.
+ * Top level car API for Android Automotive OS deployments.
* This API works only for devices with {@link PackageManager#FEATURE_AUTOMOTIVE}
* Calling this API on a device with no such feature will lead to an exception.
*/
@@ -124,13 +130,15 @@
"ro.android.car.version.platform_minor";
/**
- * @deprecated - use {@code getCarApiVersion().getMajorVersion()} instead
+ * @deprecated - This does not return the correct version. Use
+ * {@code getCarVersion().getMajorVersion()} instead.
*/
@Deprecated
public static final int API_VERSION_MAJOR_INT = 34;
/**
- * @deprecated - use {@code getCarApiVersion().getMinorVersion()} instead
+ * @deprecated - This does not return the correct version. Use
+ * {@code getCarVersion().getMinorVersion()} instead
*/
@Deprecated
public static final int API_VERSION_MINOR_INT = 0;
@@ -143,8 +151,13 @@
public static final int PLATFORM_VERSION_MINOR_INT = SystemProperties
.getInt(PROPERTY_PLATFORM_MINOR_VERSION, /* def= */ 0);
+ // These are the actual car api versions. Due to legacy reasons, we cannot modfiy
+ // API_VERSION_MAJOR_INT and API_VERSION_MINOR_INT because they were exposed as public.
+ private static final int ACTUAL_API_VERSION_MAJOR_INT = Build.VERSION.SDK_INT;
+ private static final int ACTUAL_API_VERSION_MINOR_INT = 0;
+
private static final CarVersion CAR_VERSION = CarVersion.newInstance("Car.CAR_VERSION",
- API_VERSION_MAJOR_INT, API_VERSION_MINOR_INT);
+ ACTUAL_API_VERSION_MAJOR_INT, ACTUAL_API_VERSION_MINOR_INT);
private static final PlatformVersion PLATFORM_VERSION;
@@ -169,7 +182,7 @@
int minor = SystemProperties.getInt(PROPERTY_EMULATED_PLATFORM_VERSION_MINOR,
PLATFORM_VERSION_MINOR_INT);
emulated = android.car.PlatformVersion.newInstance("EMULATED", major, minor);
- Log.i(TAG_CAR, "Emulating PLATFORM_VERSION version: " + emulated);
+ Slog.i(TAG_CAR, "Emulating PLATFORM_VERSION version: " + emulated);
}
}
PLATFORM_VERSION =
@@ -1310,14 +1323,14 @@
"android.car.permission.MANAGE_CAR_SYSTEM_UI";
/**
- * Permission necessary to query if a package requires launching in automotive compatibility
- * mode or not
+ * Permission necessary to manage packages that requires launching in automotive compatibility
+ * mode.
* @hide
*/
@FlaggedApi(Flags.FLAG_DISPLAY_COMPATIBILITY)
@SystemApi
- public static final String PERMISSION_QUERY_DISPLAY_COMPATIBILITY =
- "android.car.permission.QUERY_DISPLAY_COMPATIBILITY";
+ public static final String PERMISSION_MANAGE_DISPLAY_COMPATIBILITY =
+ "android.car.permission.MANAGE_DISPLAY_COMPATIBILITY";
/**
* Permission necessary to read state of car airbags.
@@ -1394,6 +1407,15 @@
"android.car.permission.READ_PERSIST_TETHERING_SETTINGS";
/**
+ * Permission necessary to bind with app card providers.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAR_APP_CARD)
+ @SystemApi
+ public static final String PERMISSION_BIND_APP_CARD_PROVIDER =
+ "android.car.permission.BIND_APP_CARD_PROVIDER";
+
+ /**
* Intent for connecting to the template renderer. Services that handle this intent must also
* hold {@link #PERMISSION_TEMPLATE_RENDERER}. Applications would not bind to this service
* directly, but instead they would use
@@ -1544,6 +1566,8 @@
private static final long CAR_SERVICE_BINDER_POLLING_INTERVAL_MS = 50;
private static final long CAR_SERVICE_BINDER_POLLING_MAX_RETRY = 100;
+ private static final long CAR_SERVICE_REGISTRATION_TIMEOUT_MS =
+ CAR_SERVICE_BINDER_POLLING_INTERVAL_MS * CAR_SERVICE_BINDER_POLLING_MAX_RETRY;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
@@ -1597,9 +1621,8 @@
public @interface FeaturerRequestEnum {}
private final Context mContext;
-
private final Exception mConstructionStack;
-
+ private final long mCreateUptimeMillis;
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -1613,6 +1636,19 @@
@GuardedBy("mLock")
private int mConnectionRetryCount;
+ // Whether we have registered the service listener. It should only be registered once.
+ @GuardedBy("mLock")
+ private boolean mServiceListenerRegistered;
+ // The car service binder object we get from ServiceManager.
+ @GuardedBy("mLock")
+ private IBinder mCarServiceBinder;
+ // Whether the client is currently waiting (blocked) for a car service connection.
+ @GuardedBy("mLock")
+ private boolean mWaiting;
+ // Whether client explicitly called disconnect.
+ @GuardedBy("mLock")
+ private boolean mClientRequestDisconnect;
+
private final Runnable mConnectionRetryRunnable = new Runnable() {
@Override
public void run() {
@@ -1628,14 +1664,41 @@
}
};
- private final ServiceConnection mServiceConnectionListener =
- new ServiceConnection() {
+ private final ServiceRegistrationCallbackForCar mServiceRegistrationCallback =
+ new ServiceRegistrationCallbackForCar();
+
+ private final class ServiceRegistrationCallbackForCar implements
+ ServiceManagerHelper.IServiceRegistrationCallback {
+ @Override
+ public void onRegistration(@NonNull String name, IBinder binder) {
+ Slog.i(TAG_CAR, "car_service registered");
+ if (!name.equals(CAR_SERVICE_BINDER_SERVICE_NAME)) {
+ Slog.wtf(TAG_CAR, "Unexpected service name called for onRegistration: " + name);
+ return;
+ }
+ synchronized (mLock) {
+ mCarServiceBinder = binder;
+ if (mWaiting) {
+ mLock.notifyAll();
+ return;
+ }
+ if (mClientRequestDisconnect) {
+ // Client explicitly called disconnect, do not invoke the callbacks.
+ return;
+ }
+ }
+
+ mMainThreadEventHandler.post(() -> setBinderAndNotifyReady(binder));
+ }
+ }
+
+ private final ServiceConnection mServiceConnectionListener = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
ICar newService = ICar.Stub.asInterface(service);
if (newService == null) {
- Log.wtf(TAG_CAR, "null binder service", new RuntimeException());
+ Slog.wtf(TAG_CAR, "null binder service", new RuntimeException());
return; // should not happen.
}
if (mService != null && mService.asBinder().equals(newService.asBinder())) {
@@ -1645,8 +1708,10 @@
mConnectionState = STATE_CONNECTED;
mService = newService;
}
+ Slog.i(TAG_CAR, "car_service ready on main thread, Time between Car object creation "
+ + "and car_service connected (ms): " + timeSinceCreateMillis());
if (mStatusChangeCallback != null) {
- mStatusChangeCallback.onLifecycleChanged(Car.this, true);
+ mStatusChangeCallback.onLifecycleChanged(Car.this, /* ready= */ true);
} else if (mServiceConnectionListenerClient != null) {
mServiceConnectionListenerClient.onServiceConnected(name, service);
}
@@ -1655,16 +1720,17 @@
@Override
public void onServiceDisconnected(ComponentName name) {
// Car service can pick up feature changes after restart.
+ Slog.w(TAG_CAR, "Car service disconnected, probably crashed");
mFeatures.resetCache();
synchronized (mLock) {
- if (mConnectionState == STATE_DISCONNECTED) {
+ if (mConnectionState == STATE_DISCONNECTED) {
// can happen when client calls disconnect before onServiceDisconnected call.
return;
}
handleCarDisconnectLocked();
}
if (mStatusChangeCallback != null) {
- mStatusChangeCallback.onLifecycleChanged(Car.this, false);
+ mStatusChangeCallback.onLifecycleChanged(Car.this, /* ready= */ false);
} else if (mServiceConnectionListenerClient != null) {
mServiceConnectionListenerClient.onServiceDisconnected(name);
} else {
@@ -1674,6 +1740,26 @@
}
};
+ private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ Slog.w(TAG_CAR, "Car service disconnected, probably crashed");
+ // Car service can pick up feature changes after restart.
+ mFeatures.resetCache();
+ synchronized (mLock) {
+ if (mConnectionState == STATE_DISCONNECTED) {
+ Slog.i(TAG_CAR, "State is already disconnected, ignore");
+ // can happen when client calls disconnect before onServiceDisconnected call.
+ return;
+ }
+ mCarServiceBinder = null;
+ handleCarDisconnectLocked();
+ }
+ dispatchToMainThread(isMainThread(), () -> notifyCarDisconnected());
+ registerServiceListenerIfNotRegistered();
+ }
+ };
+
@Nullable
private final ServiceConnection mServiceConnectionListenerClient;
@@ -1691,6 +1777,22 @@
private final CarFeatures mFeatures = new CarFeatures();
+ /**
+ * The dependencies used by this class.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public record Deps(ServiceManager serviceManager, Process process,
+ long carServiceBindRetryIntervalMs, long carServiceBindMaxRetry) {}
+
+ // Real system dependencies.
+ private static final Deps SYSTEM_DEPS = new Deps(
+ new SystemServiceManager(), new SystemProcess(),
+ CAR_SERVICE_BIND_RETRY_INTERVAL_MS, CAR_SERVICE_BIND_MAX_RETRY);
+
+ private final Deps mDeps;
+
static {
CAR_SERVICE_NAMES.put(CarSensorManager.class, SENSOR_SERVICE);
CAR_SERVICE_NAMES.put(CarInfoManager.class, INFO_SERVICE);
@@ -1745,8 +1847,11 @@
* <p>Starting on {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13}, the {@code Car}
* APIs can be upgraded without an OTA, so it's possible that these APIs are higher than the
* {@link #getPlatformVersion() platform's}.
+ *
+ * @deprecated - use {@code android.os.Build.VERSION#SDK_INT} instead
*/
@NonNull
+ @Deprecated
public static android.car.CarVersion getCarVersion() {
return CAR_VERSION;
}
@@ -1758,8 +1863,11 @@
* <p>Its {@link ApiVersion#getMajorVersion() major version} will be the same as
* {@link android.os.Build.VERSION#SDK_INT} for released build but will be
* {@link android.os.Build.VERSION_CODES#CUR_DEVELOPMENT} for platform still under development.
+ *
+ * @deprecated - use {@code android.os.Build.VERSION#SDK_INT} instead
*/
@NonNull
+ @Deprecated
public static android.car.PlatformVersion getPlatformVersion() {
return PLATFORM_VERSION;
}
@@ -1835,18 +1943,7 @@
@Deprecated
public static Car createCar(Context context, ServiceConnection serviceConnectionListener,
@Nullable Handler handler) {
- assertNonNullContext(context);
- if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
- Log.e(TAG_CAR, "FEATURE_AUTOMOTIVE not declared while android.car is used");
- return null;
- }
- try {
- return new Car(context, /* service= */ null , serviceConnectionListener,
- /* statusChangeListener= */ null, handler);
- } catch (IllegalArgumentException e) {
- // Expected when car service loader is not available.
- }
- return null;
+ return new CarBuilder().createCar(context, serviceConnectionListener, handler);
}
/**
@@ -1862,7 +1959,7 @@
*/
@Deprecated
public static Car createCar(Context context, ServiceConnection serviceConnectionListener) {
- return createCar(context, serviceConnectionListener, null);
+ return new CarBuilder().createCar(context, serviceConnectionListener);
}
/**
@@ -1877,7 +1974,7 @@
*/
@Nullable
public static Car createCar(Context context) {
- return createCar(context, (Handler) null);
+ return new CarBuilder().createCar(context);
}
/**
@@ -1897,58 +1994,7 @@
*/
@Nullable
public static Car createCar(Context context, @Nullable Handler handler) {
- assertNonNullContext(context);
- Car car = null;
- IBinder service = null;
- boolean started = false;
- int retryCount = 0;
- while (true) {
- service = ServiceManagerHelper.getService(CAR_SERVICE_BINDER_SERVICE_NAME);
- if (car == null) {
- // service can be still null. The constructor is safe for null service.
- car = new Car(context, ICar.Stub.asInterface(service),
- null /*serviceConnectionListener*/, null /*statusChangeListener*/, handler);
- }
- if (service != null) {
- if (!started) { // specialization for most common case.
- // Do this to crash client when car service crashes.
- car.startCarService();
- return car;
- }
- break;
- }
- if (!started) {
- car.startCarService();
- started = true;
- }
- retryCount++;
- if (retryCount > CAR_SERVICE_BINDER_POLLING_MAX_RETRY) {
- Log.e(TAG_CAR, "cannot get car_service, waited for car service (ms):"
- + CAR_SERVICE_BINDER_POLLING_INTERVAL_MS
- * CAR_SERVICE_BINDER_POLLING_MAX_RETRY,
- new RuntimeException());
- return null;
- }
- try {
- Thread.sleep(CAR_SERVICE_BINDER_POLLING_INTERVAL_MS);
- } catch (InterruptedException e) {
- Log.e(CarLibLog.TAG_CAR, "interrupted while waiting for car_service",
- new RuntimeException());
- return null;
- }
- }
- // Can be accessed from mServiceConnectionListener in main thread.
- synchronized (car.mLock) {
- if (car.mService == null) {
- car.mService = ICar.Stub.asInterface(service);
- Log.w(TAG_CAR,
- "waited for car_service (ms):"
- + CAR_SERVICE_BINDER_POLLING_INTERVAL_MS * retryCount,
- new RuntimeException());
- }
- car.mConnectionState = STATE_CONNECTED;
- }
- return car;
+ return new CarBuilder().createCar(context, handler);
}
/**
@@ -1993,88 +2039,411 @@
* interpreted as timeout value.
*/
@NonNull
- public static Car createCar(@NonNull Context context,
- @Nullable Handler handler, long waitTimeoutMs,
- @NonNull CarServiceLifecycleListener statusChangeListener) {
- assertNonNullContext(context);
- Objects.requireNonNull(statusChangeListener);
- Car car = null;
- IBinder service = null;
- boolean started = false;
- int retryCount = 0;
- long maxRetryCount = 0;
- if (waitTimeoutMs > 0) {
- maxRetryCount = waitTimeoutMs / CAR_SERVICE_BINDER_POLLING_INTERVAL_MS;
- // at least wait once if it is positive value.
- if (maxRetryCount == 0) {
- maxRetryCount = 1;
- }
+ public static Car createCar(@NonNull Context context, @Nullable Handler handler,
+ long waitTimeoutMs, @NonNull CarServiceLifecycleListener statusChangeListener) {
+ return new CarBuilder().createCar(context, handler, waitTimeoutMs, statusChangeListener);
+ }
+
+ /**
+ * A wrapper around {@code createCar} functions that allows injecting deps for testing.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static final class CarBuilder {
+ private Deps mDeps = SYSTEM_DEPS;
+
+ /**
+ * Sets the fake dependencies for testing.
+ */
+ public CarBuilder setFakeDeps(Deps deps) {
+ mDeps = deps;
+ return this;
}
- boolean isMainThread = Looper.myLooper() == Looper.getMainLooper();
- while (true) {
- service = ServiceManagerHelper.getService(CAR_SERVICE_BINDER_SERVICE_NAME);
- if (car == null) {
- // service can be still null. The constructor is safe for null service.
- car = new Car(context, ICar.Stub.asInterface(service), null, statusChangeListener,
- handler);
+
+ /**
+ * See {@link Car#createCar}.
+ */
+ @Nullable
+ public Car createCar(Context context, ServiceConnection serviceConnectionListener) {
+ return createCar(context, serviceConnectionListener, /* handler= */ null);
+ }
+
+ /**
+ * See {@link Car#createCar}.
+ */
+ @Nullable
+ public Car createCar(Context context, ServiceConnection serviceConnectionListener,
+ @Nullable Handler handler) {
+ assertNonNullContext(context);
+ if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ Slog.e(TAG_CAR, "FEATURE_AUTOMOTIVE not declared while android.car is used");
+ return null;
}
- if (service != null) {
- if (!started) { // specialization for most common case : car service already ready
- car.dispatchCarReadyToMainThread(isMainThread);
- // Needs this for CarServiceLifecycleListener. Note that ServiceConnection
- // will skip the callback as valid mService is set already.
- car.startCarService();
- return car;
- }
- // service available after starting.
- break;
+ try {
+ return new Car(context, /* service= */ null , serviceConnectionListener,
+ /* statusChangeListener= */ null, handler, mDeps);
+ } catch (IllegalArgumentException e) {
+ // Expected when car service loader is not available.
}
- if (!started) {
- car.startCarService();
- started = true;
+ return null;
+ }
+
+ /**
+ * See {@link Car#createCar}.
+ */
+ @Nullable
+ public Car createCar(Context context) {
+ return createCar(context, (Handler) null);
+ }
+
+ /**
+ * See {@link Car#createCar}.
+ */
+ @Nullable
+ public Car createCar(Context context, @Nullable Handler handler) {
+ assertNonNullContext(context);
+
+ if (Flags.createCarUseNotifications()) {
+ // New optimized logic.
+ return createCarInternal(context, handler, CAR_SERVICE_REGISTRATION_TIMEOUT_MS,
+ /* statusChangeListener= */ null);
}
- retryCount++;
- if (waitTimeoutMs < 0 && retryCount >= CAR_SERVICE_BINDER_POLLING_MAX_RETRY
- && retryCount % CAR_SERVICE_BINDER_POLLING_MAX_RETRY == 0) {
- // Log warning if car service is not alive even for waiting forever case.
- Log.w(TAG_CAR, "car_service not ready, waited for car service (ms):"
- + retryCount * CAR_SERVICE_BINDER_POLLING_INTERVAL_MS,
- new RuntimeException());
- } else if (waitTimeoutMs >= 0 && retryCount > maxRetryCount) {
- if (waitTimeoutMs > 0) {
- Log.w(TAG_CAR, "car_service not ready, waited for car service (ms):"
- + waitTimeoutMs,
- new RuntimeException());
- }
+
+ return createCarInternalLegacy(context, handler);
+ }
+
+ /**
+ * See {@link Car#createCar}.
+ */
+ @NonNull
+ public Car createCar(@NonNull Context context, @Nullable Handler handler,
+ long waitTimeoutMs, @NonNull CarServiceLifecycleListener statusChangeListener) {
+ assertNonNullContext(context);
+ Objects.requireNonNull(statusChangeListener);
+
+ if (Flags.createCarUseNotifications()) {
+ // New optimized logic.
+ // statusChangeListener is not null, so this must not return null.
+ return createCarInternal(context, handler, waitTimeoutMs, statusChangeListener);
+ }
+
+ return createCarInternalLegacy(context, handler, waitTimeoutMs, statusChangeListener);
+ }
+
+ private @Nullable Car createCarInternal(Context context,
+ @Nullable Handler handler, long waitTimeoutMs,
+ @Nullable CarServiceLifecycleListener statusChangeListener) {
+ Car car = new Car(context, /* service= */ null, /* serviceConnectionListener= */ null,
+ statusChangeListener, handler, mDeps);
+ IBinder binderService = mDeps.serviceManager().getService(
+ CAR_SERVICE_BINDER_SERVICE_NAME);
+ if (binderService != null) {
+ // Most common case when car service is already ready.
+ car.setCarService(binderService);
+ Slogf.i(TAG_CAR, "createCar car_service is already ready, took (ms): %d",
+ car.timeSinceCreateMillis());
+ car.notifyCarReadyOnMainThread(binderService);
return car;
}
+ car.registerServiceListenerIfNotRegistered();
+ IBinder serviceBinder = car.waitForCarServiceReady(waitTimeoutMs);
+
+ if (serviceBinder == null) {
+ Slog.w(TAG_CAR,
+ "createCar (waitTimeoutMs=" + waitTimeoutMs
+ + ") car_service not ready, took (ms):"
+ + car.timeSinceCreateMillis());
+ if (statusChangeListener == null) {
+ return null;
+ }
+ // Return a car instance that is not connected to the car service.
+ return car;
+ }
+
+ Slog.i(TAG_CAR, "createCar (waitTimeoutMs=" + waitTimeoutMs
+ + ") connected to car_service, took (ms): "
+ + car.timeSinceCreateMillis());
+ car.notifyCarReadyOnMainThread(serviceBinder);
+ return car;
+ }
+
+ // Legacy createCar implementation.
+ private @Nullable Car createCarInternalLegacy(Context context, @Nullable Handler handler) {
+ Car car = null;
+ IBinder service = null;
+ boolean started = false;
+ int retryCount = 0;
+ while (true) {
+ service = mDeps.serviceManager().getService(CAR_SERVICE_BINDER_SERVICE_NAME);
+ if (car == null) {
+ // service can be still null. The constructor is safe for null service.
+ car = new Car(context, ICar.Stub.asInterface(service),
+ null /*serviceConnectionListener*/, null /*statusChangeListener*/,
+ handler, mDeps);
+ }
+ if (service != null) {
+ if (!started) { // specialization for most common case.
+ // Do this to crash client when car service crashes.
+ car.startCarService();
+ return car;
+ }
+ break;
+ }
+ if (!started) {
+ car.startCarService();
+ started = true;
+ }
+ retryCount++;
+ if (retryCount > CAR_SERVICE_BINDER_POLLING_MAX_RETRY) {
+ Slog.e(TAG_CAR, "cannot get car_service, waited for car service (ms):"
+ + CAR_SERVICE_BINDER_POLLING_INTERVAL_MS
+ * CAR_SERVICE_BINDER_POLLING_MAX_RETRY,
+ new RuntimeException());
+ return null;
+ }
+ try {
+ Thread.sleep(CAR_SERVICE_BINDER_POLLING_INTERVAL_MS);
+ } catch (InterruptedException e) {
+ Slog.e(CarLibLog.TAG_CAR, "interrupted while waiting for car_service",
+ new RuntimeException());
+ return null;
+ }
+ }
+ // Can be accessed from mServiceConnectionListener in main thread.
+ synchronized (car.mLock) {
+ if (car.mService == null) {
+ car.mService = ICar.Stub.asInterface(service);
+ Slog.w(TAG_CAR,
+ "waited for car_service (ms):"
+ + CAR_SERVICE_BINDER_POLLING_INTERVAL_MS * retryCount,
+ new RuntimeException());
+ }
+ car.mConnectionState = STATE_CONNECTED;
+ }
+ return car;
+ }
+
+ // Legacy createCar implementation.
+ private Car createCarInternalLegacy(@NonNull Context context,
+ @Nullable Handler handler, long waitTimeoutMs,
+ @NonNull CarServiceLifecycleListener statusChangeListener) {
+ Car car = null;
+ IBinder service = null;
+ boolean started = false;
+ int retryCount = 0;
+ long maxRetryCount = 0;
+ if (waitTimeoutMs > 0) {
+ maxRetryCount = waitTimeoutMs / CAR_SERVICE_BINDER_POLLING_INTERVAL_MS;
+ // at least wait once if it is positive value.
+ if (maxRetryCount == 0) {
+ maxRetryCount = 1;
+ }
+ }
+ boolean isMainThread = Looper.myLooper() == Looper.getMainLooper();
+ while (true) {
+ service = mDeps.serviceManager().getService(CAR_SERVICE_BINDER_SERVICE_NAME);
+ if (car == null) {
+ // service can be still null. The constructor is safe for null service.
+ car = new Car(context, ICar.Stub.asInterface(service), null,
+ statusChangeListener, handler, mDeps);
+ }
+ if (service != null) {
+ // specialization for most common case : car service already ready
+ if (!started) {
+ car.dispatchCarReadyToMainThread(isMainThread);
+ // Needs this for CarServiceLifecycleListener. Note that ServiceConnection
+ // will skip the callback as valid mService is set already.
+ car.startCarService();
+ return car;
+ }
+ // service available after starting.
+ break;
+ }
+ if (!started) {
+ car.startCarService();
+ started = true;
+ }
+ retryCount++;
+ if (waitTimeoutMs < 0 && retryCount >= CAR_SERVICE_BINDER_POLLING_MAX_RETRY
+ && retryCount % CAR_SERVICE_BINDER_POLLING_MAX_RETRY == 0) {
+ // Log warning if car service is not alive even for waiting forever case.
+ Slog.w(TAG_CAR, "car_service not ready, waited for car service (ms):"
+ + retryCount * CAR_SERVICE_BINDER_POLLING_INTERVAL_MS,
+ new RuntimeException());
+ } else if (waitTimeoutMs >= 0 && retryCount > maxRetryCount) {
+ if (waitTimeoutMs > 0) {
+ Slog.w(TAG_CAR, "car_service not ready, waited for car service (ms):"
+ + waitTimeoutMs,
+ new RuntimeException());
+ }
+ return car;
+ }
+
+ try {
+ Thread.sleep(CAR_SERVICE_BINDER_POLLING_INTERVAL_MS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Slog.w(TAG_CAR, "interrupted", new RuntimeException());
+ return car;
+ }
+ }
+ // Can be accessed from mServiceConnectionListener in main thread.
+ synchronized (car.mLock) {
+ Slog.w(TAG_CAR,
+ "waited for car_service (ms):"
+ + retryCount * CAR_SERVICE_BINDER_POLLING_INTERVAL_MS,
+ new RuntimeException());
+ // ServiceConnection has handled everything.
+ if (car.mService != null) {
+ return car;
+ }
+ // mService check in ServiceConnection prevents calling
+ // onLifecycleChanged. So onLifecycleChanged should be called explicitly
+ // but do it outside lock.
+ car.mService = ICar.Stub.asInterface(service);
+ car.mConnectionState = STATE_CONNECTED;
+ }
+ Slog.i(TAG_CAR, "createCar car_service is ready, took (ms): "
+ + car.timeSinceCreateMillis());
+ car.dispatchCarReadyToMainThread(isMainThread);
+ return car;
+ }
+ }
+
+ private long timeSinceCreateMillis() {
+ return SystemClock.uptimeMillis() - mCreateUptimeMillis;
+ }
+
+ private void registerServiceListenerIfNotRegistered() {
+ synchronized (mLock) {
+ if (mServiceListenerRegistered) {
+ return;
+ }
try {
- Thread.sleep(CAR_SERVICE_BINDER_POLLING_INTERVAL_MS);
+ mDeps.serviceManager().registerForNotifications(CAR_SERVICE_BINDER_SERVICE_NAME,
+ mServiceRegistrationCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG_CAR, "failed to call ServiceManager.registerForNotifications", e);
+ return;
+ }
+ mServiceListenerRegistered = true;
+ return;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void waitForCarServiceBinderNoTimeoutLocked() throws InterruptedException {
+ // First wait for 5s.
+ waitForCarServiceBinderLocked(CAR_SERVICE_REGISTRATION_TIMEOUT_MS);
+ if (mCarServiceBinder != null) {
+ return;
+ }
+ // Log warning if car service is not alive even for waiting forever case.
+ Slog.w(TAG_CAR,
+ "createCar (wait indefinitely) still cannot get car_service after "
+ + CAR_SERVICE_REGISTRATION_TIMEOUT_MS + "ms");
+ // If we still cannot get car service, then wait forever.
+ while (mCarServiceBinder == null) {
+ // await in a loop to prevent spurious wakeup.
+ mLock.wait();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void waitForCarServiceBinderLocked(long waitTimeoutMs) throws InterruptedException {
+ long deadlineMillis = SystemClock.uptimeMillis() + waitTimeoutMs;
+ boolean stillWaiting = true;
+ while (mCarServiceBinder == null) {
+ long uptimeMillis = SystemClock.uptimeMillis();
+ if (uptimeMillis >= deadlineMillis) {
+ break;
+ }
+ Slog.w(TAG_CAR, "wait: " + (deadlineMillis - uptimeMillis));
+ mLock.wait(deadlineMillis - uptimeMillis);
+ Slog.w(TAG_CAR, "after wait");
+ }
+ }
+
+ private @Nullable IBinder waitForCarServiceReady(long waitTimeoutMs) {
+ if (waitTimeoutMs == 0) {
+ return null;
+ }
+
+ IBinder serviceBinder;
+ synchronized (mLock) {
+ mWaiting = true;
+ try {
+ if (waitTimeoutMs < 0) {
+ waitForCarServiceBinderNoTimeoutLocked();
+ } else {
+ waitForCarServiceBinderLocked(waitTimeoutMs);
+ }
+ serviceBinder = mCarServiceBinder;
} catch (InterruptedException e) {
+ Slog.e(TAG_CAR, "Interrupted while waiting for car_service");
Thread.currentThread().interrupt();
- Log.w(TAG_CAR, "interrupted", new RuntimeException());
- return car;
+ return null;
+ } finally {
+ mWaiting = false;
}
- }
- // Can be accessed from mServiceConnectionListener in main thread.
- synchronized (car.mLock) {
- Log.w(TAG_CAR,
- "waited for car_service (ms):"
- + retryCount * CAR_SERVICE_BINDER_POLLING_INTERVAL_MS,
- new RuntimeException());
- // ServiceConnection has handled everything.
- if (car.mService != null) {
- return car;
+ if (serviceBinder == null) {
+ // Cannot get car service binder before timeout.
+ return null;
}
- // mService check in ServiceConnection prevents calling
- // onLifecycleChanged. So onLifecycleChanged should be called explicitly
- // but do it outside lock.
- car.mService = ICar.Stub.asInterface(service);
- car.mConnectionState = STATE_CONNECTED;
+ setCarServiceLocked(serviceBinder);
}
- car.dispatchCarReadyToMainThread(isMainThread);
- return car;
+ return serviceBinder;
+ }
+
+ private void setCarService(IBinder carServiceBinder) {
+ synchronized (mLock) {
+ setCarServiceLocked(carServiceBinder);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void setCarServiceLocked(IBinder carServiceBinder) {
+ ICar newService = ICar.Stub.asInterface(carServiceBinder);
+ if (newService == null) {
+ Slogf.wtf(TAG_CAR, "null binder service", new RuntimeException());
+ return; // should not happen.
+ }
+ mConnectionState = STATE_CONNECTED;
+ mService = newService;
+ try {
+ carServiceBinder.linkToDeath(mDeathRecipient, /* flags= */ 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG_CAR, "Failed to call linkToDeath on car service binder, will not receive "
+ + "callback if car service crashes", e);
+ }
+ }
+
+ private void notifyCarReady(IBinder serviceBinder) {
+ if (mStatusChangeCallback != null) {
+ mStatusChangeCallback.onLifecycleChanged(/* car= */ this, /* ready= */ true);
+ } else if (mServiceConnectionListenerClient != null) {
+ mServiceConnectionListenerClient.onServiceConnected(
+ new ComponentName(CAR_SERVICE_PACKAGE, CAR_SERVICE_CLASS), serviceBinder);
+ }
+ }
+
+ private void notifyCarDisconnected() {
+ Slog.i(TAG_CAR, "notify car service disconnected");
+ if (mStatusChangeCallback != null) {
+ mStatusChangeCallback.onLifecycleChanged(Car.this, /* ready= */ false);
+ } else if (mServiceConnectionListenerClient != null) {
+ mServiceConnectionListenerClient.onServiceDisconnected(
+ new ComponentName(CAR_SERVICE_PACKAGE, CAR_SERVICE_CLASS));
+ } else {
+ // This client does not handle car service restart, so should be terminated.
+ finishClient();
+ }
+ }
+
+ private static boolean isMainThread() {
+ return Looper.myLooper() == Looper.getMainLooper();
}
private static void assertNonNullContext(Context context) {
@@ -2086,20 +2455,37 @@
}
}
- private void dispatchCarReadyToMainThread(boolean isMainThread) {
+ private void dispatchToMainThread(boolean isMainThread, Runnable runnable) {
if (isMainThread) {
- mStatusChangeCallback.onLifecycleChanged(this, true);
+ runnable.run();
} else {
// should dispatch to main thread.
- mMainThreadEventHandler.post(
- () -> mStatusChangeCallback.onLifecycleChanged(this, true));
+ mMainThreadEventHandler.post(runnable);
}
}
+ private void notifyCarReadyOnMainThread(IBinder serviceBinder) {
+ dispatchToMainThread(isMainThread(), () -> notifyCarReady(serviceBinder));
+ }
+
+ private void dispatchCarReadyToMainThread(boolean isMainThread) {
+ dispatchToMainThread(isMainThread,
+ () -> mStatusChangeCallback.onLifecycleChanged(/* car= */ this, /* ready= */ true));
+ }
+
+ private void setBinderAndNotifyReady(IBinder binder) {
+ setCarService(binder);
+ Slog.i(TAG_CAR, "car_service ready on main thread, Time between Car object creation"
+ + " and car_service connected (ms): " + timeSinceCreateMillis());
+ notifyCarReady(binder);
+ }
+
private Car(Context context, @Nullable ICar service,
@Nullable ServiceConnection serviceConnectionListener,
@Nullable CarServiceLifecycleListener statusChangeListener,
- @Nullable Handler handler) {
+ @Nullable Handler handler, Deps deps) {
+ mDeps = deps;
+ mCreateUptimeMillis = SystemClock.uptimeMillis();
mContext = context;
mEventHandler = determineEventHandler(handler);
mMainThreadEventHandler = determineMainThreadEventHandler(mEventHandler);
@@ -2127,7 +2513,7 @@
*/
public Car(Context context, @Nullable ICar service, @Nullable Handler handler) {
this(context, service, null /*serviceConnectionListener*/, null /*statusChangeListener*/,
- handler);
+ handler, SYSTEM_DEPS);
}
private static Handler determineMainThreadEventHandler(Handler eventHandler) {
@@ -2146,22 +2532,38 @@
}
/**
- * Connect to car service. This can be called while it is disconnected.
- * @throws IllegalStateException If connection is still on-going from previous
- * connect call or it is already connected
- *
* @deprecated this method is not need if this object is created via
* {@link #createCar(Context, Handler)}.
*/
@Deprecated
public void connect() throws IllegalStateException {
+ IBinder carServiceBinder = null;
synchronized (mLock) {
if (mConnectionState != STATE_DISCONNECTED) {
throw new IllegalStateException("already connected or connecting");
}
+ mClientRequestDisconnect = false;
mConnectionState = STATE_CONNECTING;
- startCarService();
+ if (!Flags.createCarUseNotifications()) {
+ // Ideally this should not be inside the lock, but legacy logic run this inside
+ // the lock.
+ startCarService();
+ return;
+ }
+
+ carServiceBinder = mCarServiceBinder;
}
+
+ if (carServiceBinder != null) {
+ // If we already have a car service binder ready. This means this is a reconnect
+ // after disconnect or car service crash. And car service is already ready
+ // before connecting.
+
+ // Need this to make carServiceBinder final.
+ IBinder binder = carServiceBinder;
+ dispatchToMainThread(isMainThread(), () -> setBinderAndNotifyReady(binder));
+ }
+ registerServiceListenerIfNotRegistered();
}
@GuardedBy("mLock")
@@ -2187,6 +2589,7 @@
*/
public void disconnect() {
synchronized (mLock) {
+ mClientRequestDisconnect = true;
handleCarDisconnectLocked();
if (mServiceBound) {
mContext.unbindService(mServiceConnectionListener);
@@ -2205,7 +2608,7 @@
return;
}
try {
- Log.v(TAG_CAR, "Calling disconnect() on finalize()");
+ Slog.v(TAG_CAR, "Calling disconnect() on finalize()");
disconnect();
} finally {
super.finalize();
@@ -2256,7 +2659,7 @@
CarManagerBase manager;
synchronized (mLock) {
if (mService == null) {
- Log.w(TAG_CAR, "getCarManager not working while car service not ready");
+ Slog.w(TAG_CAR, "getCarManager not working while car service not ready");
return null;
}
manager = mServiceMap.get(serviceName);
@@ -2264,13 +2667,13 @@
try {
IBinder binder = mService.getCarService(serviceName);
if (binder == null) {
- Log.w(TAG_CAR, "getCarManager could not get binder for service:"
+ Slog.w(TAG_CAR, "getCarManager could not get binder for service:"
+ serviceName);
return null;
}
manager = createCarManagerLocked(serviceName, binder);
if (manager == null) {
- Log.w(TAG_CAR, "getCarManager could not create manager for service:"
+ Slog.w(TAG_CAR, "getCarManager could not create manager for service:"
+ serviceName);
return null;
}
@@ -2473,10 +2876,10 @@
@VisibleForHiddenApiCheck
public void handleRemoteExceptionFromCarService(RemoteException e) {
if (e instanceof TransactionTooLargeException) {
- Log.w(TAG_CAR, "Car service threw TransactionTooLargeException", e);
+ Slog.w(TAG_CAR, "Car service threw TransactionTooLargeException", e);
throw new CarTransactionException(e, "Car service threw TransactionTooLargeException");
} else {
- Log.w(TAG_CAR, "Car service has crashed", e);
+ Slog.w(TAG_CAR, "Car service has crashed", e);
}
}
@@ -2487,7 +2890,7 @@
if (mContext instanceof Activity) {
Activity activity = (Activity) mContext;
if (!activity.isFinishing()) {
- Log.w(TAG_CAR,
+ Slog.w(TAG_CAR,
"Car service crashed, client not handling it, finish Activity, created "
+ "from " + mConstructionStack);
activity.finish();
@@ -2502,12 +2905,12 @@
}
private void killClient(@Nullable String clientInfo) {
- Log.w(TAG_CAR, "**Car service has crashed. Client(" + clientInfo + ") is not handling it."
+ Slog.w(TAG_CAR, "**Car service has crashed. Client(" + clientInfo + ") is not handling it."
+ " Client should use Car.createCar(..., CarServiceLifecycleListener, .."
+ ".) to handle it properly. Check printed callstack to check where other "
+ "version of Car.createCar() was called. Killing the client process**",
mConstructionStack);
- Process.killProcess(Process.myPid());
+ mDeps.process().killProcess(mDeps.process().myPid());
}
/** @hide */
@@ -2522,13 +2925,13 @@
@VisibleForHiddenApiCheck
public static void handleRemoteExceptionFromCarService(Service service, RemoteException e) {
if (e instanceof TransactionTooLargeException) {
- Log.w(TAG_CAR, "Car service threw TransactionTooLargeException, client:"
+ Slog.w(TAG_CAR, "Car service threw TransactionTooLargeException, client:"
+ service.getPackageName() + ","
+ service.getClass().getSimpleName(), e);
throw new CarTransactionException(e, "Car service threw TransactionTooLargeException, "
+ "client: %s, %s", service.getPackageName(), service.getClass().getSimpleName());
} else {
- Log.w(TAG_CAR, "Car service has crashed, client:"
+ Slog.w(TAG_CAR, "Car service has crashed, client:"
+ service.getPackageName() + ","
+ service.getClass().getSimpleName(), e);
service.stopSelf();
@@ -2633,7 +3036,7 @@
manager = new ClusterHomeManager(this, binder);
break;
case CAR_EVS_SERVICE:
- manager = new CarEvsManager(this, binder);
+ manager = new CarEvsManager(this, binder, /* featureFlags= */ null);
break;
case CAR_TELEMETRY_SERVICE:
manager = new CarTelemetryManager(this, binder);
@@ -2677,7 +3080,7 @@
return null;
}
if (className == null) {
- Log.e(TAG_CAR, "Cannot construct CarManager for service:" + serviceName
+ Slog.e(TAG_CAR, "Cannot construct CarManager for service:" + serviceName
+ " : no class defined");
return null;
}
@@ -2698,7 +3101,7 @@
return manager;
} catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException
| InstantiationException | InvocationTargetException e) {
- Log.e(TAG_CAR, "Cannot construct CarManager, class:" + className, e);
+ Slog.e(TAG_CAR, "Cannot construct CarManager, class:" + className, e);
return null;
}
}
@@ -2712,12 +3115,12 @@
synchronized (mLock) {
if (!bound) {
mConnectionRetryCount++;
- if (mConnectionRetryCount > CAR_SERVICE_BIND_MAX_RETRY) {
- Log.w(TAG_CAR, "cannot bind to car service after max retry");
+ if (mConnectionRetryCount > mDeps.carServiceBindMaxRetry()) {
+ Slog.w(TAG_CAR, "cannot bind to car service after max retry");
mMainThreadEventHandler.post(mConnectionRetryFailedRunnable);
} else {
mEventHandler.postDelayed(mConnectionRetryRunnable,
- CAR_SERVICE_BIND_RETRY_INTERVAL_MS);
+ mDeps.carServiceBindRetryIntervalMs());
}
} else {
mEventHandler.removeCallbacks(mConnectionRetryRunnable);
diff --git a/car-lib/src/android/car/CarManagerBase.java b/car-lib/src/android/car/CarManagerBase.java
index 323243c..0882354 100644
--- a/car-lib/src/android/car/CarManagerBase.java
+++ b/car-lib/src/android/car/CarManagerBase.java
@@ -26,6 +26,7 @@
import android.util.Dumpable;
import android.util.DumpableContainer;
import android.util.Log;
+import android.util.Slog;
import com.android.car.internal.ICarBase;
@@ -70,12 +71,12 @@
}
if (e instanceof RuntimeException) {
- Log.w(TAG_CAR, "Car service threw Runtime Exception.", e);
+ Slog.w(TAG_CAR, "Car service threw Runtime Exception.", e);
return returnValue;
}
// exception should be either runtime or remote exception
- Log.wtf(TAG_CAR, "Car service threw Exception.", e);
+ Slog.wtf(TAG_CAR, "Car service threw Exception.", e);
return returnValue;
}
@@ -100,7 +101,8 @@
if (container instanceof Activity) {
T dumpable = dumpableSupplier.get();
if (DEBUG) {
- Log.d(TAG_CAR, "Adding " + dumpable.getDumpableName() + " to actvity " + container);
+ Slog.d(TAG_CAR, "Adding " + dumpable.getDumpableName() + " to actvity "
+ + container);
}
((Activity) container).addDumpable(dumpable);
return dumpable;
@@ -108,13 +110,13 @@
if (container instanceof DumpableContainer) {
T dumpable = dumpableSupplier.get();
if (DEBUG) {
- Log.d(TAG_CAR, "Adding " + dumpable.getDumpableName() + " to DumpableContainer "
+ Slog.d(TAG_CAR, "Adding " + dumpable.getDumpableName() + " to DumpableContainer "
+ container);
}
((DumpableContainer) container).addDumpable(dumpable);
return dumpable;
}
- Log.v(TAG_CAR, "NOT adding dumpable to object (" + container
+ Slog.v(TAG_CAR, "NOT adding dumpable to object (" + container
+ ") that doesn't implement addDumpable");
return null;
}
diff --git a/car-lib/src/android/car/CarOccupantZoneManager.java b/car-lib/src/android/car/CarOccupantZoneManager.java
index c677f4b..e8c17df 100644
--- a/car-lib/src/android/car/CarOccupantZoneManager.java
+++ b/car-lib/src/android/car/CarOccupantZoneManager.java
@@ -34,7 +34,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
-import android.util.Log;
+import android.util.Slog;
import android.view.Display;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
@@ -121,6 +121,12 @@
*/
public static final int DISPLAY_TYPE_AUXILIARY_5 = 9;
+ /**
+ * Display specifically used for showing display compatibility apps.
+ * @hide
+ */
+ public static final int DISPLAY_TYPE_DISPLAY_COMPATIBILITY = 10;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "DISPLAY_TYPE_", value = {
@@ -134,6 +140,7 @@
DISPLAY_TYPE_AUXILIARY_3,
DISPLAY_TYPE_AUXILIARY_4,
DISPLAY_TYPE_AUXILIARY_5,
+ DISPLAY_TYPE_DISPLAY_COMPATIBILITY,
})
@Target({ElementType.TYPE_USE})
public @interface DisplayTypeEnum {}
@@ -829,7 +836,7 @@
handleOnOccupantZoneConfigChanged(msg.arg1);
break;
default:
- Log.e(TAG, "Unknown msg not handdled:" + msg.what);
+ Slog.e(TAG, "Unknown msg not handdled:" + msg.what);
break;
}
}
diff --git a/car-lib/src/android/car/CarProjectionManager.java b/car-lib/src/android/car/CarProjectionManager.java
index 4a1e3ea..3de9cc8 100644
--- a/car-lib/src/android/car/CarProjectionManager.java
+++ b/car-lib/src/android/car/CarProjectionManager.java
@@ -43,8 +43,8 @@
import android.os.Messenger;
import android.os.RemoteException;
import android.util.ArraySet;
-import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.view.KeyEvent;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
@@ -341,12 +341,12 @@
} else if (keyEvent == KEY_EVENT_VOICE_SEARCH_LONG_PRESS_KEY_DOWN) {
fromLongPress = true;
} else {
- Log.e(TAG, "Unexpected key event " + keyEvent);
+ Slog.e(TAG, "Unexpected key event " + keyEvent);
return;
}
}
- Log.d(TAG, "Voice assistant request, long-press = " + fromLongPress);
+ Slog.d(TAG, "Voice assistant request, long-press = " + fromLongPress);
legacyListener.onVoiceAssistantRequest(fromLongPress);
}
@@ -460,11 +460,11 @@
try {
if (!events.isEmpty()) {
- Log.d(TAG, "Registering handler with system for " + events);
+ Slog.d(TAG, "Registering handler with system for " + events);
byte[] eventMask = events.toByteArray();
mService.registerKeyEventHandler(mBinderHandler, eventMask);
} else {
- Log.d(TAG, "Unregistering handler with system");
+ Slog.d(TAG, "Unregistering handler with system");
mService.unregisterKeyEventHandler(mBinderHandler);
}
} catch (RemoteException e) {
@@ -825,18 +825,18 @@
mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
- Log.d(TAG, LOG_PREFIX + "handle message what: " + msg.what + " msg: " + msg);
+ Slog.d(TAG, LOG_PREFIX + "handle message what: " + msg.what + " msg: " + msg);
CarProjectionManager manager = mCarProjectionManagerRef.get();
if (manager == null) {
- Log.w(TAG, LOG_PREFIX + "handle message post GC");
+ Slog.w(TAG, LOG_PREFIX + "handle message post GC");
return;
}
switch (msg.what) {
case PROJECTION_AP_STARTED:
if (msg.obj == null) {
- Log.e(TAG, LOG_PREFIX + "config cannot be null.");
+ Slog.e(TAG, LOG_PREFIX + "config cannot be null.");
callback.onFailed(ProjectionAccessPointCallback.ERROR_GENERIC);
return;
}
@@ -847,17 +847,17 @@
}
break;
case PROJECTION_AP_STOPPED:
- Log.i(TAG, LOG_PREFIX + "hotspot stopped");
+ Slog.i(TAG, LOG_PREFIX + "hotspot stopped");
callback.onStopped();
break;
case PROJECTION_AP_FAILED:
int reasonCode = msg.arg1;
- Log.w(TAG, LOG_PREFIX + "failed to start. reason: "
+ Slog.w(TAG, LOG_PREFIX + "failed to start. reason: "
+ reasonCode);
callback.onFailed(reasonCode);
break;
default:
- Log.e(TAG, LOG_PREFIX + "unhandled message. type: " + msg.what);
+ Slog.e(TAG, LOG_PREFIX + "unhandled message. type: " + msg.what);
}
}
};
@@ -880,7 +880,7 @@
@Override
public void onKeyEvent(@KeyEventNum int event) {
- Log.d(TAG, "Received projection key event " + event);
+ Slog.d(TAG, "Received projection key event " + event);
final CarProjectionManager manager = mManager.get();
if (manager == null) {
return;
diff --git a/car-lib/src/android/car/VehicleIgnitionState.java b/car-lib/src/android/car/VehicleIgnitionState.java
index 4f77ca3..b967171 100644
--- a/car-lib/src/android/car/VehicleIgnitionState.java
+++ b/car-lib/src/android/car/VehicleIgnitionState.java
@@ -45,6 +45,10 @@
/**
* Steering wheel is not locked, engine and all accessories are off. If car can be in {@code
* LOCK} and {@code OFF} state at the same time than HAL must report {@code LOCK} state.
+ *
+ * <p>If {@link android.car.VehiclePropertyIds#IGNITION_STATE} is implemented on a BEV, then
+ * this state communicates the BEV's High Voltage battery is disconnected and thus the vehicle
+ * is OFF.
*/
public static final int OFF = 2;
@@ -57,6 +61,10 @@
/**
* Ignition is in state on. Accessories and instrument cluster available, engine might be
* running or ready to be started.
+ *
+ * <p>If {@link android.car.VehiclePropertyIds#IGNITION_STATE} is implemented on a BEV, then
+ * this state communicates the BEV's High Voltage battery is connected and thus the vehicle is
+ * ON.
*/
public static final int ON = 4;
diff --git a/car-lib/src/android/car/VehiclePropertyIds.java b/car-lib/src/android/car/VehiclePropertyIds.java
index 7a4d306..e9c5ac3 100644
--- a/car-lib/src/android/car/VehiclePropertyIds.java
+++ b/car-lib/src/android/car/VehiclePropertyIds.java
@@ -40,6 +40,7 @@
import android.car.hardware.property.EmergencyLaneKeepAssistState;
import android.car.hardware.property.ErrorState;
import android.car.hardware.property.EvChargeState;
+import android.car.hardware.property.EvChargingConnectorType;
import android.car.hardware.property.EvRegenerativeBrakingState;
import android.car.hardware.property.EvStoppingMode;
import android.car.hardware.property.ForwardCollisionWarningState;
@@ -100,6 +101,8 @@
/**
* Manufacturer of vehicle.
*
+ * <p>This property communicates the vehicle's public brand name.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
@@ -119,6 +122,8 @@
/**
* Model of vehicle.
*
+ * <p>This property communicates the vehicle's public model name.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
@@ -157,6 +162,11 @@
/**
* Fuel capacity of the vehicle in milliliters.
*
+ * <p>This property communicates the maximum amount of the fuel that can be stored in the
+ * vehicle in milliliters. This property will not be implemented for electric vehicles. That is,
+ * if {@link #INFO_FUEL_TYPE} only contains {@link FuelType#ELECTRIC}, this property will not be
+ * implemented. For EVs, see {@link #INFO_EV_BATTERY_CAPACITY}.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
@@ -182,13 +192,15 @@
* the vehicle.
*
* <p>For example:
- * <p>FHEVs (Fully Hybrid Electric Vehicles) will not include {@link FuelType#ELECTRIC} in its
+ * <ul>
+ * <li>FHEVs (Fully Hybrid Electric Vehicles) will not include {@link FuelType#ELECTRIC} in its
* {@code Integer[]} value. So {@code INFO_FUEL_TYPE} will be populated as such:
* { {@link FuelType#UNLEADED} }.
- * <p>On the other hand, PHEVs (Partially Hybrid Electric Vehicles) are plug in rechargeable,
+ * <li>On the other hand, PHEVs (Plug-in Hybrid Electric Vehicles) are plug in rechargeable,
* and hence will include {@link FuelType#ELECTRIC} in {@code INFO_FUEL_TYPE}'s {@code
* Integer[]} value. So {@code INFO_FUEL_TYPE} will be populated as such:
* { {@link FuelType#UNLEADED}, {@link FuelType#ELECTRIC} }.
+ * </ul>
*
* <p>Property Config:
* <ul>
@@ -209,13 +221,13 @@
@RequiresPermission(Car.PERMISSION_CAR_INFO)
public static final int INFO_FUEL_TYPE = 289472773;
/**
- * Nominal battery capacity for EV or hybrid vehicle.
+ * Nominal usable battery capacity for EV or hybrid vehicle.
*
* <p>Returns the nominal battery capacity in {@link android.car.VehicleUnit#WATT_HOUR}, if EV
- * or hybrid. This is the battery capacity when the vehicle is new. This value might be
+ * or hybrid. This is the usable battery capacity when the vehicle is new. This value might be
* different from {@link #EV_CURRENT_BATTERY_CAPACITY} because {@link
- * #EV_CURRENT_BATTERY_CAPACITY} returns the real-time battery capacity taking into account
- * factors such as battery aging and temperature dependency.
+ * #EV_CURRENT_BATTERY_CAPACITY} returns the real-time usable battery capacity taking into
+ * account factors such as battery aging and temperature dependency.
*
* <p>Property Config:
* <ul>
@@ -236,6 +248,9 @@
/**
* List of {@link android.car.hardware.property.EvChargingConnectorType}s this vehicle may use.
*
+ * <p>If the vehicle has multiple charging ports, this property will return all possible
+ * connector types that can be used by at least one charging port on the vehicle.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
@@ -250,13 +265,18 @@
* <li>Property is not writable.
* </ul>
*
- * @data_enum {@link EvConnectorType}
+ * @data_enum {@link EvChargingConnectorType}
*/
@RequiresPermission(Car.PERMISSION_CAR_INFO)
public static final int INFO_EV_CONNECTOR_TYPE = 289472775;
/**
* {@link PortLocationType} for the fuel door location.
*
+ * <p>This property communicates the location of the fuel door on the vehicle. This property
+ * will not be implemented for electric vehicles. That is, if {@link #INFO_FUEL_TYPE} only
+ * contains {@link FuelType#ELECTRIC}, this property will not be implemented. For EVs, see
+ * {@link #INFO_EV_PORT_LOCATION} or {@link #INFO_MULTI_EV_PORT_LOCATIONS}.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
@@ -278,6 +298,11 @@
/**
* {@link PortLocationType} for the EV port location.
*
+ * <p>This property communicates the location of the charging port on the EV. If there are
+ * multiple ports on the vehicle, this will communicate the port that enables the fastest
+ * charging on the vehicle. See {@link #INFO_MULTI_EV_PORT_LOCATIONS} to get information on all
+ * port locations.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
@@ -344,14 +369,6 @@
/**
* Vehicle's exterior dimensions in millimeters.
*
- * <p>Property Config:
- * <ul>
- * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
- * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
- * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_STATIC}
- * <li>{@code Integer[]} property type
- * </ul>
- *
* <p>Exterior dimensions defined in the {@link CarPropertyValue#getValue()} {@code Integer[]}:
* <ul>
* <li>Integer[0] = height
@@ -364,6 +381,14 @@
* <li>Integer[7] = curb to curb turning diameter
* </ul>
*
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_STATIC}
+ * <li>{@code Integer[]} property type
+ * </ul>
+ *
* <p>Required Permission:
* <ul>
* <li>Normal permission {@link Car#PERMISSION_CAR_INFO} to read property.
@@ -394,6 +419,12 @@
/**
* Speed of the vehicle in meters per second.
*
+ * <p>When the vehicle is moving forward, {@code PERF_VEHICLE_SPEED} is positive and negative
+ * when the vehicle is moving backward. Also, this value is independent of gear value ({@link
+ * #CURRENT_GEAR} or {@link #GEAR_SELECTION}). For example, if {@link #GEAR_SELECTION} is
+ * {@link VehicleGear#GEAR_NEUTRAL}, {@code PERF_VEHICLE_SPEED} is positive when the vehicle is
+ * moving forward, negative when moving backward, and zero when not moving.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
@@ -402,12 +433,6 @@
* <li>{@code Float} property type
* </ul>
*
- * <p>When the vehicle is moving forward, {@code PERF_VEHICLE_SPEED} is positive and negative
- * when the vehicle is moving backward. Also, this value is independent of gear value ({@link
- * #CURRENT_GEAR} or {@link #GEAR_SELECTION}). For example, if {@link #GEAR_SELECTION} is
- * {@link VehicleGear#GEAR_NEUTRAL}, {@code PERF_VEHICLE_SPEED} is positive when the vehicle is
- * moving forward, negative when moving backward, and zero when not moving.
- *
* <p>Required Permission:
* <ul>
* <li>Dangerous permission {@link Car#PERMISSION_SPEED} to read property.
@@ -419,6 +444,9 @@
/**
* Speed of the vehicle in meters per second for displays.
*
+ * <p>Some cars display a slightly slower speed than the actual speed. This is
+ * usually displayed on the speedometer.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
@@ -427,9 +455,6 @@
* <li>{@code Float} property type
* </ul>
*
- * <p>Some cars display a slightly slower speed than the actual speed. This is
- * usually displayed on the speedometer.
- *
* <p>Required Permission:
* <ul>
* <li>Dangerous permission {@link Car#PERMISSION_SPEED} to read property.
@@ -443,6 +468,10 @@
*
* <p>Left is negative.
*
+ * <p>This property is independent of the angle of the steering wheel. This property
+ * communicates the angle of the front wheels with respect to the vehicle, not the angle of the
+ * steering wheel.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
@@ -465,6 +494,10 @@
*
* <p>Left is negative.
*
+ * <p>This property is independent of the angle of the steering wheel. This property
+ * communicates the angle of the rear wheels with respect to the vehicle, not the angle of the
+ * steering wheel.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
@@ -632,14 +665,6 @@
/**
* Reports wheel ticks.
*
- * <p>Property Config:
- * <ul>
- * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
- * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
- * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}
- * <li>{@code Long[]} property type
- * </ul>
- *
* <p>The first element in the array is a reset count. A reset indicates
* previous tick counts are not comparable with this and future ones. Some
* sort of discontinuity in tick counting has occurred.
@@ -677,6 +702,14 @@
*
* <p>NOTE: If a wheel is not supported, its value is always 0.
*
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}
+ * <li>{@code Long[]} property type
+ * </ul>
+ *
* <p>Required Permission:
* <ul>
* <li>Dangerous permission {@link Car#PERMISSION_SPEED} to read property.
@@ -686,7 +719,14 @@
@RequiresPermission(Car.PERMISSION_SPEED)
public static final int WHEEL_TICK = 290521862;
/**
- * Fuel remaining in the vehicle in milliliters.
+ * Fuel level in milliliters.
+ *
+ * <p>This property communicates the current amount of fuel remaining in the vehicle in
+ * milliliters. This property will not be implemented for electric vehicles. That is, if {@link
+ * #INFO_FUEL_TYPE} only contains {@link FuelType#ELECTRIC}, this property will not be
+ * implemented. For EVs, see {@link #EV_BATTERY_LEVEL}.
+ *
+ * <p>This property's value will not exceed {@link #INFO_FUEL_CAPACITY}.
*
* <p>Property Config:
* <ul>
@@ -707,6 +747,11 @@
/**
* Fuel door open.
*
+ * <p>This property communicates whether the fuel door on the vehicle is open or not. This
+ * property will not be implemented for electric vehicles. That is, if {@link #INFO_FUEL_TYPE}
+ * only contains {@link FuelType#ELECTRIC}, this property will not be implemented. For EVs, see
+ * {@link #EV_CHARGE_PORT_OPEN}.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE} or
@@ -753,12 +798,12 @@
@RequiresPermission(Car.PERMISSION_ENERGY)
public static final int EV_BATTERY_LEVEL = 291504905;
/**
- * Current battery capacity for EV or hybrid vehicle.
+ * Current usable battery capacity for EV or hybrid vehicle.
*
* <p>Returns the actual value of battery capacity in {@link android.car.VehicleUnit#WATT_HOUR},
- * if EV or hybrid. This property captures the real-time battery capacity taking into account
- * factors such as battery aging and temperature dependency. Therefore, this value might be
- * different from {@link #INFO_EV_BATTERY_CAPACITY} because {@link #INFO_EV_BATTERY_CAPACITY}
+ * if EV or hybrid. This property captures the real-time usable battery capacity taking into
+ * account factors such as battery aging and temperature dependency. Therefore, this value might
+ * be different from {@link #INFO_EV_BATTERY_CAPACITY} because {@link #INFO_EV_BATTERY_CAPACITY}
* returns the nominal battery capacity from when the vehicle was new.
*
* <p>Property Config:
@@ -780,6 +825,9 @@
/**
* EV charge port open.
*
+ * <p>If the vehicle has multiple charging ports, this property will return true if any of the
+ * charge ports are open.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE} or
@@ -804,6 +852,9 @@
/**
* EV charge port connected.
*
+ * <p>If the vehicle has multiple charging ports, this property will return true if any of the
+ * charge ports are connected.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
@@ -944,14 +995,6 @@
/**
* Currently selected gear by user.
*
- * <p>Property Config:
- * <ul>
- * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
- * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
- * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
- * <li>{@code Integer} property type
- * </ul>
- *
* <p> See {@link VehicleGear} for gear value enum.
*
* <p>configArray represents the list of supported gears for the vehicle. For example,
@@ -985,15 +1028,6 @@
* <li>...
* </ul>
*
- * <p>Requires permission: {@link Car#PERMISSION_POWERTRAIN}.
- *
- * @data_enum {@link VehicleGear}
- */
- @RequiresPermission(Car.PERMISSION_POWERTRAIN)
- public static final int GEAR_SELECTION = 289408000;
- /**
- * Vehicle transmission's current {@link VehicleGear}.
- *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
@@ -1002,6 +1036,19 @@
* <li>{@code Integer} property type
* </ul>
*
+ * <p>Required Permission:
+ * <ul>
+ * <li>Normal permission {@link Car#PERMISSION_POWERTRAIN} to read property.
+ * <li>Property is not writable.
+ * </ul>
+ *
+ * @data_enum {@link VehicleGear}
+ */
+ @RequiresPermission(Car.PERMISSION_POWERTRAIN)
+ public static final int GEAR_SELECTION = 289408000;
+ /**
+ * Vehicle transmission's current {@link VehicleGear}.
+ *
* <p>{@code CURRENT_GEAR}'s value may not match that of {@link
* VehiclePropertyIds#GEAR_SELECTION}. For example, if the {@link
* VehiclePropertyIds#GEAR_SELECTION} is {@link VehicleGear#GEAR_DRIVE} in a vehicle with an
@@ -1039,6 +1086,14 @@
* <li>...
* </ul>
*
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Integer} property type
+ * </ul>
+ *
* <p>Required Permission:
* <ul>
* <li>Normal permission {@link Car#PERMISSION_POWERTRAIN} to read property.
@@ -1101,13 +1156,13 @@
@RequiresPermission(Car.PERMISSION_POWERTRAIN)
public static final int PARKING_BRAKE_AUTO_APPLY = 287310851;
/**
- * Regenerative braking level of a electronic vehicle.
+ * Regenerative braking level of an electronic vehicle.
*
- * <p>Returns the current regenerative braking level. Larger values mean more energy regenerated
- * from braking while smaller values mean less energy regenerated from braking. 0 means no
- * regenerative braking. See {@link android.car.hardware.property.AreaIdConfig#getMaxValue()}
- * and {@link android.car.hardware.property.AreaIdConfig#getMinValue()} for the range of
- * possible values.
+ * <p>Returns the current setting for the regenerative braking level. Larger setting values mean
+ * more energy regenerated from braking while smaller setting values mean less energy
+ * regenerated from braking. 0 means the setting for no regenerative braking. See {@link
+ * android.car.hardware.property.AreaIdConfig#getMaxValue()} and {@link
+ * android.car.hardware.property.AreaIdConfig#getMinValue()} for the range of possible values.
*
* <p>Property Config:
* <ul>
@@ -1194,6 +1249,9 @@
/**
* Night mode.
*
+ * <p>True indicates that the night mode sensor has detected that the car cabin environment has
+ * low light.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
@@ -1202,9 +1260,6 @@
* <li>{@code Boolean} property type
* </ul>
*
- * <p>True indicates that the night mode sensor has detected that the car cabin environment has
- * low light.
- *
* <p>Required Permission:
* <ul>
* <li>Normal permission {@link Car#PERMISSION_EXTERIOR_ENVIRONMENT} to read property.
@@ -1436,10 +1491,26 @@
@RequiresPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE)
public static final int HVAC_TEMPERATURE_CURRENT = 358614274;
/**
- * HVAC, target temperature set.
+ * HVAC target temperature set in Celsius.
*
- * <p>The {@code configArray} is used to indicate the valid values for HVAC in Fahrenheit and
- * Celsius. Android might use it in the HVAC app UI.
+ * <p>{@link android.car.hardware.property.AreaIdConfig#getMinValue()} indicates the minimum
+ * temperature setting in Celsius.
+ * <p>{@link android.car.hardware.property.AreaIdConfig#getMaxValue()} indicates the maximum
+ * temperature setting in Celsius.
+ *
+ * <p>The vehicle may not support setting a continuous range of temperature values in between
+ * the min and max values.
+ *
+ * <p>Therefore, if the vehicle supports {@link #HVAC_TEMPERATURE_VALUE_SUGGESTION}, the
+ * application should use that property to get a supported value before setting {@code
+ * HVAC_TEMPERATURE_SET}. The application should also use {@link
+ * #HVAC_TEMPERATURE_VALUE_SUGGESTION} for converting the temperature from Celsius to Fahrenheit
+ * and vice versa for this vehicle.
+ *
+ * <p>Else if the {@link android.car.hardware.CarPropertyConfig#getConfigArray()} is defined,
+ * then it represents the list of valid temperature values that can be set. It also describes a
+ * lookup table to convert the temperature from Celsius to Fahrenheit and vice versa for this
+ * vehicle.
*
* <p>The {@code configArray} is set as follows:
* <ul>
@@ -1458,18 +1529,39 @@
* <p>For example, if the vehicle supports temperature values as:
* <pre>
* [16.0, 16.5, 17.0 ,..., 28.0] in Celsius
- * [60.5, 61.5, 62.5 ,..., 85.5] in Fahrenheit
+ * [60.5, 61.5, 62.5 ,..., 84.5] in Fahrenheit
* </pre>
*
* <p>The {@code configArray} should be:
* <pre>
- * configArray = {160, 280, 5, 605, 855, 10}.
+ * {@code configArray = {160, 280, 5, 605, 845, 10}}
* </pre>
*
- * <p>If the vehicle supports {@link VehiclePropertyIds#HVAC_TEMPERATURE_VALUE_SUGGESTION},
- * the application can use that property to get the suggested value before setting
- * {@code HVAC_TEMPERATURE_SET}. Otherwise, the application may choose the
- * value in {@code configArray} of {@code HVAC_TEMPERATURE_SET} by itself.
+ * <p>If the {@code configArray} is defined, applications should not use any other method for
+ * converting temperature values besides {@link #HVAC_TEMPERATURE_VALUE_SUGGESTION}, such as the
+ * standard unit conversion formula of {@code F = (9/5) * C + 32}. Any value set that is not
+ * contained within the list of valid values is considered undefined behavior and may result in
+ * inconsistencies in the value set by the application and the value set in the VHAL.
+ *
+ * <p>For converting the temperature from Celsius to Fahrenheit use the following:
+ * <pre>
+ * {@code
+ * // Given tempC and configArray
+ * float minTempC = configArray.get(0) / 10f;
+ * float temperatureIncrementCelsius = configArray.get(2) / 10f;
+ * float minTempF = configArray.get(3) / 10f;
+ * float temperatureIncrementFahrenheit = configArray.get(5) / 10f;
+ * // Round to the closest increment
+ * int numIncrements = Math.round((tempC - minTempC) / temperatureIncrementCelsius);
+ * float tempF = temperatureIncrementFahrenheit * numIncrements + minTempF;
+ * }
+ * </pre>
+ * <p>For converting the temperature from Fahrenheit to Celsius, use the same method as above
+ * except changing the Celsius values to the relevant Fahrenheit values.
+ *
+ * <p>Othwerise, if neither {@link #HVAC_TEMPERATURE_VALUE_SUGGESTION} nor the {@code
+ * configArray} are defined, the application should use the standard unit conversion formula of
+ * {@code F = (9/5) * C + 32}.
*
* <p>Property Config:
* <ul>
@@ -2130,10 +2222,9 @@
*
* <p>This property is not supported.
*
- * use {@link android.car.hardware.power.CarPowerManager} instead.
- *
- * @to_be_deprecated
+ * @deprecated use {@link android.car.hardware.power.CarPowerManager} instead.
*/
+ @Deprecated
@RequiresPermission(Car.PERMISSION_CAR_POWER)
public static final int AP_POWER_STATE_REQ = 289475072;
/**
@@ -2143,10 +2234,9 @@
*
* <p>This property is not supported.
*
- * use {@link android.car.hardware.power.CarPowerManager} instead.
- *
- * @to_be_deprecated
+ * @deprecated use {@link android.car.hardware.power.CarPowerManager} instead.
*/
+ @Deprecated
@RequiresPermission(Car.PERMISSION_CAR_POWER)
public static final int AP_POWER_STATE_REPORT = 289475073;
/**
@@ -2156,10 +2246,9 @@
*
* <p>This property is not supported.
*
- * use {@link android.car.hardware.power.CarPowerManager} instead.
- *
- * @to_be_deprecated
+ * @deprecated use {@link android.car.hardware.power.CarPowerManager} instead.
*/
+ @Deprecated
@RequiresPermission(Car.PERMISSION_CAR_POWER)
public static final int AP_POWER_BOOTUP_REASON = 289409538;
/**
@@ -2169,10 +2258,9 @@
*
* <p>This property is not supported.
*
- * use {@link android.car.hardware.power.CarPowerManager} instead.
- *
- * @to_be_deprecated
+ * @deprecated use {@link android.car.hardware.power.CarPowerManager} instead.
*/
+ @Deprecated
@RequiresPermission(Car.PERMISSION_CAR_POWER)
public static final int DISPLAY_BRIGHTNESS = 289409539;
/**
@@ -2182,10 +2270,9 @@
*
* <p>This property is not supported.
*
- * use {@link android.car.hardware.power.CarPowerManager} instead.
- *
- * @to_be_deprecated
+ * @deprecated use {@link android.car.hardware.power.CarPowerManager} instead.
*/
+ @Deprecated
@RequiresPermission(Car.PERMISSION_CAR_POWER)
public static final int PER_DISPLAY_BRIGHTNESS = 289475076;
/**
@@ -2267,10 +2354,9 @@
*
* <p>This property is not supported.
*
- * use {@link android.car.input.CarInputManager} instead.
- *
- * @to_be_deprecated
+ * @deprecated car service internal use only.
*/
+ @Deprecated
public static final int HW_KEY_INPUT = 289475088;
/**
* Door position.
@@ -2338,6 +2424,8 @@
/**
* Door lock.
*
+ * <p>True indicates that the door is locked.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE} or
@@ -2512,6 +2600,8 @@
/**
* Mirror Lock.
*
+ * <p>True indicates all mirror positions are locked and not changeable.
+ *
* <p>Property Config:
* <ul>
* <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE} or
@@ -4017,7 +4107,9 @@
@RequiresPermission(Car.PERMISSION_CONTROL_CAR_WINDOWS)
public static final int WINDOW_MOVE = 322964417;
/**
- * Window Lock.
+ * Window Child Lock.
+ *
+ * <p>True indicates that the window is child-locked.
*
* <p>Property Config:
* <ul>
@@ -4385,10 +4477,9 @@
*
* <p>This property is not supported.
*
- * use {@link android.car.vms.VmsClientManager} instead.
- *
- * @to_be_deprecated
+ * @deprecated use {@link android.car.vms.VmsClientManager} instead.
*/
+ @Deprecated
@RequiresPermission(anyOf = {Car.PERMISSION_VMS_PUBLISHER, Car.PERMISSION_VMS_SUBSCRIBER})
public static final int VEHICLE_MAP_SERVICE = 299895808;
/**
@@ -4681,10 +4772,9 @@
*
* <p>This property is not supported.
*
- * use {@link android.car.diagnostic.CarDiagnosticManager} instead.
- *
- * @to_be_deprecated
+ * @deprecated use {@link android.car.diagnostic.CarDiagnosticManager} instead.
*/
+ @Deprecated
@RequiresPermission(Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL)
public static final int OBD2_LIVE_FRAME = 299896064;
/**
@@ -4694,10 +4784,9 @@
*
* <p>This property is not supported.
*
- * use {@link android.car.diagnostic.CarDiagnosticManager} instead.
- *
- * @to_be_deprecated
+ * @deprecated use {@link android.car.diagnostic.CarDiagnosticManager} instead.
*/
+ @Deprecated
@RequiresPermission(Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL)
public static final int OBD2_FREEZE_FRAME = 299896065;
/**
@@ -4707,10 +4796,9 @@
*
* <p>This property is not supported.
*
- * use {@link android.car.diagnostic.CarDiagnosticManager} instead.
- *
- * @to_be_deprecated
+ * @deprecated use {@link android.car.diagnostic.CarDiagnosticManager} instead.
*/
+ @Deprecated
@RequiresPermission(Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL)
public static final int OBD2_FREEZE_FRAME_INFO = 299896066;
/**
@@ -4720,10 +4808,9 @@
*
* <p>This property is not supported.
*
- * use {@link android.car.diagnostic.CarDiagnosticManager} instead.
- *
- * @to_be_deprecated
+ * @deprecated use {@link android.car.diagnostic.CarDiagnosticManager} instead.
*/
+ @Deprecated
@RequiresPermission(Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR)
public static final int OBD2_FREEZE_FRAME_CLEAR = 299896067;
/**
@@ -5662,9 +5749,12 @@
/**
* Charging state of the car.
*
- * <p>Returns the current charging state of the car. See
- * {@link android.car.hardware.property.EvChargeState} for possible values for
- * {@code EV_CHARGE_STATE}.
+ * <p>Returns the current charging state of the car. See {@link
+ * android.car.hardware.property.EvChargeState} for possible values for {@code EV_CHARGE_STATE}.
+ *
+ * <p>If the vehicle has a target charge percentage other than 100, this property will return
+ * {@link EvChargeState#STATE_FULLY_CHARGED} when the battery charge level has reached the
+ * target level. See {@link #EV_CHARGE_PERCENT_LIMIT} for more context.
*
* <p>Property Config:
* <ul>
@@ -5736,12 +5826,15 @@
public static final int EV_CHARGE_TIME_REMAINING = 289410883;
/**
- * Regenerative braking or one-pedal drive state of the car.
+ * Regenerative braking or one-pedal drive setting on the car.
*
* <p>Returns the current state associated with the regenerative braking
- * setting in the car. See
- * {@link android.car.hardware.property.EvRegenerativeBrakingState} for possible values for
- * {@code EV_REGENERATIVE_BRAKING_STATE}.
+ * setting in the car. See {@link android.car.hardware.property.EvRegenerativeBrakingState} for
+ * possible values for {@code EV_REGENERATIVE_BRAKING_STATE}.
+ *
+ * <p>If the {@link #EV_BRAKE_REGENERATION_LEVEL} property has been implemented, it is likely
+ * that the OEM supports a more granular set of regeneration levels than those provided by this
+ * property through {@link EvRegenerativeBrakingState}.
*
* <p>Property Config:
* <ul>
@@ -5763,10 +5856,13 @@
public static final int EV_REGENERATIVE_BRAKING_STATE = 289410884;
/**
- * Vehicle’s curb weight.
+ * Vehicle’s curb weight in kilograms.
*
- * <p>Returns the vehicle's curb weight in kilograms. configArray[0] specifies the vehicle’s
- * gross weight in kilograms.
+ * <p>Returns the vehicle's curb weight in kilograms. This is the total weight of a vehicle,
+ * inclusive of standard equipment and necessary operating fluids such as motor oil,
+ * transmission oil and brake fluid, but without passengers or cargo. configArray[0] specifies
+ * the vehicle’s gross weight in kilograms. This is the vehicle curb weight plus the maximum
+ * payload (passengers + cargo) the vehicle can support.
*
* <p>Property Config:
* <ul>
diff --git a/car-lib/src/android/car/admin/CarDevicePolicyManager.java b/car-lib/src/android/car/admin/CarDevicePolicyManager.java
index 2638528..e49c4fc 100644
--- a/car-lib/src/android/car/admin/CarDevicePolicyManager.java
+++ b/car-lib/src/android/car/admin/CarDevicePolicyManager.java
@@ -37,7 +37,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.Log;
+import android.util.Slog;
import com.android.car.internal.ResultCallbackImpl;
import com.android.car.internal.common.UserHelperLite;
@@ -163,9 +163,9 @@
TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
- Log.e(TAG, "CarDevicePolicyManager removeUser(user): ", e);
+ Slog.e(TAG, "CarDevicePolicyManager removeUser(user): ", e);
} catch (TimeoutException e) {
- Log.e(TAG, "CarDevicePolicyManager removeUser(user): ", e);
+ Slog.e(TAG, "CarDevicePolicyManager removeUser(user): ", e);
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e,
new RemoveUserResult(UserRemovalResult.STATUS_ANDROID_FAILURE));
diff --git a/car-lib/src/android/car/admin/CreateUserResult.java b/car-lib/src/android/car/admin/CreateUserResult.java
index 7681330..a52cf44 100644
--- a/car-lib/src/android/car/admin/CreateUserResult.java
+++ b/car-lib/src/android/car/admin/CreateUserResult.java
@@ -21,7 +21,7 @@
import android.annotation.SystemApi;
import android.car.user.UserCreationResult;
import android.os.UserHandle;
-import android.util.Log;
+import android.util.Slog;
import com.android.car.internal.util.DebugUtils;
import com.android.internal.annotations.VisibleForTesting;
@@ -84,7 +84,7 @@
if (status == UserCreationResult.STATUS_SUCCESSFUL) {
mUserHandle = result.getUser();
if (mUserHandle == null) {
- Log.w(TAG, "Successful UserCreationResult with no user: " + result);
+ Slog.w(TAG, "Successful UserCreationResult with no user: " + result);
mStatus = STATUS_FAILURE_GENERIC;
} else {
mStatus = STATUS_SUCCESS;
diff --git a/car-lib/src/android/car/annotation/AddedIn.java b/car-lib/src/android/car/annotation/AddedIn.java
index 962284f..fd65f1d 100644
--- a/car-lib/src/android/car/annotation/AddedIn.java
+++ b/car-lib/src/android/car/annotation/AddedIn.java
@@ -28,8 +28,7 @@
/**
* Tells in which version of car API this method / type / field was added.
* <p> For items marked with this, the client need to make sure to check car API version using
- * {@link android.car.Car#API_VERSION_MAJOR_INT} for major version and
- * {@link android.car.Car#API_VERSION_MINOR_INT} for minor version.
+ * {@link android.car.Car#getCarVersion}.
*
* @deprecated use {@link ApiRequirements} instead.
*
diff --git a/car-lib/src/android/car/annotation/AddedInOrBefore.java b/car-lib/src/android/car/annotation/AddedInOrBefore.java
index afea970..13f165c 100644
--- a/car-lib/src/android/car/annotation/AddedInOrBefore.java
+++ b/car-lib/src/android/car/annotation/AddedInOrBefore.java
@@ -29,8 +29,7 @@
* Tells in or before which version of car API this method / type / field was added.
*
* <p> For items marked with this, the client need to make sure to check car API version using
- * {@link android.car.Car#API_VERSION_MAJOR_INT} for major version and
- * {@link android.car.Car#API_VERSION_MINOR_INT} for minor version. Should only be used for
+ * {@link android.car.Car#getCarVersio()}. Should only be used for
* {@code majorVersion = 33, minorVersion = 0}.
*
* @deprecated - use {@code ApiRequirements} instead.
diff --git a/car-lib/src/android/car/app/CarActivityManager.java b/car-lib/src/android/car/app/CarActivityManager.java
index bb02c16..0c4f803 100644
--- a/car-lib/src/android/car/app/CarActivityManager.java
+++ b/car-lib/src/android/car/app/CarActivityManager.java
@@ -41,8 +41,8 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
-import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.view.Display;
import android.view.SurfaceControl;
@@ -509,7 +509,7 @@
private boolean hasValidToken() {
boolean valid = mTaskMonitorToken != null;
if (!valid) {
- Log.w(TAG, "Has invalid token, skip the operation: "
+ Slog.w(TAG, "Has invalid token, skip the operation: "
+ new Throwable().getStackTrace()[1].getMethodName());
}
return valid;
diff --git a/car-lib/src/android/car/app/CarDisplayCompatContainer.java b/car-lib/src/android/car/app/CarDisplayCompatContainer.java
index 9aadc00..cd4f815 100644
--- a/car-lib/src/android/car/app/CarDisplayCompatContainer.java
+++ b/car-lib/src/android/car/app/CarDisplayCompatContainer.java
@@ -15,19 +15,31 @@
*/
package android.car.app;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.UiThread;
import android.app.Activity;
import android.car.Car;
+import android.car.builtin.util.Slogf;
+import android.car.builtin.view.ViewHelper;
import android.car.feature.Flags;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.SurfaceView;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Consumer;
/**
@@ -35,56 +47,52 @@
*
* @hide
*/
+//TODO: b/329677726 Allow setting width, height and density from CarDisplayCompatContainer
@FlaggedApi(Flags.FLAG_DISPLAY_COMPATIBILITY)
@SystemApi
public final class CarDisplayCompatContainer {
+ private static final String TAG = CarDisplayCompatContainer.class.getSimpleName();
+ @NonNull
+ private final CarActivityManager mCarActivityManager;
+ @NonNull
+ private Activity mActivity;
+ @Nullable
+ private Consumer<SurfaceView> mSurfaceViewCallback;
+ @Nullable
+ private CarTaskViewController mCarTaskViewController;
+ @Nullable
+ private RemoteCarTaskView mRemoteCarTaskView;
+ @Nullable
+ private Intent mIntent;
+ @FlaggedApi(Flags.FLAG_DISPLAY_COMPATIBILITY)
public static final class Builder {
@NonNull
private Activity mActivity;
- private int mWidth;
- private int mHeight;
- private int mDensityDpi;
@Nullable
- private Consumer<SurfaceView> mCallback;
+ private Consumer<SurfaceView> mSurfaceViewCallBack;
+ @NonNull
+ private CarActivityManager mCarActivityManager;
public Builder(@NonNull Activity activity) {
mActivity = activity;
}
/**
- * set width in px
- */
- @NonNull
- public Builder setWidth(int width) {
- mWidth = width;
- return this;
- }
-
- /**
- * set height in px
- */
- @NonNull
- public Builder setHeight(int height) {
- mHeight = height;
- return this;
- }
-
- /**
- * set density in dpi
- */
- @NonNull
- public Builder setDensity(int densityDpi) {
- mDensityDpi = densityDpi;
- return this;
- }
-
- /**
* set density in dpi
*/
@NonNull
public Builder setSurfaceViewCallback(@Nullable Consumer<SurfaceView> callback) {
- mCallback = callback;
+ mSurfaceViewCallBack = callback;
+ return this;
+ }
+
+ /**
+ * set a car instance
+ */
+ @NonNull
+ Builder setCarActivityManager(@NonNull CarActivityManager carActivityManager) {
+ mCarActivityManager = carActivityManager;
return this;
}
@@ -94,15 +102,19 @@
@NonNull
CarDisplayCompatContainer build() {
return new CarDisplayCompatContainer(
- mActivity, mWidth, mHeight, mDensityDpi, mCallback);
+ mActivity, mSurfaceViewCallBack, mCarActivityManager);
}
}
/**
* @hide
*/
- CarDisplayCompatContainer(@NonNull Activity activity, int width, int height, int densityDpi,
- @Nullable Consumer<SurfaceView> callback) {
+ CarDisplayCompatContainer(@NonNull Activity activity,
+ @Nullable Consumer<SurfaceView> callback,
+ @NonNull CarActivityManager carActivityManager) {
+ mActivity = activity;
+ mSurfaceViewCallback = callback;
+ mCarActivityManager = carActivityManager;
}
/**
@@ -110,21 +122,17 @@
*
* @hide
*/
+ @UiThread
@SystemApi
- @RequiresPermission(Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY)
+ @RequiresPermission(Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY)
@NonNull
public Rect setWindowBounds(@NonNull Rect windowBounds) {
- return new Rect();
- }
-
- /**
- * Set the density of the display compat container
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY)
- public void setDensity(int density) {
+ if (mRemoteCarTaskView != null) {
+ mRemoteCarTaskView.setWindowBounds(windowBounds);
+ }
+ Rect actualWindowBounds = new Rect();
+ ViewHelper.getBoundsOnScreen(mRemoteCarTaskView, actualWindowBounds);
+ return actualWindowBounds;
}
/**
@@ -133,9 +141,13 @@
*
* @hide
*/
+ @UiThread
@SystemApi
- @RequiresPermission(Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY)
+ @RequiresPermission(Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY)
public void setVisibility(int visibility) {
+ if (mRemoteCarTaskView != null) {
+ mRemoteCarTaskView.setVisibility(visibility);
+ }
}
/**
@@ -143,9 +155,22 @@
*
* @hide
*/
+ @UiThread
@SystemApi
- @RequiresPermission(Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY)
+ @RequiresPermission(allOf = {Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY,
+ Car.PERMISSION_MANAGE_CAR_SYSTEM_UI,
+ INTERACT_ACROSS_USERS})
public void startActivity(@NonNull Intent intent, @Nullable Bundle bundle) {
+ mIntent = intent;
+ Context context = mActivity.getApplicationContext();
+ RemoteCarRootTaskViewCallback remoteCarTaskViewCallback =
+ new RemoteCarRootTaskViewCallbackImpl(mRemoteCarTaskView,
+ intent, context, mSurfaceViewCallback);
+ CarTaskViewControllerCallback carTaskViewControllerCallback =
+ new CarTaskViewControllerCallbackImpl(remoteCarTaskViewCallback);
+
+ mCarActivityManager.getCarTaskViewController(mActivity, mActivity.getMainExecutor(),
+ carTaskViewControllerCallback);
}
/**
@@ -153,8 +178,90 @@
*
* @hide
*/
+ @UiThread
@SystemApi
- @RequiresPermission(Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY)
- public void onBackPressed() {
+ @RequiresPermission(Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY)
+ public void notifyBackPressed() {
+ }
+
+ private static final class RemoteCarRootTaskViewCallbackImpl implements
+ RemoteCarRootTaskViewCallback {
+ private RemoteCarTaskView mRemoteCarTaskView;
+ private final Intent mIntent;
+ private final Context mContext;
+ private Consumer<SurfaceView> mSurfaceViewCallback;
+ private RemoteCarRootTaskViewCallbackImpl(
+ RemoteCarTaskView remoteCarTaskView,
+ Intent intent,
+ Context context,
+ Consumer<SurfaceView> callback) {
+ mRemoteCarTaskView = remoteCarTaskView;
+ mIntent = intent;
+ mContext = context;
+ mSurfaceViewCallback = callback;
+ }
+
+ @Override
+ public void onTaskViewCreated(@NonNull RemoteCarRootTaskView taskView) {
+ mRemoteCarTaskView = taskView;
+ if (mSurfaceViewCallback != null) {
+ mSurfaceViewCallback.accept(mRemoteCarTaskView);
+ }
+ if (mIntent != null) {
+ mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(mIntent);
+ }
+ }
+ }
+
+ private final class CarTaskViewControllerCallbackImpl implements CarTaskViewControllerCallback {
+ private final RemoteCarRootTaskViewCallback mRemoteCarRootTaskViewCallback;
+
+ private CarTaskViewControllerCallbackImpl(
+ @Nullable RemoteCarRootTaskViewCallback remoteCarRootTaskViewCallback) {
+ mRemoteCarRootTaskViewCallback = remoteCarRootTaskViewCallback;
+ }
+
+ @RequiresPermission("android.car.permission.CONTROL_CAR_APP_LAUNCH")
+ @Override
+ public void onConnected(@NonNull CarTaskViewController carTaskViewController) {
+ mCarTaskViewController = carTaskViewController;
+ if (mIntent != null) {
+ String packageName = mIntent.getComponent().getPackageName();
+ if (packageName != null) {
+ List<ComponentName> componentNames = queryActivitiesFromPackage(mActivity,
+ packageName);
+ mCarTaskViewController.createRemoteCarRootTaskView(
+ new RemoteCarRootTaskViewConfig.Builder()
+ .setAllowListedActivities(componentNames)
+ .build(),
+ mActivity.getMainExecutor(),
+ mRemoteCarRootTaskViewCallback);
+ }
+ }
+ }
+
+ @Override
+ public void onDisconnected(@NonNull CarTaskViewController carTaskViewController) {
+ mRemoteCarTaskView = null;
+ }
+ }
+
+ private static List<ComponentName> queryActivitiesFromPackage(
+ Activity activity, String packageName) {
+ List<ComponentName> componentNames = new ArrayList<>();
+ PackageInfo packageInfo;
+ try {
+ packageInfo = activity.getPackageManager().getPackageInfo(
+ packageName,
+ PackageManager.GET_ACTIVITIES);
+ for (ActivityInfo info: packageInfo.activities) {
+ ComponentName componentName = new ComponentName(packageName, info.name);
+ componentNames.add(componentName);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slogf.e(TAG, "Package '%s' not found : %s", packageName , e);
+ }
+ return componentNames;
}
}
diff --git a/car-lib/src/android/car/app/CarDisplayCompatManager.java b/car-lib/src/android/car/app/CarDisplayCompatManager.java
index 311a73b..14e5a18 100644
--- a/car-lib/src/android/car/app/CarDisplayCompatManager.java
+++ b/car-lib/src/android/car/app/CarDisplayCompatManager.java
@@ -33,21 +33,24 @@
@FlaggedApi(Flags.FLAG_DISPLAY_COMPATIBILITY)
@SystemApi
public final class CarDisplayCompatManager extends CarManagerBase {
+ private CarActivityManager mCarActivityManager;
/**
* @hide
*/
@SystemApi
- @RequiresPermission(Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY)
+ @RequiresPermission(Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY)
@Nullable
public CarDisplayCompatContainer initializeDisplayCompatContainer(
@NonNull CarDisplayCompatContainer.Builder builder) {
+ builder.setCarActivityManager(mCarActivityManager);
return builder.build();
}
/** @hide */
public CarDisplayCompatManager(Car car, @NonNull IBinder service) {
super(car);
+ mCarActivityManager = (CarActivityManager) car.getCarManager(Car.CAR_ACTIVITY_SERVICE);
}
/** @hide */
diff --git a/car-lib/src/android/car/app/CarTaskViewController.java b/car-lib/src/android/car/app/CarTaskViewController.java
index ad2cc8c..1759a2d 100644
--- a/car-lib/src/android/car/app/CarTaskViewController.java
+++ b/car-lib/src/android/car/app/CarTaskViewController.java
@@ -33,6 +33,7 @@
import android.os.RemoteException;
import android.os.UserManager;
import android.util.Log;
+import android.util.Slog;
import java.util.ArrayList;
import java.util.Iterator;
@@ -194,12 +195,12 @@
void onRemoteCarTaskViewReleased(@NonNull RemoteCarTaskView taskView) {
if (mReleased) {
- Log.w(TAG, "Failed to remove the taskView as the "
+ Slog.w(TAG, "Failed to remove the taskView as the "
+ "CarTaskViewController is already released");
return;
}
if (!mRemoteCarTaskViews.contains(taskView)) {
- Log.w(TAG, "This taskView has already been removed");
+ Slog.w(TAG, "This taskView has already been removed");
return;
}
mRemoteCarTaskViews.remove(taskView);
diff --git a/car-lib/src/android/car/app/CarTaskViewInputInterceptor.java b/car-lib/src/android/car/app/CarTaskViewInputInterceptor.java
index c03c916..635c3a4 100644
--- a/car-lib/src/android/car/app/CarTaskViewInputInterceptor.java
+++ b/car-lib/src/android/car/app/CarTaskViewInputInterceptor.java
@@ -31,6 +31,7 @@
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.util.Log;
+import android.util.Slog;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
@@ -111,7 +112,7 @@
@MainThread
void init() {
if (mInitialized) {
- Log.w(TAG, "Already initialized");
+ Slog.w(TAG, "Already initialized");
return;
}
mInitialized = true;
@@ -126,7 +127,7 @@
@MainThread
void release() {
if (!mInitialized) {
- Log.w(TAG, "Failed to release as it is not initialized");
+ Slog.w(TAG, "Failed to release as it is not initialized");
return;
}
mInitialized = false;
@@ -136,10 +137,10 @@
private void startInterceptingGestures() {
if (DBG) {
- Log.d(TAG, "Start intercepting gestures");
+ Slog.d(TAG, "Start intercepting gestures");
}
if (mSpyWindow != null) {
- Log.d(TAG, "Already intercepting gestures");
+ Slog.d(TAG, "Already intercepting gestures");
return;
}
createAndAddSpyWindow();
@@ -147,10 +148,10 @@
private void stopInterceptingGestures() {
if (DBG) {
- Log.d(TAG, "Stop intercepting gestures");
+ Slog.d(TAG, "Stop intercepting gestures");
}
if (mSpyWindow == null) {
- Log.d(TAG, "Already not intercepting gestures");
+ Slog.d(TAG, "Already not intercepting gestures");
return;
}
removeSpyWindow();
@@ -177,7 +178,7 @@
private void removeSpyWindow() {
if (mSpyWindow == null) {
- Log.e(TAG, "Spy window is not present");
+ Slog.e(TAG, "Spy window is not present");
return;
}
mWm.removeView(mSpyWindow);
@@ -276,7 +277,7 @@
&& ((ControlledRemoteCarTaskView) tv).getConfig().mShouldCaptureGestures
&& isIn(e, tv)) {
if (DBG) {
- Log.d(TAG, "Long press captured for taskView: " + tv);
+ Slog.d(TAG, "Long press captured for taskView: " + tv);
}
InputManagerHelper.pilferPointers(mInputManager, mSpyWindow);
tv.performLongClick();
@@ -284,7 +285,7 @@
}
}
if (DBG) {
- Log.d(TAG, "Long press not captured");
+ Slog.d(TAG, "Long press not captured");
}
}
}
diff --git a/car-lib/src/android/car/app/ControlledRemoteCarTaskView.java b/car-lib/src/android/car/app/ControlledRemoteCarTaskView.java
index 4e0415f..a139e17 100644
--- a/car-lib/src/android/car/app/ControlledRemoteCarTaskView.java
+++ b/car-lib/src/android/car/app/ControlledRemoteCarTaskView.java
@@ -34,7 +34,7 @@
import android.graphics.Region;
import android.os.Binder;
import android.os.UserManager;
-import android.util.Log;
+import android.util.Slog;
import android.view.Display;
import android.view.SurfaceControl;
@@ -163,7 +163,7 @@
private void stopTheStartActivityBackoffIfExists() {
if (mStartActivityWithBackoff == null) {
if (CarTaskViewController.DBG) {
- Log.d(TAG, "mStartActivityWithBackoff is not present.");
+ Slog.d(TAG, "mStartActivityWithBackoff is not present.");
}
return;
}
diff --git a/car-lib/src/android/car/app/RemoteCarDefaultRootTaskView.java b/car-lib/src/android/car/app/RemoteCarDefaultRootTaskView.java
index 4d882cd..f052b65 100644
--- a/car-lib/src/android/car/app/RemoteCarDefaultRootTaskView.java
+++ b/car-lib/src/android/car/app/RemoteCarDefaultRootTaskView.java
@@ -48,8 +48,9 @@
private final CarTaskViewController mCarTaskViewController;
private final RemoteCarDefaultRootTaskViewConfig mConfig;
private final Rect mTmpRect = new Rect();
- private final RootTaskStackManager mRootTaskStackManager = new RootTaskStackManager();
private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final RootTaskStackManager mRootTaskStackManager = new RootTaskStackManager();
@GuardedBy("mLock")
private ActivityManager.RunningTaskInfo mRootTask;
@@ -85,9 +86,9 @@
}
updateWindowBounds();
}
+ mRootTaskStackManager.taskAppeared(taskInfo, leash);
}
- mRootTaskStackManager.taskAppeared(taskInfo, leash);
long identity = Binder.clearCallingIdentity();
try {
mCallbackExecutor.execute(() -> {
@@ -113,8 +114,8 @@
RemoteCarDefaultRootTaskView.this,
taskInfo.taskDescription.getBackgroundColor());
}
+ mRootTaskStackManager.taskInfoChanged(taskInfo);
}
- mRootTaskStackManager.taskInfoChanged(taskInfo);
long identity = Binder.clearCallingIdentity();
try {
mCallbackExecutor.execute(() -> {
@@ -138,8 +139,8 @@
if (mRootTask.taskId == taskInfo.taskId) {
mRootTask = null;
}
+ mRootTaskStackManager.taskVanished(taskInfo);
}
- mRootTaskStackManager.taskVanished(taskInfo);
long identity = Binder.clearCallingIdentity();
try {
mCallbackExecutor.execute(() -> {
@@ -188,7 +189,9 @@
*/
@Nullable
public ActivityManager.RunningTaskInfo getTopTaskInfo() {
- return mRootTaskStackManager.getTopTask();
+ synchronized (mLock) {
+ return mRootTaskStackManager.getTopTask();
+ }
}
@Override
diff --git a/car-lib/src/android/car/app/RemoteCarRootTaskView.java b/car-lib/src/android/car/app/RemoteCarRootTaskView.java
index 90798fc..e40e760 100644
--- a/car-lib/src/android/car/app/RemoteCarRootTaskView.java
+++ b/car-lib/src/android/car/app/RemoteCarRootTaskView.java
@@ -63,9 +63,10 @@
private final ICarActivityService mCarActivityService;
private final CarTaskViewController mCarTaskViewController;
private final Rect mTmpRect = new Rect();
- private final RootTaskStackManager mRootTaskStackManager = new RootTaskStackManager();
private final Object mLock = new Object();
private final int mDisplayId;
+ @GuardedBy("mLock")
+ private final RootTaskStackManager mRootTaskStackManager = new RootTaskStackManager();
/**
* List of activities that appear in this {@link RemoteCarRootTaskView}. It's initialized
* with the value from {@link RemoteCarRootTaskViewConfig#getAllowListedActivities()} and
@@ -111,9 +112,8 @@
}
updateWindowBounds();
}
+ mRootTaskStackManager.taskAppeared(taskInfo, leash);
}
-
- mRootTaskStackManager.taskAppeared(taskInfo, leash);
}
@Override
@@ -127,8 +127,8 @@
RemoteCarRootTaskView.this,
taskInfo.taskDescription.getBackgroundColor());
}
+ mRootTaskStackManager.taskInfoChanged(taskInfo);
}
- mRootTaskStackManager.taskInfoChanged(taskInfo);
}
@Override
@@ -140,8 +140,8 @@
if (mRootTask.taskId == taskInfo.taskId) {
mRootTask = null;
}
+ mRootTaskStackManager.taskVanished(taskInfo);
}
- mRootTaskStackManager.taskVanished(taskInfo);
}
@Override
@@ -183,7 +183,9 @@
*/
@Nullable
public ActivityManager.RunningTaskInfo getTopTaskInfo() {
- return mRootTaskStackManager.getTopTask();
+ synchronized (mLock) {
+ return mRootTaskStackManager.getTopTask();
+ }
}
@Override
diff --git a/car-lib/src/android/car/app/RemoteCarTaskView.java b/car-lib/src/android/car/app/RemoteCarTaskView.java
index 7dd35d7..24dc258 100644
--- a/car-lib/src/android/car/app/RemoteCarTaskView.java
+++ b/car-lib/src/android/car/app/RemoteCarTaskView.java
@@ -34,7 +34,7 @@
import android.graphics.Region;
import android.os.DeadObjectException;
import android.os.RemoteException;
-import android.util.Log;
+import android.util.Slog;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
@@ -206,7 +206,7 @@
try {
mICarTaskViewHost.addInsets(index, type, frame);
} catch (RemoteException e) {
- Log.e(TAG, "exception in addInsets", e);
+ Slog.e(TAG, "exception in addInsets", e);
}
}
@@ -225,7 +225,7 @@
try {
mICarTaskViewHost.removeInsets(index, type);
} catch (RemoteException e) {
- Log.e(TAG, "exception in removeInsets", e);
+ Slog.e(TAG, "exception in removeInsets", e);
}
}
diff --git a/car-lib/src/android/car/app/RootTaskStackManager.java b/car-lib/src/android/car/app/RootTaskStackManager.java
index 987be70..0f93912 100644
--- a/car-lib/src/android/car/app/RootTaskStackManager.java
+++ b/car-lib/src/android/car/app/RootTaskStackManager.java
@@ -23,6 +23,9 @@
import java.util.Iterator;
import java.util.LinkedHashMap;
+/**
+ * Methods in this class ar not thread safe and the caller should hold the lock before calling them.
+ */
final class RootTaskStackManager {
private ActivityManager.RunningTaskInfo mRootTask;
private final LinkedHashMap<Integer, ActivityManager.RunningTaskInfo> mChildrenTaskStack =
diff --git a/car-lib/src/android/car/cluster/ClusterHomeManager.java b/car-lib/src/android/car/cluster/ClusterHomeManager.java
index 62a3fe7..628e1df 100644
--- a/car-lib/src/android/car/cluster/ClusterHomeManager.java
+++ b/car-lib/src/android/car/cluster/ClusterHomeManager.java
@@ -41,6 +41,7 @@
import android.view.ViewTreeObserver.OnPreDrawListener;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+import com.android.car.internal.ICarBase;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
@@ -188,7 +189,7 @@
/** @hide */
@VisibleForTesting
- public ClusterHomeManager(Car car, IBinder service) {
+ public ClusterHomeManager(ICarBase car, IBinder service) {
super(car);
mService = IClusterHomeService.Stub.asInterface(service);
mClusterStateListenerBinderCallback = new IClusterStateListenerImpl(this);
diff --git a/car-lib/src/android/car/content/pm/CarAppBlockingPolicyService.java b/car-lib/src/android/car/content/pm/CarAppBlockingPolicyService.java
index 38a9338..229f193 100644
--- a/car-lib/src/android/car/content/pm/CarAppBlockingPolicyService.java
+++ b/car-lib/src/android/car/content/pm/CarAppBlockingPolicyService.java
@@ -21,7 +21,7 @@
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Log;
+import android.util.Slog;
/**
* Service to be implemented by Service which wants to control app blocking policy.
@@ -54,13 +54,13 @@
@Override
public IBinder onBind(Intent intent) {
- Log.i(TAG, "onBind");
+ Slog.i(TAG, "onBind");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
- Log.i(TAG, "onUnbind");
+ Slog.i(TAG, "onUnbind");
stopSelf();
return false;
}
@@ -70,7 +70,7 @@
@Override
public void setAppBlockingPolicySetter(ICarAppBlockingPolicySetter setter) {
- Log.i(TAG, "setAppBlockingPolicySetter will set policy");
+ Slog.i(TAG, "setAppBlockingPolicySetter will set policy");
CarAppBlockingPolicy policy = CarAppBlockingPolicyService.this.getAppBlockingPolicy();
try {
setter.setAppBlockingPolicy(policy);
diff --git a/car-lib/src/android/car/content/pm/CarPackageManager.java b/car-lib/src/android/car/content/pm/CarPackageManager.java
index 15c9e1a..ede3047 100644
--- a/car-lib/src/android/car/content/pm/CarPackageManager.java
+++ b/car-lib/src/android/car/content/pm/CarPackageManager.java
@@ -17,10 +17,11 @@
package android.car.content.pm;
import static android.car.Car.PERMISSION_CONTROL_APP_BLOCKING;
-import static android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY;
+import static android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY;
import static android.car.CarLibLog.TAG_CAR;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -35,19 +36,25 @@
import android.car.feature.Flags;
import android.content.ComponentName;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
-import android.util.Log;
+import android.util.ArrayMap;
+import android.util.Slog;
+import com.android.car.internal.ICarBase;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
/**
* Provides car specific API related with package management.
@@ -166,16 +173,25 @@
* <p>Format is in the form {@code major:minor} or {@code major}.
*
* <p>For example, for {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13}, it would be:
- * <code><meta-data android:name="android.car.targetCarVersion" android:value="33"/></code>
+ * <code>
+ * <meta-data android:name="android.car.targetCarVersion" android:value="33"/>
+ * </code>
*
* <p>Or:
*
- * <code><meta-data android:name="android.car.targetCarVersion" android:value="33:0"/></code>
+ * <code>
+ * <meta-data android:name="android.car.targetCarVersion" android:value="33:0"/>
+ * </code>
*
* <p>And for {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13} first update:
*
- * <code><meta-data android:name="android.car.targetCarVersion" android:value="33:1"/></code>
+ * <code>
+ * <meta-data android:name="android.car.targetCarVersion" android:value="33:1"/>
+ * </code>
+ *
+ * @deprecated Car version is no longer supported by the CarService.
*/
+ @Deprecated
public static final String MANIFEST_METADATA_TARGET_CAR_VERSION =
"android.car.targetCarVersion";
@@ -187,15 +203,25 @@
public @interface SetPolicyFlags {}
private final ICarPackageManager mService;
+ private final Object mLock = new Object();
+ /**
+ * Map that stores externally created {@link ICarBlockingUiCommandListener} objects keyed by
+ * their corresponding internally provided {@link BlockingUiCommandListener} objects. Since
+ * ArrayMap's initial size is 10, and the blocking ui will have at most 1~2 listeners,
+ * initialize size of the map to be 2.
+ */
+ @GuardedBy("mLock")
+ private final Map<BlockingUiCommandListener, ICarBlockingUiCommandListener>
+ mICarBlockingUiCommandListener = new ArrayMap<>(2);
/** @hide */
- public CarPackageManager(Car car, IBinder service) {
+ public CarPackageManager(ICarBase car, IBinder service) {
this(car, ICarPackageManager.Stub.asInterface(service));
}
/** @hide */
@VisibleForTesting
- public CarPackageManager(Car car, ICarPackageManager service) {
+ public CarPackageManager(ICarBase car, ICarPackageManager service) {
super(car);
mService = service;
}
@@ -461,16 +487,18 @@
* @throws NameNotFoundException If the given package does not exist for the user.
*
* @hide
+ * @deprecated CarVersion is no longer supported by the CarService.
*/
@SystemApi
@RequiresPermission(Manifest.permission.QUERY_ALL_PACKAGES)
@NonNull
+ @Deprecated
public CarVersion getTargetCarVersion(@NonNull String packageName)
throws NameNotFoundException {
try {
return mService.getTargetCarVersion(packageName);
} catch (ServiceSpecificException e) {
- Log.w(TAG, "Failed to get CarVersion for " + packageName, e);
+ Slog.w(TAG, "Failed to get CarVersion for " + packageName, e);
handleServiceSpecificFromCarService(e, packageName);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -488,14 +516,16 @@
* version} as major and {@code 0} as minor instead.
*
* @return targeted Car API version (as defined above)
+ * @deprecated CarVersion is no longer supported by the CarService.
*/
@NonNull
+ @Deprecated
public CarVersion getTargetCarVersion() {
String pkgName = mCar.getContext().getPackageName();
try {
return mService.getSelfTargetCarVersion(pkgName);
} catch (RemoteException e) {
- Log.w(TAG_CAR, "Car service threw exception calling getTargetCarVersion(" + pkgName
+ Slog.w(TAG_CAR, "Car service threw exception calling getTargetCarVersion(" + pkgName
+ ")", e);
e.rethrowFromSystemServer();
return null;
@@ -510,7 +540,7 @@
*/
@FlaggedApi(Flags.FLAG_DISPLAY_COMPATIBILITY)
@SystemApi
- @RequiresPermission(allOf = {PERMISSION_QUERY_DISPLAY_COMPATIBILITY,
+ @RequiresPermission(allOf = {PERMISSION_MANAGE_DISPLAY_COMPATIBILITY,
android.Manifest.permission.QUERY_ALL_PACKAGES})
public boolean requiresDisplayCompat(@NonNull String packageName) throws NameNotFoundException {
if (!Flags.displayCompatibility()) {
@@ -519,18 +549,18 @@
try {
return mService.requiresDisplayCompat(packageName);
} catch (ServiceSpecificException e) {
- Log.w(TAG_CAR, "Car service threw exception calling requiresDisplayCompat("
+ Slog.w(TAG_CAR, "Car service threw exception calling requiresDisplayCompat("
+ packageName + ")", e);
if (e.errorCode == ERROR_CODE_NO_PACKAGE) {
throw new NameNotFoundException("cannot find " + packageName);
}
throw new RuntimeException(e);
} catch (SecurityException e) {
- Log.w(TAG_CAR, "Car service threw exception calling requiresDisplayCompat("
+ Slog.w(TAG_CAR, "Car service threw exception calling requiresDisplayCompat("
+ packageName + ")", e);
throw e;
} catch (RemoteException e) {
- Log.w(TAG_CAR, "Car service threw exception calling requiresDisplayCompat("
+ Slog.w(TAG_CAR, "Car service threw exception calling requiresDisplayCompat("
+ packageName + ")", e);
e.rethrowFromSystemServer();
}
@@ -558,4 +588,94 @@
// don't know what this is
throw new IllegalStateException(e);
}
+
+ /**
+ * Callback interface to finish the blocking ui.
+ *
+ * @hide
+ */
+ public interface BlockingUiCommandListener {
+ /**
+ * Called when the blocking ui needs to be finished
+ */
+ void finishBlockingUi();
+ }
+
+ /**
+ * Registers the {@link BlockingUiCommandListener} for the BlockingUi.
+ *
+ * <p>Note: A listener can only listen for one displayId.
+ * <p>Note: A listener cannot be registered twice. Registering the second time would result in a
+ * no-op.
+ *
+ * @param displayId {@link android.view.Display} for which the blocking ui is registered.
+ * @param callbackExecutor the executor to execute the callback with.
+ * @param listener {@link BlockingUiCommandListener} listener.
+ * @throws IllegalStateException if the listener is already registered.
+ * @hide
+ */
+ public void registerBlockingUiCommandListener(int displayId,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull BlockingUiCommandListener listener) {
+ synchronized (mLock) {
+ if (mICarBlockingUiCommandListener.get(listener) != null) {
+ throw new IllegalStateException("BlockingUiListener already registered");
+ }
+ CarBlockingUiCommandListenerImpl carBlockingUiListener =
+ new CarBlockingUiCommandListenerImpl(callbackExecutor, listener);
+ try {
+ mService.registerBlockingUiCommandListener(carBlockingUiListener, displayId);
+ mICarBlockingUiCommandListener.put(listener, carBlockingUiListener);
+ } catch (RemoteException e) {
+ handleRemoteExceptionFromCarService(e);
+ }
+ }
+ }
+
+ /**
+ * Unregisters the {@link BlockingUiCommandListener}.
+ *
+ * @hide
+ */
+ public void unregisterBlockingUiCommandListener(@NonNull BlockingUiCommandListener listener) {
+ synchronized (mLock) {
+ try {
+ if (mICarBlockingUiCommandListener.get(listener) == null) {
+ Slog.e(TAG, "BlockingUiListener already unregistered");
+ return;
+ }
+ mService.unregisterBlockingUiCommandListener(
+ mICarBlockingUiCommandListener.get(listener));
+ } catch (RemoteException e) {
+ handleRemoteExceptionFromCarService(e);
+ }
+ mICarBlockingUiCommandListener.remove(listener);
+ }
+ }
+
+ private class CarBlockingUiCommandListenerImpl extends ICarBlockingUiCommandListener.Stub {
+ private final Executor mExecutor;
+ private final BlockingUiCommandListener mListener;
+
+ CarBlockingUiCommandListenerImpl(Executor callbackExecutor,
+ BlockingUiCommandListener listener) {
+ mExecutor = callbackExecutor;
+ mListener = listener;
+ }
+
+ public void finishBlockingUi() throws RemoteException {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ if (mICarBlockingUiCommandListener.get(mListener) == null) {
+ Slog.e(TAG, "BlockingUiListener already unregistered");
+ return;
+ }
+ mExecutor.execute(mListener::finishBlockingUi);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
}
diff --git a/car-lib/src/android/car/content/pm/ICarBlockingUiCommandListener.aidl b/car-lib/src/android/car/content/pm/ICarBlockingUiCommandListener.aidl
new file mode 100644
index 0000000..c3401ea
--- /dev/null
+++ b/car-lib/src/android/car/content/pm/ICarBlockingUiCommandListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.content.pm;
+/**
+ * Callback interface to finish the blocking ui.
+ * @hide
+ */
+oneway interface ICarBlockingUiCommandListener {
+ /** Called when the blocking ui needs to be finished */
+ void finishBlockingUi();
+}
diff --git a/car-lib/src/android/car/content/pm/ICarPackageManager.aidl b/car-lib/src/android/car/content/pm/ICarPackageManager.aidl
index ff6803d..d34c059 100644
--- a/car-lib/src/android/car/content/pm/ICarPackageManager.aidl
+++ b/car-lib/src/android/car/content/pm/ICarPackageManager.aidl
@@ -19,6 +19,7 @@
import android.app.PendingIntent;
import android.car.CarVersion;
import android.car.content.pm.CarAppBlockingPolicy;
+import android.car.content.pm.ICarBlockingUiCommandListener;
import android.content.ComponentName;
/** @hide */
@@ -38,4 +39,6 @@
CarVersion getTargetCarVersion(String packageName) = 10;
CarVersion getSelfTargetCarVersion(in String packageName) = 11;
boolean requiresDisplayCompat(in String packageName) = 12;
+ void registerBlockingUiCommandListener(in ICarBlockingUiCommandListener listener, int displayId) = 13;
+ void unregisterBlockingUiCommandListener(in ICarBlockingUiCommandListener listener) = 14;
}
diff --git a/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java b/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java
index 217506e..21482fd 100644
--- a/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java
+++ b/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java
@@ -28,7 +28,7 @@
import android.car.diagnostic.ICarDiagnosticEventListener.Stub;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.car.internal.CarPermission;
@@ -399,7 +399,7 @@
// throw away old data as oneway binder call can change order.
long updateTime = event.timestamp;
if (updateTime < mLastUpdateTime) {
- Log.w(CarLibLog.TAG_DIAGNOSTIC, "dropping old data");
+ Slog.w(CarLibLog.TAG_DIAGNOSTIC, "dropping old data");
return;
}
mLastUpdateTime = updateTime;
diff --git a/car-lib/src/android/car/drivingstate/CarDrivingStateManager.java b/car-lib/src/android/car/drivingstate/CarDrivingStateManager.java
index f32f427..413175f 100644
--- a/car-lib/src/android/car/drivingstate/CarDrivingStateManager.java
+++ b/car-lib/src/android/car/drivingstate/CarDrivingStateManager.java
@@ -31,7 +31,7 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -45,7 +45,6 @@
@SystemApi
public final class CarDrivingStateManager extends CarManagerBase {
private static final String TAG = "CarDrivingStateMgr";
- private static final boolean DBG = false;
private static final boolean VDBG = false;
private static final int MSG_HANDLE_DRIVING_STATE_CHANGE = 0;
@@ -101,7 +100,7 @@
public void registerListener(@NonNull CarDrivingStateEventListener listener) {
if (listener == null) {
if (VDBG) {
- Log.v(TAG, "registerCarDrivingStateEventListener(): null listener");
+ Slog.v(TAG, "registerCarDrivingStateEventListener(): null listener");
}
throw new IllegalArgumentException("Listener is null");
}
@@ -109,7 +108,7 @@
synchronized (mLock) {
// Check if the listener has been already registered for this event type
if (mDrvStateEventListener != null) {
- Log.w(TAG, "Listener already registered");
+ Slog.w(TAG, "Listener already registered");
return;
}
if (mListenerToService == null) {
@@ -137,7 +136,7 @@
CarDrivingStateChangeListenerToService localListenerToService;
synchronized (mLock) {
if (mDrvStateEventListener == null) {
- Log.w(TAG, "Listener was not previously registered");
+ Slog.w(TAG, "Listener was not previously registered");
return;
}
localListenerToService = mListenerToService;
diff --git a/car-lib/src/android/car/drivingstate/CarUxRestrictionsConfiguration.java b/car-lib/src/android/car/drivingstate/CarUxRestrictionsConfiguration.java
index 60489c0..ea4ea78 100644
--- a/car-lib/src/android/car/drivingstate/CarUxRestrictionsConfiguration.java
+++ b/car-lib/src/android/car/drivingstate/CarUxRestrictionsConfiguration.java
@@ -42,6 +42,7 @@
import android.util.JsonToken;
import android.util.JsonWriter;
import android.util.Log;
+import android.util.Slog;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
@@ -167,7 +168,7 @@
if (BuildHelper.isEngBuild() || BuildHelper.isUserDebugBuild()) {
throw new IllegalArgumentException("Invalid currentSpeed: " + currentSpeed);
}
- Log.e(TAG, "getUxRestrictions: Invalid currentSpeed: " + currentSpeed);
+ Slog.e(TAG, "getUxRestrictions: Invalid currentSpeed: " + currentSpeed);
return createDefaultUxRestrictionsEvent();
}
RestrictionsPerSpeedRange restriction = null;
@@ -178,7 +179,7 @@
if (restriction == null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG,
+ Slog.d(TAG,
String.format("No restrictions specified for (mode: %s, drive state: %s)",
mode,
drivingState));
@@ -429,7 +430,7 @@
try {
writeJson(writer);
} catch (IOException e) {
- Log.e(TAG, "Failed printing UX restrictions configuration", e);
+ Slog.e(TAG, "Failed printing UX restrictions configuration", e);
}
return charWriter.toString();
}
@@ -570,7 +571,7 @@
PASSENGER_MODE_NAME_FOR_MIGRATION, builder);
break;
default:
- Log.e(TAG, "Unknown name parsing json config: " + name);
+ Slog.e(TAG, "Unknown name parsing json config: " + name);
reader.skipValue();
}
}
@@ -595,7 +596,7 @@
readRestrictionsList(reader, DRIVING_STATE_UNKNOWN, mode, builder);
break;
default:
- Log.e(TAG, "Unknown name parsing restriction mode json config: " + name);
+ Slog.e(TAG, "Unknown name parsing restriction mode json config: " + name);
}
}
reader.endObject();
@@ -637,7 +638,7 @@
} else if (Objects.equals(n, JSON_NAME_MAX_SPEED)) {
maxSpeed = Double.valueOf(reader.nextDouble()).floatValue();
} else {
- Log.e(TAG, "Unknown name parsing json config: " + n);
+ Slog.e(TAG, "Unknown name parsing json config: " + n);
reader.skipValue();
}
}
@@ -1086,7 +1087,7 @@
List<RestrictionsPerSpeedRange> restrictions =
container.getRestrictionsForDriveState(drivingState);
if (restrictions.size() == 0) {
- Log.i(TAG, "Using default restrictions for driving state: "
+ Slog.i(TAG, "Using default restrictions for driving state: "
+ getDrivingStateName(drivingState));
restrictions.add(new RestrictionsPerSpeedRange(
true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED));
diff --git a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
index ba947bb..77107d8 100644
--- a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
+++ b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
@@ -30,7 +30,7 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.UserManager;
-import android.util.Log;
+import android.util.Slog;
import android.view.Display;
import com.android.internal.annotations.GuardedBy;
@@ -121,17 +121,20 @@
public void registerListener(@NonNull OnUxRestrictionsChangedListener listener, int displayId) {
setListener(displayId, listener);
}
+
/**
* @hide
+ * @deprecated use {@link CarUxRestrictionsManager#registerListener} instead.
*/
@SystemApi
+ @Deprecated
public void setListener(int displayId, @NonNull OnUxRestrictionsChangedListener listener) {
CarUxRestrictionsChangeListenerToService serviceListener;
synchronized (mLock) {
// Check if the listener has been already registered.
if (mUxRListener != null) {
if (DBG) {
- Log.d(TAG, "Listener already registered listener");
+ Slog.d(TAG, "Listener already registered listener");
}
return;
}
@@ -158,7 +161,7 @@
synchronized (mLock) {
if (mUxRListener == null) {
if (DBG) {
- Log.d(TAG, "Listener was not previously registered");
+ Slog.d(TAG, "Listener was not previously registered");
}
return;
}
@@ -410,18 +413,18 @@
// For example, if it is an Activity context, a valid display id will already be obtained
// here. But if it is an Application context, it will return invalid display id.
mDisplayId = ContextHelper.getAssociatedDisplayId(getContext());
- Log.d(TAG, "Context returns associated display ID " + mDisplayId);
+ Slog.d(TAG, "Context returns associated display ID " + mDisplayId);
if (mDisplayId == Display.INVALID_DISPLAY) {
// If there is no display id associated with the context, further obtain the display
// id by mapping the user to display id.
mDisplayId = UserManagerHelper.getMainDisplayIdAssignedToUser(mUserManager);
- Log.d(TAG, "Display ID assigned to user is display " + mDisplayId);
+ Slog.d(TAG, "Display ID assigned to user is display " + mDisplayId);
}
if (mDisplayId == Display.INVALID_DISPLAY) {
mDisplayId = Display.DEFAULT_DISPLAY;
- Log.e(TAG, "Could not retrieve display id. Using default: " + mDisplayId);
+ Slog.e(TAG, "Could not retrieve display id. Using default: " + mDisplayId);
}
return mDisplayId;
diff --git a/car-lib/src/android/car/evs/CarEvsBufferDescriptor.java b/car-lib/src/android/car/evs/CarEvsBufferDescriptor.java
index f0d2a00..731f7b4 100644
--- a/car-lib/src/android/car/evs/CarEvsBufferDescriptor.java
+++ b/car-lib/src/android/car/evs/CarEvsBufferDescriptor.java
@@ -120,7 +120,8 @@
@Override
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
public String toString() {
- return "CarEvsBufferDescriptor: id = " + mId + ", buffer = " + mHardwareBuffer;
+ return "CarEvsBufferDescriptor: id = " + mId + ", type = " + mType +
+ ", buffer = " + mHardwareBuffer;
}
@Override
diff --git a/car-lib/src/android/car/evs/CarEvsManager.java b/car-lib/src/android/car/evs/CarEvsManager.java
index 8f70ff1..172ce9f 100644
--- a/car-lib/src/android/car/evs/CarEvsManager.java
+++ b/car-lib/src/android/car/evs/CarEvsManager.java
@@ -18,6 +18,7 @@
import static android.car.feature.Flags.FLAG_CAR_EVS_QUERY_SERVICE_STATUS;
import static android.car.feature.Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT;
+
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
import android.annotation.CallbackExecutor;
@@ -31,15 +32,20 @@
import android.car.Car;
import android.car.CarManagerBase;
import android.car.annotation.RequiredFeature;
+import android.car.builtin.os.TraceHelper;
import android.car.builtin.util.Slogf;
-import android.car.feature.Flags;
+import android.car.feature.FeatureFlags;
+import android.car.feature.FeatureFlagsImpl;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.Log;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+import com.android.car.internal.ICarBase;
import com.android.car.internal.evs.CarEvsUtils;
import com.android.internal.annotations.GuardedBy;
@@ -67,6 +73,8 @@
private final ICarEvsService mService;
private final Object mStreamLock = new Object();
+ private final FeatureFlags mFeatureFlags;
+
// This array maintains mappings between service type and its client.
@GuardedBy("mStreamLock")
private SparseArray<CarEvsStreamCallback> mStreamCallbacks = new SparseArray<>();
@@ -280,9 +288,10 @@
*
* @hide
*/
- public CarEvsManager(Car car, IBinder service) {
+ public CarEvsManager(ICarBase car, IBinder service, @Nullable FeatureFlags featureFlags) {
super(car);
+ mFeatureFlags = Objects.requireNonNullElse(featureFlags, new FeatureFlagsImpl());
// Gets CarEvsService
mService = ICarEvsService.Stub.asInterface(service);
}
@@ -418,8 +427,7 @@
}
/**
- * Application registers {@link #CarEvsStreamCallback} object to listen to EVS services' status
- * changes.
+ * Application registers {@link #CarEvsStreamCallback} object to listen to EVS streams.
*
* CarEvsManager supports two client types; one is a System UI type client and another is a
* normal Android activity type client. The former client type has a priority over
@@ -433,11 +441,28 @@
* Called when any EVS stream events occur.
*
* @param event {@link #CarEvsStreamEvent}; e.g. a stream started
+ *
+ * @deprecated Use {@link CarEvsStreamCallback#onStreamEvent(origin, event) instead.
*/
+ @Deprecated
@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
default void onStreamEvent(@CarEvsStreamEvent int event) {}
/**
+ * Called when any EVS stream events occur with its origin and event type.
+ *
+ * @param origin {@link #CarEvsServiceType}; e.g. SERVICE_TYPE_REARVIEW
+ * @param event {@link #CarEvsStreamEvent}; e.g. a stream started
+ */
+ @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
+ @FlaggedApi(FLAG_CAR_EVS_STREAM_MANAGEMENT)
+ default void onStreamEvent(@CarEvsServiceType int origin, @CarEvsStreamEvent int event) {
+ // By default, we forward this event callback to
+ // {@link CarEvsStreamCallback#onStreamEvent(int)}.
+ onStreamEvent(CarEvsUtils.putTag(origin, event));
+ }
+
+ /**
* Called when new frame arrives.
*
* @param buffer {@link android.car.evs.CarEvsBufferDescriptor} contains a EVS frame
@@ -452,23 +477,34 @@
*/
private static class CarEvsStreamListenerToService extends ICarEvsStreamCallback.Stub {
private static final int DEFAULT_STREAM_EVENT_WAIT_TIMEOUT_IN_SEC = 1;
+ private static final int KEY_NOT_EXIST = Integer.MIN_VALUE;
private final WeakReference<CarEvsManager> mManager;
private final Semaphore mStreamEventOccurred = new Semaphore(/* permits= */ 0);
- private @CarEvsStreamEvent int mLastStreamEvent;
+ private final SparseIntArray mLastStreamEvent = new SparseIntArray();
+ private final Object mLock = new Object();
CarEvsStreamListenerToService(CarEvsManager manager) {
mManager = new WeakReference<>(manager);
}
@Override
- public void onStreamEvent(@CarEvsStreamEvent int event) {
- mLastStreamEvent = event;
- mStreamEventOccurred.release();
+ public void onStreamEvent(@CarEvsServiceType int origin, @CarEvsStreamEvent int event) {
+ Trace.asyncTraceBegin(TraceHelper.TRACE_TAG_CAR_EVS_SERVICE,
+ "CarEvsManager#onStreamEvent", origin);
+ if (DBG) {
+ Slogf.d(TAG, "Received an event %d from %d.", event, origin);
+ }
+ synchronized (mLock) {
+ mLastStreamEvent.append(origin, event);
+ mStreamEventOccurred.release();
+ }
CarEvsManager manager = mManager.get();
if (manager != null) {
- manager.handleStreamEvent(event);
+ manager.handleStreamEvent(origin, event);
}
+ Trace.asyncTraceEnd(TraceHelper.TRACE_TAG_CAR_EVS_SERVICE,
+ "CarEvsManager#onStreamEvent", origin);
}
@Override
@@ -479,26 +515,44 @@
}
}
- public boolean waitForStreamEvent(@CarEvsStreamEvent int expected) {
- return waitForStreamEvent(expected, DEFAULT_STREAM_EVENT_WAIT_TIMEOUT_IN_SEC);
+ public boolean waitForStreamEvent(@CarEvsServiceType int from,
+ @CarEvsStreamEvent int expected) {
+ return waitForStreamEvent(from, expected, DEFAULT_STREAM_EVENT_WAIT_TIMEOUT_IN_SEC);
}
- public boolean waitForStreamEvent(@CarEvsStreamEvent int expected, int timeoutInSeconds) {
- while (true) {
- try {
+ public boolean waitForStreamEvent(@CarEvsServiceType int from,
+ @CarEvsStreamEvent int expected, int timeoutInSeconds) {
+ Trace.asyncTraceBegin(TraceHelper.TRACE_TAG_CAR_EVS_SERVICE,
+ "CarEvsManager#waitForStreamEvent", from);
+ try {
+ while (true) {
if (!mStreamEventOccurred.tryAcquire(timeoutInSeconds, TimeUnit.SECONDS)) {
Slogf.w(TAG, "Timer for a new stream event expired.");
return false;
}
- if (mLastStreamEvent == expected) {
- return true;
+ int lastEvent;
+ synchronized (mLock) {
+ lastEvent = mLastStreamEvent.get(from, KEY_NOT_EXIST);
+
+ if (lastEvent == KEY_NOT_EXIST) {
+ // We have not received any event from a target service type yet.
+ continue;
+ }
+
+ if (lastEvent == expected) {
+ mLastStreamEvent.delete(from);
+ return true;
+ }
}
- } catch (InterruptedException e) {
- Slogf.w(TAG, "Interrupted while waiting for an event %d.\nException = %s",
- expected, Log.getStackTraceString(e));
- return false;
}
+ } catch (InterruptedException e) {
+ Slogf.w(TAG, "Interrupted while waiting for an event %d.\nException = %s",
+ expected, Log.getStackTraceString(e));
+ return false;
+ } finally {
+ Trace.asyncTraceEnd(TraceHelper.TRACE_TAG_CAR_EVS_SERVICE,
+ "CarEvsManager#waitForStreamEvent", from);
}
}
}
@@ -510,27 +564,40 @@
*
* @param event {@link #CarEvsStreamEvent} from the service this manager subscribes to.
*/
- private void handleStreamEvent(@CarEvsStreamEvent int event) {
+ private void handleStreamEvent(@CarEvsServiceType int origin, @CarEvsStreamEvent int event) {
synchronized(mStreamLock) {
- handleStreamEventLocked(event);
+ handleStreamEventLocked(origin, event);
}
}
@GuardedBy("mStreamLock")
- private void handleStreamEventLocked(@CarEvsStreamEvent int event) {
+ private void handleStreamEventLocked(@CarEvsServiceType int origin,
+ @CarEvsStreamEvent int event) {
if (DBG) {
Slogf.d(TAG, "Received: " + event);
}
- CarEvsStreamCallback callback = mStreamCallbacks.get(CarEvsUtils.getTag(event));
+ CarEvsStreamCallback callback = mStreamCallbacks.get(origin);
Executor executor = mStreamCallbackExecutor;
if (callback != null) {
- executor.execute(() -> callback.onStreamEvent(CarEvsUtils.getValue(event)));
- } else if (DBG) {
+ handleStreamEventLocked(origin, event, callback, executor);
+ }
+
+ if (DBG) {
Slogf.w(TAG, "No client seems active; a current stream event is ignored.");
}
}
+ @GuardedBy("mStreamLock")
+ private void handleStreamEventLocked(@CarEvsServiceType int origin,
+ @CarEvsStreamEvent int event, CarEvsStreamCallback cb, Executor executor) {
+ if (mFeatureFlags.carEvsStreamManagement()) {
+ executor.execute(() -> cb.onStreamEvent(origin, event));
+ } else {
+ executor.execute(() -> cb.onStreamEvent(CarEvsUtils.putTag(origin, event)));
+ }
+ }
+
/**
* Gets the {@link android.car.evs.CarEvsBufferDescriptor} from the service listener
* {@link #CarEvsStreamListenerToService} and dispatches it to an executor provided
@@ -539,6 +606,11 @@
* @param buffer {@link android.car.evs.CarEvsBufferDescriptor}
*/
private void handleNewFrame(@NonNull CarEvsBufferDescriptor buffer) {
+ int type = mFeatureFlags.carEvsStreamManagement() ?
+ buffer.getType() : CarEvsUtils.getTag(buffer.getId());
+ Trace.asyncTraceBegin(TraceHelper.TRACE_TAG_CAR_EVS_SERVICE,
+ "CarEvsManager#handleNewFrame", type);
+
Objects.requireNonNull(buffer);
if (DBG) {
Slogf.d(TAG, "Received a buffer: " + buffer);
@@ -546,20 +618,26 @@
final CarEvsStreamCallback callback;
final Executor executor;
+
synchronized (mStreamLock) {
- callback = mStreamCallbacks.get(CarEvsUtils.getTag(buffer.getId()));
+ callback = mStreamCallbacks.get(type);
executor = mStreamCallbackExecutor;
}
if (callback != null) {
executor.execute(() -> callback.onNewFrame(buffer));
- } else {
- if (DBG) {
- Slogf.w(TAG, "A buffer is being returned back to the service because no active "
- + "clients exist.");
- }
- returnFrameBuffer(buffer);
+ Trace.asyncTraceEnd(TraceHelper.TRACE_TAG_CAR_EVS_SERVICE,
+ "CarEvsManager#handleNewFrame", type);
+ return;
}
+
+ if (DBG) {
+ Slogf.w(TAG, "A buffer is being returned back to the service because no active "
+ + "clients exist.");
+ }
+ returnFrameBuffer(buffer);
+ Trace.asyncTraceEnd(TraceHelper.TRACE_TAG_CAR_EVS_SERVICE,
+ "CarEvsManager#handleNewFrame", type);
}
@@ -578,13 +656,17 @@
}
// Wait for a confirmation.
- if (!mStreamListenerToService.waitForStreamEvent(STREAM_EVENT_STREAM_STOPPED)) {
- Slogf.w(TAG, "EVS did not notify us that target streams are stopped " +
- "before a time expires.");
- }
+ for (int i = 0; i < mStreamCallbacks.size(); i++) {
+ if (!mStreamListenerToService.waitForStreamEvent(mStreamCallbacks.keyAt(i),
+ STREAM_EVENT_STREAM_STOPPED)) {
+ Slogf.w(TAG, "EVS did not notify us that target streams are stopped "
+ + "before a time expires.");
+ }
- // Notify clients that streams are stopped.
- handleStreamEventLocked(STREAM_EVENT_STREAM_STOPPED);
+ // Notify clients that streams are stopped.
+ handleStreamEventLocked(mStreamCallbacks.keyAt(i), STREAM_EVENT_STREAM_STOPPED,
+ mStreamCallbacks.valueAt(i), mStreamCallbackExecutor);
+ }
// We're not interested in frames and events anymore. The client can safely assume
// the service is stopped properly.
@@ -597,7 +679,7 @@
private void stopVideoStreamLocked(@CarEvsServiceType int type) {
CarEvsStreamCallback cb = mStreamCallbacks.get(type);
if (cb == null) {
- Slogf.i(TAG, "A requested service type %d is not active.", type);
+ Slogf.i(TAG, "A requested service type " + type + " is not active.");
return;
}
@@ -609,13 +691,13 @@
// Wait for a confirmation.
// TODO(b/321913871): Check whether or not we need to verify the origin of a received event.
- if (!mStreamListenerToService.waitForStreamEvent(STREAM_EVENT_STREAM_STOPPED)) {
- Slogf.w(TAG, "EVS did not notify us that target streams are stopped " +
- "before a time expires.");
+ if (!mStreamListenerToService.waitForStreamEvent(type, STREAM_EVENT_STREAM_STOPPED)) {
+ Slogf.w(TAG, "EVS did not notify us that target streams are stopped "
+ + "before a time expires.");
}
// Notify clients that streams are stopped.
- handleStreamEventLocked(STREAM_EVENT_STREAM_STOPPED);
+ handleStreamEventLocked(type, STREAM_EVENT_STREAM_STOPPED);
// We're not interested in frames and events anymore from a given stream type.
mStreamCallbacks.remove(type);
@@ -633,6 +715,10 @@
*/
@RequiresPermission(Car.PERMISSION_USE_CAR_EVS_CAMERA)
public void returnFrameBuffer(@NonNull CarEvsBufferDescriptor buffer) {
+ int type = mFeatureFlags.carEvsStreamManagement() ?
+ buffer.getType() : CarEvsUtils.getTag(buffer.getId());
+ Trace.asyncTraceBegin(TraceHelper.TRACE_TAG_CAR_EVS_SERVICE,
+ "CarEvsManager#returnFrameBuffer", type);
Objects.requireNonNull(buffer);
try {
mService.returnFrameBuffer(buffer);
@@ -641,6 +727,8 @@
} finally {
// We are done with this HardwareBuffer object.
buffer.getHardwareBuffer().close();
+ Trace.asyncTraceEnd(TraceHelper.TRACE_TAG_CAR_EVS_SERVICE,
+ "CarEvsManager#returnFrameBuffer", type);
}
}
@@ -693,7 +781,8 @@
* {@link #ERROR_UNAVAILABLE} will be returned if the CarEvsService is not connected to
* the native EVS service or the binder transaction fails.
* {@link #ERROR_BUSY} will be returned if the CarEvsService is handling a service
- * request with a valid session token.
+ * request with a valid session token or the same service is already active via another
+ * version of callback object.
* {@link #ERROR_NONE} for all other cases.
*/
@RequiresPermission(Car.PERMISSION_USE_CAR_EVS_CAMERA)
diff --git a/car-lib/src/android/car/evs/ICarEvsStreamCallback.aidl b/car-lib/src/android/car/evs/ICarEvsStreamCallback.aidl
index bd770e3..c8db06e 100644
--- a/car-lib/src/android/car/evs/ICarEvsStreamCallback.aidl
+++ b/car-lib/src/android/car/evs/ICarEvsStreamCallback.aidl
@@ -28,7 +28,7 @@
/**
* Called upon a new EVS stream event
*/
- void onStreamEvent(int event);
+ void onStreamEvent(int origin, int event);
/**
* Called when a new EVS frame arrives
diff --git a/car-lib/src/android/car/hardware/CarPropertyConfig.java b/car-lib/src/android/car/hardware/CarPropertyConfig.java
index 6853b52..03370e8 100644
--- a/car-lib/src/android/car/hardware/CarPropertyConfig.java
+++ b/car-lib/src/android/car/hardware/CarPropertyConfig.java
@@ -546,7 +546,10 @@
*
* @return Builder<T>
* @hide
+ * @deprecated marked as deprecated because clients should not have direct access to the
+ * CarPropertyConfig.Builder class
*/
+ @Deprecated
@SystemApi
public static <T> Builder<T> newBuilder(Class<T> type, int propertyId, int areaType,
int areaCapacity) {
@@ -570,7 +573,10 @@
*
* @param <T>
* @hide
- * */
+ * @deprecated marked as deprecated because clients should not have direct access to the
+ * CarPropertyConfig.Builder class
+ */
+ @Deprecated
@SystemApi
public static class Builder<T> {
private int mAccess = VEHICLE_PROPERTY_ACCESS_NONE;
@@ -598,7 +604,7 @@
* function.
*
* @return Builder<T>
- * @deprecated - use {@link #addAreaIdConfig(AreaIdConfig)} instead.
+ * @removed - use {@link #addAreaIdConfig(AreaIdConfig)} instead.
*/
@Deprecated
public Builder<T> addAreas(int[] areaIds) {
@@ -619,7 +625,7 @@
* function.
*
* @return Builder<T>
- * @deprecated - use {@link #addAreaIdConfig(AreaIdConfig)} instead.
+ * @removed - use {@link #addAreaIdConfig(AreaIdConfig)} instead.
*/
@Deprecated
public Builder<T> addArea(int areaId) {
@@ -638,7 +644,7 @@
* function.
*
* @return Builder<T>
- * @deprecated - use {@link #addAreaIdConfig(AreaIdConfig)} instead.
+ * @removed - use {@link #addAreaIdConfig(AreaIdConfig)} instead.
*/
@Deprecated
public Builder<T> addAreaConfig(int areaId, T min, T max) {
diff --git a/car-lib/src/android/car/hardware/CarSensorManager.java b/car-lib/src/android/car/hardware/CarSensorManager.java
index 353bb4f..5981665 100644
--- a/car-lib/src/android/car/hardware/CarSensorManager.java
+++ b/car-lib/src/android/car/hardware/CarSensorManager.java
@@ -29,7 +29,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArraySet;
-import android.util.Log;
+import android.util.Slog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -478,7 +478,7 @@
}
break;
default:
- Log.e(TAG, "unhandled VehiclePropertyType for propId="
+ Slog.e(TAG, "unhandled VehiclePropertyType for propId="
+ propertyValue.getPropertyId());
break;
}
diff --git a/car-lib/src/android/car/hardware/hvac/CarHvacManager.java b/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
index 10c5976..33a4e82 100644
--- a/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
+++ b/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
@@ -27,7 +27,7 @@
import android.car.hardware.property.ICarProperty;
import android.os.IBinder;
import android.util.ArraySet;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -349,7 +349,7 @@
}
} catch (RuntimeException e) {
- Log.e(TAG, "getPropertyList exception ", e);
+ Slog.e(TAG, "getPropertyList exception ", e);
}
if (mCallbacks.isEmpty()) {
mCarPropertyMgr.unregisterCallback(mListenerToBase);
diff --git a/car-lib/src/android/car/hardware/power/CarPowerManager.java b/car-lib/src/android/car/hardware/power/CarPowerManager.java
index 9522c09..7097378 100644
--- a/car-lib/src/android/car/hardware/power/CarPowerManager.java
+++ b/car-lib/src/android/car/hardware/power/CarPowerManager.java
@@ -28,11 +28,11 @@
import android.annotation.TestApi;
import android.car.Car;
import android.car.CarManagerBase;
+import android.car.builtin.util.Slogf;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
-import android.util.Log;
import android.util.Pair;
import android.util.SparseIntArray;
@@ -486,7 +486,7 @@
}
if (listenerToService == null) {
- Log.w(TAG, "clearListener: listener was not registered");
+ Slogf.w(TAG, "clearListener: listener was not registered");
return;
}
@@ -794,7 +794,7 @@
private void cleanupFutureLocked() {
if (mFuture != null) {
mFuture.invalidate();
- Log.w(TAG, "The current future becomes invalid");
+ Slogf.w(TAG, "The current future becomes invalid");
mFuture = null;
}
}
@@ -884,7 +884,7 @@
public void complete() {
synchronized (mCompletionLock) {
if (!mCanBeCompleted) {
- Log.w(TAG, "Cannot complete: already completed or invalid state");
+ Slogf.w(TAG, "Cannot complete: already completed or invalid state");
return;
}
// Once completed, this instance cannot be completed again.
diff --git a/car-lib/src/android/car/hardware/property/AreaIdConfig.java b/car-lib/src/android/car/hardware/property/AreaIdConfig.java
index 8f7e3d8..37fb8a3 100644
--- a/car-lib/src/android/car/hardware/property/AreaIdConfig.java
+++ b/car-lib/src/android/car/hardware/property/AreaIdConfig.java
@@ -201,7 +201,10 @@
* method in {@code AreaIdConfig}.
*
* @hide
+ * @deprecated marked as deprecated because clients should not have direct access to the
+ * AreaIdConfig.Builder class
*/
+ @Deprecated
@SystemApi
public static final class Builder<T> {
private final @CarPropertyConfig.VehiclePropertyAccessType int mAccess;
diff --git a/car-lib/src/android/car/hardware/property/CarPropertyManager.java b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
index 4001be5..5703ffe 100644
--- a/car-lib/src/android/car/hardware/property/CarPropertyManager.java
+++ b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
@@ -51,6 +51,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
@@ -209,7 +210,7 @@
default void onErrorEvent(int propertyId, int areaId,
@CarSetPropertyErrorCode int errorCode) {
if (DBG) {
- Log.d(TAG, "onErrorEvent propertyId: " + VehiclePropertyIds.toString(propertyId)
+ Slog.d(TAG, "onErrorEvent propertyId: " + VehiclePropertyIds.toString(propertyId)
+ " areaId: 0x" + toHexString(areaId) + " ErrorCode: " + errorCode);
}
onErrorEvent(propertyId, areaId);
@@ -802,14 +803,14 @@
public void onSuccess(GetPropertyCallback callback, GetPropertyResult result) {
if (DBG) {
- Log.d(TAG, "delivering success get property result: " + result);
+ Slog.d(TAG, "delivering success get property result: " + result);
}
callback.onSuccess(result);
}
public void onFailure(GetPropertyCallback callback, PropertyAsyncError error) {
if (DBG) {
- Log.d(TAG, "delivering error get property result: " + error);
+ Slog.d(TAG, "delivering error get property result: " + error);
}
callback.onFailure(error);
}
@@ -827,14 +828,14 @@
public void onSuccess(SetPropertyCallback callback, SetPropertyResult result) {
if (DBG) {
- Log.d(TAG, "delivering success set property result: " + result);
+ Slog.d(TAG, "delivering success set property result: " + result);
}
callback.onSuccess(result);
}
public void onFailure(SetPropertyCallback callback, PropertyAsyncError error) {
if (DBG) {
- Log.d(TAG, "delivering error set property result: " + error);
+ Slog.d(TAG, "delivering error set property result: " + error);
}
callback.onFailure(error);
}
@@ -878,7 +879,7 @@
mRequestIdToAsyncRequestInfo.remove(requestId);
}
if (requestInfo == null) {
- Log.w(TAG, "onResults: Request ID: " + requestId
+ Slog.w(TAG, "onResults: Request ID: " + requestId
+ " might have been completed, cancelled or an exception might have "
+ "been thrown");
continue;
@@ -897,14 +898,14 @@
// This is a get result.
int valuePropertyId = carPropertyValue.getPropertyId();
if (propertyId != valuePropertyId) {
- Log.e(TAG, "onResults: Request ID: " + requestId + " received get "
+ Slog.e(TAG, "onResults: Request ID: " + requestId + " received get "
+ "property value result, but has mismatch property ID, "
+ " expect: " + propertyName + ", got: "
+ VehiclePropertyIds.toString(valuePropertyId));
}
int valueAreaId = carPropertyValue.getAreaId();
if (areaId != valueAreaId) {
- Log.e(TAG, "onResults: Property: " + propertyName + " Request ID: "
+ Slog.e(TAG, "onResults: Property: " + propertyName + " Request ID: "
+ requestId + " received get property value result, but has "
+ "mismatch area ID, expect: " + areaId + ", got: "
+ valueAreaId);
@@ -1061,7 +1062,7 @@
ArraySet<CarPropertyEventCallbackController> cpeCallbackControllerSet =
mPropIdToCpeCallbackControllerList.get(propertyId);
if (cpeCallbackControllerSet == null) {
- Log.w(TAG, "handleEvent: could not find any callbacks for propertyId="
+ Slog.w(TAG, "handleEvent: could not find any callbacks for propertyId="
+ VehiclePropertyIds.toString(propertyId));
return;
}
@@ -1078,6 +1079,14 @@
}
/**
+ * @deprecated Use
+ * {@link CarPropertyManager#subscribePropertyEvents(int, float, CarPropertyEventCallback)}
+ * instead. Note that {@code subscribePropertyEvents} by default has variable update rate on
+ * for continuous properties, but {@code registerCallback} by default has variable update rate
+ * off. If you want to keep the current behavior of receiving property events for duplicate
+ * values (which hurts performance), please specify the variable update rate option via
+ * {@link Subscription.Builder#setVariableUpdateRateEnabled}.
+ *
* Registers {@link CarPropertyEventCallback} to get property updates.
* Multiple callbacks can be registered for a single property or the same callback can be used
* for different properties. If the same callback is registered again for the same property,
@@ -1147,6 +1156,11 @@
* <p>Note that the callback will be executed on the event handler provided to the
* {@link android.car.Car} or the main thread if none was provided.
*
+ * <p>
+ * If one {@link CarPropertyEventCallback} is already registered using
+ * {@link CarPropertyManager#subscribePropertyEvents}, caller must make sure the executor was
+ * null (using the default executor) when calling subscribePropertyEvents.
+ *
* @param carPropertyEventCallback the CarPropertyEventCallback to be registered
* @param propertyId the property ID to subscribe
* @param updateRateHz how fast the property events are delivered in Hz
@@ -1154,11 +1168,12 @@
* @throws SecurityException if the property is supported and the caller has write permission,
* but does not have read permission.
*/
+ @Deprecated
@SuppressWarnings("FormatString")
public boolean registerCallback(@NonNull CarPropertyEventCallback carPropertyEventCallback,
int propertyId, @FloatRange(from = 0.0, to = 100.0) float updateRateHz) {
if (DBG) {
- Log.d(TAG, String.format("registerCallback, callback: %s propertyId: %s, "
+ Slog.d(TAG, String.format("registerCallback, callback: %s propertyId: %s, "
+ "updateRateHz: %f", carPropertyEventCallback,
VehiclePropertyIds.toString(propertyId), updateRateHz));
}
@@ -1196,7 +1211,7 @@
return subscribePropertyEventsInternal(List.of(subscribeOption),
/* callbackExecutor= */ null, carPropertyEventCallback);
} catch (IllegalArgumentException | SecurityException e) {
- Log.w(TAG, "register: PropertyId=" + propertyId + ", exception=", e);
+ Slog.w(TAG, "register: PropertyId=" + propertyId + ", exception=", e);
return false;
}
}
@@ -1497,7 +1512,7 @@
requireNonNull(carPropertyEventCallback);
validateAreaDisjointness(subscribeOptions);
if (DBG) {
- Log.d(TAG, String.format("subscribePropertyEvents, callback: %s subscribeOptions: %s",
+ Slog.d(TAG, String.format("subscribePropertyEvents, callback: %s subscribeOptions: %s",
carPropertyEventCallback, subscribeOptions));
}
int[] noReadPermPropertyIds;
@@ -1525,7 +1540,7 @@
try {
sanitizedSubscribeOptions = sanitizeSubscribeOptions(subscribeOptions);
} catch (IllegalStateException e) {
- Log.e(TAG, "failed to sanitize update rate", e);
+ Slog.e(TAG, "failed to sanitize update rate", e);
return false;
}
@@ -1542,10 +1557,18 @@
sanitizedSubscribeOptions);
if (!applySubscriptionChangesLocked()) {
- Log.e(TAG, "Subscription failed: failed to apply subscription changes");
+ Slog.e(TAG, "Subscription failed: failed to apply subscription changes");
return false;
}
+ if (cpeCallbackController == null) {
+ cpeCallbackController =
+ new CarPropertyEventCallbackController(carPropertyEventCallback,
+ callbackExecutor);
+ mCpeCallbackToCpeCallbackController.put(carPropertyEventCallback,
+ cpeCallbackController);
+ }
+
// Must use sanitizedSubscribeOptions instead of subscribeOptions here since we need to
// use sanitized update rate.
for (int i = 0; i < sanitizedSubscribeOptions.size(); i++) {
@@ -1553,14 +1576,6 @@
int propertyId = option.propertyId;
float sanitizedUpdateRateHz = option.updateRateHz;
int[] areaIds = option.areaIds;
-
- if (cpeCallbackController == null) {
- cpeCallbackController =
- new CarPropertyEventCallbackController(carPropertyEventCallback,
- callbackExecutor);
- mCpeCallbackToCpeCallbackController.put(carPropertyEventCallback,
- cpeCallbackController);
- }
// After {@code sanitizeSubscribeOptions}, update rate must be 0
// for on-change property and non-0 for continuous property.
// There is an edge case where minSampleRate is 0 and client uses 0 as sample rate
@@ -1646,13 +1661,13 @@
propertiesToUnsubscribe);
if (propertiesToUnsubscribe.isEmpty() && updatedCarSubscriptions.isEmpty()) {
- Log.d(TAG, "There is nothing to subscribe or unsubscribe to CarPropertyService");
+ Slog.d(TAG, "There is nothing to subscribe or unsubscribe to CarPropertyService");
mSubscriptionManager.commit();
return true;
}
if (DBG) {
- Log.d(TAG, "updatedCarSubscriptions to subscribe is: "
+ Slog.d(TAG, "updatedCarSubscriptions to subscribe is: "
+ updatedCarSubscriptions + " and the list of properties to unsubscribe is: "
+ CarPropertyHelper.propertyIdsToString(propertiesToUnsubscribe));
}
@@ -1668,7 +1683,7 @@
if (!propertiesToUnsubscribe.isEmpty()) {
for (int i = 0; i < propertiesToUnsubscribe.size(); i++) {
if (!unregisterLocked(propertiesToUnsubscribe.get(i))) {
- Log.w(TAG, "Failed to unsubscribe to: " + VehiclePropertyIds.toString(
+ Slog.w(TAG, "Failed to unsubscribe to: " + VehiclePropertyIds.toString(
propertiesToUnsubscribe.get(i)));
mSubscriptionManager.dropCommit();
return false;
@@ -1700,7 +1715,7 @@
} catch (SecurityException e) {
throw e;
} catch (Exception e) {
- Log.w(TAG, "registerLocked with options: " + options
+ Slog.w(TAG, "registerLocked with options: " + options
+ ", unexpected exception=", e);
return false;
}
@@ -1723,7 +1738,7 @@
} catch (SecurityException e) {
throw e;
} catch (Exception e) {
- Log.w(TAG, "unregisterLocked with property: "
+ Slog.w(TAG, "unregisterLocked with property: "
+ VehiclePropertyIds.toString(propertyId)
+ ", unexpected exception=", e);
return false;
@@ -1745,14 +1760,14 @@
@NonNull CarPropertyEventCallback carPropertyEventCallback) {
requireNonNull(carPropertyEventCallback);
if (DBG) {
- Log.d(TAG, "unsubscribePropertyEvents, callback: " + carPropertyEventCallback);
+ Slog.d(TAG, "unsubscribePropertyEvents, callback: " + carPropertyEventCallback);
}
int[] propertyIds;
synchronized (mLock) {
CarPropertyEventCallbackController cpeCallbackController =
mCpeCallbackToCpeCallbackController.get(carPropertyEventCallback);
if (cpeCallbackController == null) {
- Log.w(TAG, "unsubscribePropertyEvents: callback was not previously registered.");
+ Slog.w(TAG, "unsubscribePropertyEvents: callback was not previously registered.");
return;
}
propertyIds = cpeCallbackController.getSubscribedProperties();
@@ -1765,6 +1780,9 @@
}
/**
+ * @deprecated Use
+ * {@link CarPropertyManager#unsubscribePropertyEvents(CarPropertyEventCallback)} instead.
+ *
* Stop getting property updates for the given {@link CarPropertyEventCallback}. If there are
* multiple registrations for this {@link CarPropertyEventCallback}, all listening will be
* stopped.
@@ -1773,9 +1791,10 @@
* @throws SecurityException if the caller does not have read permission to the properties
* registered for this callback.
*/
+ @Deprecated
public void unregisterCallback(@NonNull CarPropertyEventCallback carPropertyEventCallback) {
if (DBG) {
- Log.d(TAG, "unregisterCallback, callback: " + carPropertyEventCallback);
+ Slog.d(TAG, "unregisterCallback, callback: " + carPropertyEventCallback);
}
requireNonNull(carPropertyEventCallback);
int[] propertyIds;
@@ -1783,7 +1802,7 @@
CarPropertyEventCallbackController cpeCallbackController =
mCpeCallbackToCpeCallbackController.get(carPropertyEventCallback);
if (cpeCallbackController == null) {
- Log.w(TAG, "unregisterCallback: callback was not previously registered.");
+ Slog.w(TAG, "unregisterCallback: callback was not previously registered.");
return;
}
propertyIds = cpeCallbackController.getSubscribedProperties();
@@ -1812,6 +1831,9 @@
}
/**
+ * @deprecated Use
+ * {@link CarPropertyManager#unsubscribePropertyEvents(int, CarPropertyEventCallback)} instead.
+ *
* Stop getting update for {@code propertyId} to the given {@link CarPropertyEventCallback}. If
* the same {@link CarPropertyEventCallback} is used for other properties, those subscriptions
* will not be affected.
@@ -1820,11 +1842,12 @@
* @param propertyId The property ID to unsubscribe.
* @throws SecurityException if the caller does not have read permission to the property.
*/
+ @Deprecated
@SuppressWarnings("FormatString")
public void unregisterCallback(@NonNull CarPropertyEventCallback carPropertyEventCallback,
int propertyId) {
if (DBG) {
- Log.d(TAG, String.format("unregisterCallback, callback: %s, property Id: %s",
+ Slog.d(TAG, String.format("unregisterCallback, callback: %s, property Id: %s",
carPropertyEventCallback, VehiclePropertyIds.toString(propertyId)));
}
requireNonNull(carPropertyEventCallback);
@@ -1845,24 +1868,24 @@
CarPropertyConfigs configs = getPropertyConfigsFromService(filteredPropertyIds);
if (configs == null) {
- Log.e(TAG, "failed to get property config list from car service, do nothing");
+ Slog.e(TAG, "failed to get property config list from car service, do nothing");
return;
}
for (int i = 0; i < filteredPropertyIds.size(); i++) {
int propertyId = filteredPropertyIds.get(i);
if (DBG) {
- Log.d(TAG, String.format(
+ Slog.d(TAG, String.format(
"unsubscribePropertyEvents, callback: %s, property Id: %s",
carPropertyEventCallback, VehiclePropertyIds.toString(propertyId)));
}
if (configs.isNotSupported(propertyId)) {
- Log.e(TAG, "unsubscribePropertyEvents: not supported property: "
+ Slog.e(TAG, "unsubscribePropertyEvents: not supported property: "
+ VehiclePropertyIds.toString(propertyId));
continue;
}
if (configs.missingPermission(propertyId)) {
- Log.e(TAG, "unsubscribePropertyEvents: missing read/write permission for "
+ Slog.e(TAG, "unsubscribePropertyEvents: missing read/write permission for "
+ "property: " + VehiclePropertyIds.toString(propertyId));
continue;
}
@@ -1870,11 +1893,11 @@
mPropIdToCpeCallbackControllerList.get(propertyId);
if (cpeCallbackControllerSet == null) {
- Log.e(TAG,
+ Slog.e(TAG,
"unsubscribePropertyEvents: callback was not previously registered.");
continue;
} else if (!cpeCallbackControllerSet.contains(cpeCallbackController)) {
- Log.e(TAG,
+ Slog.e(TAG,
"unsubscribePropertyEvents: callback was not previously registered for"
+ " propertyId=" + VehiclePropertyIds.toString(propertyId));
continue;
@@ -1906,19 +1929,19 @@
@NonNull
public List<CarPropertyConfig> getPropertyList() {
if (DBG) {
- Log.d(TAG, "getPropertyList");
+ Slog.d(TAG, "getPropertyList");
}
List<CarPropertyConfig> configs;
try {
configs = mService.getPropertyList().getConfigs();
} catch (RemoteException e) {
- Log.e(TAG, "getPropertyList exception ", e);
+ Slog.e(TAG, "getPropertyList exception ", e);
return handleRemoteExceptionFromCarService(e, new ArrayList<>());
}
if (DBG) {
- Log.d(TAG, "getPropertyList returns " + configs.size() + " configs");
+ Slog.d(TAG, "getPropertyList returns " + configs.size() + " configs");
for (int i = 0; i < configs.size(); i++) {
- Log.v(TAG, i + ": " + configs.get(i));
+ Slog.v(TAG, i + ": " + configs.get(i));
}
}
return configs;
@@ -1935,7 +1958,7 @@
@NonNull
public List<CarPropertyConfig> getPropertyList(@NonNull ArraySet<Integer> propertyIds) {
if (DBG) {
- Log.d(TAG, "getPropertyList(" + CarPropertyHelper.propertyIdsToString(propertyIds)
+ Slog.d(TAG, "getPropertyList(" + CarPropertyHelper.propertyIdsToString(propertyIds)
+ ")");
}
CarPropertyConfigs configs = getPropertyConfigsFromService(propertyIds);
@@ -1943,19 +1966,19 @@
return new ArrayList<>();
}
if (configs.getMissingPermissionPropIds().length != 0) {
- Log.w(TAG, "Missing required permissions to access properties: "
+ Slog.w(TAG, "Missing required permissions to access properties: "
+ CarPropertyHelper.propertyIdsToString(configs.getMissingPermissionPropIds()));
}
if (configs.getUnsupportedPropIds().length != 0) {
- Log.w(TAG, "The following properties are not supported: "
+ Slog.w(TAG, "The following properties are not supported: "
+ CarPropertyHelper.propertyIdsToString(configs.getUnsupportedPropIds()));
}
List<CarPropertyConfig> configList = configs.getConfigs();
if (DBG) {
- Log.d(TAG, "getPropertyList(" + CarPropertyHelper.propertyIdsToString(propertyIds)
+ Slog.d(TAG, "getPropertyList(" + CarPropertyHelper.propertyIdsToString(propertyIds)
+ ") returns " + configList.size() + " configs");
for (int i = 0; i < configList.size(); i++) {
- Log.v(TAG, i + ": " + configList.get(i));
+ Slog.v(TAG, i + ": " + configList.get(i));
}
}
return configList;
@@ -1971,11 +1994,11 @@
@Nullable
public CarPropertyConfig<?> getCarPropertyConfig(int propertyId) {
if (DBG) {
- Log.d(TAG, "getCarPropertyConfig(" + VehiclePropertyIds.toString(propertyId) + ")");
+ Slog.d(TAG, "getCarPropertyConfig(" + VehiclePropertyIds.toString(propertyId) + ")");
}
assertNotUserHalProperty(propertyId);
if (!CarPropertyHelper.isSupported(propertyId)) {
- Log.w(TAG, "Property: " + VehiclePropertyIds.toString(propertyId)
+ Slog.w(TAG, "Property: " + VehiclePropertyIds.toString(propertyId)
+ " is not supported");
return null;
}
@@ -1987,19 +2010,19 @@
}
if (configs.missingPermission(propertyId)) {
- Log.w(TAG, "Missing required permissions to access property: "
+ Slog.w(TAG, "Missing required permissions to access property: "
+ VehiclePropertyIds.toString(propertyId));
return null;
}
if (configs.isNotSupported(propertyId)) {
- Log.w(TAG, "The property is not supported: "
+ Slog.w(TAG, "The property is not supported: "
+ VehiclePropertyIds.toString(propertyId));
return null;
}
CarPropertyConfig<?> config = configs.getConfigs().get(0);
if (DBG) {
- Log.d(TAG, "getCarPropertyConfig(" + VehiclePropertyIds.toString(propertyId)
+ Slog.d(TAG, "getCarPropertyConfig(" + VehiclePropertyIds.toString(propertyId)
+ ") returns " + config);
}
return config;
@@ -2018,7 +2041,7 @@
assertNotUserHalProperty(propertyId);
String propertyIdStr = VehiclePropertyIds.toString(propertyId);
if (DBG) {
- Log.d(TAG, "getAreaId(propertyId = " + propertyIdStr + ", area = " + area + ")");
+ Slog.d(TAG, "getAreaId(propertyId = " + propertyIdStr + ", area = " + area + ")");
}
CarPropertyConfigs configs = getPropertyConfigsFromService(
new ArraySet<>(Set.of(propertyId)));
@@ -2036,14 +2059,14 @@
// For the global property, areaId is 0
if (propConfig.isGlobalProperty()) {
if (DBG) {
- Log.d(TAG, "getAreaId returns the global area ID (0)");
+ Slog.d(TAG, "getAreaId returns the global area ID (0)");
}
return 0;
}
for (int areaId : propConfig.getAreaIds()) {
if ((area & areaId) == area) {
if (DBG) {
- Log.d(TAG, "getAreaId returns " + areaId);
+ Slog.d(TAG, "getAreaId returns " + areaId);
}
return areaId;
}
@@ -2069,7 +2092,7 @@
try {
String permission = mService.getReadPermission(propId);
if (DBG) {
- Log.d(TAG, "getReadPermission(propId =" + VehiclePropertyIds.toString(propId)
+ Slog.d(TAG, "getReadPermission(propId =" + VehiclePropertyIds.toString(propId)
+ ") returns " + permission);
}
return permission;
@@ -2094,7 +2117,7 @@
try {
String permission = mService.getWritePermission(propId);
if (DBG) {
- Log.d(TAG, "getWritePermission(propId = " + VehiclePropertyIds.toString(propId)
+ Slog.d(TAG, "getWritePermission(propId = " + VehiclePropertyIds.toString(propId)
+ ") returns " + permission);
}
return permission;
@@ -2114,13 +2137,13 @@
*/
public boolean isPropertyAvailable(int propertyId, int areaId) {
if (DBG) {
- Log.d(TAG, "isPropertyAvailable(propertyId = "
+ Slog.d(TAG, "isPropertyAvailable(propertyId = "
+ VehiclePropertyIds.toString(propertyId) + ", areaId = " + areaId + ")");
}
assertNotUserHalProperty(propertyId);
if (!CarPropertyHelper.isSupported(propertyId)) {
if (DBG) {
- Log.d(TAG, "Property: " + VehiclePropertyIds.toString(propertyId)
+ Slog.d(TAG, "Property: " + VehiclePropertyIds.toString(propertyId)
+ " is not supported");
}
return false;
@@ -2129,7 +2152,7 @@
try {
CarPropertyValue propValue = runSyncOperation(() -> {
if (DBG) {
- Log.d(TAG, "calling getProperty to check property's availability");
+ Slog.d(TAG, "calling getProperty to check property's availability");
}
return mService.getProperty(propertyId, areaId);
});
@@ -2138,7 +2161,7 @@
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, false);
} catch (ServiceSpecificException e) {
- Log.e(TAG, "unable to get property, error: " + e);
+ Slog.e(TAG, "unable to get property, error: " + e);
return false;
}
}
@@ -2218,9 +2241,9 @@
* @throws CarInternalErrorException when there is an unexpected error detected in cars
* @throws PropertyAccessDeniedSecurityException when cars denied the access of the
* property
- * @throws PropertyNotAvailableAndRetryException when the property is temporarily
+ * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily
* not available and likely that retrying will be successful
- * @throws PropertyNotAvailableException when the property is not available and might be
+ * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
* unavailable for a while.
* @throws IllegalArgumentException when the [propertyId, areaId] is not supported for U and
* later client, or when the property is of wrong type.
@@ -2246,9 +2269,9 @@
* @throws CarInternalErrorException when there is an unexpected error detected in cars
* @throws PropertyAccessDeniedSecurityException when cars denied the access of the
* property
- * @throws PropertyNotAvailableAndRetryException when the property is temporarily
+ * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily
* not available and likely that retrying will be successful
- * @throws PropertyNotAvailableException when the property is not available and might be
+ * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
* unavailable for a while.
* @throws IllegalArgumentException when the [propertyId, areaId] is not supported for U and
* later client, or when the property is of wrong type.
@@ -2274,9 +2297,9 @@
* @throws CarInternalErrorException when there is an unexpected error detected in cars
* @throws PropertyAccessDeniedSecurityException when cars denied the access of the
* property
- * @throws PropertyNotAvailableAndRetryException when the property is temporarily
+ * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily
* not available and likely that retrying will be successful
- * @throws PropertyNotAvailableException when the property is not available and might be
+ * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
* unavailable for a while.
* @throws IllegalArgumentException when the [propertyId, areaId] is not supported for U and
* later client, or when the property is of wrong type.
@@ -2302,9 +2325,9 @@
* @throws CarInternalErrorException when there is an unexpected error detected in cars
* @throws PropertyAccessDeniedSecurityException when cars denied the access of the
* property
- * @throws PropertyNotAvailableAndRetryException when the property is temporarily
+ * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily
* not available and likely that retrying will be successful
- * @throws PropertyNotAvailableException when the property is not available and might be
+ * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
* unavailable for a while.
* @throws IllegalArgumentException when the [propertyId, areaId] is not supported for U and
* later client, or when the property is of wrong type.
@@ -2370,7 +2393,7 @@
}
// If car service don't have enough binder thread to handle this request. Sleep for
// 10ms and try again.
- Log.d(TAG, "too many sync request, sleeping for " + SYNC_OP_RETRY_SLEEP_IN_MS
+ Slog.d(TAG, "too many sync request, sleeping for " + SYNC_OP_RETRY_SLEEP_IN_MS
+ " ms before retry");
SystemClock.sleep(SYNC_OP_RETRY_SLEEP_IN_MS);
} catch (RemoteException e) {
@@ -2451,7 +2474,7 @@
*
* <p>For pre-U client, the returned {@link CarPropertyValue} might contain unavailable or
* error status. Client must use {@link CarPropertyValue#getStatus} to check. If the returned
- * status is not {@link CarPropertyValue.STATUS_AVAILABLE}, then the value returned via
+ * status is not {@link CarPropertyValue#STATUS_AVAILABLE}, then the value returned via
* {@link CarPropertyValue#getValue} is undefined.
*
* <p>For U and later client, when the [propertyId, areaId] is not supported, this is
@@ -2459,11 +2482,11 @@
* {@code null}.
*
* <p>For U and later client, if the property's status is
- * {@link CarPropertyValue.STATUS_UNAVAILABLE}, then {@link PropertyNotAvailableException} will
- * be thrown. If the property's status is {@link CarPropertyValue.STATUS_ERROR}, then
+ * {@link CarPropertyValue#STATUS_UNAVAILABLE}, then {@link PropertyNotAvailableException} will
+ * be thrown. If the property's status is {@link CarPropertyValue#STATUS_ERROR}, then
* {@link CarInternalErrorException} will be thrown. If no exception is thrown, the returned
* {@link CarPropertyValue#getStatus} is guaranteed to be
- * {@link CarPropertyValue.STATUS_AVAILABLE} so client do not need to check.
+ * {@link CarPropertyValue#STATUS_AVAILABLE} so client do not need to check.
*
* @param clazz the class object for the CarPropertyValue
* @param propertyId the property ID to get
@@ -2472,9 +2495,9 @@
* @throws CarInternalErrorException when there is an unexpected error detected in cars
* @throws PropertyAccessDeniedSecurityException when cars denied the access of the
* property
- * @throws PropertyNotAvailableAndRetryException when the property is temporarily
+ * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily
* not available and likely that retrying will be successful
- * @throws PropertyNotAvailableException when the property is not available and might be
+ * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
* unavailable for a while.
* @throws IllegalArgumentException when the [propertyId, areaId] is not supported for U and
* later client, or when the specified class does not match the property type.
@@ -2563,7 +2586,7 @@
*
* <p>For pre-U client, the returned {@link CarPropertyValue} might contain unavailable or
* error status. Client must use {@link CarPropertyValue#getStatus} to check. If the returned
- * status is not {@link CarPropertyValue.STATUS_AVAILABLE}, then the value returned via
+ * status is not {@link CarPropertyValue#STATUS_AVAILABLE}, then the value returned via
* {@link CarPropertyValue#getValue} is undefined.
*
* <p>For U and later client, when the [propertyId, areaId] is not supported, this is
@@ -2571,11 +2594,11 @@
* {@code null}.
*
* <p>For U and later client, if the property's status is
- * {@link CarPropertyValue.STATUS_UNAVAILABLE}, then {@link PropertyNotAvailableException} will
- * be thrown. If the property's status is {@link CarPropertyValue.STATUS_ERROR}, then
+ * {@link CarPropertyValue#STATUS_UNAVAILABLE}, then {@link PropertyNotAvailableException} will
+ * be thrown. If the property's status is {@link CarPropertyValue#STATUS_ERROR}, then
* {@link CarInternalErrorException} will be thrown. If no exception is thrown, the returned
* {@link CarPropertyValue#getStatus} is guaranteed to be
- * {@link CarPropertyValue.STATUS_AVAILABLE} so client do not need to check.
+ * {@link CarPropertyValue#STATUS_AVAILABLE} so client do not need to check.
*
* @param propertyId the property ID to get
* @param areaId the area ID of the property to get
@@ -2584,9 +2607,9 @@
* @throws CarInternalErrorException when there is an unexpected error detected in cars
* @throws PropertyAccessDeniedSecurityException when cars denied the access of the
* property
- * @throws PropertyNotAvailableAndRetryException when the property is temporarily
+ * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily
* not available and likely that retrying will be successful
- * @throws PropertyNotAvailableException when the property is not available and might be
+ * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
* unavailable for a while.
* @throws IllegalArgumentException when the [propertyId, areaId] is not supported for U and
* later client.
@@ -2596,7 +2619,7 @@
@Nullable
public <E> CarPropertyValue<E> getProperty(int propertyId, int areaId) {
if (DBG) {
- Log.d(TAG, "getProperty, propertyId: " + VehiclePropertyIds.toString(propertyId)
+ Slog.d(TAG, "getProperty, propertyId: " + VehiclePropertyIds.toString(propertyId)
+ ", areaId: 0x" + toHexString(areaId));
}
@@ -2644,7 +2667,8 @@
return handleRemoteExceptionFromCarService(e, null);
} catch (ServiceSpecificException e) {
if (DBG) {
- Log.d(TAG, "getProperty received service specific exception, code: " + e.errorCode);
+ Slog.d(TAG, "getProperty received service specific exception, code: "
+ + e.errorCode);
}
if (mAppTargetSdk < Build.VERSION_CODES.R) {
if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) {
@@ -2720,16 +2744,16 @@
*
* @throws CarInternalErrorException when there is an unexpected error detected in cars.
* @throws PropertyAccessDeniedSecurityException when cars denied the access of the property.
- * @throws PropertyNotAvailableException when the property is not available and might be
+ * @throws PropertyNotAvailableException when [propertyId, areaId] is not available and might be
* unavailable for a while.
- * @throws PropertyNotAvailableAndRetryException when the property is temporarily not available
- * and likely that retrying will be successful.
+ * @throws PropertyNotAvailableAndRetryException when [propertyId, areaId] is temporarily not
+ * available and likely that retrying will be successful.
* @throws IllegalArgumentException when the [propertyId, areaId] is not supported.
*/
public <E> void setProperty(@NonNull Class<E> clazz, int propertyId, int areaId,
@NonNull E val) {
if (DBG) {
- Log.d(TAG, "setProperty, propertyId: " + VehiclePropertyIds.toString(propertyId)
+ Slog.d(TAG, "setProperty, propertyId: " + VehiclePropertyIds.toString(propertyId)
+ ", areaId: 0x" + toHexString(areaId) + ", class: " + clazz + ", val: " + val);
}
@@ -2748,7 +2772,7 @@
handleRemoteExceptionFromCarService(e);
} catch (ServiceSpecificException e) {
if (DBG) {
- Log.d(TAG, "setProperty received service specific exception", e);
+ Slog.d(TAG, "setProperty received service specific exception", e);
}
if (mAppTargetSdk < Build.VERSION_CODES.R) {
if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) {
@@ -2816,7 +2840,7 @@
private void handleCarServiceSpecificException(
ServiceSpecificException e, int propertyId, int areaId) {
// We are not passing the error message down, so log it here.
- Log.w(TAG, "received ServiceSpecificException: " + e);
+ Slog.w(TAG, "received ServiceSpecificException: " + e);
int errorCode = CarPropertyErrorCodes.getVhalSystemErrorCode(e.errorCode);
int vendorErrorCode = CarPropertyErrorCodes.getVhalVendorErrorCode(e.errorCode);
@@ -2839,7 +2863,7 @@
throw new PropertyNotAvailableException(propertyId, areaId,
getPropertyNotAvailableErrorCodeFromStatusCode(errorCode), vendorErrorCode);
default:
- Log.e(TAG, "Invalid errorCode: " + errorCode + " in CarService");
+ Slog.e(TAG, "Invalid errorCode: " + errorCode + " in CarService");
throw new CarInternalErrorException(propertyId, areaId);
}
}
@@ -2928,7 +2952,7 @@
private void clearRequestIdToAsyncRequestInfo(
List<? extends AsyncPropertyRequest> asyncPropertyRequests) {
if (DBG) {
- Log.d(TAG, "clear pending async requests: " + asyncPropertyRequests);
+ Slog.d(TAG, "clear pending async requests: " + asyncPropertyRequests);
}
synchronized (mLock) {
for (int i = 0; i < asyncPropertyRequests.size(); i++) {
@@ -2984,7 +3008,7 @@
public GetPropertyRequest generateGetPropertyRequest(int propertyId, int areaId) {
int requestIdCounter = mRequestIdCounter.getAndIncrement();
if (DBG) {
- Log.d(TAG, String.format("generateGetPropertyRequest, requestId: %d, propertyId: %s, "
+ Slog.d(TAG, String.format("generateGetPropertyRequest, requestId: %d, propertyId: %s, "
+ "areaId: %d", requestIdCounter, VehiclePropertyIds.toString(propertyId),
areaId));
}
@@ -3008,7 +3032,7 @@
requireNonNull(value);
int requestIdCounter = mRequestIdCounter.getAndIncrement();
if (DBG) {
- Log.d(TAG, String.format("generateSetPropertyRequest, requestId: %d, propertyId: %s, "
+ Slog.d(TAG, String.format("generateSetPropertyRequest, requestId: %d, propertyId: %s, "
+ "areaId: %d, value: %s", requestIdCounter,
VehiclePropertyIds.toString(propertyId), areaId, value));
}
@@ -3060,7 +3084,7 @@
@Nullable @CallbackExecutor Executor callbackExecutor,
@NonNull GetPropertyCallback getPropertyCallback) {
if (DBG) {
- Log.d(TAG, "getPropertiesAsync, requests: " + getPropertyRequests + ", timeoutInMs: "
+ Slog.d(TAG, "getPropertiesAsync, requests: " + getPropertyRequests + ", timeoutInMs: "
+ timeoutInMs + ", callback: " + getPropertyCallback);
}
@@ -3085,12 +3109,12 @@
try {
if (DBG) {
- Log.d(TAG, "calling CarPropertyService.getPropertiesAsync");
+ Slog.d(TAG, "calling CarPropertyService.getPropertiesAsync");
}
mService.getPropertiesAsync(new AsyncPropertyServiceRequestList(
getPropertyServiceRequests), mAsyncPropertyResultCallback, timeoutInMs);
if (DBG) {
- Log.d(TAG, "CarPropertyService.getPropertiesAsync succeed");
+ Slog.d(TAG, "CarPropertyService.getPropertiesAsync succeed");
}
} catch (RemoteException e) {
clearRequestIdToAsyncRequestInfo(getPropertyRequests);
@@ -3177,7 +3201,7 @@
@Nullable @CallbackExecutor Executor callbackExecutor,
@NonNull SetPropertyCallback setPropertyCallback) {
if (DBG) {
- Log.d(TAG, "setPropertiesAsync, requests: " + setPropertyRequests + ", timeoutInMs: "
+ Slog.d(TAG, "setPropertiesAsync, requests: " + setPropertyRequests + ", timeoutInMs: "
+ timeoutInMs + ", callback: " + setPropertyCallback);
}
@@ -3203,12 +3227,12 @@
try {
if (DBG) {
- Log.d(TAG, "calling CarPropertyService.setPropertiesAsync");
+ Slog.d(TAG, "calling CarPropertyService.setPropertiesAsync");
}
mService.setPropertiesAsync(new AsyncPropertyServiceRequestList(
setPropertyServiceRequests), mAsyncPropertyResultCallback, timeoutInMs);
if (DBG) {
- Log.d(TAG, "CarPropertyService.setPropertiesAsync succeed");
+ Slog.d(TAG, "CarPropertyService.setPropertiesAsync succeed");
}
} catch (RemoteException e) {
clearRequestIdToAsyncRequestInfo(setPropertyRequests);
@@ -3248,7 +3272,7 @@
storePendingRequestInfo(
List<RequestType> requests, Executor callbackExecutor, CallbackType callback) {
if (DBG) {
- Log.d(TAG, "store pending async requests: " + requests);
+ Slog.d(TAG, "store pending async requests: " + requests);
}
List<Integer> requestIds = new ArrayList<>();
SparseArray<AsyncPropertyRequestInfo<?, ?>> requestInfoToAdd = new SparseArray<>();
@@ -3293,7 +3317,7 @@
if (configs.isNotSupported(propertyId)) {
String errorMessage = "propertyId is not in carPropertyConfig list: "
+ VehiclePropertyIds.toString(propertyId);
- Log.e(TAG, "sanitizeUpdateRate: " + errorMessage);
+ Slog.e(TAG, "sanitizeUpdateRate: " + errorMessage);
throw new IllegalArgumentException(errorMessage);
}
if (configs.missingPermission(propertyId)) {
@@ -3302,7 +3326,7 @@
// read or write permission, {@code SecurityException} should be thrown before this.
String errorMessage = "missing required read/write permission for: "
+ VehiclePropertyIds.toString(propertyId);
- Log.wtf(TAG, "sanitizeUpdateRate: " + errorMessage);
+ Slog.wtf(TAG, "sanitizeUpdateRate: " + errorMessage);
throw new SecurityException(errorMessage);
}
@@ -3467,7 +3491,7 @@
try {
result = mService.getPropertyConfigList(filteredPropertyIds.toArray());
} catch (RemoteException e) {
- Log.e(TAG, "CarPropertyService.getPropertyConfigList exception ", e);
+ Slog.e(TAG, "CarPropertyService.getPropertyConfigList exception ", e);
return handleRemoteExceptionFromCarService(e, null);
}
return new CarPropertyConfigs(result, unsupportedPropertyIds);
diff --git a/car-lib/src/android/car/hardware/property/EvChargingConnectorType.java b/car-lib/src/android/car/hardware/property/EvChargingConnectorType.java
index e574957..59736f6 100644
--- a/car-lib/src/android/car/hardware/property/EvChargingConnectorType.java
+++ b/car-lib/src/android/car/hardware/property/EvChargingConnectorType.java
@@ -39,52 +39,62 @@
/**
* IEC 62196 Type 1 connector
*
- * <p>It is colloquially known as the "Yazaki connector" or "J1772 connector".
+ * <p>Also known as the "Yazaki connector" or "J1772 connector".
*/
public static final int IEC_TYPE_1_AC = 1;
/**
* IEC 62196 Type 2 connector
*
- * <p>It is colloquially known as the "Mennekes connector".
+ * <p>Also known as the "Mennekes connector".
*/
public static final int IEC_TYPE_2_AC = 2;
/**
* IEC 62196 Type 3 connector
*
- * <p>It is colloquially known as the "Scame connector".
+ * <p>Also known as the "Scame connector".
*/
public static final int IEC_TYPE_3_AC = 3;
/**
* IEC 62196 Type AA connector
*
- * <p>It is colloquially known as the "Chademo connector".
+ * <p>Also known as the "Chademo connector".
*/
public static final int IEC_TYPE_4_DC = 4;
/**
* IEC 62196 Type EE connector
*
- * <p>It is colloquially known as the “CCS1 connector” or “Combo1 connector".
+ * <p>Also known as the “CCS1 connector” or “Combo1 connector".
*/
public static final int IEC_TYPE_1_CCS_DC = 5;
/**
* IEC 62196 Type EE connector
*
- * <p>It is colloquially known as the “CCS2 connector” or “Combo2 connector”.
+ * <p>Also known as the “CCS2 connector” or “Combo2 connector”.
*/
public static final int IEC_TYPE_2_CCS_DC = 6;
/** Connector of Tesla Roadster */
public static final int TESLA_ROADSTER = 7;
- /** High Power Wall Charger of Tesla */
+ /**
+ * High Power Wall Charger of Tesla.
+ *
+ * <p>This is the same connector as the {@link #TESLA_SUPERCHARGER}.
+ * @see #TESLA_SUPERCHARGER
+ */
public static final int TESLA_HPWC = 8;
- /** Supercharger of Tesla */
+ /**
+ * SAE J3400 connector
+ *
+ * <p>Also known as the "North American Charging Standard" (NACS)
+ * or the "Tesla charging standard" connector.
+ */
public static final int TESLA_SUPERCHARGER = 9;
/** GBT_AC Fast Charging Standard */
diff --git a/car-lib/src/android/car/input/CarInputHandlingService.java b/car-lib/src/android/car/input/CarInputHandlingService.java
index d230014..77ce968 100644
--- a/car-lib/src/android/car/input/CarInputHandlingService.java
+++ b/car-lib/src/android/car/input/CarInputHandlingService.java
@@ -32,6 +32,7 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Slog;
import android.view.KeyEvent;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
@@ -64,7 +65,7 @@
@ExcludeFromCodeCoverageGeneratedReport(reason = DEPRECATED_CODE)
public abstract class CarInputHandlingService extends Service {
private static final String TAG = CarLibLog.TAG_INPUT;
- private static final boolean DBG = false;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
public static final String INPUT_CALLBACK_BINDER_KEY = "callback_binder";
public static final int INPUT_CALLBACK_BINDER_CODE = IBinder.FIRST_CALL_TRANSACTION;
@@ -86,7 +87,7 @@
@CallSuper
public IBinder onBind(Intent intent) {
if (DBG) {
- Log.d(TAG, "onBind, intent: " + intent);
+ Slog.d(TAG, "onBind, intent: " + intent);
}
doCallbackIfPossible(intent.getExtras());
@@ -100,12 +101,12 @@
private void doCallbackIfPossible(Bundle extras) {
if (extras == null) {
- Log.i(TAG, "doCallbackIfPossible: extras are null");
+ Slog.i(TAG, "doCallbackIfPossible: extras are null");
return;
}
IBinder callbackBinder = extras.getBinder(INPUT_CALLBACK_BINDER_KEY);
if (callbackBinder == null) {
- Log.i(TAG, "doCallbackIfPossible: callback IBinder is null");
+ Slog.i(TAG, "doCallbackIfPossible: callback IBinder is null");
return;
}
Parcel dataIn = Parcel.obtain();
diff --git a/car-lib/src/android/car/media/CarAudioManager.java b/car-lib/src/android/car/media/CarAudioManager.java
index e00c768..fc1b7f7 100644
--- a/car-lib/src/android/car/media/CarAudioManager.java
+++ b/car-lib/src/android/car/media/CarAudioManager.java
@@ -44,9 +44,10 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.util.Log;
+import android.util.Slog;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+import com.android.car.internal.ICarBase;
import com.android.car.internal.annotation.AttributeUsage;
import com.android.internal.annotations.GuardedBy;
@@ -378,7 +379,7 @@
synchronized (mLock) {
if (mPrimaryZoneMediaAudioRequestCallbackExecutor == null
|| mPrimaryZoneMediaAudioRequestCallback == null) {
- Log.w(TAG, "Media request removed before change dispatched");
+ Slog.w(TAG, "Media request removed before change dispatched");
return;
}
callback = mPrimaryZoneMediaAudioRequestCallback;
@@ -1069,7 +1070,7 @@
synchronized (mLock) {
if (mZoneConfigurationsChangeCallbackWrapper == null) {
- Log.w(TAG, "Audio zone configs callback was already cleared");
+ Slog.w(TAG, "Audio zone configs callback was already cleared");
return;
}
wrapper = mZoneConfigurationsChangeCallbackWrapper;
@@ -1742,7 +1743,7 @@
}
/** @hide */
- public CarAudioManager(Car car, IBinder service) {
+ public CarAudioManager(ICarBase car, IBinder service) {
super(car);
mService = ICarAudio.Stub.asInterface(service);
mAudioManager = getContext().getSystemService(AudioManager.class);
@@ -1785,7 +1786,7 @@
try {
mService.registerVolumeCallback(mCarVolumeCallbackImpl.asBinder());
} catch (RemoteException e) {
- Log.e(CarLibLog.TAG_CAR, "registerVolumeCallback failed", e);
+ Slog.e(CarLibLog.TAG_CAR, "registerVolumeCallback failed", e);
}
}
@@ -1833,7 +1834,7 @@
return false;
}
} catch (RemoteException e) {
- Log.e(CarLibLog.TAG_CAR, "registerCarVolumeEventCallback failed", e);
+ Slog.e(CarLibLog.TAG_CAR, "registerCarVolumeEventCallback failed", e);
return handleRemoteExceptionFromCarService(e, /* returnValue= */ false);
}
@@ -1870,12 +1871,12 @@
private boolean unregisterVolumeGroupEventCallback() {
try {
if (!mService.unregisterCarVolumeEventCallback(mCarVolumeEventCallbackImpl)) {
- Log.e(CarLibLog.TAG_CAR,
+ Slog.e(CarLibLog.TAG_CAR,
"unregisterCarVolumeEventCallback failed with service");
return false;
}
} catch (RemoteException e) {
- Log.e(CarLibLog.TAG_CAR,
+ Slog.e(CarLibLog.TAG_CAR,
"unregisterCarVolumeEventCallback failed with exception", e);
handleRemoteExceptionFromCarService(e);
}
@@ -1978,7 +1979,7 @@
List<CarVolumeGroupEvent> events = (List<CarVolumeGroupEvent>) msg.obj;
handleOnVolumeGroupEvent(events);
default:
- Log.e(CarLibLog.TAG_CAR, "Unknown message not handled:" + msg.what);
+ Slog.e(CarLibLog.TAG_CAR, "Unknown message not handled:" + msg.what);
break;
}
}
@@ -2070,11 +2071,12 @@
*
* <p><b>Notes:</b>
* <ul>
- * <li>If both {@link CarVolumeCallback} and {@link CarVolumeGroupEventCallback}
+ * <li>If both {@link CarVolumeCallback} and {@code CarVolumeGroupEventCallback}
* are registered by the same app, then volume group index changes are <b>only</b>
- * propagated through CarVolumeGroupEventCallback (until it is unregistered)</li>
+ * propagated through {@code CarVolumeGroupEventCallback}
+ * (until it is unregistered)</li>
* <li>Apps are encouraged to migrate to the new callback
- * {@link CarVolumeGroupEventCallback}</li>
+ * {@code CarVolumeGroupEventCallback}</li>
* </ul>
*
* @param zoneId Id of the audio zone that volume change happens
@@ -2110,11 +2112,12 @@
* <li>If {@link CarAudioManager#AUDIO_FEATURE_VOLUME_GROUP_MUTING} is enabled
* this will be triggered on mute changes. Otherwise, car audio mute changes will
* trigger {@link #onMasterMuteChanged(int, int)}</li>
- * <li>If both {@link CarVolumeCallback} and {@link CarVolumeGroupEventCallback}
+ * <li>If both {@link CarVolumeCallback} and {@code CarVolumeGroupEventCallback}
* are registered by the same app, then volume group mute changes are <b>only</b>
- * propagated through CarVolumeGroupEventCallback (until it is unregistered)</li>
+ * propagated through {@code CarVolumeGroupEventCallback}
+ * (until it is unregistered)</li>
* <li>Apps are encouraged to migrate to the new callback
- * {@link CarVolumeGroupEventCallback}</li>
+ * {@code CarVolumeGroupEventCallback}</li>
* </ul>
*
* @param zoneId Id of the audio zone that volume change happens
diff --git a/car-lib/src/android/car/media/CarMediaManager.java b/car-lib/src/android/car/media/CarMediaManager.java
index 32e5863..1d3239e 100644
--- a/car-lib/src/android/car/media/CarMediaManager.java
+++ b/car-lib/src/android/car/media/CarMediaManager.java
@@ -20,13 +20,13 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.car.Car;
import android.car.CarManagerBase;
import android.content.ComponentName;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import com.android.car.internal.ICarBase;
import com.android.internal.annotations.GuardedBy;
import java.lang.annotation.Retention;
@@ -65,7 +65,7 @@
* Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
* @hide
*/
- public CarMediaManager(Car car, IBinder service) {
+ public CarMediaManager(ICarBase car, IBinder service) {
super(car);
mService = ICarMedia.Stub.asInterface(service);
}
diff --git a/car-lib/src/android/car/occupantawareness/OccupantAwarenessManager.java b/car-lib/src/android/car/occupantawareness/OccupantAwarenessManager.java
index 671bf0e..6ea394e 100644
--- a/car-lib/src/android/car/occupantawareness/OccupantAwarenessManager.java
+++ b/car-lib/src/android/car/occupantawareness/OccupantAwarenessManager.java
@@ -28,7 +28,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -135,7 +135,7 @@
@RequiresPermission(value = Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE)
public void registerChangeCallback(@NonNull ChangeCallback callback) {
if (DBG) {
- Log.d(TAG, "Registering change listener");
+ Slog.d(TAG, "Registering change listener");
}
synchronized (mLock) {
@@ -164,12 +164,12 @@
@RequiresPermission(value = Car.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE)
public void unregisterChangeCallback() {
if (DBG) {
- Log.d(TAG, "Unregistering change listener");
+ Slog.d(TAG, "Unregistering change listener");
}
synchronized (mLock) {
if (mChangeCallback == null) {
- Log.e(TAG, "No listener exists to unregister.");
+ Slog.e(TAG, "No listener exists to unregister.");
return;
}
mChangeCallback = null;
diff --git a/car-lib/src/android/car/os/CpuAvailabilityInfo.java b/car-lib/src/android/car/os/CpuAvailabilityInfo.java
index a2ded43..95143fd 100644
--- a/car-lib/src/android/car/os/CpuAvailabilityInfo.java
+++ b/car-lib/src/android/car/os/CpuAvailabilityInfo.java
@@ -16,9 +16,12 @@
package android.car.os;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
import android.car.annotation.ExperimentalFeature;
import android.os.Parcelable;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.util.DataClass;
/**
@@ -28,6 +31,7 @@
*/
@ExperimentalFeature
@DataClass(genToString = true, genHiddenBuilder = true)
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public final class CpuAvailabilityInfo implements Parcelable {
/** Returns the CPUSET, whose availability info is recorded in this object.
*
diff --git a/car-lib/src/android/car/os/CpuAvailabilityMonitoringConfig.java b/car-lib/src/android/car/os/CpuAvailabilityMonitoringConfig.java
index 82e75fa..fac0cc1 100644
--- a/car-lib/src/android/car/os/CpuAvailabilityMonitoringConfig.java
+++ b/car-lib/src/android/car/os/CpuAvailabilityMonitoringConfig.java
@@ -16,10 +16,13 @@
package android.car.os;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
import android.annotation.SuppressLint;
import android.car.annotation.ExperimentalFeature;
import android.os.Parcelable;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.util.DataClass;
/**
@@ -29,6 +32,7 @@
*/
@ExperimentalFeature
@DataClass(genToString = true, genBuilder = true, genHiddenConstDefs = true)
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public final class CpuAvailabilityMonitoringConfig implements Parcelable {
/** Constant to monitor all cpusets. */
@Cpuset
diff --git a/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java b/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java
index e55fe7b..766b924 100644
--- a/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java
+++ b/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java
@@ -36,7 +36,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
-import android.util.Log;
+import android.util.Slog;
import com.android.car.internal.ICarBase;
import com.android.internal.annotations.GuardedBy;
@@ -193,7 +193,7 @@
Executor executor;
synchronized (mLock) {
if (mRemoteTaskClientCallback == null || mExecutor == null) {
- Log.w(TAG, "Cannot call onRegistrationUpdated because no remote task client "
+ Slog.w(TAG, "Cannot call onRegistrationUpdated because no remote task client "
+ "is registered");
return;
}
@@ -211,7 +211,7 @@
Executor executor;
synchronized (mLock) {
if (mRemoteTaskClientCallback == null || mExecutor == null) {
- Log.w(TAG, "Cannot call onRegistrationUpdated because no remote task client "
+ Slog.w(TAG, "Cannot call onRegistrationUpdated because no remote task client "
+ "is registered");
return;
}
@@ -223,7 +223,7 @@
Binder.clearCallingIdentity();
executor.execute(() -> callback.onServerlessClientRegistered());
} else {
- Log.e(TAG, "Serverless remote access flag is not enabled, "
+ Slog.e(TAG, "Serverless remote access flag is not enabled, "
+ "the callback must not be called");
}
}
@@ -234,7 +234,7 @@
Executor executor;
synchronized (mLock) {
if (mRemoteTaskClientCallback == null || mExecutor == null) {
- Log.w(TAG, "Cannot call onRegistrationFailed because no remote task client "
+ Slog.w(TAG, "Cannot call onRegistrationFailed because no remote task client "
+ "is registered");
return;
}
@@ -252,7 +252,7 @@
Executor executor;
synchronized (mLock) {
if (mCurrentClientId == null || !mCurrentClientId.equals(clientId)) {
- Log.w(TAG, "Received a task for a mismatched client ID(" + clientId
+ Slog.w(TAG, "Received a task for a mismatched client ID(" + clientId
+ "): the current client ID = " + mCurrentClientId);
return;
}
@@ -260,7 +260,7 @@
executor = mExecutor;
}
if (callback == null || executor == null) {
- Log.w(TAG, "Cannot call onRemoteTaskRequested because no remote task client is "
+ Slog.w(TAG, "Cannot call onRemoteTaskRequested because no remote task client is "
+ "registered");
return;
}
@@ -280,7 +280,7 @@
executor = mExecutor;
}
if (clientId == null || callback == null || executor == null) {
- Log.w(TAG, "Cannot call onShutdownStarting because no remote task client is "
+ Slog.w(TAG, "Cannot call onShutdownStarting because no remote task client is "
+ "registered");
return;
}
@@ -354,7 +354,7 @@
*/
@FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS)
default void onServerlessClientRegistered() {
- Log.i(TAG, "onServerlessClientRegistered called");
+ Slog.i(TAG, "onServerlessClientRegistered called");
}
/**
@@ -445,7 +445,7 @@
public void clearRemoteTaskClient() {
synchronized (mLock) {
if (mRemoteTaskClientCallback == null) {
- Log.w(TAG, "No registered remote task client to clear");
+ Slog.w(TAG, "No registered remote task client to clear");
return;
}
mRemoteTaskClientCallback = null;
@@ -474,7 +474,7 @@
String currentClientId;
synchronized (mLock) {
if (mCurrentClientId == null) {
- Log.w(TAG, "Failed to report remote task completion: no remote task client is "
+ Slog.w(TAG, "Failed to report remote task completion: no remote task client is "
+ "registered");
throw new IllegalStateException("No remote task client is registered");
}
@@ -483,7 +483,7 @@
try {
mService.reportRemoteTaskDone(currentClientId, taskId);
} catch (IllegalStateException e) {
- Log.w(TAG, "Task ID(" + taskId + ") is not valid", e);
+ Slog.w(TAG, "Task ID(" + taskId + ") is not valid", e);
throw e;
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
@@ -556,7 +556,7 @@
@Nullable
public InVehicleTaskScheduler getInVehicleTaskScheduler() {
if (!isTaskScheduleSupported()) {
- Log.w(TAG, "getInVehicleTaskScheduler: Task schedule is not supported, return null");
+ Slog.w(TAG, "getInVehicleTaskScheduler: Task schedule is not supported, return null");
return null;
}
return mInVehicleTaskScheduler;
@@ -824,6 +824,42 @@
}
/**
+ * For testing only. Check whether the VHAL property: {@code VEHICLE_IN_USE} is supported.
+ *
+ * This property must be supported if serverless remote access is supported.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS)
+ @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS)
+ public boolean isVehicleInUseSupported() {
+ try {
+ return mService.isVehicleInUseSupported();
+ } catch (RemoteException e) {
+ return handleRemoteExceptionFromCarService(e, false);
+ }
+ }
+
+ /**
+ * For testing only. Check whether the VHAL property: {@code SHUTDOWN_REQUEST} is supported.
+ *
+ * This property must be supported if serverless remote access is supported.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS)
+ @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS)
+ public boolean isShutdownRequestSupported() {
+ try {
+ return mService.isShutdownRequestSupported();
+ } catch (RemoteException e) {
+ return handleRemoteExceptionFromCarService(e, false);
+ }
+ }
+
+ /**
* A scheduler for scheduling a task to be executed later.
*
* <p>It schedules a task via sending a scheduled task message to a device in the same vehicle,
diff --git a/car-lib/src/android/car/remoteaccess/ICarRemoteAccessService.aidl b/car-lib/src/android/car/remoteaccess/ICarRemoteAccessService.aidl
index 6ac32df..ce38c0c 100644
--- a/car-lib/src/android/car/remoteaccess/ICarRemoteAccessService.aidl
+++ b/car-lib/src/android/car/remoteaccess/ICarRemoteAccessService.aidl
@@ -116,4 +116,14 @@
* For testing only. Remove a package as serverless remote access client.
*/
void removeServerlessRemoteTaskClient(in String packageName);
+
+ /**
+ * For testing only. Check whether the VHAL property: {@code VEHICLE_IN_USE} is supported.
+ */
+ boolean isVehicleInUseSupported();
+
+ /**
+ * For testing only. Check whether the VHAL property: {@code SHUTDOWN_REQUEST} is supported.
+ */
+ boolean isShutdownRequestSupported();
}
diff --git a/car-lib/src/android/car/user/CarUserManager.java b/car-lib/src/android/car/user/CarUserManager.java
index ed5a9b5..369d17c 100644
--- a/car-lib/src/android/car/user/CarUserManager.java
+++ b/car-lib/src/android/car/user/CarUserManager.java
@@ -31,7 +31,6 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
-import android.car.Car;
import android.car.CarManagerBase;
import android.car.ICarResultReceiver;
import android.car.ICarUserService;
@@ -53,7 +52,9 @@
import android.util.Dumpable;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
+import com.android.car.internal.ICarBase;
import com.android.car.internal.ResultCallbackImpl;
import com.android.car.internal.common.CommonConstants;
import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
@@ -360,6 +361,7 @@
private final ICarUserService mService;
private final UserManager mUserManager;
+ private final boolean mIsHeadlessSystemUserMode;
/**
* Map of listeners registers by the app.
@@ -393,24 +395,26 @@
/**
* @hide
*/
- public CarUserManager(@NonNull Car car, @NonNull IBinder service) {
+ public CarUserManager(@NonNull ICarBase car, @NonNull IBinder service) {
this(car, ICarUserService.Stub.asInterface(service),
- car.getContext().getSystemService(UserManager.class));
+ car.getContext().getSystemService(UserManager.class),
+ UserManager.isHeadlessSystemUserMode());
}
/**
* @hide
*/
@VisibleForTesting
- public CarUserManager(@NonNull Car car, @NonNull ICarUserService service,
- @NonNull UserManager userManager) {
+ public CarUserManager(@NonNull ICarBase car, @NonNull ICarUserService service,
+ @NonNull UserManager userManager, boolean isHeadlessSystemUserMode) {
super(car);
mDumper = addDumpable(car.getContext(), () -> new Dumper());
- Log.d(TAG, "CarUserManager(): DBG= " + DBG + ", mDumper=" + mDumper);
+ Slog.d(TAG, "CarUserManager(): DBG= " + DBG + ", mDumper=" + mDumper);
mService = service;
mUserManager = userManager;
+ mIsHeadlessSystemUserMode = isHeadlessSystemUserMode;
}
/**
@@ -441,7 +445,7 @@
};
mService.startUser(request, callbackImpl);
} catch (SecurityException e) {
- Log.e(TAG, "startUser(userId=" + userId + ", displayId=" + displayId + ")", e);
+ Slog.e(TAG, "startUser(userId=" + userId + ", displayId=" + displayId + ")", e);
throw e;
} catch (RemoteException | RuntimeException e) {
UserStartResponse response = handleExceptionFromCarService(e,
@@ -477,7 +481,7 @@
};
mService.stopUser(request, callbackImpl);
} catch (SecurityException e) {
- Log.e(TAG, "stopUser(userId=" + userId + ")", e);
+ Slog.e(TAG, "stopUser(userId=" + userId + ")", e);
throw e;
} catch (RemoteException | RuntimeException e) {
UserStopResponse response = handleExceptionFromCarService(e,
@@ -529,7 +533,7 @@
@NonNull @CallbackExecutor Executor executor,
@NonNull ResultCallback<UserSwitchResult> callback, boolean ignoreUxRestriction) {
if (DBG) {
- Log.d(TAG, "switchuser(): userHandle=" + userSwitchRequest.getUserHandle()
+ Slog.d(TAG, "switchuser(): userHandle=" + userSwitchRequest.getUserHandle()
+ ", ignoreUxRestriction=" + ignoreUxRestriction);
}
int uid = myUid();
@@ -554,7 +558,7 @@
mService.switchUser(targetUserId, HAL_TIMEOUT_MS, resultCallbackImpl,
ignoreUxRestriction);
} catch (SecurityException e) {
- Log.w(TAG, "switchUser(" + targetUserId + ") failed: " + e);
+ Slog.w(TAG, "switchUser(" + targetUserId + ") failed: " + e);
throw e;
} catch (RemoteException | RuntimeException e) {
UserSwitchResult result = handleExceptionFromCarService(e,
@@ -740,7 +744,7 @@
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public void updatePreCreatedUsers() {
- Log.w(TAG, "updatePreCreatedUsers(): This method should not be called."
+ Slog.w(TAG, "updatePreCreatedUsers(): This method should not be called."
+ " Pre-created users are no longer supported.");
}
@@ -777,7 +781,7 @@
mService.removeUser(userRemovalRequest.getUserHandle().getIdentifier(),
resultCallbackImpl);
} catch (SecurityException e) {
- Log.e(TAG, "CarUserManager removeUser", e);
+ Slog.e(TAG, "CarUserManager removeUser", e);
throw e;
} catch (RemoteException | RuntimeException e) {
UserRemovalResult result = handleExceptionFromCarService(e,
@@ -817,10 +821,10 @@
userRemovalResult = userRemovalResultCallback.get(USER_CALL_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
- Log.e(TAG, "CarUserManager removeUser(" + userId + "): ", e);
+ Slog.e(TAG, "CarUserManager removeUser(" + userId + "): ", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
- Log.e(TAG, "CarUserManager removeUser(" + userId + "): ", e);
+ Slog.e(TAG, "CarUserManager removeUser(" + userId + "): ", e);
}
return userRemovalResult;
@@ -865,7 +869,7 @@
int uid = myUid();
String packageName = getContext().getPackageName();
if (DBG) {
- Log.d(TAG, "addListener(): uid=" + uid + ", pkg=" + packageName
+ Slog.d(TAG, "addListener(): uid=" + uid + ", pkg=" + packageName
+ ", listener=" + listener + ", filter= " + filter);
}
synchronized (mLock) {
@@ -874,12 +878,12 @@
if (mReceiver == null) {
mReceiver = new LifecycleResultReceiver();
if (DBG) {
- Log.d(TAG, "Setting lifecycle receiver with filter " + filter
+ Slog.d(TAG, "Setting lifecycle receiver with filter " + filter
+ " for uid " + uid + " and package " + packageName);
}
} else {
if (DBG) {
- Log.d(TAG, "Already set receiver for uid " + uid + " and package "
+ Slog.d(TAG, "Already set receiver for uid " + uid + " and package "
+ packageName + " adding new filter " + filter);
}
}
@@ -894,13 +898,13 @@
if (mListeners == null) {
mListeners = new ArrayMap<>(1); // Most likely app will have just one listener
} else if (DBG) {
- Log.d(TAG, "addListener(" + getLambdaName(listener) + "): context " + getContext()
+ Slog.d(TAG, "addListener(" + getLambdaName(listener) + "): context " + getContext()
+ " already has " + mListeners.size() + " listeners: "
+ mListeners.keySet().stream()
.map((l) -> getLambdaName(l))
.collect(Collectors.toList()), new Exception("caller's stack"));
}
- if (DBG) Log.d(TAG, "Adding listener: " + listener + " with filter " + filter);
+ if (DBG) Slog.d(TAG, "Adding listener: " + listener + " with filter " + filter);
mListeners.put(listener, Pair.create(filter, executor));
}
}
@@ -920,7 +924,7 @@
int uid = myUid();
String packageName = getContext().getPackageName();
if (DBG) {
- Log.d(TAG, "removeListener(): uid=" + uid + ", pkg=" + packageName
+ Slog.d(TAG, "removeListener(): uid=" + uid + ", pkg=" + packageName
+ ", listener=" + listener);
}
synchronized (mLock) {
@@ -933,19 +937,19 @@
// due to unnecessary receiver calls. It will still be functionally correct, because the
// removed listener will no longer be invoked.
if (!mListeners.isEmpty()) {
- if (DBG) Log.d(TAG, "removeListeners(): still " + mListeners.size() + " left");
+ if (DBG) Slog.d(TAG, "removeListeners(): still " + mListeners.size() + " left");
return;
}
mListeners = null;
if (mReceiver == null) {
- Log.wtf(TAG, "removeListener(): receiver already null");
+ Slog.wtf(TAG, "removeListener(): receiver already null");
return;
}
EventLogHelper.writeCarUserManagerRemoveListener(uid, packageName);
if (DBG) {
- Log.d(TAG, "Removing lifecycle receiver for uid=" + uid + " and package "
+ Slog.d(TAG, "Removing lifecycle receiver for uid=" + uid + " and package "
+ packageName);
}
try {
@@ -1050,7 +1054,7 @@
EventLogHelper.writeCarUserManagerSetUserAuthResp(loggedValues);
}
} else {
- Log.w(TAG, "setUserIdentificationAssociation(" + Arrays.toString(types)
+ Slog.w(TAG, "setUserIdentificationAssociation(" + Arrays.toString(types)
+ ", " + Arrays.toString(values) + ") failed: " + err);
}
super.onCompleted(result, err);
@@ -1132,7 +1136,7 @@
@Override
public void send(int resultCode, Bundle resultData) {
if (resultData == null) {
- Log.w(TAG, "Received result (" + resultCode + ") without data");
+ Slog.w(TAG, "Received result (" + resultCode + ") without data");
return;
}
int from = resultData.getInt(BUNDLE_PARAM_PREVIOUS_USER_ID,
@@ -1143,7 +1147,7 @@
ArrayMap<UserLifecycleListener, Pair<UserLifecycleEventFilter, Executor>> listeners;
synchronized (mLock) {
if (mListeners == null) {
- Log.w(TAG, "No listeners for event " + event);
+ Slog.w(TAG, "No listeners for event " + event);
return;
}
listeners = new ArrayMap<>(mListeners);
@@ -1155,7 +1159,7 @@
UserLifecycleEventFilter filter = listeners.valueAt(i).first;
if (filter != null && !filter.apply(event)) {
if (DBG) {
- Log.d(TAG, "Listener " + getLambdaName(listener)
+ Slog.d(TAG, "Listener " + getLambdaName(listener)
+ " is skipped for the event " + event + " due to the filter "
+ filter);
}
@@ -1163,7 +1167,7 @@
}
Executor executor = listeners.valueAt(i).second;
if (DBG) {
- Log.d(TAG, "Calling " + getLambdaName(listener) + " for event " + event);
+ Slog.d(TAG, "Calling " + getLambdaName(listener) + " for event " + event);
}
executor.execute(() -> listener.onEvent(event));
}
@@ -1295,7 +1299,7 @@
for (int i = 0; i < allUsers.size(); i++) {
UserHandle user = allUsers.get(i);
if (user.equals(userHandle) && (!userHandle.equals(UserHandle.SYSTEM)
- || !UserManager.isHeadlessSystemUserMode())) {
+ || !mIsHeadlessSystemUserMode)) {
return true;
}
}
diff --git a/car-lib/src/android/car/user/ExperimentalCarUserManager.java b/car-lib/src/android/car/user/ExperimentalCarUserManager.java
index f9f666d..3db49bc 100644
--- a/car-lib/src/android/car/user/ExperimentalCarUserManager.java
+++ b/car-lib/src/android/car/user/ExperimentalCarUserManager.java
@@ -29,7 +29,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -152,7 +152,7 @@
@Override
protected void onCompleted(UserSwitchResult result, Throwable err) {
if (result == null) {
- Log.w(TAG, "switchDriver(" + driverId + ") failed: " + err);
+ Slog.w(TAG, "switchDriver(" + driverId + ") failed: " + err);
}
super.onCompleted(result, err);
}
diff --git a/car-lib/src/android/car/user/UserStopRequest.java b/car-lib/src/android/car/user/UserStopRequest.java
index 9c03398..652e9ed 100644
--- a/car-lib/src/android/car/user/UserStopRequest.java
+++ b/car-lib/src/android/car/user/UserStopRequest.java
@@ -44,6 +44,7 @@
private final @NonNull UserHandle mUserHandle;
// withDelayedLocking is true by default, as it is the most common use case.
private boolean mWithDelayedLocking = true;
+ // Note that mForce doesn't actually do anything. Its value is irrelevant.
private boolean mForce;
public Builder(@NonNull UserHandle userHandle) {
diff --git a/car-lib/src/android/car/util/concurrent/AndroidFuture.java b/car-lib/src/android/car/util/concurrent/AndroidFuture.java
index 1512246..0e30612 100644
--- a/car-lib/src/android/car/util/concurrent/AndroidFuture.java
+++ b/car-lib/src/android/car/util/concurrent/AndroidFuture.java
@@ -26,7 +26,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.util.Log;
+import android.util.Slog;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.internal.annotations.GuardedBy;
@@ -153,7 +153,7 @@
cancelTimeout();
if (DEBUG) {
- Log.i(LOG_TAG, this + " completed with result " + (err == null ? res : err),
+ Slog.i(LOG_TAG, this + " completed with result " + (err == null ? res : err),
new RuntimeException());
}
@@ -171,7 +171,7 @@
try {
mRemoteOrigin.complete(this /* resultContainer */);
} catch (RemoteException e) {
- Log.e(LOG_TAG, "Failed to propagate completion", e);
+ Slog.e(LOG_TAG, "Failed to propagate completion", e);
}
}
}
@@ -258,7 +258,7 @@
}
} catch (Throwable t2) {
// give up on listener and log the result & exception to logcat
- Log.e(LOG_TAG, "Failed to call whenComplete listener. res = " + res, t2);
+ Slog.e(LOG_TAG, "Failed to call whenComplete listener. res = " + res, t2);
}
}
@@ -521,7 +521,7 @@
changed = completeExceptionally(unwrapExecutionException(t));
}
if (!changed) {
- Log.w(LOG_TAG, "Remote result " + resultContainer
+ Slog.w(LOG_TAG, "Remote result " + resultContainer
+ " ignored, as local future is already completed: "
+ AndroidFuture.this);
}
diff --git a/car-lib/src/android/car/view/MirroredSurfaceView.java b/car-lib/src/android/car/view/MirroredSurfaceView.java
index 8b58701..c09000f 100644
--- a/car-lib/src/android/car/view/MirroredSurfaceView.java
+++ b/car-lib/src/android/car/view/MirroredSurfaceView.java
@@ -100,7 +100,7 @@
Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
(car, ready) -> {
if (!ready) {
- Log.w(TAG, "CarService looks crashed");
+ Slog.w(TAG, "CarService looks crashed");
mCarAM = null;
return;
}
diff --git a/car-lib/src/android/car/vms/VmsClient.java b/car-lib/src/android/car/vms/VmsClient.java
index 9e9e6d0..0740aae 100644
--- a/car-lib/src/android/car/vms/VmsClient.java
+++ b/car-lib/src/android/car/vms/VmsClient.java
@@ -29,7 +29,7 @@
import android.os.RemoteException;
import android.os.SharedMemory;
import android.system.ErrnoException;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -105,11 +105,11 @@
@Nullable
@RequiresPermission(anyOf = {Car.PERMISSION_VMS_PUBLISHER, Car.PERMISSION_VMS_SUBSCRIBER})
public byte[] getProviderDescription(int providerId) {
- if (DBG) Log.d(TAG, "Getting provider information for " + providerId);
+ if (DBG) Slog.d(TAG, "Getting provider information for " + providerId);
try {
return mService.getProviderInfo(mClientToken, providerId).getDescription();
} catch (RemoteException e) {
- Log.e(TAG, "While getting publisher information for " + providerId, e);
+ Slog.e(TAG, "While getting publisher information for " + providerId, e);
mExceptionHandler.accept(e);
return null;
}
@@ -124,11 +124,11 @@
*/
@RequiresPermission(Car.PERMISSION_VMS_SUBSCRIBER)
public void setSubscriptions(@NonNull Set<VmsAssociatedLayer> layers) {
- if (DBG) Log.d(TAG, "Setting subscriptions to " + layers);
+ if (DBG) Slog.d(TAG, "Setting subscriptions to " + layers);
try {
mService.setSubscriptions(mClientToken, new ArrayList<>(layers));
} catch (RemoteException e) {
- Log.e(TAG, "While setting subscriptions", e);
+ Slog.e(TAG, "While setting subscriptions", e);
mExceptionHandler.accept(e);
}
}
@@ -141,14 +141,14 @@
*/
@RequiresPermission(Car.PERMISSION_VMS_SUBSCRIBER)
public void setMonitoringEnabled(boolean enabled) {
- if (DBG) Log.d(TAG, "Setting monitoring state to " + enabled);
+ if (DBG) Slog.d(TAG, "Setting monitoring state to " + enabled);
try {
mService.setMonitoringEnabled(mClientToken, enabled);
synchronized (mLock) {
mMonitoringEnabled = enabled;
}
} catch (RemoteException e) {
- Log.e(TAG, "While setting monitoring state to " + enabled, e);
+ Slog.e(TAG, "While setting monitoring state to " + enabled, e);
mExceptionHandler.accept(e);
}
}
@@ -183,13 +183,13 @@
*/
@RequiresPermission(Car.PERMISSION_VMS_PUBLISHER)
public int registerProvider(@NonNull byte[] providerDescription) {
- if (DBG) Log.d(TAG, "Registering provider");
+ if (DBG) Slog.d(TAG, "Registering provider");
Objects.requireNonNull(providerDescription, "providerDescription cannot be null");
try {
return mService.registerProvider(mClientToken,
new VmsProviderInfo(providerDescription));
} catch (RemoteException e) {
- Log.e(TAG, "While registering provider", e);
+ Slog.e(TAG, "While registering provider", e);
mExceptionHandler.accept(e);
return -1;
}
@@ -202,11 +202,11 @@
*/
@RequiresPermission(Car.PERMISSION_VMS_PUBLISHER)
public void unregisterProvider(int providerId) {
- if (DBG) Log.d(TAG, "Unregistering provider");
+ if (DBG) Slog.d(TAG, "Unregistering provider");
try {
setProviderOfferings(providerId, Collections.emptySet());
} catch (IllegalArgumentException e) {
- Log.e(TAG, "While unregistering provider " + providerId, e);
+ Slog.e(TAG, "While unregistering provider " + providerId, e);
}
}
@@ -222,12 +222,12 @@
*/
@RequiresPermission(Car.PERMISSION_VMS_PUBLISHER)
public void setProviderOfferings(int providerId, @NonNull Set<VmsLayerDependency> offerings) {
- if (DBG) Log.d(TAG, "Setting provider offerings for " + providerId);
+ if (DBG) Slog.d(TAG, "Setting provider offerings for " + providerId);
Objects.requireNonNull(offerings, "offerings cannot be null");
try {
mService.setProviderOfferings(mClientToken, providerId, new ArrayList<>(offerings));
} catch (RemoteException e) {
- Log.e(TAG, "While setting provider offerings for " + providerId, e);
+ Slog.e(TAG, "While setting provider offerings for " + providerId, e);
mExceptionHandler.accept(e);
}
}
@@ -245,7 +245,7 @@
Objects.requireNonNull(layer, "layer cannot be null");
Objects.requireNonNull(packet, "packet cannot be null");
if (DBG) {
- Log.d(TAG, "Publishing packet as " + providerId + " (" + packet.length + " bytes)");
+ Slog.d(TAG, "Publishing packet as " + providerId + " (" + packet.length + " bytes)");
}
try {
if (packet.length < LARGE_PACKET_THRESHOLD) {
@@ -257,7 +257,7 @@
}
}
} catch (RemoteException e) {
- Log.e(TAG, "While publishing packet as " + providerId);
+ Slog.e(TAG, "While publishing packet as " + providerId);
mExceptionHandler.accept(e);
}
}
@@ -307,7 +307,7 @@
@Override
public void onLayerAvailabilityChanged(VmsAvailableLayers availableLayers) {
- if (DBG) Log.d(TAG, "Received new layer availability: " + availableLayers);
+ if (DBG) Slog.d(TAG, "Received new layer availability: " + availableLayers);
executeCallback((client, callback) -> {
synchronized (client.mLock) {
client.mAvailableLayers = availableLayers;
@@ -318,7 +318,7 @@
@Override
public void onSubscriptionStateChanged(VmsSubscriptionState subscriptionState) {
- if (DBG) Log.d(TAG, "Received new subscription state: " + subscriptionState);
+ if (DBG) Slog.d(TAG, "Received new subscription state: " + subscriptionState);
executeCallback((client, callback) -> {
synchronized (client.mLock) {
client.mSubscriptionState = subscriptionState;
@@ -330,7 +330,7 @@
@Override
public void onPacketReceived(int providerId, VmsLayer layer, byte[] packet) {
if (DBG) {
- Log.d(TAG, "Received packet from " + providerId + " for: " + layer
+ Slog.d(TAG, "Received packet from " + providerId + " for: " + layer
+ " (" + packet.length + " bytes)");
}
executeCallback((client, callback) ->
@@ -340,7 +340,7 @@
@Override
public void onLargePacketReceived(int providerId, VmsLayer layer, SharedMemory packet) {
if (DBG) {
- Log.d(TAG, "Received large packet from " + providerId + " for: " + layer
+ Slog.d(TAG, "Received large packet from " + providerId + " for: " + layer
+ " (" + packet.getSize() + " bytes)");
}
byte[] largePacket;
@@ -358,7 +358,7 @@
private void executeCallback(BiConsumer<VmsClient, VmsClientCallback> callbackOperation) {
final VmsClient client = mClient.get();
if (client == null) {
- Log.w(TAG, "VmsClient unavailable");
+ Slog.w(TAG, "VmsClient unavailable");
return;
}
diff --git a/car-lib/src/android/car/vms/VmsClientManager.java b/car-lib/src/android/car/vms/VmsClientManager.java
index db43342..9957748 100644
--- a/car-lib/src/android/car/vms/VmsClientManager.java
+++ b/car-lib/src/android/car/vms/VmsClientManager.java
@@ -26,7 +26,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -122,7 +122,7 @@
VmsClient client;
synchronized (mLock) {
if (mClients.containsKey(callback)) {
- Log.w(TAG, "VmsClient already registered");
+ Slog.w(TAG, "VmsClient already registered");
return;
}
@@ -130,14 +130,14 @@
/* autoCloseMemory */ true,
this::handleRemoteExceptionFromCarService);
mClients.put(callback, client);
- if (DBG) Log.d(TAG, "Client count: " + mClients.size());
+ if (DBG) Slog.d(TAG, "Client count: " + mClients.size());
}
try {
- if (DBG) Log.d(TAG, "Registering VmsClient");
+ if (DBG) Slog.d(TAG, "Registering VmsClient");
client.register();
} catch (RemoteException e) {
- Log.e(TAG, "Error while registering", e);
+ Slog.e(TAG, "Error while registering", e);
synchronized (mLock) {
mClients.remove(callback);
}
@@ -145,7 +145,7 @@
return;
}
- if (DBG) Log.d(TAG, "Triggering callbacks for new VmsClient");
+ if (DBG) Slog.d(TAG, "Triggering callbacks for new VmsClient");
executor.execute(() -> {
callback.onClientConnected(client);
if (!legacyClient) {
@@ -169,11 +169,11 @@
client = mClients.remove(callback);
}
if (client == null) {
- Log.w(TAG, "Unregister called for unknown callback");
+ Slog.w(TAG, "Unregister called for unknown callback");
return;
}
- if (DBG) Log.d(TAG, "Unregistering VmsClient");
+ if (DBG) Slog.d(TAG, "Unregistering VmsClient");
try {
client.unregister();
} catch (RemoteException e) {
@@ -187,7 +187,7 @@
@Override
protected void onCarDisconnected() {
synchronized (mLock) {
- Log.w(TAG, "Car disconnected with " + mClients.size() + " active clients");
+ Slog.w(TAG, "Car disconnected with " + mClients.size() + " active clients");
mClients.clear();
}
}
diff --git a/car-lib/src/android/car/vms/VmsOperationRecorder.java b/car-lib/src/android/car/vms/VmsOperationRecorder.java
index aa3f09d..02606a9 100644
--- a/car-lib/src/android/car/vms/VmsOperationRecorder.java
+++ b/car-lib/src/android/car/vms/VmsOperationRecorder.java
@@ -18,6 +18,7 @@
import android.annotation.SystemApi;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -191,7 +192,7 @@
try {
write(new JSONObject().put(operation, new JSONObject()));
} catch (JSONException e) {
- Log.e(TAG, e.toString());
+ Slog.e(TAG, e.toString());
}
}
}
@@ -201,7 +202,7 @@
try {
recordOp(operation, new JSONObject().put("layer", toJson(layer)));
} catch (JSONException e) {
- Log.e(TAG, e.toString());
+ Slog.e(TAG, e.toString());
}
}
}
@@ -217,7 +218,7 @@
}
recordOp(operation, args);
} catch (JSONException e) {
- Log.e(TAG, e.toString());
+ Slog.e(TAG, e.toString());
}
}
}
@@ -227,7 +228,7 @@
try {
recordOp(operation, new JSONObject().put(intArgName, arg));
} catch (JSONException e) {
- Log.e(TAG, e.toString());
+ Slog.e(TAG, e.toString());
}
}
}
@@ -238,7 +239,7 @@
recordOp(operation,
new JSONObject().put(intArgName, arg).put("layer", toJson(layer)));
} catch (JSONException e) {
- Log.e(TAG, e.toString());
+ Slog.e(TAG, e.toString());
}
}
}
@@ -248,7 +249,7 @@
try {
write(new JSONObject().put(operation, args));
} catch (JSONException e) {
- Log.e(TAG, e.toString());
+ Slog.e(TAG, e.toString());
}
}
}
diff --git a/car-lib/src/android/car/vms/VmsPublisherClientService.java b/car-lib/src/android/car/vms/VmsPublisherClientService.java
index 720a024..3a10fc0 100644
--- a/car-lib/src/android/car/vms/VmsPublisherClientService.java
+++ b/car-lib/src/android/car/vms/VmsPublisherClientService.java
@@ -30,6 +30,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
+import android.util.Slog;
import com.android.car.internal.os.HandlerExecutor;
import com.android.internal.annotations.GuardedBy;
@@ -55,8 +56,9 @@
@Deprecated
@SystemApi
public abstract class VmsPublisherClientService extends Service {
- private static final boolean DBG = false;
- private static final String TAG = "VmsPublisherClientService";
+ private static final String TAG = "VmsPublisherClientSvc";
+
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -70,7 +72,7 @@
@Override
public void onCreate() {
- if (DBG) Log.d(TAG, "Connecting to Car service");
+ if (DBG) Slog.d(TAG, "Connecting to Car service");
synchronized (mLock) {
mCar = Car.createCar(this, mHandler, Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
this::onCarLifecycleChanged);
@@ -79,7 +81,7 @@
@Override
public void onDestroy() {
- if (DBG) Log.d(TAG, "Disconnecting from Car service");
+ if (DBG) Slog.d(TAG, "Disconnecting from Car service");
synchronized (mLock) {
if (mCar != null) {
mCar.disconnect();
@@ -90,7 +92,7 @@
@Override
public IBinder onBind(Intent intent) {
- if (DBG) Log.d(TAG, "onBind, intent: " + intent);
+ if (DBG) Slog.d(TAG, "onBind, intent: " + intent);
return new Binder();
}
@@ -99,13 +101,13 @@
*/
@VisibleForTesting
protected void onCarLifecycleChanged(Car car, boolean ready) {
- if (DBG) Log.d(TAG, "Car service ready: " + ready);
+ if (DBG) Slog.d(TAG, "Car service ready: " + ready);
if (ready) {
VmsClientManager clientManager =
(VmsClientManager) car.getCarManager(Car.VEHICLE_MAP_SERVICE);
- if (DBG) Log.d(TAG, "VmsClientManager: " + clientManager);
+ if (DBG) Slog.d(TAG, "VmsClientManager: " + clientManager);
if (clientManager == null) {
- Log.e(TAG, "VmsClientManager is not available");
+ Slog.e(TAG, "VmsClientManager is not available");
return;
}
clientManager.registerVmsClientCallback(new HandlerExecutor(mHandler), mClientCallback,
diff --git a/car-lib/src/android/car/vms/VmsSubscriptionHelper.java b/car-lib/src/android/car/vms/VmsSubscriptionHelper.java
index 7c5be88..64be6d5 100644
--- a/car-lib/src/android/car/vms/VmsSubscriptionHelper.java
+++ b/car-lib/src/android/car/vms/VmsSubscriptionHelper.java
@@ -120,8 +120,14 @@
* Gets the current set of subscriptions.
*/
@NonNull
- @GuardedBy("mLock")
public Set<VmsAssociatedLayer> getSubscriptions() {
+ synchronized (mLock) {
+ return getSubscriptionsLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private Set<VmsAssociatedLayer> getSubscriptionsLocked() {
Set<VmsAssociatedLayer> vmsAssociatedLayerSet = new ArraySet<>();
for (int i = 0; i < mLayerSubscriptions.size(); i++) {
VmsLayer layer = mLayerSubscriptions.valueAt(i);
@@ -141,7 +147,7 @@
private void publishSubscriptionUpdate() {
synchronized (mLock) {
if (mPendingUpdate) {
- mUpdateHandler.accept(getSubscriptions());
+ mUpdateHandler.accept(getSubscriptionsLocked());
}
mPendingUpdate = false;
}
diff --git a/car-lib/src/android/car/watchdog/CarWatchdogManager.java b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
index 4f164ec..9aa26d0 100644
--- a/car-lib/src/android/car/watchdog/CarWatchdogManager.java
+++ b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
@@ -30,7 +30,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.Log;
+import android.util.Slog;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
@@ -47,8 +47,10 @@
import java.util.concurrent.Executor;
/**
- * CarWatchdogManager allows applications to collect latest system resource overuse statistics, add
- * listener for resource overuse notifications, and update resource overuse configurations.
+ * CarWatchdogManager enables applications to track and manage system resource usage.
+ *
+ * <p>It allows applications to collect latest system resource overuse statistics, add listener for
+ * resource overuse notifications, and update resource overuse configurations.
*/
public final class CarWatchdogManager extends CarManagerBase {
@@ -61,7 +63,7 @@
private final Runnable mMainThreadCheck = () -> checkMainThread();
/**
- * Timeout for services which should be responsive. The length is 3,000 milliseconds.
+ * Timeout for critical services that need to be responsive in 3000 milliseconds.
*
* @hide
*/
@@ -69,7 +71,7 @@
public static final int TIMEOUT_CRITICAL = 0;
/**
- * Timeout for services which are relatively responsive. The length is 5,000 milliseconds.
+ * Timeout for moderate services that need to be responsive in 5000 milliseconds.
*
* @hide
*/
@@ -77,7 +79,7 @@
public static final int TIMEOUT_MODERATE = 1;
/**
- * Timeout for all other services. The length is 10,000 milliseconds.
+ * Timeout for normal services that need to be responsive in 10000 milliseconds.
*
* @hide
*/
@@ -113,13 +115,16 @@
private int mRemainingConditions;
/**
- * CarWatchdogClientCallback is implemented by the clients which want to be health-checked by
- * car watchdog server. Every time onCheckHealthStatus is called, they are expected to
- * respond by calling {@link #tellClientAlive} within timeout. If they don't
- * respond, car watchdog server reports the current state and kills them.
+ * A callback class for handling the CarWatchdog daemon's health check pings.
*
- * <p>Before car watchdog server kills the client, it calls onPrepareProcessTermination to allow
- * them to prepare the termination. They will be killed in 1 second.
+ * <p>CarWatchdogClientCallback is implemented by the clients who want to be health-checked by
+ * the CarWatchdog daemon. On each onCheckHealthStatus call, clients are expected to respond by
+ * calling {@link CarWatchdogManager#tellClientAlive} within the timeout. If they fail to
+ * respond, the CarWatchdog daemon reports the current state and kills them.
+ *
+ * <p>Before termination, the CarWatchdog daemon calls {@link
+ * CarWatchdogClientCallback#onPrepareProcessTermination} to allow clients to prepare for the
+ * termination, which will occur after 1 second.
*
* @hide
*/
@@ -167,9 +172,8 @@
/**
* Registers the car watchdog clients to {@link CarWatchdogManager}.
*
- * <p>It is allowed to register a client from any thread, but only one client can be
- * registered. If two or more clients are needed, create a new {@link Car} and register a client
- * to it.
+ * <p>It is allowed to register a client from any thread, but only one client can be registered.
+ * If two or more clients are needed, create a new {@link Car} and register a client to it.
*
* @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface.
* @param timeout The time duration within which the client desires to respond. The actual
@@ -197,7 +201,7 @@
try {
mService.registerClient(mClientImpl, timeout);
if (DEBUG) {
- Log.d(TAG, "Car watchdog client is successfully registered");
+ Slog.d(TAG, "Car watchdog client is successfully registered");
}
} catch (RemoteException e) {
synchronized (mLock) {
@@ -227,7 +231,7 @@
synchronized (CarWatchdogManager.this.mLock) {
if (!mHealthCheckingClient.hasClientLocked()
|| mHealthCheckingClient.callback != client) {
- Log.w(TAG, "Cannot unregister the client. It has not been registered.");
+ Slog.w(TAG, "Cannot unregister the client. It has not been registered.");
return;
}
if (mHealthCheckingClient.isRegistrationInProgressLocked()) {
@@ -240,7 +244,7 @@
try {
mService.unregisterClient(mClientImpl);
if (DEBUG) {
- Log.d(TAG, "Car watchdog client is successfully unregistered");
+ Slog.d(TAG, "Car watchdog client is successfully unregistered");
}
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
@@ -272,7 +276,7 @@
"Cannot report client status. Received session id (" + sessionId
+ ") doesn't match the current one (" + mSession.currentId + ").");
if (mSession.lastReportedId == sessionId) {
- Log.w(TAG, "The given session id is already reported.");
+ Slog.w(TAG, "The given session id is already reported.");
return;
}
mSession.lastReportedId = sessionId;
@@ -321,39 +325,73 @@
/**
* Constants that define the stats period in days.
+ *
+ * <p>The following constants represent the stats period for the past N days, It is used to
+ * specify that the stats should be gathered for the last N days, including today and the N-1
+ * previous days.
+ *
+ * <p>The stats period for the current day.
*/
public static final int STATS_PERIOD_CURRENT_DAY = 1;
+
+ /** The stats period for the past 3 days. */
public static final int STATS_PERIOD_PAST_3_DAYS = 2;
+
+ /** The stats period for the past 7 days */
public static final int STATS_PERIOD_PAST_7_DAYS = 3;
+
+ /** The stats period for the past 15 days */
public static final int STATS_PERIOD_PAST_15_DAYS = 4;
+
+ /** The stats period for the past 30 days */
public static final int STATS_PERIOD_PAST_30_DAYS = 5;
- /**
- * Constants that define the type of resource overuse.
- */
+ /** Constants that define the type of resource overuse. */
public static final int FLAG_RESOURCE_OVERUSE_IO = 1 << 0;
/**
* Constants that define the minimum stats for each resource type.
*
- * Below constants specify the minimum amount of data written to disk.
+ * <p>The following constants represent the minimum amount of data written to disk.
+ *
+ * <p>The minimum amount of data that should be written to disk is 1 MB.
*
* @hide
*/
@SystemApi
public static final int FLAG_MINIMUM_STATS_IO_1_MB = 1 << 0;
- /** @hide */
+
+ /**
+ * The minimum amount of data that should be written to disk is 100 MB.
+ *
+ * @hide
+ */
@SystemApi
public static final int FLAG_MINIMUM_STATS_IO_100_MB = 1 << 1;
- /** @hide */
+
+ /**
+ * The minimum amount of data that should be written to disk is 1 GB.
+ *
+ * @hide
+ */
@SystemApi
public static final int FLAG_MINIMUM_STATS_IO_1_GB = 1 << 2;
- // Return codes used to indicate the result of a request.
- /** @hide */
+ /**
+ * Returns codes used to indicate the result of a request.
+ *
+ * <p>The return code indicating a successful request.
+ *
+ * @hide
+ */
@SystemApi
public static final int RETURN_CODE_SUCCESS = 0;
- /** @hide */
+
+ /**
+ * The return code indicating an error in the request.
+ *
+ * @hide
+ */
@SystemApi
public static final int RETURN_CODE_ERROR = -1;
@@ -387,18 +425,15 @@
*
* @param resourceOveruseFlag Flag to indicate the types of resource overuse stats to return.
* @param minimumStatsFlag Flag to specify the minimum stats for each resource overuse type.
- * Only stats above the specified minimum stats for a resource overuse
- * type will be returned. May provide only one minimum stats flag for
- * each resource overuse type. When no minimum stats flag is specified,
- * all stats are returned.
+ * Only stats greater than the specified minimum stats for a resource overuse type will be
+ * returned. May provide only one minimum stats flag for each resource overuse type. When no
+ * minimum stats flag is specified, all stats are returned.
* @param maxStatsPeriod Maximum period to aggregate the resource overuse stats.
- *
* @return Resource overuse stats for all monitored packages. If any package doesn't have stats
- * for a specified resource type, null value is returned for the corresponding resource
- * overuse stats. If any package doesn't have sufficient stats for
- * {@code maxStatsPeriod} for a specified resource overuse type, the stats are returned
- * only for the period returned in the individual resource stats.
- *
+ * for a specified resource type, null value is returned for the corresponding resource
+ * overuse stats. If any package doesn't have sufficient stats for {@code maxStatsPeriod}
+ * for a specified resource overuse type, the stats are returned only for the period
+ * returned in the individual resource stats.
* @hide
*/
@SystemApi
@@ -460,11 +495,11 @@
*
* <p>The listener is called at the executor which is specified in {@link
* CarWatchdogManager#addResourceOveruseListener} or
- * {@code addResourceOveruseListenerForSystem}.
+ * {@link CarWatchdogManager#addResourceOveruseListenerForSystem}.
*
* <p>The listener is called only on overusing one of the resources specified at the
* {@code resourceOveruseFlag} in {@link CarWatchdogManager#addResourceOveruseListener} or
- * {@code addResourceOveruseListenerForSystem}.
+ * {@link CarWatchdogManager#addResourceOveruseListenerForSystem}.
*
* @param resourceOveruseStats Resource overuse stats containing stats only for resources
* overuse types that are either overused or about to be
@@ -483,7 +518,7 @@
* @param listener Listener implementing {@link ResourceOveruseListener} interface.
* @param resourceOveruseFlag Flag to indicate the types of resource overuses to listen.
*
- * @throws IllegalStateException if (@code listener} is already added.
+ * @throws IllegalStateException if {@code listener} is already added.
*/
public void addResourceOveruseListener(
@NonNull @CallbackExecutor Executor executor,
@@ -534,7 +569,7 @@
}
}
if (index == mResourceOveruseListenerInfos.size()) {
- Log.w(TAG, "Cannot remove the listener. It has not been added.");
+ Slog.w(TAG, "Cannot remove the listener. It has not been added.");
return;
}
mResourceOveruseListenerInfos.remove(index);
@@ -621,7 +656,7 @@
}
}
if (index == mResourceOveruseListenerForSystemInfos.size()) {
- Log.w(TAG, "Cannot remove the listener. It has not been added.");
+ Slog.w(TAG, "Cannot remove the listener. It has not been added.");
return;
}
mResourceOveruseListenerForSystemInfos.remove(index);
@@ -759,7 +794,7 @@
> Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
throw new IllegalStateException(message);
}
- Log.e(TAG, "Suppressing illegal state exception on target SDK <= UDC: " + message);
+ Slog.e(TAG, "Suppressing illegal state exception on target SDK <= UDC: " + message);
}
private void checkClientStatus(int sessionId, int timeout) {
@@ -768,7 +803,7 @@
mMainHandler.removeCallbacks(mMainThreadCheck);
synchronized (CarWatchdogManager.this.mLock) {
if (!mHealthCheckingClient.hasClientLocked()) {
- Log.w(TAG, "Cannot check client status. The client has not been registered.");
+ Slog.w(TAG, "Cannot check client status. The client has not been registered.");
return;
}
mSession.currentId = sessionId;
@@ -783,7 +818,7 @@
// Call the client callback to check if the client is active.
executor.execute(() -> {
boolean checkDone = clientCallback.onCheckHealthStatus(sessionId, timeout);
- Log.e(TAG, "Called clientCallback.onCheckHealthStatus");
+ Slog.i(TAG, "Called clientCallback.onCheckHealthStatus");
if (checkDone) {
boolean shouldReport;
synchronized (mLock) {
@@ -817,7 +852,7 @@
@GuardedBy("mLock")
private boolean checkConditionLocked() {
if (mRemainingConditions < 0) {
- Log.wtf(TAG, "Remaining condition is less than zero: should not happen");
+ Slog.wtf(TAG, "Remaining condition is less than zero: should not happen");
}
return mRemainingConditions == 0;
}
@@ -826,7 +861,7 @@
try {
mService.tellClientAlive(mClientImpl, sessionId);
if (DEBUG) {
- Log.d(TAG, "Informed CarService that client is alive");
+ Slog.d(TAG, "Informed CarService that client is alive");
}
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
@@ -838,7 +873,7 @@
Executor executor;
synchronized (CarWatchdogManager.this.mLock) {
if (!mHealthCheckingClient.hasClientLocked()) {
- Log.w(TAG, "Cannot notify the client. The client has not been registered.");
+ Slog.w(TAG, "Cannot notify the client. The client has not been registered.");
return;
}
clientCallback = mHealthCheckingClient.callback;
@@ -853,7 +888,7 @@
mResourceOveruseListenerImpl.resourceOveruseFlag(),
mResourceOveruseListenerImpl);
if (DEBUG) {
- Log.d(TAG, "Resource overuse listener implementation is successfully added to "
+ Slog.d(TAG, "Resource overuse listener implementation is successfully added to "
+ "service");
}
} catch (RemoteException e) {
@@ -868,7 +903,7 @@
try {
mService.removeResourceOveruseListener(mResourceOveruseListenerImpl);
if (DEBUG) {
- Log.d(TAG, "Resource overuse listener implementation is successfully removed "
+ Slog.d(TAG, "Resource overuse listener implementation is successfully removed "
+ "from service");
}
} catch (RemoteException e) {
@@ -882,7 +917,7 @@
mResourceOveruseListenerForSystemImpl.resourceOveruseFlag(),
mResourceOveruseListenerForSystemImpl);
if (DEBUG) {
- Log.d(TAG, "Resource overuse listener for system implementation is successfully "
+ Slog.d(TAG, "Resource overuse listener for system implementation is successfully "
+ "added to service");
}
} catch (RemoteException e) {
@@ -897,7 +932,7 @@
try {
mService.removeResourceOveruseListenerForSystem(mResourceOveruseListenerForSystemImpl);
if (DEBUG) {
- Log.d(TAG, "Resource overuse listener for system implementation is successfully "
+ Slog.d(TAG, "Resource overuse listener for system implementation is successfully "
+ "removed from service");
}
} catch (RemoteException e) {
@@ -907,7 +942,7 @@
private void onResourceOveruse(ResourceOveruseStats resourceOveruseStats, boolean isSystem) {
if (resourceOveruseStats.getIoOveruseStats() == null) {
- Log.w(TAG, "Skipping resource overuse notification as the stats are missing");
+ Slog.w(TAG, "Skipping resource overuse notification as the stats are missing");
return;
}
List<ResourceOveruseListenerInfo> listenerInfos;
@@ -919,7 +954,7 @@
}
}
if (listenerInfos.isEmpty()) {
- Log.w(TAG, "Cannot notify resource overuse listener " + (isSystem ? "for system " : "")
+ Slog.w(TAG, "Cannot notify resource overuse listener " + (isSystem ? "for system " : "")
+ "as it is not registered.");
return;
}
@@ -949,7 +984,7 @@
this.executor = executor;
mIsRegistrationInProgress = true;
if (DEBUG) {
- Log.d(TAG, "Set CarWatchdog client callback to " + callback);
+ Slog.d(TAG, "Set CarWatchdog client callback to " + callback);
}
}
@@ -959,7 +994,7 @@
executor = null;
mIsRegistrationInProgress = false;
if (DEBUG) {
- Log.d(TAG, "Reset CarWatchdog client callback");
+ Slog.d(TAG, "Reset CarWatchdog client callback");
}
}
@@ -968,7 +1003,7 @@
mIsRegistrationInProgress = false;
mLock.notify();
if (DEBUG) {
- Log.d(TAG, "Marked registration completed");
+ Slog.d(TAG, "Marked registration completed");
}
}
@@ -981,7 +1016,7 @@
mLock.wait(endMillis - nowMillis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
- Log.w(TAG, "Interrupted while waiting for registration to complete. "
+ Slog.w(TAG, "Interrupted while waiting for registration to complete. "
+ "Continuing to wait");
} finally {
nowMillis = System.currentTimeMillis();
diff --git a/car-lib/src/android/car/watchdog/ResourceOveruseStats.java b/car-lib/src/android/car/watchdog/ResourceOveruseStats.java
index e8a1647..d02ad30 100644
--- a/car-lib/src/android/car/watchdog/ResourceOveruseStats.java
+++ b/car-lib/src/android/car/watchdog/ResourceOveruseStats.java
@@ -16,14 +16,11 @@
package android.car.watchdog;
-import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcelable;
import android.os.UserHandle;
-import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.util.AnnotationValidations;
import com.android.car.internal.util.DataClass;
@@ -45,14 +42,17 @@
*/
private @NonNull UserHandle mUserHandle;
- /*
- * I/O overuse stats for the package. If the package didn't opt-in to receive I/O overuse stats
- * or the package doesn't have I/O overuse stats, this value will be null.
+ /**
+ * I/O overuse stats for the package.
+ *
+ * If the package didn't opt-in to receive I/O overuse stats or the package doesn't have
+ * I/O overuse stats, this value will be null.
*/
private @Nullable IoOveruseStats mIoOveruseStats = null;
+
// Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
@@ -60,7 +60,6 @@
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/watchdog/ResourceOveruseStats.java
- // Added AddedInOrBefore or ApiRequirement Annotation manually
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -102,6 +101,12 @@
return mUserHandle;
}
+ /**
+ * I/O overuse stats for the package.
+ *
+ * If the package didn't opt-in to receive I/O overuse stats or the package doesn't have
+ * I/O overuse stats, this value will be null.
+ */
@DataClass.Generated.Member
public @Nullable IoOveruseStats getIoOveruseStats() {
return mIoOveruseStats;
@@ -136,7 +141,6 @@
@Override
@DataClass.Generated.Member
- @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public int describeContents() { return 0; }
/** @hide */
@@ -237,6 +241,12 @@
return this;
}
+ /**
+ * I/O overuse stats for the package.
+ *
+ * If the package didn't opt-in to receive I/O overuse stats or the package doesn't have
+ * I/O overuse stats, this value will be null.
+ */
@DataClass.Generated.Member
public @NonNull Builder setIoOveruseStats(@NonNull IoOveruseStats value) {
checkNotUsed();
@@ -269,12 +279,11 @@
}
@DataClass.Generated(
- time = 1628099343131L,
+ time = 1715718593235L,
codegenVersion = "1.0.23",
sourceFile = "packages/services/Car/car-lib/src/android/car/watchdog/ResourceOveruseStats.java",
inputSignatures = "private @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull android.os.UserHandle mUserHandle\nprivate @android.annotation.Nullable android.car.watchdog.IoOveruseStats mIoOveruseStats\nclass ResourceOveruseStats extends java.lang.Object implements [android.os.Parcelable]\n@com.android.car.internal.util.DataClass(genToString=true, genHiddenBuilder=true)")
@Deprecated
- @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
private void __metadata() {}
diff --git a/car-lib/src/android/car/wifi/CarWifiManager.java b/car-lib/src/android/car/wifi/CarWifiManager.java
index 7d17b79..a87b9b6 100644
--- a/car-lib/src/android/car/wifi/CarWifiManager.java
+++ b/car-lib/src/android/car/wifi/CarWifiManager.java
@@ -25,6 +25,8 @@
import android.os.IBinder;
import android.os.RemoteException;
+import com.android.car.internal.ICarBase;
+
/**
* CarWifiManager provides API to allow for applications to perform Wi-Fi specific operations.
*
@@ -36,7 +38,7 @@
private final ICarWifi mService;
/** @hide */
- public CarWifiManager(Car car, IBinder service) {
+ public CarWifiManager(ICarBase car, IBinder service) {
super(car);
mService = ICarWifi.Stub.asInterface(service);
}
diff --git a/car-lib/src/com/android/car/internal/ICarSystemServerClient.aidl b/car-lib/src/com/android/car/internal/ICarSystemServerClient.aidl
index d92e199..3d0218e 100644
--- a/car-lib/src/com/android/car/internal/ICarSystemServerClient.aidl
+++ b/car-lib/src/com/android/car/internal/ICarSystemServerClient.aidl
@@ -33,28 +33,14 @@
* @param fromUserId - user id of previous user when type is SWITCHING (or UserHandle.USER_NULL)
* @param toUserId - user id of new user.
*/
- void onUserLifecycleEvent(int eventType, int fromUserId, int toUserId) = 0;
-
- /**
- * Nofity when a user is removed.
- *
- * NOTE: this is different from onUserLifecycleEvent(), whic is used on user switching events.
- *
- * @param user info about the user that was removed.
- */
- void onUserRemoved(in UserHandle user) = 1;
-
- /**
- * Notify to init boot user.
- */
- void initBootUser() = 2;
+ void onUserLifecycleEvent(int eventType, int fromUserId, int toUserId);
/**
* Notify that the device must be factory reset, so CarService can ask user to confirm.
*
* @param callback used to trigger the factory reset.
*/
- void onFactoryReset(ICarResultReceiver callback) = 3;
+ void onFactoryReset(ICarResultReceiver callback);
/**
* Initial user is decided by HAL and information is saved in CarUserService. It is possible
@@ -63,5 +49,7 @@
* {@link ICarServiceHelper.sendInitialUser}. If car service reconnects after crash, then this
* call will set the initial user information in CarUserService.
*/
- void setInitialUser(in UserHandle user) = 4;
+ void setInitialUser(in UserHandle user);
+
+ void notifyFocusChanged(int pid, int uid);
}
diff --git a/car-lib/src/com/android/car/internal/LargeParcelable.java b/car-lib/src/com/android/car/internal/LargeParcelable.java
index 9071e06..4299681 100644
--- a/car-lib/src/com/android/car/internal/LargeParcelable.java
+++ b/car-lib/src/com/android/car/internal/LargeParcelable.java
@@ -23,7 +23,7 @@
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.SharedMemory;
-import android.util.Log;
+import android.util.Slog;
import java.io.IOException;
import java.lang.ref.WeakReference;
@@ -100,7 +100,7 @@
}
dest.writeParcelable(mParcelable, flags);
if (DBG_PAYLOAD) {
- Log.d(TAG, "serialize-payload, start:" + startPosition
+ Slog.d(TAG, "serialize-payload, start:" + startPosition
+ " size:" + (dest.dataPosition() - startPosition));
}
}
@@ -113,7 +113,7 @@
}
dest.writeParcelable(null, 0);
if (DBG_PAYLOAD) {
- Log.d(TAG, "serializeNullPayload-payload, start:" + startPosition
+ Slog.d(TAG, "serializeNullPayload-payload, start:" + startPosition
+ " size:" + (dest.dataPosition() - startPosition));
}
}
@@ -128,7 +128,7 @@
}
mParcelable = src.readParcelable(loader);
if (DBG_PAYLOAD) {
- Log.d(TAG, "deserialize-payload, start:" + startPosition
+ Slog.d(TAG, "deserialize-payload, start:" + startPosition
+ " size:" + (src.dataPosition() - startPosition)
+ " mParcelable:" + mParcelable);
}
@@ -179,7 +179,7 @@
}
Class parcelableClass = p.getClass();
if (DBG_STABLE_AIDL_CLASS) {
- Log.d(TAG, "toLargeParcelable stable AIDL Parcelable:"
+ Slog.d(TAG, "toLargeParcelable stable AIDL Parcelable:"
+ parcelableClass.getSimpleName());
}
Field field;
@@ -200,7 +200,7 @@
if (payloadSize <= LargeParcelableBase.MAX_DIRECT_PAYLOAD_SIZE) {
// direct path, no re-write to shared memory.
if (DBG_PAYLOAD) {
- Log.d(TAG, "toLargeParcelable send directly, payload size:" + payloadSize);
+ Slog.d(TAG, "toLargeParcelable send directly, payload size:" + payloadSize);
}
return p;
}
@@ -257,7 +257,7 @@
}
Class parcelableClass = p.getClass();
if (DBG_STABLE_AIDL_CLASS) {
- Log.d(TAG, "reconstructStableAIDLParcelable stable AIDL Parcelable:"
+ Slog.d(TAG, "reconstructStableAIDLParcelable stable AIDL Parcelable:"
+ parcelableClass.getSimpleName());
}
ParcelFileDescriptor sharedMemoryFd = null;
@@ -271,7 +271,7 @@
}
if (sharedMemoryFd == null) {
if (DBG_PAYLOAD) {
- Log.d(TAG, "reconstructStableAIDLParcelable null shared memory");
+ Slog.d(TAG, "reconstructStableAIDLParcelable null shared memory");
}
return p;
}
@@ -291,7 +291,7 @@
fieldSharedMemory.set(retParcelable, sharedMemoryFd);
}
if (DBG_PAYLOAD) {
- Log.d(TAG, "reconstructStableAIDLParcelable read shared memory, data size:"
+ Slog.d(TAG, "reconstructStableAIDLParcelable read shared memory, data size:"
+ in.dataPosition());
}
} catch (Exception e) {
diff --git a/car-lib/src/com/android/car/internal/LargeParcelableBase.java b/car-lib/src/com/android/car/internal/LargeParcelableBase.java
index 60e8651..b0df632 100644
--- a/car-lib/src/com/android/car/internal/LargeParcelableBase.java
+++ b/car-lib/src/com/android/car/internal/LargeParcelableBase.java
@@ -27,6 +27,7 @@
import android.os.SharedMemory;
import android.system.ErrnoException;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -108,7 +109,7 @@
}
in.setDataPosition(startPosition + totalPayloadSize);
if (DBG_PAYLOAD) {
- Log.d(TAG, "Read, start:" + startPosition + " totalPayloadSize:" + totalPayloadSize
+ Slog.d(TAG, "Read, start:" + startPosition + " totalPayloadSize:" + totalPayloadSize
+ " sharedMemoryPosition:" + sharedMemoryPosition
+ " hasSharedMemory:" + hasSharedMemory + " dataAvail:" + in.dataAvail());
}
@@ -127,7 +128,7 @@
// created shared memory
totalPayloadSize = serializeMemoryFdOrPayloadToParcel(dest, flags, sharedMemory);
if (DBG_PAYLOAD) {
- Log.d(TAG, "Write, reusing shared memory, start:" + startPosition
+ Slog.d(TAG, "Write, reusing shared memory, start:" + startPosition
+ " totalPayloadSize:" + totalPayloadSize);
}
return;
@@ -141,13 +142,13 @@
boolean hasNonNullPayload = true;
if (noSharedMemory) {
if (DBG_PAYLOAD) {
- Log.d(TAG, "not using shared memory");
+ Slog.d(TAG, "not using shared memory");
}
dest.appendFrom(dataParcel, 0, totalPayloadSize);
dataParcel.recycle();
} else {
if (DBG_PAYLOAD) {
- Log.d(TAG, "using shared memory");
+ Slog.d(TAG, "using shared memory");
}
sharedMemory = serializeParcelToSharedMemory(dataParcel);
dataParcel.recycle();
@@ -162,7 +163,7 @@
totalPayloadSize = serializeMemoryFdOrPayloadToParcel(dest, flags, sharedMemory);
}
if (DBG_PAYLOAD) {
- Log.d(TAG, "Write, start:" + startPosition + " totalPayloadSize:" + totalPayloadSize
+ Slog.d(TAG, "Write, start:" + startPosition + " totalPayloadSize:" + totalPayloadSize
+ " hasNonNullPayload:" + hasNonNullPayload
+ " hasSharedMemory:" + !noSharedMemory + " dataSize:" + dest.dataSize());
}
@@ -246,7 +247,7 @@
bd.append(',');
}
}
- Log.d(TAG, bd.toString());
+ Slog.d(TAG, bd.toString());
}
if (!memory.setProtect(PROT_READ)) {
memory.close();
@@ -297,7 +298,7 @@
bd.append(payload[i]);
if (i != dumpSize - 1) bd.append(',');
}
- Log.d(TAG, bd.toString());
+ Slog.d(TAG, bd.toString());
in.setDataPosition(parcelStartPosition);
}
} catch (ErrnoException e) {
@@ -322,7 +323,7 @@
// the data position.
int fileSize = in.readInt();
if (DBG_PAYLOAD) {
- Log.d(TAG, "file size in shared memory file: " + fileSize);
+ Slog.d(TAG, "file size in shared memory file: " + fileSize);
}
deserialize(in);
// There is an additional 0 in the parcel, but we ignore that.
diff --git a/car-lib/src/com/android/car/internal/StaticBinderInterface.java b/car-lib/src/com/android/car/internal/StaticBinderInterface.java
new file mode 100644
index 0000000..d60ac55
--- /dev/null
+++ b/car-lib/src/com/android/car/internal/StaticBinderInterface.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.internal;
+
+/**
+ * StaticBinderInterface provides static methods from {@code android.os.Binder} that are used
+ * internally by car service or car manager.
+ *
+ * This interface allows faking the implementation in unit tests.
+ *
+ * @hide
+ */
+public interface StaticBinderInterface {
+ /**
+ * Return the ID of the process that sent you the current transaction
+ * that is being processed. This PID can be used with higher-level
+ * system services to determine its identity and check permissions.
+ * If the current thread is not currently executing an incoming transaction,
+ * then its own PID is returned.
+ *
+ * Warning: oneway transactions do not receive PID. Even if you expect
+ * a transaction to be synchronous, a misbehaving client could send it
+ * as a asynchronous call and result in a 0 PID here. Additionally, if
+ * there is a race and the calling process dies, the PID may still be
+ * 0 for a synchronous call.
+ */
+ int getCallingUid();
+ /**
+ * Return the Linux UID assigned to the process that sent you the
+ * current transaction that is being processed. This UID can be used with
+ * higher-level system services to determine its identity and check
+ * permissions. If the current thread is not currently executing an
+ * incoming transaction, then its own UID is returned.
+ */
+ int getCallingPid();
+}
diff --git a/car-lib/src/com/android/car/internal/SystemStaticBinder.java b/car-lib/src/com/android/car/internal/SystemStaticBinder.java
new file mode 100644
index 0000000..4108088
--- /dev/null
+++ b/car-lib/src/com/android/car/internal/SystemStaticBinder.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.internal;
+
+import android.os.Binder;
+
+/**
+ * A real implementation for {@link com.android.car.internal.StaticBinderInterface}.
+ *
+ * @hide
+ */
+public final class SystemStaticBinder implements StaticBinderInterface {
+ @Override
+ public int getCallingUid() {
+ return Binder.getCallingUid();
+ }
+
+ @Override
+ public int getCallingPid() {
+ return Binder.getCallingPid();
+ }
+}
diff --git a/car-lib/src/com/android/car/internal/os/Process.java b/car-lib/src/com/android/car/internal/os/Process.java
new file mode 100644
index 0000000..9e84e59
--- /dev/null
+++ b/car-lib/src/com/android/car/internal/os/Process.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.internal.os;
+
+/**
+ * An interface to inject fake {@link android.os.Process}.
+ *
+ * @hide
+ */
+public interface Process {
+ /** Check {@link android.os.Process#killProcess}. */
+ void killProcess(int pid);
+ /** Check {@link android.os.Process#myPid}. */
+ int myPid();
+}
diff --git a/car-lib/src/com/android/car/internal/os/ServiceManager.java b/car-lib/src/com/android/car/internal/os/ServiceManager.java
new file mode 100644
index 0000000..969c806
--- /dev/null
+++ b/car-lib/src/com/android/car/internal/os/ServiceManager.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.internal.os;
+
+import android.annotation.NonNull;
+import android.car.builtin.os.ServiceManagerHelper;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * An interface to inject fake
+ * {@link android.car.builtin.os.ServiceManagerHelper}.
+ *
+ * @hide
+ */
+public interface ServiceManager {
+ /** Check {@link ServiceManagerHelper#getService(String)} */
+ IBinder getService(String name);
+
+ /** Check {@link ServiceManagerHelper#registerForNotifications} */
+ void registerForNotifications(@NonNull String name,
+ @NonNull ServiceManagerHelper.IServiceRegistrationCallback callback)
+ throws RemoteException;
+}
diff --git a/car-lib/src/com/android/car/internal/os/SystemProcess.java b/car-lib/src/com/android/car/internal/os/SystemProcess.java
new file mode 100644
index 0000000..9c4a7b6
--- /dev/null
+++ b/car-lib/src/com/android/car/internal/os/SystemProcess.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.internal.os;
+
+/**
+ * A real implementation for Process.
+ *
+ * @hide
+ */
+public final class SystemProcess implements Process {
+ @Override
+ public void killProcess(int pid) {
+ android.os.Process.killProcess(pid);
+ }
+
+ @Override
+ public int myPid() {
+ return android.os.Process.myPid();
+ }
+}
diff --git a/car-lib/src/com/android/car/internal/os/SystemServiceManager.java b/car-lib/src/com/android/car/internal/os/SystemServiceManager.java
new file mode 100644
index 0000000..ecb4b6b
--- /dev/null
+++ b/car-lib/src/com/android/car/internal/os/SystemServiceManager.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.internal.os;
+
+import android.annotation.NonNull;
+import android.car.builtin.os.ServiceManagerHelper;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * A real implementation for ServiceManager.
+ *
+ * @hide
+ */
+public final class SystemServiceManager implements ServiceManager {
+ @Override
+ public IBinder getService(String name) {
+ return ServiceManagerHelper.getService(name);
+ }
+
+ @Override
+ public void registerForNotifications(@NonNull String name,
+ @NonNull ServiceManagerHelper.IServiceRegistrationCallback callback)
+ throws RemoteException {
+ ServiceManagerHelper.registerForNotifications(name, callback);
+ }
+}
diff --git a/car-lib/src/com/android/car/internal/property/CarPropertyEventCallbackController.java b/car-lib/src/com/android/car/internal/property/CarPropertyEventCallbackController.java
index 4d27015..e926c03 100644
--- a/car-lib/src/com/android/car/internal/property/CarPropertyEventCallbackController.java
+++ b/car-lib/src/com/android/car/internal/property/CarPropertyEventCallbackController.java
@@ -22,6 +22,7 @@
import android.car.hardware.property.CarPropertyEvent;
import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
import android.util.Log;
+import android.util.Slog;
import java.util.concurrent.Executor;
@@ -56,7 +57,7 @@
getCarPropertyValueIfCallbackRequired(carPropertyEvent);
if (carPropertyValue == null) {
if (DBG) {
- Log.d(TAG, "onEvent should not be invoked, event: " + carPropertyEvent);
+ Slog.d(TAG, "onEvent should not be invoked, event: " + carPropertyEvent);
}
return;
}
@@ -73,14 +74,14 @@
break;
case CarPropertyEvent.PROPERTY_EVENT_ERROR:
if (DBG) {
- Log.d(TAG, "onErrorEvent for event: " + updatedCarPropertyEvent);
+ Slog.d(TAG, "onErrorEvent for event: " + updatedCarPropertyEvent);
}
mExecutor.execute(() -> mCarPropertyEventCallback.onErrorEvent(
carPropertyValue.getPropertyId(), carPropertyValue.getAreaId(),
updatedCarPropertyEvent.getErrorCode()));
break;
default:
- Log.e(TAG, "onEvent: unknown errorCode=" + updatedCarPropertyEvent.getErrorCode()
+ Slog.e(TAG, "onEvent: unknown errorCode=" + updatedCarPropertyEvent.getErrorCode()
+ ", for event: " + updatedCarPropertyEvent);
break;
}
diff --git a/car-lib/src/com/android/car/internal/property/ContCarPropertyEventTracker.java b/car-lib/src/com/android/car/internal/property/ContCarPropertyEventTracker.java
index 87ea634..9805526 100644
--- a/car-lib/src/com/android/car/internal/property/ContCarPropertyEventTracker.java
+++ b/car-lib/src/com/android/car/internal/property/ContCarPropertyEventTracker.java
@@ -128,7 +128,7 @@
public boolean hasUpdate(CarPropertyValue<?> carPropertyValue) {
if (carPropertyValue.getTimestamp() < mNextUpdateTimeNanos) {
if (mLogger.dbg()) {
- mLogger.logD(String.format("hasUpdate: Dropping carPropertyValue: %s, "
+ mLogger.logV(String.format("hasUpdate: Dropping carPropertyValue: %s, "
+ "because getTimestamp()=%d < nextUpdateTimeNanos=%d",
carPropertyValue, carPropertyValue.getTimestamp(), mNextUpdateTimeNanos));
}
@@ -142,7 +142,7 @@
if (mEnableVur && status == mCurrentStatus && mCurrentCarPropertyValue != null
&& Objects.deepEquals(value, mCurrentCarPropertyValue.getValue())) {
if (mLogger.dbg()) {
- mLogger.logD(String.format("hasUpdate: Dropping carPropertyValue: %s, "
+ mLogger.logV(String.format("hasUpdate: Dropping carPropertyValue: %s, "
+ "because VUR is enabled and value is the same",
sanitizedCarPropertyValue));
}
diff --git a/car-lib/src/com/android/car/internal/property/InputSanitizationUtils.java b/car-lib/src/com/android/car/internal/property/InputSanitizationUtils.java
index c42e8a7..725e276 100644
--- a/car-lib/src/com/android/car/internal/property/InputSanitizationUtils.java
+++ b/car-lib/src/com/android/car/internal/property/InputSanitizationUtils.java
@@ -24,6 +24,7 @@
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.property.CarPropertyManager;
import android.util.Log;
+import android.util.Slog;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.internal.annotations.VisibleForTesting;
@@ -89,7 +90,7 @@
return;
}
double log = Math.log10(resolution);
- Preconditions.checkArgument(Math.abs(log - (int) log) < 0.0000001f,
+ Preconditions.checkArgument(Math.abs(log - Math.round(log)) < 0.0000001f,
"resolution must be an integer power of 10. Instead, got resolution: " + resolution
+ ", whose log10 value is: " + log);
}
@@ -105,14 +106,14 @@
CarPropertyConfig<?> carPropertyConfig) {
if (!featureFlags.variableUpdateRate()) {
if (DBG) {
- Log.d(TAG, "VUR feature is not enabled, VUR is always off");
+ Slog.d(TAG, "VUR feature is not enabled, VUR is always off");
}
return false;
}
if (carPropertyConfig.getChangeMode()
!= CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
if (DBG) {
- Log.d(TAG, "VUR is always off for non-continuous property");
+ Slog.d(TAG, "VUR is always off for non-continuous property");
}
return false;
}
@@ -165,7 +166,7 @@
// Do nothing.
}
if (DBG) {
- Log.d(TAG, "VUR is enabled but not supported for areaId: " + areaId);
+ Slog.d(TAG, "VUR is enabled but not supported for areaId: " + areaId);
}
disabledAreaIds.add(areaId);
}
diff --git a/car-lib/src/com/android/car/internal/property/Logger.java b/car-lib/src/com/android/car/internal/property/Logger.java
index c548c08..b3916c1 100644
--- a/car-lib/src/com/android/car/internal/property/Logger.java
+++ b/car-lib/src/com/android/car/internal/property/Logger.java
@@ -64,6 +64,17 @@
}
/**
+ * Logs a verbose message.
+ */
+ public void logV(String msg) {
+ if (mUseSystemLogger) {
+ Slogf.v(mTag, msg);
+ } else {
+ Log.v(mTag, msg);
+ }
+ }
+
+ /**
* Whether debug logging should be enabled.
*/
public boolean dbg() {
diff --git a/car-lib/src/com/android/car/internal/property/SubscriptionManager.java b/car-lib/src/com/android/car/internal/property/SubscriptionManager.java
index 908e1d6..02a9bf1 100644
--- a/car-lib/src/com/android/car/internal/property/SubscriptionManager.java
+++ b/car-lib/src/com/android/car/internal/property/SubscriptionManager.java
@@ -25,6 +25,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Slog;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.util.IndentingPrintWriter;
@@ -274,7 +275,7 @@
*/
public void stageNewOptions(ClientType client, List<CarSubscription> options) {
if (DBG) {
- Log.d(TAG, "stageNewOptions: options: " + options);
+ Slog.d(TAG, "stageNewOptions: options: " + options);
}
cloneCurrentToStageIfClean();
@@ -302,7 +303,7 @@
*/
public void stageUnregister(ClientType client, ArraySet<Integer> propertyIdsToUnregister) {
if (DBG) {
- Log.d(TAG, "stageUnregister: propertyIdsToUnregister: " + propertyIdsToString(
+ Slog.d(TAG, "stageUnregister: propertyIdsToUnregister: " + propertyIdsToString(
propertyIdsToUnregister));
}
@@ -318,7 +319,7 @@
RateInfoForClients<ClientType> rateInfoForClients =
mStagedRateInfoByClientByPropIdAreaId.get(propertyId, areaId);
if (rateInfoForClients == null) {
- Log.e(TAG, "The property: " + VehiclePropertyIds.toString(propertyId)
+ Slog.e(TAG, "The property: " + VehiclePropertyIds.toString(propertyId)
+ ", area ID: " + areaId + " was not registered, do nothing");
continue;
}
@@ -339,7 +340,7 @@
public void commit() {
if (mStagedAffectedPropIdAreaIds.isEmpty()) {
if (DBG) {
- Log.d(TAG, "No changes has been staged, nothing to commit");
+ Slog.d(TAG, "No changes has been staged, nothing to commit");
}
return;
}
@@ -356,7 +357,7 @@
public void dropCommit() {
if (mStagedAffectedPropIdAreaIds.isEmpty()) {
if (DBG) {
- Log.d(TAG, "No changes has been staged, nothing to drop");
+ Slog.d(TAG, "No changes has been staged, nothing to drop");
}
return;
}
@@ -412,7 +413,7 @@
List<Integer> outPropertyIdsToUnsubscribe) {
if (mStagedAffectedPropIdAreaIds.isEmpty()) {
if (DBG) {
- Log.d(TAG, "No changes has been staged, no diff");
+ Slog.d(TAG, "No changes has been staged, no diff");
}
return;
}
@@ -426,8 +427,8 @@
if (!mStagedRateInfoByClientByPropIdAreaId.contains(propertyId, areaId)) {
// The [PropertyId, areaId] is no longer subscribed.
if (DBG) {
- Log.d(TAG, String.format("The property: %s, areaId: %d is no longer subscribed",
- VehiclePropertyIds.toString(propertyId), areaId));
+ Slog.d(TAG, String.format("The property: %s, areaId: %d is no longer "
+ + "subscribed", VehiclePropertyIds.toString(propertyId), areaId));
}
possiblePropIdsToUnsubscribe.add(propertyId);
continue;
@@ -441,7 +442,7 @@
.get(propertyId, areaId).getCombinedRateInfo()
.equals(newCombinedRateInfo))) {
if (DBG) {
- Log.d(TAG, String.format(
+ Slog.d(TAG, String.format(
"New combined subscription rate info for property: %s, areaId: %d, %s",
VehiclePropertyIds.toString(propertyId), areaId, newCombinedRateInfo));
}
@@ -456,7 +457,7 @@
possiblePropIdToUnsubscribe).isEmpty()) {
// We should only unsubscribe the property if all area IDs are unsubscribed.
if (DBG) {
- Log.d(TAG, String.format(
+ Slog.d(TAG, String.format(
"All areas for the property: %s are no longer subscribed, "
+ "unsubscribe it", VehiclePropertyIds.toString(
possiblePropIdToUnsubscribe)));
@@ -544,7 +545,7 @@
+ rateInfoForClients.getUpdateRateHz(client) + " hz"
+ ", enableVur: "
+ rateInfoForClients.isVariableUpdateRateEnabled(client)
- + ", resoliution: " + rateInfoForClients.getResolution(client));
+ + ", resolution: " + rateInfoForClients.getResolution(client));
}
writer.decreaseIndent();
}
diff --git a/car-lib/src/com/android/car/internal/util/ArrayUtils.java b/car-lib/src/com/android/car/internal/util/ArrayUtils.java
index 52e6cee..6ada595 100644
--- a/car-lib/src/com/android/car/internal/util/ArrayUtils.java
+++ b/car-lib/src/com/android/car/internal/util/ArrayUtils.java
@@ -104,7 +104,7 @@
cache = Array.newInstance(kind, 0);
sCache[bucket] = cache;
- // Log.e("cache", "new empty " + kind.getName() + " at " + bucket);
+ // Slog.e("cache", "new empty " + kind.getName() + " at " + bucket);
}
return (T[]) cache;
diff --git a/car-lib/src/com/android/car/internal/util/ConstantDebugUtils.java b/car-lib/src/com/android/car/internal/util/ConstantDebugUtils.java
index 2389ffd..feb5d5e 100644
--- a/car-lib/src/com/android/car/internal/util/ConstantDebugUtils.java
+++ b/car-lib/src/com/android/car/internal/util/ConstantDebugUtils.java
@@ -20,7 +20,7 @@
import android.annotation.Nullable;
import android.util.ArrayMap;
-import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
@@ -155,7 +155,7 @@
constantNameToValue.put(candidateField.getName(), candidateField.getInt(null));
}
} catch (IllegalAccessException e) {
- Log.wtf(TAG, "Failed trying to find value for " + candidateField.getName(), e);
+ Slog.wtf(TAG, "Failed trying to find value for " + candidateField.getName(), e);
}
}
return constantNameToValue;
@@ -174,7 +174,7 @@
constantValueToName.put(candidateField.getInt(null), candidateField.getName());
}
} catch (IllegalAccessException e) {
- Log.wtf(TAG, "Failed trying to find value for " + candidateField.getName(), e);
+ Slog.wtf(TAG, "Failed trying to find value for " + candidateField.getName(), e);
}
}
return constantValueToName;
diff --git a/car-lib/src/com/android/car/internal/util/PairSparseArray.java b/car-lib/src/com/android/car/internal/util/PairSparseArray.java
index 1d79ff2..f63d379 100644
--- a/car-lib/src/com/android/car/internal/util/PairSparseArray.java
+++ b/car-lib/src/com/android/car/internal/util/PairSparseArray.java
@@ -33,7 +33,7 @@
*
* @hide
*/
-public class PairSparseArray<E> {
+public class PairSparseArray<E> implements Cloneable {
/** Bitmask for casting an {@code int} into a {@code long} without sign extension. */
private static final long LEAST_SIGNIFICANT_BITMASK = 0xffffffffL;
private static final int INITIAL_CAPACITY = 10;
@@ -46,7 +46,7 @@
* First key to second keys mapping to allow easier operation applied to all the entries with
* the first key.
*/
- private final SparseArray<ArraySet<Integer>> mSecondKeysByFirstKey = new SparseArray<>();
+ private final SparseArray<ArraySet<Integer>> mSecondKeysByFirstKey;
/** Creates a new PairSparseArray with initial capacity of {@link #INITIAL_CAPACITY}. */
public PairSparseArray() {
@@ -56,6 +56,18 @@
/** Creates a new PairSparseArray. */
public PairSparseArray(int initialCapacity) {
mValues = new LongSparseArray<>(initialCapacity);
+ mSecondKeysByFirstKey = new SparseArray<>();
+ }
+
+ private PairSparseArray(PairSparseArray<E> other) {
+ mValues = other.mValues.clone();
+ mSecondKeysByFirstKey = other.mSecondKeysByFirstKey.clone();
+ }
+
+ /** Creates a clone. */
+ @Override
+ public PairSparseArray<E> clone() {
+ return new PairSparseArray<E>(this);
}
/**
diff --git a/car-usb-handler/Android.bp b/car-usb-handler/Android.bp
index 30a1d97..3d8b193 100644
--- a/car-usb-handler/Android.bp
+++ b/car-usb-handler/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/car-usb-handler/src/android/car/usb/handler/BootUsbService.java b/car-usb-handler/src/android/car/usb/handler/BootUsbService.java
index c98b902..1aec143 100644
--- a/car-usb-handler/src/android/car/usb/handler/BootUsbService.java
+++ b/car-usb-handler/src/android/car/usb/handler/BootUsbService.java
@@ -15,16 +15,21 @@
*/
package android.car.usb.handler;
+import static android.content.Intent.ACTION_USER_UNLOCKED;
+
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Binder;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import java.util.ArrayList;
@@ -42,6 +47,27 @@
private ArrayList<UsbDevice> mDeviceList;
+ private final UserUnlockedBroadcastReceiver mUserUnlockedBroadcastReceiver =
+ new UserUnlockedBroadcastReceiver();
+ private boolean mReceiverRegistered = false;
+
+ private class UserUnlockedBroadcastReceiver extends BroadcastReceiver {
+ private int mStartId;
+
+ public void setStartId(int startId) {
+ mStartId = startId;
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ // We could have been unregistered after receiving the intent but before processing it,
+ // so make sure we are still registered.
+ if (mReceiverRegistered) {
+ unregisterUserUnlockedReceiver();
+ processDevices(mStartId);
+ }
+ }
+ }
+
@Override
public Binder onBind(Intent intent) {
return null;
@@ -68,16 +94,48 @@
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mDeviceList = intent.getParcelableArrayListExtra(USB_DEVICE_LIST_KEY);
- processDevices();
+ UserManager userManager = getSystemService(UserManager.class);
+ if (!userManager.isUserUnlocked() && getUserId() != UserHandle.USER_SYSTEM) {
+ Log.i(TAG, "Waiting for user unlocked to process connected devices.");
+ registerUserUnlockedReceiver(startId, userManager);
+ return START_REDELIVER_INTENT;
+ }
+ processDevices(startId);
return START_NOT_STICKY;
}
- private void processDevices() {
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ unregisterUserUnlockedReceiver();
+ }
+
+ private void registerUserUnlockedReceiver(int startId, UserManager userManager) {
+ mReceiverRegistered = true;
+ mUserUnlockedBroadcastReceiver.setStartId(startId);
+ registerReceiver(mUserUnlockedBroadcastReceiver, new IntentFilter(ACTION_USER_UNLOCKED),
+ Context.RECEIVER_NOT_EXPORTED);
+ // in case the car was unlocked while the receiver was being registered
+ if (userManager.isUserUnlocked()) {
+ mUserUnlockedBroadcastReceiver.onReceive(this, new Intent(ACTION_USER_UNLOCKED));
+ }
+ }
+
+ private void unregisterUserUnlockedReceiver() {
+ if (mReceiverRegistered) {
+ Log.d(TAG, "Unregistering USER_UNLOCKED broadcast");
+ unregisterReceiver(mUserUnlockedBroadcastReceiver);
+ mReceiverRegistered = false;
+ }
+ }
+
+ private void processDevices(int startId) {
+ Log.i(TAG, "Processing devices");
for (UsbDevice device : mDeviceList) {
Log.d(TAG, "Processing device: " + device.getProductName());
handle(this, device);
}
- stopSelf();
+ stopSelf(startId);
}
private void handle(Context context, UsbDevice device) {
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java b/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java
index 0bc17c1..6ccc123 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbHostManagementActivity.java
@@ -52,6 +52,8 @@
* this case handler will be launched. b) Device has not handler assigned. In this case supported
* handlers will be captured, and user will be presented with choice to assign default handler.
* After that handler will be launched.
+ *
+ * <p>Note: Activity launched by the system and by {@link BootUsbService}.
*/
public class UsbHostManagementActivity extends Activity {
private static final String TAG = UsbHostManagementActivity.class.getSimpleName();
@@ -69,6 +71,7 @@
private void unregisterResolveBroadcastReceiver() {
if (mReceiverRegistered) {
+ Log.d(TAG, "Unregistering USER_UNLOCKED broadcast");
unregisterReceiver(mResolveBroadcastReceiver);
mReceiverRegistered = false;
}
@@ -78,8 +81,10 @@
UsbDevice connectedDevice = getDevice();
if (connectedDevice != null) {
+ Log.d(TAG, "Processing device: " + connectedDevice.getProductName());
mController.processDevice(connectedDevice);
} else {
+ Log.d(TAG, "Device not found.");
finish();
}
}
@@ -138,9 +143,12 @@
super.onResume();
UserManager userManager = getSystemService(UserManager.class);
+ // Checks should pass if activity is started from BootUsbService, but necessary for system
+ // calls.
if (userManager.isUserUnlocked() || getUserId() == UserHandle.USER_SYSTEM) {
processDevice();
} else {
+ Log.d(TAG, "Waiting for user unlocked to process device.");
mReceiverRegistered = true;
registerReceiver(mResolveBroadcastReceiver, new IntentFilter(ACTION_USER_UNLOCKED),
Context.RECEIVER_NOT_EXPORTED);
diff --git a/car_product/OWNERS b/car_product/OWNERS
new file mode 100644
index 0000000..9c2bb37
--- /dev/null
+++ b/car_product/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1316886
+# Project owners
+include platform/packages/services/Car:/OWNERS
+
+# Bluetooth property file
+per-file properties/bluetooth.prop = file: platform/packages/modules/Bluetooth:/OWNERS_automotive
diff --git a/car_product/app_overlays/car-ui-customizations/res/values/styles.xml b/car_product/app_overlays/car-ui-customizations/res/values/styles.xml
index 5c1d8da..4b82d19 100644
--- a/car_product/app_overlays/car-ui-customizations/res/values/styles.xml
+++ b/car_product/app_overlays/car-ui-customizations/res/values/styles.xml
@@ -48,6 +48,7 @@
<item name="android:drawableTint">@color/car_ui_toolbar_menu_item_icon_color</item>
<item name="android:drawablePadding">10dp</item>
<item name="android:maxWidth">350dp</item>
+ <item name="android:minHeight">75dp</item>
</style>
<style name="Widget.CarUi.Toolbar.TextButton.WithIcon">
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index 40c4a60..e010bc7 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -52,6 +52,7 @@
RailwayReferenceApp \
CarHotwordDetectionServiceOne \
KitchenSinkServerlessRemoteTaskClientRRO \
+ AaosCustomizationTool \
# SEPolicy for test apps / services
PRODUCT_PRIVATE_SEPOLICY_DIRS += packages/services/Car/car_product/sepolicy/test
@@ -121,10 +122,6 @@
PRODUCT_PROPERTY_OVERRIDES += \
keyguard.no_require_sim=true
-# TODO(b/255631687): Enable the shell transition as soon as all CTS issues are resolved.
-PRODUCT_SYSTEM_PROPERTIES += \
- persist.wm.debug.shell_transit=0
-
# TODO(b/198516172): Find a better location to add this read only property
# It is added here to check the functionality, will be updated in next CL
PRODUCT_SYSTEM_PROPERTIES += \
@@ -143,6 +140,7 @@
CarService \
CarShell \
CarDialerApp \
+ CarDocumentsUI \
CarRadioApp \
OverviewApp \
CarLauncher \
diff --git a/car_product/build/car_base.mk b/car_product/build/car_base.mk
index d5d8892..b767dac 100644
--- a/car_product/build/car_base.mk
+++ b/car_product/build/car_base.mk
@@ -54,7 +54,6 @@
atrace \
libandroidfw \
libaudioutils \
- libmdnssd \
libnfc_ndef \
libpowermanager \
libvariablespeed \
diff --git a/car_product/build/car_generic_system.mk b/car_product/build/car_generic_system.mk
index 66ce870..902ab0a 100644
--- a/car_product/build/car_generic_system.mk
+++ b/car_product/build/car_generic_system.mk
@@ -44,6 +44,7 @@
NetworkPreferenceApp \
RailwayReferenceApp \
SampleCustomInputService \
+ AaosCustomizationTool \
# Default boot animation for AAOS
PRODUCT_COPY_FILES += \
diff --git a/car_product/build/car_product.mk b/car_product/build/car_product.mk
index f8601b5..63e048d 100644
--- a/car_product/build/car_product.mk
+++ b/car_product/build/car_product.mk
@@ -18,6 +18,12 @@
# automotive device.
$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_product.mk)
+ifneq ($(TARGET_NO_TELEPHONY), true)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_product.mk)
+PRODUCT_COPY_FILES += \
+ device/sample/etc/apns-full-conf.xml:$(TARGET_COPY_OUT_PRODUCT)/etc/apns-conf.xml
+endif
+
# Default AOSP sounds
$(call inherit-product-if-exists, frameworks/base/data/sounds/AllAudio.mk)
@@ -83,4 +89,9 @@
endif
$(call inherit-product, device/sample/products/location_overlay.mk)
-$(call inherit-product, packages/services/Car/car_product/rro/ThemeSamples/product.mk)
+
+# Theme RROs that are used to control the system theme on the runtime.
+$(call inherit-product-if-exists, packages/services/Car/car_product/rro/ThemeSamples/product.mk)
+
+# SystemUI RROs that are used to control the CarSystemUI features on the runtime.
+$(call inherit-product-if-exists, packages/apps/Car/SystemUI/samples/systemui_sample_rros.mk)
diff --git a/car_product/build/car_system.mk b/car_product/build/car_system.mk
index f2b34fd..a7939b2 100644
--- a/car_product/build/car_system.mk
+++ b/car_product/build/car_system.mk
@@ -14,6 +14,8 @@
# limitations under the License.
#
+PRODUCT_RELEASE_CONFIG_MAPS += $(wildcard build/release/automotive/release_config_map.textproto)
+
# This makefile comprises the minimal system partition content for an
# automotive device.
$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_system.mk)
@@ -93,10 +95,10 @@
libpolicy-subsystem
# Include all zygote init scripts. "ro.zygote" will select one of them.
-PRODUCT_COPY_FILES += \
- system/core/rootdir/init.zygote32.rc:system/etc/init/hw/init.zygote32.rc \
- system/core/rootdir/init.zygote64.rc:system/etc/init/hw/init.zygote64.rc \
- system/core/rootdir/init.zygote64_32.rc:system/etc/init/hw/init.zygote64_32.rc \
+PRODUCT_PACKAGES += \
+ init.zygote32.rc \
+ init.zygote64.rc \
+ init.zygote64_32.rc
# Enable dynamic partition size
PRODUCT_USE_DYNAMIC_PARTITION_SIZE := true
@@ -135,10 +137,6 @@
### end of multi-user properties ###
-# TODO(b/255631687): Enable the shell transition as soon as all CTS issues are resolved.
-PRODUCT_SYSTEM_PROPERTIES += \
- persist.wm.debug.shell_transit=0
-
# TODO(b/198516172): Find a better location to add this read only property
# It is added here to check the functionality, will be updated in next CL
PRODUCT_SYSTEM_PROPERTIES += \
@@ -174,7 +172,6 @@
atrace \
libandroidfw \
libaudioutils \
- libmdnssd \
libpowermanager \
libvariablespeed \
PackageInstaller \
@@ -207,6 +204,7 @@
PRODUCT_IS_AUTOMOTIVE := true
PRODUCT_PACKAGES += \
+ CarDocumentsUI \
CarFrameworkPackageStubs \
CarService \
CarShell \
@@ -239,8 +237,6 @@
CarPermissionControllerRRO \
CarSystemUIRRO
-$(call inherit-product-if-exists, packages/apps/Car/SystemUI/samples/systemui_sample_rros.mk)
-
# System Server components
# Order is important: if X depends on Y, then Y should precede X on the list.
PRODUCT_SYSTEM_SERVER_JARS += car-frameworks-service
diff --git a/car_product/build/car_system_ext.mk b/car_product/build/car_system_ext.mk
index 3059ab0..a9fdcd5 100644
--- a/car_product/build/car_system_ext.mk
+++ b/car_product/build/car_system_ext.mk
@@ -18,6 +18,10 @@
# automotive device.
$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_system_ext.mk)
+ifneq ($(TARGET_NO_TELEPHONY), true)
+$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system_ext.mk)
+endif
+
# Window Extensions
$(call inherit-product, $(SRC_TARGET_DIR)/product/window_extensions.mk)
diff --git a/car_product/build/car_vendor.mk b/car_product/build/car_vendor.mk
index 4ee3473..916fae2 100644
--- a/car_product/build/car_vendor.mk
+++ b/car_product/build/car_vendor.mk
@@ -29,3 +29,10 @@
# Include the reference EVS HAL implementation.
PRODUCT_PACKAGES += android.hardware.automotive.evs-default
endif # ENABLE_EVS_SAMPLE
+
+# Sensor features
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/android.hardware.sensor.accelerometer_limited_axes.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.accelerometer_limited_axes.xml \
+ frameworks/native/data/etc/android.hardware.sensor.gyroscope_limited_axes.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.gyroscope_limited_axes.xml \
+ frameworks/native/data/etc/android.hardware.sensor.accelerometer_limited_axes_uncalibrated.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.accelerometer_limited_axes_uncalibrated.xml \
+ frameworks/native/data/etc/android.hardware.sensor.gyroscope_limited_axes_uncalibrated.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.sensor.gyroscope_limited_axes_uncalibrated.xml \
diff --git a/car_product/build/preinstalled-packages-product-car-base.xml b/car_product/build/preinstalled-packages-product-car-base.xml
index 98e3546..042132a 100644
--- a/car_product/build/preinstalled-packages-product-car-base.xml
+++ b/car_product/build/preinstalled-packages-product-car-base.xml
@@ -272,12 +272,6 @@
<install-in user-type="SYSTEM" />
</install-in-user-type>
- <!-- RRO package to make systembars persistent. -->
- <install-in-user-type package="com.android.systemui.rro.persistent">
- <install-in user-type="SYSTEM" />
- <install-in user-type="FULL" />
- </install-in-user-type>
-
<!--
Apps that initially were marked as FULL only but are actually
required on SYSTEM user as well.
@@ -328,9 +322,6 @@
<install-in-user-type package="com.android.stk">
<install-in user-type="FULL" />
</install-in-user-type>
- <install-in-user-type package="com.android.dreams.phototable">
- <install-in user-type="FULL" />
- </install-in-user-type>
<install-in-user-type package="com.android.carrierdefaultapp">
<install-in user-type="FULL" />
</install-in-user-type>
@@ -349,9 +340,6 @@
<install-in-user-type package="com.android.simappdialog">
<install-in user-type="FULL" />
</install-in-user-type>
- <install-in-user-type package="com.android.dreams.basic">
- <install-in user-type="FULL" />
- </install-in-user-type>
<install-in-user-type package="com.android.companiondevicemanager">
<install-in user-type="FULL" />
</install-in-user-type>
@@ -479,4 +467,157 @@
<install-in-user-type package="com.android.car.rotary">
<install-in user-type="FULL" />
</install-in-user-type>
+
+ <!-- RRO packages that config the app theme. the user-ype should be the same as target apps. -->
+ <install-in-user-type package="android.googlecarui.theme.orange.rro">
+ <install-in user-type="SYSTEM" />
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="android.googlecarui.theme.pink.rro">
+ <install-in user-type="SYSTEM" />
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.calendar.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.calendar.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.carlauncher.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.carlauncher.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.developeroptions.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.developeroptions.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.dialer.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.dialer.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.home.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.home.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.linkviewer.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.linkviewer.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.media.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.media.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.messenger.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.messenger.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.notification.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.notification.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.radio.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.radio.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.rotaryplayground.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.rotaryplayground.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.settings.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.settings.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.systemupdater.googlecarui.theme.orange.rro">
+ <install-in user-type="SYSTEM" />
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.systemupdater.googlecarui.theme.pink.rro">
+ <install-in user-type="SYSTEM" />
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.themeplayground.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.themeplayground.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.htmlviewer.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.htmlviewer.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.managedprovisioning.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.managedprovisioning.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.settings.intelligence.googlecarui.theme.orange.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.settings.intelligence.googlecarui.theme.pink.rro">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.systemui.googlecarui.theme.orange.rro">
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.systemui.googlecarui.theme.pink.rro">
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+
+ <!-- RRO packages that config the systemUi features. -->
+ <install-in-user-type package="com.android.systemui.controls.systembar.insets.rro">
+ <install-in user-type="SYSTEM" />
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.systemui.rro.bottom">
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.systemui.rro.bottom.rounded">
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.systemui.rro.left">
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.systemui.rro.right">
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.systemui.systembar.persistency.immersive">
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.systemui.systembar.persistency.immersive_with_nav">
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.systemui.systembar.persistency.non_immersive">
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.systemui.systembar.transparency.navbar.translucent">
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.car.systemui.systembar.transparency.statusbar.translucent">
+ <install-in user-type="SYSTEM" />
+ </install-in-user-type>
</config>
diff --git a/car_product/car_ui_portrait/Android.mk b/car_product/car_ui_portrait/Android.mk
deleted file mode 100644
index 53ad94b..0000000
--- a/car_product/car_ui_portrait/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright (C) 2021 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-car_ui_portrait_modules := \
- rro/car-ui-customizations \
- rro/car-ui-toolbar-customizations \
- apps/HideApps
-
-include $(call all-named-subdir-makefiles,$(car_ui_portrait_modules))
diff --git a/car_product/car_ui_portrait/OWNERS b/car_product/car_ui_portrait/OWNERS
index b9f6a0c..5f058f7 100644
--- a/car_product/car_ui_portrait/OWNERS
+++ b/car_product/car_ui_portrait/OWNERS
@@ -5,4 +5,3 @@
priyanksingh@google.com
snekkalapudi@google.com
stenning@google.com
-igorr@google.com
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitCarUiProxy/plugin/src/main/java/com/chassis/car/ui/plugin/appstyledview/AppStyledViewControllerAdapterProxyV1.java b/car_product/car_ui_portrait/apps/CarUiPortraitCarUiProxy/plugin/src/main/java/com/chassis/car/ui/plugin/appstyledview/AppStyledViewControllerAdapterProxyV1.java
index d8a8529..af8fa16 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitCarUiProxy/plugin/src/main/java/com/chassis/car/ui/plugin/appstyledview/AppStyledViewControllerAdapterProxyV1.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitCarUiProxy/plugin/src/main/java/com/chassis/car/ui/plugin/appstyledview/AppStyledViewControllerAdapterProxyV1.java
@@ -22,7 +22,7 @@
import androidx.annotation.NonNull;
import com.android.car.ui.appstyledview.AppStyledDialogController.NavIcon;
-import com.android.car.ui.appstyledview.AppStyledViewController;
+import com.android.car.ui.appstyledview.AppStyledViewControllerImpl;
import com.android.car.ui.plugin.oemapis.appstyledview.AppStyledViewControllerOEMV1;
/**
@@ -32,21 +32,27 @@
public class AppStyledViewControllerAdapterProxyV1 implements AppStyledViewControllerOEMV1 {
@NonNull
- private final AppStyledViewController mStaticController;
+ private final AppStyledViewControllerImpl mStaticController;
private View mContentView;
- public AppStyledViewControllerAdapterProxyV1(@NonNull AppStyledViewController controller) {
+ public AppStyledViewControllerAdapterProxyV1(@NonNull AppStyledViewControllerImpl controller) {
mStaticController = controller;
}
@Override
public View getView() {
- return mStaticController.getAppStyledView(mContentView);
+ if (mContentView == null) {
+ return null;
+ }
+
+ //TODO(b/333901191): Rename api or cache
+ return mStaticController.createAppStyledView(mContentView);
}
@Override
public void setContent(View view) {
mContentView = view;
+ mStaticController.setContent(mContentView);
}
@Override
@@ -68,8 +74,10 @@
}
}
+ @NonNull
@Override
- public LayoutParams getDialogWindowLayoutParam(LayoutParams params) {
- return mStaticController.getDialogWindowLayoutParam(params);
+ public LayoutParams getDialogWindowLayoutParam(@NonNull LayoutParams params) {
+ return mStaticController.getDialog().getDialogWindowLayoutParam(params);
}
}
+
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitCarUiProxy/plugin/src/main/java/com/chassis/car/ui/plugin/appstyledview/AppStyledViewControllerAdapterProxyV3.java b/car_product/car_ui_portrait/apps/CarUiPortraitCarUiProxy/plugin/src/main/java/com/chassis/car/ui/plugin/appstyledview/AppStyledViewControllerAdapterProxyV3.java
index f548469..0390813 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitCarUiProxy/plugin/src/main/java/com/chassis/car/ui/plugin/appstyledview/AppStyledViewControllerAdapterProxyV3.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitCarUiProxy/plugin/src/main/java/com/chassis/car/ui/plugin/appstyledview/AppStyledViewControllerAdapterProxyV3.java
@@ -17,6 +17,7 @@
package com.chassis.car.ui.plugin.appstyledview;
import android.view.View;
+import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import androidx.annotation.NonNull;
@@ -24,6 +25,7 @@
import com.android.car.ui.appstyledview.AppStyledDialogController.NavIcon;
import com.android.car.ui.appstyledview.AppStyledDialogController.SceneType;
import com.android.car.ui.appstyledview.AppStyledViewController;
+import com.android.car.ui.appstyledview.AppStyledViewControllerImpl;
import com.android.car.ui.plugin.oemapis.appstyledview.AppStyledViewControllerOEMV3;
/**
@@ -32,21 +34,26 @@
public class AppStyledViewControllerAdapterProxyV3 implements AppStyledViewControllerOEMV3 {
@NonNull
- private final AppStyledViewController mStaticController;
+ private final AppStyledViewControllerImpl mStaticController;
private View mContentView;
- public AppStyledViewControllerAdapterProxyV3(@NonNull AppStyledViewController controller) {
+ public AppStyledViewControllerAdapterProxyV3(@NonNull AppStyledViewControllerImpl controller) {
mStaticController = controller;
}
@Override
public View getView() {
- return mStaticController.getAppStyledView(mContentView);
+ if (mContentView == null) {
+ return null;
+ }
+
+ return mStaticController.createAppStyledView(mContentView);
}
@Override
public void setContent(View view) {
mContentView = view;
+ mStaticController.setContent(mContentView);
}
@Override
@@ -88,18 +95,32 @@
}
}
+ @NonNull
@Override
- public LayoutParams getDialogWindowLayoutParam(LayoutParams params) {
- return mStaticController.getDialogWindowLayoutParam(params);
+ public LayoutParams getDialogWindowLayoutParam(@NonNull LayoutParams params) {
+ return mStaticController.getDialog().getDialogWindowLayoutParam(params);
}
@Override
public int getContentAreaWidth() {
+ WindowManager.LayoutParams latestParams =
+ mStaticController.getDialog().getDialogWindowLayoutParam(
+ new WindowManager.LayoutParams());
+ mStaticController.getDialog().getWindow().setAttributes(latestParams);
+
return mStaticController.getContentAreaWidth();
}
@Override
public int getContentAreaHeight() {
+ // Update params
+ WindowManager.LayoutParams latestParams =
+ mStaticController.getDialog().getDialogWindowLayoutParam(
+ new WindowManager.LayoutParams());
+ mStaticController.getDialog().getWindow().setAttributes(latestParams);
+
+
return mStaticController.getContentAreaHeight();
}
}
+
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitCarUiProxy/plugin/src/main/java/com/chassis/car/ui/plugin/preference/PreferenceAdapterProxy.java b/car_product/car_ui_portrait/apps/CarUiPortraitCarUiProxy/plugin/src/main/java/com/chassis/car/ui/plugin/preference/PreferenceAdapterProxy.java
index 6352528..82691cb 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitCarUiProxy/plugin/src/main/java/com/chassis/car/ui/plugin/preference/PreferenceAdapterProxy.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitCarUiProxy/plugin/src/main/java/com/chassis/car/ui/plugin/preference/PreferenceAdapterProxy.java
@@ -30,8 +30,9 @@
import static com.android.car.ui.preference.CarUiPreferenceViewStub.TWO_ACTION_TEXT_BORDERLESS;
import android.content.Context;
-import android.content.ContextWrapper;
+import android.content.res.AssetManager;
import android.content.res.Resources;
+import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -43,6 +44,9 @@
import com.chassis.car.ui.plugin.R;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
/**
* Adapter to load preference from plugin.
*/
@@ -50,6 +54,7 @@
private final Context mPluginContext;
private final Context mSourceContext;
+ private String mAppPackageName;
public PreferenceAdapterProxy(Context pluginContext, Context sourceContext) {
mPluginContext = pluginContext;
@@ -145,8 +150,7 @@
int sharedLibId = getSharedLibViewId(name);
int appViewId = getAppViewId(name);
View view = carUiPreferenceView.findViewById(sharedLibId);
- ViewGroup.LayoutParams currentViewLayoutParam =
- (ViewGroup.LayoutParams) view.getLayoutParams();
+ ViewGroup.LayoutParams currentViewLayoutParam = view.getLayoutParams();
ViewGroup parent = (ViewGroup) view.getParent();
int index = parent.indexOfChild(view);
parent.removeView(view);
@@ -161,13 +165,57 @@
private int getSharedLibViewId(String resName) {
Resources res = mPluginContext.getResources();
- return res.getIdentifier(resName, "id",
- ((ContextWrapper) mPluginContext).getBaseContext().getPackageName());
+ return res.getIdentifier(resName, "id", "com.chassis.car.ui.plugin");
}
private int getAppViewId(String resName) {
Resources res = mSourceContext.getResources();
- return res.getIdentifier(resName, "id",
- mSourceContext.getPackageName());
+ return res.getIdentifier(resName, "id", getAppPackageName());
+ }
+
+ private String getAppPackageName() {
+ if (mAppPackageName != null) {
+ return mAppPackageName;
+ }
+
+ SparseArray<String> r = getAssignedPackageIdentifiers(mSourceContext.getAssets());
+ for (int i = 0, n = r.size(); i < n; i++) {
+ final int id = r.keyAt(i);
+ // skip anything not in the app space (0x7f)
+ if (id != 0x7f) {
+ continue;
+ }
+
+ String packageName = mSourceContext.getResources().getResourcePackageName(id);
+ // Check if car-ui-lib resources are present under this package name
+ if (mSourceContext.getResources().getIdentifier(
+ "car_ui_plugin_package_provider_authority_name", "string", packageName) != 0) {
+ mAppPackageName = packageName;
+ return mAppPackageName;
+ }
+ }
+
+ mAppPackageName = mSourceContext.getPackageName();
+ return mAppPackageName;
+ }
+
+ private static SparseArray<String> getAssignedPackageIdentifiers(AssetManager am) {
+ final Class<? extends AssetManager> rClazz = am.getClass();
+ Throwable cause;
+ try {
+ final Method callback = rClazz.getMethod("getAssignedPackageIdentifiers");
+ Object invoke = callback.invoke(am);
+ return (SparseArray<String>) invoke;
+ } catch (NoSuchMethodException e) {
+ // No rewriting to be done.
+ return new SparseArray<>();
+ } catch (IllegalAccessException e) {
+ cause = e;
+ } catch (InvocationTargetException e) {
+ cause = e.getCause();
+ }
+
+ throw new RuntimeException("Failed to find R classes ", cause);
}
}
+
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/AndroidManifest.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/AndroidManifest.xml
index a7b8443..61d5b09 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/AndroidManifest.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/AndroidManifest.xml
@@ -26,8 +26,8 @@
<!-- Permission to manage USB -->
<uses-permission android:name="android.permission.MANAGE_USB"/>
- <!-- Permissions to support autoenhance -->
- <uses-permission android:name="android.car.permission.QUERY_DISPLAY_COMPATIBILITY"/>
+ <!-- Permissions to support display compat -->
+ <uses-permission android:name="android.car.permission.MANAGE_DISPLAY_COMPATIBILITY"/>
<application
android:label="@string/app_title"
@@ -96,7 +96,7 @@
launcher. -->
<intent-filter tools:node="removeAll" />
</activity>
- <activity android:name=".calmmode.CalmModeActivity"
+ <activity android:name=".calmmode.PortraitCalmModeActivity"
android:theme="@style/Theme.CalmMode"
android:excludeFromRecents="true"
android:exported="true"
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/color/control_bar_media_card_panel_button_background.xml
similarity index 61%
copy from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
copy to car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/color/control_bar_media_card_panel_button_background.xml
index a6ac85e..eb71a3b 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/color/control_bar_media_card_panel_button_background.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,10 +14,9 @@
~ limitations under the License.
-->
-<ImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/statusbar_sensor_text_width"
- android:layout_height="@*android:dimen/status_bar_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:tag="@string/qc_icon_tag"/>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/car_surface_variant"
+ android:state_selected="false"/>
+ <item android:color="@color/car_on_surface_variant"
+ android:state_selected="true"/>
+</selector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/color/control_bar_media_card_panel_button_foreground.xml
similarity index 61%
copy from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
copy to car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/color/control_bar_media_card_panel_button_foreground.xml
index a6ac85e..3cdf6ab 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/color/control_bar_media_card_panel_button_foreground.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,10 +14,9 @@
~ limitations under the License.
-->
-<ImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/statusbar_sensor_text_width"
- android:layout_height="@*android:dimen/status_bar_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:tag="@string/qc_icon_tag"/>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/car_surface"
+ android:state_selected="true"/>
+ <item android:color="@color/car_on_surface"
+ android:state_selected="false"/>
+</selector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/drawable/ic_cancel.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/drawable/ic_cancel.xml
new file mode 100644
index 0000000..68c5147
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/drawable/ic_cancel.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="40dp"
+ android:height="40dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M332,674.67L480,526.67L628,674.67L674.67,628L526.67,480L674.67,332L628,285.33L480,433.33L332,285.33L285.33,332L433.33,480L285.33,628L332,674.67ZM480,880Q397.67,880 324.67,848.5Q251.67,817 197.33,762.67Q143,708.33 111.5,635.33Q80,562.33 80,480Q80,397 111.5,324Q143,251 197.33,197Q251.67,143 324.67,111.5Q397.67,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,562.33 848.5,635.33Q817,708.33 763,762.67Q709,817 636,848.5Q563,880 480,880Z"/>
+</vector>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/drawable/left_half_pill_button_shape.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/drawable/left_half_pill_button_shape.xml
new file mode 100644
index 0000000..c38089c
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/drawable/left_half_pill_button_shape.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:bottomLeftRadius="@dimen/control_bar_media_card_half_pill_button_radius"
+ android:topLeftRadius="@dimen/control_bar_media_card_half_pill_button_radius" />
+ <solid android:color="@color/car_surface_variant" />
+</shape>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/drawable/radius_24_background.xml
similarity index 61%
copy from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
copy to car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/drawable/radius_24_background.xml
index a6ac85e..862f6a7 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/drawable/radius_24_background.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,10 +14,7 @@
~ limitations under the License.
-->
-<ImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/statusbar_sensor_text_width"
- android:layout_height="@*android:dimen/status_bar_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:tag="@string/qc_icon_tag"/>
\ No newline at end of file
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="24dp"/>
+</shape>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/drawable/right_half_pill_button_shape.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/drawable/right_half_pill_button_shape.xml
new file mode 100644
index 0000000..ac8a27f
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/drawable/right_half_pill_button_shape.xml
@@ -0,0 +1,23 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners
+ android:bottomRightRadius="@dimen/control_bar_media_card_half_pill_button_radius"
+ android:topRightRadius="@dimen/control_bar_media_card_half_pill_button_radius" />
+ <solid android:color="@color/car_surface_variant" />
+</shape>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/car_ui_portrait_launcher.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/car_ui_portrait_launcher.xml
index 61bbbf5..8888fe7 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/car_ui_portrait_launcher.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/car_ui_portrait_launcher.xml
@@ -19,7 +19,7 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/car_under_surface"
+ android:background="@color/car_shadow"
android:clipToPadding="false"
android:clipChildren="false">
@@ -28,8 +28,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- <include layout="@layout/car_ui_portrait_panel" android:id="@+id/app_grid_panel"/>
-
<include layout="@layout/car_ui_portrait_panel" android:id="@+id/application_panel"/>
<LinearLayout
@@ -40,7 +38,6 @@
android:background="@drawable/control_bar_background"
android:layout_gravity="bottom"
android:gravity="bottom"
- android:elevation="@dimen/panel_elevation"
android:orientation="vertical">
<FrameLayout
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/car_ui_portrait_toolbar.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/car_ui_portrait_toolbar.xml
index 9c16a1d..7102737 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/car_ui_portrait_toolbar.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/car_ui_portrait_toolbar.xml
@@ -20,7 +20,7 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="@*android:dimen/status_bar_height"
- android:background="@color/car_under_surface"
+ android:background="@color/car_shadow"
android:visibility="gone">
<ImageView
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/control_bar_media_card.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/control_bar_media_card.xml
new file mode 100644
index 0000000..09a6836
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/control_bar_media_card.xml
@@ -0,0 +1,428 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<androidx.cardview.widget.CardView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/control_bar_media_card_height"
+ android:layout_gravity="center_horizontal|bottom"
+ app:cardBackgroundColor="@color/car_surface"
+ app:cardCornerRadius="@dimen/control_bar_media_card_radius"
+ app:cardElevation="0dp">
+
+ <androidx.constraintlayout.motion.widget.MotionLayout
+ android:id="@+id/control_bar_media_card_motion_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/control_bar_media_card_padding"
+ app:layoutDescription="@xml/control_bar_media_card_queue_history_motion_scene">
+
+ <ImageButton
+ android:id="@+id/play_pause_button"
+ android:layout_width="@dimen/control_bar_media_card_pill_button_width"
+ android:layout_height="@dimen/media_card_large_button_size"
+ android:src="@drawable/ic_play_pause_selector"
+ android:tint="@color/car_surface"
+ android:scaleType="center"
+ android:background="@drawable/pill_button_shape"
+ android:backgroundTint="@color/car_on_surface"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+ <ImageButton
+ android:id="@+id/playback_action_id1"
+ android:layout_width="@dimen/control_bar_media_card_half_pill_button_width"
+ android:layout_height="@dimen/media_card_large_button_size"
+ android:layout_marginStart="@dimen/media_card_view_separation_margin"
+ android:background="@drawable/left_half_pill_button_shape"
+ android:src="@drawable/ic_skip_previous"
+ android:tint="@color/car_on_surface"
+ app:layout_constraintStart_toEndOf="@id/play_pause_button"
+ app:layout_constraintBottom_toBottomOf="@id/play_pause_button"
+ app:layout_constraintTop_toTopOf="@id/play_pause_button" />
+
+ <ImageButton
+ android:id="@+id/playback_action_id2"
+ android:layout_width="@dimen/control_bar_media_card_half_pill_button_width"
+ android:layout_height="@dimen/media_card_large_button_size"
+ android:layout_marginStart="4dp"
+ android:background="@drawable/right_half_pill_button_shape"
+ android:src="@drawable/ic_skip_next"
+ android:tint="@color/car_on_surface"
+ app:layout_goneMarginStart="@dimen/media_card_view_separation_margin"
+ app:layout_constraintStart_toEndOf="@id/playback_action_id1"
+ app:layout_constraintBottom_toBottomOf="@id/play_pause_button"
+ app:layout_constraintTop_toTopOf="@id/play_pause_button" />
+
+ <ImageButton
+ android:id="@+id/queue_button"
+ android:layout_width="@dimen/media_card_large_button_size"
+ android:layout_height="@dimen/media_card_large_button_size"
+ android:layout_marginEnd="@dimen/control_bar_media_card_view_end_margin"
+ android:src="@drawable/ic_queue"
+ android:tint="@color/control_bar_media_card_panel_button_foreground"
+ android:background="@drawable/dark_circle_button_background"
+ android:backgroundTint="@color/control_bar_media_card_panel_button_background"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintEnd_toStartOf="@id/history_button"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+
+ <ImageButton
+ android:id="@+id/history_button"
+ android:layout_width="@dimen/media_card_large_button_size"
+ android:layout_height="@dimen/media_card_large_button_size"
+ android:layout_marginEnd="@dimen/media_card_view_separation_margin"
+ android:src="@drawable/ic_history"
+ android:tint="@color/control_bar_media_card_panel_button_foreground"
+ android:background="@drawable/dark_circle_button_background"
+ android:backgroundTint="@color/control_bar_media_card_panel_button_background"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+ <ImageView
+ android:id="@+id/media_widget_app_icon"
+ android:layout_width="@dimen/control_bar_media_card_app_icon_size"
+ android:layout_height="@dimen/control_bar_media_card_app_icon_size"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <ImageView
+ android:id="@+id/album_art"
+ android:layout_width="@dimen/control_bar_media_card_album_art_size"
+ android:layout_height="@dimen/control_bar_media_card_album_art_size"
+ android:background="@drawable/radius_24_background"
+ android:clipToOutline="true"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/guideline"
+ app:layout_constraintGuide_end="@dimen/control_bar_media_card_album_art_size"
+ android:orientation="vertical"/>
+
+ <com.android.car.media.common.ContentFormatView
+ android:id="@+id/content_format"
+ android:layout_width="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_height="@dimen/control_bar_media_card_custom_action_size"
+ android:background="@android:color/transparent"
+ android:layout_marginTop="@dimen/control_bar_media_card_view_separation_margin"
+ android:layout_marginEnd="@dimen/control_bar_media_card_view_end_margin"
+ app:logoSize="small"
+ app:logoTint="@color/car_on_surface_variant"
+ app:layout_constraintTop_toBottomOf="@id/title"
+ app:layout_constraintEnd_toStartOf="@id/guideline" />
+
+ <LinearLayout
+ android:id="@+id/custom_action_container"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/media_card_large_button_size"
+ android:layout_marginEnd="@dimen/control_bar_media_card_view_end_margin"
+ android:layout_marginStart="@dimen/media_card_view_separation_margin"
+ android:paddingStart="@dimen/control_bar_media_card_custom_action_layout_horizontal_padding"
+ android:paddingEnd="@dimen/control_bar_media_card_custom_action_layout_horizontal_padding"
+ app:layout_constraintBottom_toBottomOf="@id/play_pause_button"
+ app:layout_constraintStart_toEndOf="@id/playback_action_id2"
+ app:layout_constraintEnd_toStartOf="@id/queue_button"
+ app:layout_constraintTop_toTopOf="@id/play_pause_button"
+ app:layout_constraintHorizontal_bias="1">
+
+ <ImageButton
+ android:id="@+id/playback_action_id3"
+ android:layout_width="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_height="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_gravity="center_vertical"
+ style="@style/ControlBarCustomActionStyle" />
+
+ <ImageButton
+ android:id="@+id/playback_action_id4"
+ android:layout_width="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_height="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_gravity="center_vertical"
+ style="@style/ControlBarCustomActionStyle"
+ android:layout_marginStart="@dimen/control_bar_media_card_custom_action_spacing"/>
+
+ <ImageButton
+ android:id="@+id/playback_action_id5"
+ android:layout_width="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_height="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_gravity="center_vertical"
+ style="@style/ControlBarCustomActionStyle"
+ android:layout_marginStart="@dimen/control_bar_media_card_custom_action_spacing"/>
+
+ <ImageButton
+ android:id="@+id/playback_action_id6"
+ android:layout_width="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_height="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_gravity="center_vertical"
+ style="@style/ControlBarCustomActionStyle"
+ android:layout_marginStart="@dimen/control_bar_media_card_custom_action_spacing"/>
+
+ <ImageButton
+ android:id="@+id/playback_action_id7"
+ android:layout_width="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_height="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_gravity="center_vertical"
+ style="@style/ControlBarCustomActionStyle"
+ android:layout_marginStart="@dimen/control_bar_media_card_custom_action_spacing"/>
+
+ <ImageButton
+ android:id="@+id/playback_action_id8"
+ android:layout_width="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_height="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_gravity="center_vertical"
+ style="@style/ControlBarCustomActionStyle"
+ android:layout_marginStart="@dimen/control_bar_media_card_custom_action_spacing"/>
+
+ <ImageButton
+ android:id="@+id/overflow_button"
+ android:layout_width="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_height="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_gravity="center_vertical"
+ style="@style/ControlBarOverflowButtonStyle"
+ android:layout_marginStart="@dimen/control_bar_media_card_custom_action_spacing"
+ android:src="@drawable/ic_overflow_horizontal" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/custom_action_overflow_container"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/media_card_large_button_size"
+ android:layout_marginEnd="@dimen/control_bar_media_card_view_end_margin"
+ android:layout_marginStart="@dimen/media_card_view_separation_margin"
+ android:paddingStart="@dimen/control_bar_media_card_custom_action_layout_horizontal_padding"
+ android:paddingEnd="@dimen/control_bar_media_card_custom_action_layout_horizontal_padding"
+ android:background="@drawable/pill_button_shape"
+ android:backgroundTint="@color/car_on_surface_variant"
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="@id/play_pause_button"
+ app:layout_constraintStart_toEndOf="@id/playback_action_id2"
+ app:layout_constraintEnd_toStartOf="@id/queue_button"
+ app:layout_constraintTop_toTopOf="@id/play_pause_button"
+ app:layout_constraintHorizontal_bias="1">
+
+ <ImageButton
+ android:id="@+id/playback_action_id9"
+ android:layout_width="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_height="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_gravity="center_vertical"
+ style="@style/ControlBarCustomActionOverflowStyle"/>
+
+ <ImageButton
+ android:id="@+id/playback_action_id10"
+ android:layout_width="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_height="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_gravity="center_vertical"
+ style="@style/ControlBarCustomActionOverflowStyle"
+ android:layout_marginStart="@dimen/control_bar_media_card_custom_action_spacing" />
+
+ <ImageButton
+ android:id="@+id/playback_action_id11"
+ android:layout_width="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_height="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_gravity="center_vertical"
+ style="@style/ControlBarCustomActionOverflowStyle"
+ android:layout_marginStart="@dimen/control_bar_media_card_custom_action_spacing" />
+
+ <ImageButton
+ android:id="@+id/overflow_exit_button"
+ android:layout_width="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_height="@dimen/control_bar_media_card_custom_action_size"
+ android:layout_gravity="center_vertical"
+ style="@style/ControlBarOverflowExitButtonStyle"
+ android:layout_marginStart="@dimen/control_bar_media_card_custom_action_spacing"
+ android:src="@drawable/ic_cancel" />
+ </LinearLayout>
+
+ <SeekBar
+ android:id="@+id/playback_seek_bar"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/control_bar_media_card_seek_bar_height"
+ android:layout_marginBottom="@dimen/media_card_view_separation_margin"
+ android:layout_marginEnd="@dimen/control_bar_media_card_view_end_margin"
+ android:paddingEnd="0dp"
+ android:paddingStart="0dp"
+ android:progressBackgroundTint="@color/car_on_surface_variant"
+ android:progressDrawable="@drawable/media_card_seekbar_progress"
+ android:progressTint="@color/car_on_surface"
+ android:splitTrack="true"
+ android:thumb="@drawable/media_card_seekbar_thumb"
+ android:thumbTint="@color/car_on_surface"
+ android:thumbOffset="0px"
+ android:clickable="true"
+ android:focusable="true"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/play_pause_button"
+ app:layout_constraintEnd_toStartOf="@id/current_time"
+ app:layout_constraintHorizontal_bias="0" />
+
+ <TextView
+ android:id="@+id/current_time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/car_on_surface"
+ android:textAppearance="@style/TextAppearance.Car.Sub.Large"
+ app:layout_constraintStart_toEndOf="@id/playback_seek_bar"
+ app:layout_constraintEnd_toStartOf="@id/inner_separator"
+ app:layout_constraintTop_toTopOf="@id/max_time"
+ app:layout_constraintBottom_toBottomOf="@id/max_time"/>
+
+ <TextView
+ android:id="@+id/inner_separator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/car_on_surface_variant"
+ android:text="@string/times_separator"
+ android:textAppearance="@style/TextAppearance.Car.Sub.Large"
+ app:layout_constraintStart_toEndOf="@id/current_time"
+ app:layout_constraintEnd_toStartOf="@id/max_time"
+ app:layout_constraintTop_toTopOf="@id/max_time"
+ app:layout_constraintBottom_toBottomOf="@id/max_time" />
+
+ <TextView
+ android:id="@+id/max_time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/control_bar_media_card_view_end_margin"
+ android:textColor="@color/car_on_surface_variant"
+ android:textAppearance="@style/TextAppearance.Car.Sub.Large"
+ app:layout_constraintStart_toEndOf="@id/inner_separator"
+ app:layout_constraintEnd_toStartOf="@id/guideline"
+ app:layout_constraintTop_toTopOf="@id/playback_seek_bar"
+ app:layout_constraintBottom_toBottomOf="@id/playback_seek_bar"
+ app:layout_constraintHorizontal_bias="1"/>
+
+ <TextView
+ android:id="@+id/album_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/media_card_view_separation_margin"
+ android:layout_marginEnd="@dimen/control_bar_media_card_view_end_margin"
+ android:gravity="center_vertical"
+ android:includeFontPadding="false"
+ android:textColor="@color/car_on_surface_variant"
+ android:textAppearance="@style/TextAppearance.Car.Sub.Large"
+ android:maxLines="1"
+ android:ellipsize="end"
+ app:layout_constraintStart_toEndOf="@id/media_widget_app_icon"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/guideline"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <TextView
+ android:id="@id/title"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/control_bar_media_card_title_height"
+ android:layout_marginTop="@dimen/control_bar_media_card_view_separation_margin"
+ android:layout_marginEnd="@dimen/control_bar_media_card_view_end_margin"
+ android:gravity="center_vertical"
+ android:textColor="@color/car_on_surface"
+ android:maxLines="1"
+ android:ellipsize="end"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/media_widget_app_icon"
+ app:layout_constraintEnd_toStartOf="@id/guideline"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/control_bar_media_card_view_separation_margin"
+ android:layout_marginEnd="@dimen/control_bar_media_card_view_end_margin"
+ android:gravity="center_vertical"
+ android:includeFontPadding="false"
+ android:textColor="@color/car_on_surface_variant"
+ android:textAppearance="@style/TextAppearance.Car.Body.Medium"
+ android:maxLines="1"
+ android:ellipsize="end"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/title"
+ app:layout_constraintEnd_toStartOf="@id/guideline"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <FrameLayout
+ android:id="@+id/control_bar_media_card_queue_history_panel_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginBottom="@dimen/control_bar_media_card_queue_margin"
+ app:layout_constraintStart_toEndOf="@id/album_art"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/play_pause_button"
+ app:layout_constrainedHeight="true"
+ app:layout_constrainedWidth="true">
+ <LinearLayout
+ android:id="@+id/queue_list_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/queue_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:includeFontPadding="false"
+ android:text="@string/media_card_queue_header_title"
+ android:maxLines="1"
+ android:drawableStart="@drawable/ic_queue"
+ android:drawableTint="@color/car_on_surface"
+ android:drawablePadding="@dimen/media_card_view_separation_margin"
+ android:textAppearance="@style/TextAppearance.Car.Body.Medium"
+ android:textColor="@color/car_on_surface" />
+ <com.android.car.apps.common.CarUiRecyclerViewNoScrollbar
+ android:id="@+id/queue_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/media_card_view_separation_margin"
+ android:orientation="horizontal"
+ android:requiresFadingEdge="horizontal"
+ android:fadingEdgeLength="@dimen/media_card_recycler_view_fading_edge_length"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
+ </LinearLayout>
+ <LinearLayout
+ android:id="@+id/history_list_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/history_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:includeFontPadding="false"
+ android:text="@string/media_card_history_header_title"
+ android:maxLines="1"
+ android:drawableStart="@drawable/ic_history"
+ android:drawableTint="@color/car_on_surface"
+ android:drawablePadding="@dimen/media_card_view_separation_margin"
+ android:textAppearance="@style/TextAppearance.Car.Body.Medium"
+ android:textColor="@color/car_on_surface" />
+ <com.android.car.apps.common.CarUiRecyclerViewNoScrollbar
+ android:id="@+id/history_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/media_card_view_separation_margin"
+ android:orientation="horizontal"
+ android:requiresFadingEdge="horizontal"
+ android:fadingEdgeLength="@dimen/media_card_recycler_view_fading_edge_length"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
+ </LinearLayout>
+ </FrameLayout>
+
+ </androidx.constraintlayout.motion.widget.MotionLayout>
+</androidx.cardview.widget.CardView>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/control_bar_media_history_item.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/control_bar_media_history_item.xml
new file mode 100644
index 0000000..71fcb46
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/control_bar_media_history_item.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<androidx.cardview.widget.CardView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginEnd="@dimen/media_card_view_separation_margin"
+ app:cardBackgroundColor="@color/car_surface_variant"
+ app:cardCornerRadius="@dimen/control_bar_media_card_item_card_radius"
+ app:cardElevation="0dp">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/history_card_container_active"
+ android:layout_height="match_parent"
+ android:layout_width="@dimen/control_bar_media_card_history_item_width_active">
+
+ <ImageView
+ android:id="@+id/history_card_album_art"
+ android:layout_width="@dimen/control_bar_media_card_history_main_icon_size"
+ android:layout_height="@dimen/control_bar_media_card_history_main_icon_size"
+ android:background="@drawable/radius_24_background"
+ android:clipToOutline="true"
+ android:scaleType="centerCrop"
+ android:layout_marginTop="@dimen/control_bar_media_card_history_item_margin_small"
+ android:layout_marginStart="@dimen/control_bar_media_card_history_item_margin_small"
+ android:layout_marginEnd="@dimen/control_bar_media_card_history_item_margin_small"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/history_card_title_active"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ android:id="@id/history_card_title_active"
+ android:layout_height="0dp"
+ android:layout_width="0dp"
+ android:textAppearance="@style/TextAppearance.Car.Body.Small"
+ android:textColor="@color/car_on_surface"
+ android:maxLines="3"
+ android:ellipsize="end"
+ android:layout_marginStart="@dimen/control_bar_media_card_history_item_margin_large"
+ app:layout_constraintTop_toTopOf="@id/history_card_album_art"
+ app:layout_constraintBottom_toBottomOf="@id/history_card_album_art"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/history_card_album_art"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <TextView
+ android:id="@+id/history_card_subtitle_active"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:textAppearance="@style/TextAppearance.Car.Body.Small"
+ android:textColor="@color/car_on_surface_variant"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_marginStart="@dimen/control_bar_media_card_history_item_margin_large"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/history_card_album_art"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/history_card_album_art"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <ImageView
+ android:id="@+id/history_card_app_thumbnail"
+ android:layout_width="@dimen/control_bar_media_card_history_thumbnail_size"
+ android:layout_height="@dimen/control_bar_media_card_history_thumbnail_size"
+ app:layout_constraintTop_toBottomOf="@id/history_card_album_art"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="@id/history_card_album_art"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/history_card_container_inactive"
+ android:layout_height="match_parent"
+ android:layout_width="@dimen/control_bar_media_card_history_item_width_inactive">
+
+ <ImageView
+ android:id="@+id/history_item_app_icon_inactive"
+ android:layout_width="@dimen/control_bar_media_card_history_main_icon_size"
+ android:layout_height="@dimen/control_bar_media_card_history_main_icon_size"
+ android:scaleType="centerCrop"
+ android:layout_marginTop="@dimen/control_bar_media_card_history_item_margin_small"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ android:id="@+id/history_card_app_title_inactive"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:gravity="center"
+ android:textAppearance="@style/TextAppearance.Car.Body.Small"
+ android:textColor="@color/car_on_surface"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_marginStart="@dimen/control_bar_media_card_history_item_margin_small"
+ android:layout_marginEnd="@dimen/control_bar_media_card_history_item_margin_small"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/history_item_app_icon_inactive"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="0"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.cardview.widget.CardView>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/control_bar_media_queue_item.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/control_bar_media_queue_item.xml
new file mode 100644
index 0000000..ee9c7c3
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/layout/control_bar_media_queue_item.xml
@@ -0,0 +1,84 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<androidx.cardview.widget.CardView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="@dimen/control_bar_media_card_queue_item_card_width"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ app:cardBackgroundColor="@color/car_surface_variant"
+ app:cardCornerRadius="@dimen/control_bar_media_card_item_card_radius"
+ app:cardElevation="0dp"
+ android:layout_marginEnd="@dimen/media_card_view_separation_margin">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:padding="@dimen/media_card_view_separation_margin">
+
+ <TextView
+ android:id="@+id/queue_list_item_title"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:textAppearance="@style/TextAppearance.Car.Body.Small"
+ android:textColor="@color/car_on_surface"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_marginEnd="@dimen/media_card_view_separation_margin"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/thumbnail"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <TextView
+ android:id="@+id/queue_list_item_subtitle"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:textAppearance="@style/TextAppearance.Car.Body.Small"
+ android:textColor="@color/car_on_surface_variant"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_marginEnd="@dimen/media_card_view_separation_margin"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/max_time"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <ImageView
+ android:id="@+id/thumbnail"
+ android:layout_width="@dimen/control_bar_media_card_queue_item_thumbnail_size"
+ android:layout_height="@dimen/control_bar_media_card_queue_item_thumbnail_size"
+ android:background="@drawable/radius_8_background"
+ android:clipToOutline="true"
+ android:scaleType="centerCrop"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toEndOf="@id/queue_list_item_title"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="1" />
+
+ <TextView
+ android:id="@+id/max_time"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textAppearance="@style/TextAppearance.Car.Body.Small"
+ android:textColor="@color/car_on_surface_variant"
+ android:maxLines="1"
+ android:ellipsize="end"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="1" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</androidx.cardview.widget.CardView>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/config.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/config.xml
index fea722c..db8533a 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/config.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/config.xml
@@ -42,7 +42,7 @@
<!-- A list of package names that provide the cards to display on the home screen -->
<string-array name="config_homeCardModuleClasses" translatable="false">
- <item>com.android.car.carlauncher.homescreen.audio.AudioCardModule</item>
+ <item>com.android.car.portraitlauncher.controlbar.ControlBarModule</item>
</string-array>
<!--
@@ -50,7 +50,6 @@
-->
<string-array name="config_fullScreenActivities" translatable="false">
<!-- CTS test activities that pass when fullscreen -->
- <item>android.assist.cts/android.assist.cts.TestStartActivity</item>
<item>android.widget.cts/android.widget.cts.ListViewCtsActivity</item>
<item>android.widget.cts/android.widget.cts.TextViewCtsActivity</item>
<item>android.widget.cts/android.widget.cts.RelativeLayoutCtsActivity</item>
@@ -78,9 +77,9 @@
<item>android.server.wm.app/android.server.wm.app.AssistantActivity</item>
<item>android.server.wm.app/android.server.wm.app.AnimationTestActivity</item>
<!-- Add activity to full screen since focus is lost when all the windows are removed. -->
- <item>android.server.wm.cts/android.server.wm.window.LayoutTests$TestActivity</item>
+ <item>android.server.wm.cts/android.server.wm.animations.LayoutTests$TestActivity</item>
<!-- Add activity to full screen due to some issue with focus. -->
- <item>android.server.wm.cts/android.server.wm.window.WindowUntrustedTouchTest$TestActivity</item>
+ <item>android.server.wm.cts/android.server.wm.input.WindowUntrustedTouchTest$TestActivity</item>
<!-- Add activity to full screen due to slow animations -->
<item>android.server.wm.cts/android.server.wm.display.DisplayHashManagerTest$TestActivity</item>
<!-- TODO(b/308859591): Remove activities from full screen config once proper solution
@@ -88,14 +87,23 @@
<!-- START: Add activities to full screen since the panel open animation interferes with the
screenshot which is taken in the test. This is likely due to the core transitions being
completed right away but surface animations taking time to complete. -->
- <item>android.server.wm.cts/android.server.wm.activity.ActivityTransitionTests$LauncherActivity</item>
- <item>android.server.wm.cts/android.server.wm.activity.ActivityTransitionTests$EdgeExtensionActivity</item>
- <item>android.server.wm.cts/android.server.wm.activity.ActivityTransitionTests$CustomWindowAnimationActivity</item>
- <item>android.server.wm.cts/android.server.wm.activity.ActivityTransitionTests$TransitionActivity</item>
+ <item>android.server.wm.cts/android.server.wm.animations.ActivityTransitionTests$LauncherActivity</item>
+ <item>android.server.wm.cts/android.server.wm.animations.ActivityTransitionTests$EdgeExtensionActivity</item>
+ <item>android.server.wm.cts/android.server.wm.animations.ActivityTransitionTests$CustomWindowAnimationActivity</item>
+ <item>android.server.wm.cts/android.server.wm.animations.ActivityTransitionTests$TransitionActivity</item>
<!-- END -->
</string-array>
<!-- Calm mode overrides -->
- <string name="config_calmMode_componentName">com.android.car.portraitlauncher/com.android.car.portraitlauncher.calmmode.CalmModeActivity</string>
+ <string name="config_calmMode_componentName">com.android.car.portraitlauncher/com.android.car.portraitlauncher.calmmode.PortraitCalmModeActivity</string>
+ <!--
+ Config for allowing NDO apps to be opened while driving if they contain an active media
+ session
+ TODO(b/310687870): Add RB to testing plan
+ -->
+ <bool name="config_enableMediaSessionAppsWhileDriving">false</bool>
+
+ <!-- Boolean value to indicate if the recents activity should open the most recent task on dismiss. -->
+ <bool name="config_launch_most_recent_task_on_recents_dismiss">false</bool>
</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/dimens.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/dimens.xml
index 6053cd2..c7a910d 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/dimens.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/dimens.xml
@@ -36,4 +36,37 @@
<dimen name="calm_mode_clock_translationY">100dp</dimen>
<dimen name="calm_mode_clock_media_title_margin">100dp</dimen>
+ <!-- Control bar dimens -->
+ <dimen name="control_bar_media_card_height">408dp</dimen>
+ <dimen name="control_bar_media_card_radius">48dp</dimen>
+ <dimen name="control_bar_media_card_padding">32dp</dimen>
+ <dimen name="control_bar_media_card_view_end_margin">48dp</dimen>
+ <dimen name="control_bar_media_card_icon_size">48dp</dimen>
+ <dimen name="control_bar_media_card_custom_action_spacing">72dp</dimen>
+ <dimen name="control_bar_media_card_custom_action_size">40dp</dimen>
+ <dimen name="control_bar_media_card_seek_bar_height">56dp</dimen>
+ <dimen name="control_bar_media_card_view_separation_margin">32dp</dimen>
+ <dimen name="control_bar_media_card_half_pill_button_width">120dp</dimen>
+ <dimen name="control_bar_media_card_half_pill_button_radius">120dp</dimen>
+ <dimen name="control_bar_media_card_pill_button_width">160dp</dimen>
+ <dimen name="control_bar_media_card_app_icon_size">26dp</dimen>
+ <dimen name="control_bar_media_card_album_art_size">240dp</dimen>
+ <item name="control_bar_media_card_album_art_drawable_corner_ratio" format="float" type="dimen">0.1</item>
+ <dimen name="control_bar_media_card_custom_action_layout_horizontal_padding">38dp</dimen>
+ <dimen name="control_bar_media_card_item_card_radius">16dp</dimen>
+ <dimen name="control_bar_media_card_queue_item_thumbnail_size">112dp</dimen>
+ <dimen name="control_bar_media_card_queue_margin">24dp</dimen>
+ <dimen name="control_bar_media_card_queue_item_card_width">320dp</dimen>
+ <dimen name="control_bar_media_card_title_height">56dp</dimen>
+ <dimen name="control_bar_media_card_title_default_text_size">44sp</dimen>
+ <dimen name="control_bar_media_card_title_animation_height">40dp</dimen>
+ <dimen name="control_bar_media_card_history_main_icon_size">112dp</dimen>
+ <dimen name="control_bar_media_card_history_title_size">145dp</dimen>
+ <dimen name="control_bar_media_card_history_subtitle_size">128dp</dimen>
+ <dimen name="control_bar_media_card_history_thumbnail_size">30dp</dimen>
+ <dimen name="control_bar_media_card_history_item_margin_small">12dp</dimen>
+ <dimen name="control_bar_media_card_history_item_margin_large">24dp</dimen>
+ <dimen name="control_bar_media_card_history_item_width_active">316dp</dimen>
+ <dimen name="control_bar_media_card_history_item_width_inactive">160dp</dimen>
+
</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/integers.xml
similarity index 61%
copy from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
copy to car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/integers.xml
index a6ac85e..427fa50 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/integers.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,10 +14,7 @@
~ limitations under the License.
-->
-<ImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/statusbar_sensor_text_width"
- android:layout_height="@*android:dimen/status_bar_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:tag="@string/qc_icon_tag"/>
\ No newline at end of file
+<resources>
+ <!-- Control bar media card panel animation duration in ms -->
+ <integer name="control_bar_side_panel_animation_duration">400</integer>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/strings.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/strings.xml
index 4e75e6b..740270e 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/strings.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/strings.xml
@@ -29,8 +29,8 @@
<string name="audio_route_phone_speaker">Phone speaker</string>
<!-- Label for routing phone audio to the phone earpiece. [CHAR LIMIT=30] -->
<string name="audio_route_handset">Phone</string>
- <!-- AutoEnhance back button [CHAR LIMIT=30] -->
+ <!-- DisplayCompat back button [CHAR LIMIT=30] -->
<string name="back">Back</string>
- <!-- AutoEnhance fullscreen button [CHAR LIMIT=30] -->
+ <!-- DisplayCompat fullscreen button [CHAR LIMIT=30] -->
<string name="fullscreen">Full Screen</string>
</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/styles.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/styles.xml
index dfb7318..1e9789e 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/styles.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/values/styles.xml
@@ -28,4 +28,22 @@
<style name="CalmModeText.MediaTitle">
<item name="android:maxWidth">800dp</item>
</style>
+ <style name="ControlBarCustomActionStyle">
+ <item name="android:tint">@color/car_on_surface_variant</item>
+ <item name="android:scaleType">fitCenter</item>
+ <item name="android:background">@android:color/transparent</item>
+ <item name="android:visibility">gone</item>
+ </style>
+ <style name="ControlBarOverflowButtonStyle">
+ <item name="android:tint">@color/car_on_surface_variant</item>
+ <item name="android:scaleType">fitCenter</item>
+ <item name="android:background">@android:color/transparent</item>
+ <item name="android:visibility">gone</item>
+ </style>
+ <style name="ControlBarCustomActionOverflowStyle" parent="ControlBarCustomActionStyle">
+ <item name="android:tint">@color/car_surface_variant</item>
+ </style>
+ <style name="ControlBarOverflowExitButtonStyle" parent="ControlBarCustomActionStyle">
+ <item name="android:tint">@color/car_surface_variant</item>
+ </style>
</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/xml/control_bar_media_card_queue_history_motion_scene.xml b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/xml/control_bar_media_card_queue_history_motion_scene.xml
new file mode 100644
index 0000000..30d3a59
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/res/xml/control_bar_media_card_queue_history_motion_scene.xml
@@ -0,0 +1,238 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<MotionScene
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:motion="http://schemas.android.com/apk/res-auto">
+
+ <Transition
+ motion:constraintSetEnd="@id/end"
+ motion:constraintSetStart="@id/start"
+ motion:duration="@integer/control_bar_side_panel_animation_duration"
+ motion:motionInterpolator="standard">
+ <KeyFrameSet>
+ </KeyFrameSet>
+ </Transition>
+
+ <ConstraintSet
+ android:id="@+id/start">
+ <ConstraintOverride
+ android:id="@id/title">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ <CustomAttribute
+ motion:attributeName="textSize"
+ motion:customDimension="@dimen/control_bar_media_card_title_default_text_size" />
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/playback_seek_bar">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@+id/album_art">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/custom_action_container">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/custom_action_overflow_container">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/media_widget_app_icon">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/subtitle">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/album_title">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/current_time">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/max_time">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/inner_separator">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/content_format">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/playback_action_id1">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/playback_action_id2">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ </ConstraintSet>
+
+ <ConstraintSet
+ android:id="@+id/end">
+ <Constraint
+ android:id="@id/title"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/control_bar_media_card_title_animation_height"
+ android:layout_marginStart="@dimen/control_bar_media_card_view_end_margin"
+ android:layout_marginTop="@dimen/control_bar_media_card_view_separation_margin"
+ android:layout_marginEnd="@dimen/control_bar_media_card_view_end_margin"
+ motion:layout_goneMarginBottom="@dimen/media_card_view_separation_margin"
+ android:gravity="center_vertical"
+ android:textColor="@color/car_on_surface"
+ android:maxLines="1"
+ android:ellipsize="end"
+ motion:layout_constraintStart_toEndOf="@id/playback_action_id2"
+ motion:layout_constraintTop_toTopOf="@id/play_pause_button"
+ motion:layout_constraintBottom_toTopOf="@id/playback_seek_bar"
+ motion:layout_constraintEnd_toStartOf="@id/guideline"
+ motion:layout_constraintHorizontal_bias="0">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ <CustomAttribute
+ motion:attributeName="lineHeight"
+ motion:customDimension="@dimen/media_card_title_animated_line_height" />
+ <CustomAttribute
+ motion:attributeName="textSize"
+ motion:customDimension="@dimen/media_card_title_animated_text_size" />
+ </Constraint>
+ <Constraint
+ android:id="@+id/playback_seek_bar"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/control_bar_media_card_seek_bar_height"
+ android:layout_marginBottom="@dimen/media_card_view_separation_margin"
+ android:layout_marginEnd="0dp"
+ android:clickable="false"
+ android:paddingEnd="0dp"
+ android:paddingStart="0dp"
+ android:progressBackgroundTint="@color/car_on_surface_variant"
+ android:progressDrawable="@drawable/media_card_seekbar_progress"
+ android:progressTint="@color/car_on_surface"
+ android:splitTrack="true"
+ android:thumb="@drawable/media_card_seekbar_thumb"
+ android:thumbTint="@color/car_on_surface"
+ android:thumbOffset="0px"
+ motion:layout_constraintStart_toStartOf="@id/title"
+ motion:layout_constraintEnd_toEndOf="@id/title"
+ motion:layout_constraintTop_toBottomOf="@id/title"
+ motion:layout_constraintBottom_toBottomOf="@id/play_pause_button"
+ motion:layout_constraintHorizontal_bias="0">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </Constraint>
+ <Constraint
+ android:id="@+id/album_art"
+ android:layout_width="@dimen/control_bar_media_card_album_art_size"
+ android:layout_height="@dimen/control_bar_media_card_album_art_size"
+ android:layout_marginEnd="@dimen/control_bar_media_card_queue_margin"
+ android:background="@drawable/radius_24_background"
+ android:clipToOutline="true"
+ motion:layout_constraintStart_toStartOf="parent"
+ motion:layout_constraintTop_toTopOf="parent"
+ motion:layout_constraintEnd_toStartOf="@id/control_bar_media_card_queue_history_panel_container"
+ motion:layout_constraintHorizontal_bias="0">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </Constraint>
+ <ConstraintOverride
+ android:id="@id/custom_action_container"
+ android:alpha="0">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/custom_action_overflow_container"
+ android:alpha="0">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/media_widget_app_icon"
+ android:alpha="0">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/subtitle"
+ android:alpha="0">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/album_title"
+ android:alpha="0">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/current_time"
+ android:alpha="0">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/max_time"
+ android:alpha="0">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/inner_separator"
+ android:alpha="0">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/content_format"
+ android:alpha="0">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/playback_action_id1">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ <ConstraintOverride
+ android:id="@id/playback_action_id2">
+ <PropertySet
+ motion:visibilityMode="ignore"/>
+ </ConstraintOverride>
+ </ConstraintSet>
+</MotionScene>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/calmmode/CalmModeActivity.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/calmmode/CalmModeActivity.java
deleted file mode 100644
index adb1e71..0000000
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/calmmode/CalmModeActivity.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.portraitlauncher.calmmode;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Insets;
-import android.os.Build;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowInsets;
-
-import androidx.annotation.Nullable;
-import androidx.fragment.app.FragmentActivity;
-
-import com.android.car.portraitlauncher.R;
-
-public class CalmModeActivity extends FragmentActivity {
- private static final String TAG = CalmModeActivity.class.getSimpleName();
- private static final boolean DEBUG = Build.isDebuggable();
-
- private final BroadcastReceiver mCloseSystemDialogsReceiver =
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
- if (DEBUG) {
- Log.v(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS broadcast:"
- + " intent=" + intent + ", user=" + context.getUser());
- }
- finish();
- return;
- }
- if (DEBUG) {
- Log.w(TAG, "Unexpected intent " + intent);
- }
- }
- };
-
- private final View.OnApplyWindowInsetsListener mOnApplyWindowInsetsListener =
- (v, insets) -> {
- int insetTypes = WindowInsets.Type.systemBars();
- Insets appliedInsets = insets.getInsets(insetTypes);
- v.setPadding(
- appliedInsets.left, appliedInsets.top,
- appliedInsets.right, /* bottom= */ 0);
- return insets.inset(appliedInsets);
- };
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getWindow().setDecorFitsSystemWindows(/* decorFitsSystemWindows= */ false);
- setContentView(R.layout.calm_mode_fragment);
- getWindow()
- .getDecorView()
- .getRootView()
- .setOnApplyWindowInsetsListener(mOnApplyWindowInsetsListener);
- registerBroadcastReceiver();
- setContentView(R.layout.calm_mode_activity);
- }
-
- private void registerBroadcastReceiver() {
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- registerReceiver(mCloseSystemDialogsReceiver, filter, /* broadcastPermission= */ null,
- /* scheduler= */ null, Context.RECEIVER_EXPORTED);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- unregisterReceiver(mCloseSystemDialogsReceiver);
- }
-}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/calmmode/PortraitCalmModeActivity.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/calmmode/PortraitCalmModeActivity.java
new file mode 100644
index 0000000..258a72f
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/calmmode/PortraitCalmModeActivity.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.portraitlauncher.calmmode;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Insets;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.car.portraitlauncher.R;
+
+// TODO(b/329713280): Add portrait-specific tests
+public class PortraitCalmModeActivity extends FragmentActivity {
+ private static final String TAG = PortraitCalmModeActivity.class.getSimpleName();
+ private static final boolean DEBUG = Build.isDebuggable();
+ private static final String APP_PACKAGE_NAME = "com.android.car.portraitlauncher";
+ public static final String INTENT_ACTION_DISMISS_CALM_MODE =
+ APP_PACKAGE_NAME + ".ACTION_DISMISS_CALM_MODE";
+ public static final Intent INTENT_DISMISS_CALM_MODE;
+
+ static {
+ INTENT_DISMISS_CALM_MODE = new Intent();
+ INTENT_DISMISS_CALM_MODE.setPackage(APP_PACKAGE_NAME);
+ INTENT_DISMISS_CALM_MODE.setAction(INTENT_ACTION_DISMISS_CALM_MODE);
+ }
+
+ /** Sends a broadcast intent to end Calm mode
+ * @param context used for sending broadcast
+ */
+ public static void dismissCalmMode(@NonNull Context context) {
+ context.sendBroadcast(INTENT_DISMISS_CALM_MODE);
+ }
+
+ private final BroadcastReceiver mDismissReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Log.v(TAG, "Received broadcast:" + " intent=" + intent
+ + ", extras=" + intent.getExtras() + ", user= " + context.getUser());
+ }
+ finish();
+ }
+ };
+
+ private final View.OnApplyWindowInsetsListener mOnApplyWindowInsetsListener =
+ (v, insets) -> {
+ int insetTypes = WindowInsets.Type.systemBars();
+ Insets appliedInsets = insets.getInsets(insetTypes);
+ v.setPadding(
+ appliedInsets.left, appliedInsets.top,
+ appliedInsets.right, /* bottom= */ 0);
+ return insets.inset(appliedInsets);
+ };
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().setDecorFitsSystemWindows(/* decorFitsSystemWindows= */ false);
+ getWindow()
+ .getDecorView()
+ .getRootView()
+ .setOnApplyWindowInsetsListener(mOnApplyWindowInsetsListener);
+ registerBroadcastReceiver();
+ setContentView(R.layout.calm_mode_activity);
+ }
+
+ private void registerBroadcastReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(PortraitCalmModeActivity.INTENT_ACTION_DISMISS_CALM_MODE);
+ registerReceiver(mDismissReceiver, filter, /* broadcastPermission= */ null,
+ /* scheduler= */ null, Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ unregisterReceiver(mDismissReceiver);
+ }
+
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/controlbar/ControlBarAudioFragment.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/controlbar/ControlBarAudioFragment.java
new file mode 100644
index 0000000..1a99263
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/controlbar/ControlBarAudioFragment.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.portraitlauncher.controlbar;
+
+import com.android.car.carlauncher.homescreen.audio.AudioCardFragment;
+import com.android.car.carlauncher.homescreen.audio.media.MediaCardFragment;
+
+public class ControlBarAudioFragment extends AudioCardFragment {
+
+ @Override
+ protected MediaCardFragment createMediaFragment() {
+ return new ControlBarMediaFragment();
+ }
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/controlbar/ControlBarMediaController.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/controlbar/ControlBarMediaController.java
new file mode 100644
index 0000000..d00d469
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/controlbar/ControlBarMediaController.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.portraitlauncher.controlbar;
+
+import static com.android.car.media.common.ui.PlaybackCardControllerUtilities.updateActionsWithPlaybackState;
+import static com.android.car.media.common.ui.PlaybackCardControllerUtilities.updatePlayButtonWithPlaybackState;
+import static com.android.car.media.common.ui.PlaybackCardControllerUtilities.updateTextViewAndVisibility;
+
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+
+import androidx.constraintlayout.motion.widget.MotionLayout;
+
+import com.android.car.apps.common.RoundedDrawable;
+import com.android.car.apps.common.util.ViewUtils;
+import com.android.car.carlauncher.homescreen.audio.media.MediaIntentRouter;
+import com.android.car.media.common.MediaItemMetadata;
+import com.android.car.media.common.R;
+import com.android.car.media.common.playback.PlaybackProgress;
+import com.android.car.media.common.playback.PlaybackViewModel;
+import com.android.car.media.common.playback.PlaybackViewModel.PlaybackController;
+import com.android.car.media.common.source.MediaSource;
+import com.android.car.media.common.source.MediaSourceColors;
+import com.android.car.media.common.ui.PlaybackCardController;
+import com.android.car.media.common.ui.PlaybackHistoryController;
+import com.android.car.media.common.ui.PlaybackQueueController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ControlBarMediaController extends PlaybackCardController {
+
+ private static final int MAX_ACTIONS_IN_DEFAULT_LAYOUT = 6;
+ private final MediaIntentRouter mMediaIntentRouter = MediaIntentRouter.getInstance();
+
+ private ViewGroup mCustomActionLayout;
+ private ViewGroup mCustomActionOverflowLayout;
+ private ImageButton mActionOverflowExitButton;
+ private ViewGroup mQueueContainer;
+ private ViewGroup mHistoryContainer;
+
+ private PlaybackQueueController mPlaybackQueueController;
+ private PlaybackHistoryController mPlaybackHistoryController;
+ private MotionLayout mMotionLayout;
+
+ private int mSubtitleVisibility;
+ private int mDescriptionVisibility;
+ private int mLogoVisibility;
+ private int mCurrentTimeVisibility;
+ private int mTimeSeparatorVisibility;
+ private int mMaxTimeVisibility;
+ private int mCustomActionLayoutVisibility;
+ private int mCustomActionOverflowLayoutVisibility;
+
+ /**
+ * Builder for {@link ControlBarMediaController}. Overrides build() method to return
+ * ControlBarMediaController rather than base {@link PlaybackCardController}
+ */
+ public static class Builder extends PlaybackCardController.Builder {
+
+ @Override
+ public ControlBarMediaController build() {
+ ControlBarMediaController controller = new ControlBarMediaController(this);
+ controller.setupController();
+ return controller;
+ }
+ }
+
+ public ControlBarMediaController(ControlBarMediaController.Builder builder) {
+ super(builder);
+
+ mView.setOnClickListener(view -> {
+ MediaSource mediaSource = mDataModel.getMediaSource().getValue();
+ Intent intent = mediaSource != null ? mediaSource.getIntent() : null;
+ mMediaIntentRouter.handleMediaIntent(intent);
+ });
+
+ mCustomActionLayout = mView.findViewById(R.id.custom_action_container);
+ mCustomActionOverflowLayout = mView.findViewById(R.id.custom_action_overflow_container);
+ mActionOverflowExitButton = mView.findViewById(R.id.overflow_exit_button);
+ mActionOverflowExitButton.setOnClickListener(view ->
+ handleCustomActionsOverflowButtonClicked(mActionOverflowButton));
+
+ mQueueContainer = mView.findViewById(R.id.queue_list_container);
+ mHistoryContainer = mView.findViewById(R.id.history_list_container);
+
+ mPlaybackQueueController = new PlaybackQueueController(
+ mView.findViewById(R.id.queue_list), Resources.ID_NULL,
+ R.layout.control_bar_media_queue_item, Resources.ID_NULL, getViewLifecycleOwner(),
+ mDataModel, mViewModel.getMediaItemsRepository(),
+ /* LifeCycleObserverUxrContentLimiter */ null, /* uxrConfigurationId */ 0);
+ mPlaybackQueueController.setShowTimeForActiveQueueItem(true);
+ mPlaybackQueueController.setShowIconForActiveQueueItem(false);
+ mPlaybackQueueController.setShowThumbnailForQueueItem(true);
+ mPlaybackQueueController.setShowSubtitleForQueueItem(true);
+
+ mPlaybackHistoryController = new PlaybackHistoryController(getViewLifecycleOwner(),
+ mViewModel, mHistoryContainer, R.layout.control_bar_media_history_item,
+ Resources.ID_NULL, /* uxrConfigurationId */ 0);
+ mPlaybackHistoryController.setupView();
+
+ mMotionLayout = mView.findViewById(R.id.control_bar_media_card_motion_layout);
+ mMotionLayout.addTransitionListener(new MotionLayout.TransitionListener() {
+ @Override
+ public void onTransitionStarted(MotionLayout motionLayout, int i, int i1) {
+ }
+
+ @Override
+ public void onTransitionChange(MotionLayout motionLayout, int i, int i1, float v) {
+ }
+
+ @Override
+ public void onTransitionCompleted(MotionLayout motionLayout, int i) {
+ if (isPanelOpen()) {
+ ViewUtils.setVisible(mCustomActionLayout, false);
+ ViewUtils.setVisible(mCustomActionOverflowLayout, false);
+ } else {
+ ViewUtils.setVisible(mQueueContainer, false);
+ ViewUtils.setVisible(mHistoryContainer, false);
+ }
+ }
+
+ @Override
+ public void onTransitionTrigger(MotionLayout motionLayout, int i, boolean b, float v) {
+ }
+ });
+ }
+
+ @Override
+ protected void setupController() {
+ super.setupController();
+
+ mSubtitleVisibility = mSubtitle.getVisibility();
+ mDescriptionVisibility = mDescription.getVisibility();
+ mLogoVisibility = mLogo.getVisibility();
+ mCurrentTimeVisibility = mCurrentTime.getVisibility();
+ mTimeSeparatorVisibility = mTimeSeparator.getVisibility();
+ mMaxTimeVisibility = mMaxTime.getVisibility();
+
+ mCustomActionLayoutVisibility = mCustomActionLayout.getVisibility();
+ mCustomActionOverflowLayoutVisibility = mCustomActionOverflowLayout.getVisibility();
+ }
+
+ @Override
+ protected void updateMetadata(MediaItemMetadata metadata) {
+ super.updateMetadata(metadata);
+ if (metadata == null) {
+ updateTextViewAndVisibility(mTitle,
+ mView.getContext().getResources().getString(R.string.default_media_song_title));
+ }
+ if (isPanelOpen()) {
+ mSubtitleVisibility = mSubtitle.getVisibility();
+ mDescriptionVisibility = mDescription.getVisibility();
+ mSubtitle.setVisibility(View.GONE);
+ mDescription.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ protected void updateAlbumCoverWithDrawable(Drawable drawable) {
+ RoundedDrawable roundedDrawable = new RoundedDrawable(drawable, mView.getResources()
+ .getFloat(R.dimen.control_bar_media_card_album_art_drawable_corner_ratio));
+ super.updateAlbumCoverWithDrawable(roundedDrawable);
+ }
+
+ @Override
+ protected void updateLogoWithDrawable(Drawable drawable) {
+ super.updateLogoWithDrawable(drawable);
+ if (isPanelOpen()) {
+ mLogoVisibility = mLogo.getVisibility();
+ mLogo.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ protected void updateMediaSource(MediaSource mediaSource) {
+ super.updateMediaSource(mediaSource);
+ if (isPanelOpen()) {
+ mAppIcon.setVisibility(View.GONE);
+ }
+ }
+
+ // TODO b/336857625: Possibly move SeekBar hide logic to parent class
+ @Override
+ protected void updateProgress(PlaybackProgress progress) {
+ super.updateProgress(progress);
+ if (progress == null || !progress.hasTime()) {
+ mSeekBar.setVisibility(View.GONE);
+ }
+ if (isPanelOpen()) {
+ mCurrentTimeVisibility = mCurrentTime.getVisibility();
+ mMaxTimeVisibility = mMaxTime.getVisibility();
+ mTimeSeparatorVisibility = mTimeSeparator.getVisibility();
+ mCurrentTime.setVisibility(View.GONE);
+ mMaxTime.setVisibility(View.GONE);
+ mTimeSeparator.setVisibility(View.GONE);
+ }
+ }
+
+ // TODO b/336857156: Add disabled state for play/pause button and make sure it reflects here
+ @Override
+ protected void updateViewsWithMediaSourceColors(MediaSourceColors colors) {
+ int defaultColor = mView.getResources().getColor(R.color.car_on_surface, null);
+ ColorStateList accentColor = colors != null ? ColorStateList.valueOf(
+ colors.getAccentColor(defaultColor)) :
+ ColorStateList.valueOf(defaultColor);
+
+ if (mPlayPauseButton != null) {
+ mPlayPauseButton.setBackgroundTintList(accentColor);
+ }
+ if (mSeekBar != null) {
+ mSeekBar.setProgressTintList(accentColor);
+ }
+ }
+
+ @Override
+ protected void updatePlaybackState(PlaybackViewModel.PlaybackStateWrapper playbackState) {
+ boolean hasOverflow = false;
+ PlaybackController playbackController = mDataModel.getPlaybackController().getValue();
+ if (playbackState != null) {
+ updatePlayButtonWithPlaybackState(mPlayPauseButton, playbackState, playbackController);
+ int count = 0;
+ if (playbackState.isSkipNextEnabled() || playbackState.isSkipNextReserved()) {
+ count++;
+ }
+ if (playbackState.isSkipPreviousEnabled() || playbackState.iSkipPreviousReserved()) {
+ count++;
+ }
+ List<ImageButton> mActionsCopy = new ArrayList<>(mActions);
+ if (playbackState.getCustomActions().size() > (MAX_ACTIONS_IN_DEFAULT_LAYOUT - count)) {
+ while (count >= 0) {
+ int actionSlotIndexToSkip = 8 - count - 1;
+ mActionsCopy.remove(actionSlotIndexToSkip);
+ mActionsCopy.add(actionSlotIndexToSkip, null);
+ count--;
+ }
+ hasOverflow = true;
+ mActionOverflowButton.setVisibility(View.VISIBLE);
+ } else {
+ mActionOverflowButton.setVisibility(View.GONE);
+ }
+ updateActionsWithPlaybackState(mView.getContext(), mActionsCopy, playbackState,
+ playbackController, mView.getContext().getDrawable(R.drawable.ic_skip_previous),
+ mView.getContext().getDrawable(R.drawable.ic_skip_next),
+ mView.getContext().getDrawable(R.drawable.left_half_pill_button_shape),
+ mView.getContext().getDrawable(R.drawable.right_half_pill_button_shape),
+ /* reserveSkipSlots */ true, /* defaultButtonDrawable */ null);
+
+ if (!hasOverflow && mViewModel.getOverflowExpanded()) {
+ handleCustomActionsOverflowButtonClicked(mActionOverflowButton);
+ }
+ if (isPanelOpen()) {
+ mCustomActionLayout.setVisibility(View.GONE);
+ mCustomActionOverflowLayout.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ @Override
+ protected void setUpActionsOverflowButton() {
+ super.setUpActionsOverflowButton();
+ setOverflowState(mViewModel.getOverflowExpanded());
+ }
+
+ @Override
+ protected void handleCustomActionsOverflowButtonClicked(View overflow) {
+ super.handleCustomActionsOverflowButtonClicked(overflow);
+ setOverflowState(mViewModel.getOverflowExpanded());
+ }
+
+ private void setOverflowState(boolean isExpanded) {
+ ViewUtils.setVisible(mCustomActionLayout, !isExpanded);
+ ViewUtils.setVisible(mCustomActionOverflowLayout, isExpanded);
+ ViewUtils.setVisible(mActionOverflowExitButton, isExpanded);
+ mCustomActionLayoutVisibility = mCustomActionLayout.getVisibility();
+ mCustomActionOverflowLayoutVisibility = mCustomActionOverflowLayout.getVisibility();
+ }
+
+ @Override
+ protected void setUpQueueButton() {
+ super.setUpQueueButton();
+ setQueueState(mViewModel.getQueueVisible(), /* stateSetThroughClick */ false);
+ }
+
+ @Override
+ protected void updateQueueState(boolean hasQueue, boolean isQueueVisible) {
+ super.updateQueueState(hasQueue, isQueueVisible);
+ if (isPanelOpen() && mViewModel.getQueueVisible() && !hasQueue) {
+ unselectPanelButtons();
+
+ mViewModel.setQueueVisible(false);
+ mViewModel.setHistoryVisible(false);
+
+ showViewsAndTransitionPanelClosed();
+ }
+ }
+
+ @Override
+ protected void handleQueueButtonClicked(View queue) {
+ super.handleQueueButtonClicked(queue);
+ setQueueState(mViewModel.getQueueVisible(), /* stateSetThroughClick */ true);
+ }
+
+ @Override
+ protected void setUpHistoryButton() {
+ super.setUpHistoryButton();
+ setHistoryState(mViewModel.getHistoryVisible(), /* stateSetThroughClick */ false);
+ }
+
+ @Override
+ protected void handleHistoryButtonClicked(View history) {
+ super.handleHistoryButtonClicked(history);
+ setHistoryState(mViewModel.getHistoryVisible(), /* stateSetThroughClick */ true);
+ }
+
+ private void setQueueState(boolean isVisible, boolean stateSetThroughClick) {
+ if (isVisible) {
+ showQueue(true);
+ showHistory(false);
+ if (mViewModel.getHistoryVisible()) {
+ mViewModel.setHistoryVisible(false);
+ } else {
+ saveViewVisibilityAndTransitionPanelOpen();
+ }
+ } else if (stateSetThroughClick) {
+ unselectPanelButtons();
+
+ mViewModel.setHistoryVisible(false);
+
+ showViewsAndTransitionPanelClosed();
+ }
+ }
+
+ private void setHistoryState(boolean isVisible, boolean stateSetThroughClick) {
+ if (isVisible) {
+ showHistory(true);
+ showQueue(false);
+ if (mViewModel.getQueueVisible()) {
+ mViewModel.setQueueVisible(false);
+ } else {
+ saveViewVisibilityAndTransitionPanelOpen();
+ }
+ } else if (stateSetThroughClick) {
+ unselectPanelButtons();
+
+ mViewModel.setQueueVisible(false);
+
+ showViewsAndTransitionPanelClosed();
+ }
+ }
+
+ private boolean isPanelOpen() {
+ return mViewModel.getQueueVisible() || mViewModel.getHistoryVisible();
+ }
+
+ private void saveViewVisibilityAndTransitionPanelOpen() {
+ mSubtitleVisibility = mSubtitle.getVisibility();
+ mDescriptionVisibility = mDescription.getVisibility();
+ mLogoVisibility = mLogo.getVisibility();
+ mCurrentTimeVisibility = mCurrentTime.getVisibility();
+ mTimeSeparatorVisibility = mTimeSeparator.getVisibility();
+ mMaxTimeVisibility = mMaxTime.getVisibility();
+
+ mCustomActionLayoutVisibility = mCustomActionLayout.getVisibility();
+ mCustomActionOverflowLayoutVisibility = mCustomActionOverflowLayout.getVisibility();
+
+ mSeekBar.getThumb().mutate().setAlpha(0);
+
+ mMotionLayout.transitionToEnd();
+ }
+
+ private void showViewsAndTransitionPanelClosed() {
+ mAppIcon.setVisibility(View.VISIBLE);
+ mSubtitle.setVisibility(mSubtitleVisibility);
+ mDescription.setVisibility(mDescriptionVisibility);
+ mCurrentTime.setVisibility(mCurrentTimeVisibility);
+ mTimeSeparator.setVisibility(mTimeSeparatorVisibility);
+ mMaxTime.setVisibility(mMaxTimeVisibility);
+ mLogo.setVisibility(mLogoVisibility);
+
+ mCustomActionLayout.setVisibility(mCustomActionLayoutVisibility);
+ mCustomActionOverflowLayout.setVisibility(mCustomActionOverflowLayoutVisibility);
+
+ mSeekBar.getThumb().mutate().setAlpha(255);
+
+ mMotionLayout.transitionToStart();
+ }
+
+ private void showQueue(boolean shouldShow) {
+ ViewUtils.setVisible(mQueueContainer, shouldShow);
+ mQueueButton.setSelected(shouldShow);
+ }
+
+ private void showHistory(boolean shouldShow) {
+ ViewUtils.setVisible(mHistoryContainer, shouldShow);
+ mHistoryButton.setSelected(shouldShow);
+ }
+
+ private void unselectPanelButtons() {
+ mQueueButton.setSelected(false);
+ mHistoryButton.setSelected(false);
+ }
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/controlbar/ControlBarMediaFragment.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/controlbar/ControlBarMediaFragment.java
new file mode 100644
index 0000000..2b1f91d
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/controlbar/ControlBarMediaFragment.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.portraitlauncher.controlbar;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.car.carlauncher.homescreen.audio.media.MediaCardFragment;
+import com.android.car.portraitlauncher.R;
+
+public class ControlBarMediaFragment extends MediaCardFragment {
+
+ private ControlBarMediaController mControlBarController;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.control_bar_media_card, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ mControlBarController = (ControlBarMediaController) new ControlBarMediaController.Builder()
+ .setModels(mViewModel.getPlaybackViewModel(),
+ mViewModel,
+ mViewModel.getMediaItemsRepository())
+ .setViewGroup((ViewGroup) view)
+ .build();
+ }
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/controlbar/ControlBarModule.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/controlbar/ControlBarModule.java
new file mode 100644
index 0000000..8ff2ade
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/controlbar/ControlBarModule.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.portraitlauncher.controlbar;
+
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.car.carlauncher.Flags;
+import com.android.car.carlauncher.homescreen.audio.AudioCardModel;
+import com.android.car.carlauncher.homescreen.audio.AudioCardModule;
+import com.android.car.carlauncher.homescreen.audio.AudioCardPresenter;
+import com.android.car.carlauncher.homescreen.audio.dialer.DialerCardPresenter;
+import com.android.car.carlauncher.homescreen.audio.media.MediaCardPresenter;
+
+public class ControlBarModule extends AudioCardModule {
+ @Override
+ public void setViewModelProvider(ViewModelProvider viewModelProvider) {
+ if (Flags.mediaCardFullscreen()) {
+ if (mViewModelProvider != null) {
+ throw new IllegalStateException("Cannot reset the view model provider");
+ }
+ mViewModelProvider = viewModelProvider;
+
+ mAudioCardPresenter = new AudioCardPresenter(
+ new DialerCardPresenter(), new MediaCardPresenter());
+ mAudioCardPresenter.setModel(new AudioCardModel(mViewModelProvider));
+ mAudioCardView = new ControlBarAudioFragment();
+ mAudioCardPresenter.setView(mAudioCardView);
+ } else {
+ super.setViewModelProvider(viewModelProvider);
+ }
+ }
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/homeactivities/CarUiPortraitHomeScreen.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/homeactivities/CarUiPortraitHomeScreen.java
index 8b264ea..5cb93c8 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/homeactivities/CarUiPortraitHomeScreen.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/homeactivities/CarUiPortraitHomeScreen.java
@@ -18,12 +18,10 @@
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static com.android.car.caruiportrait.common.service.CarUiPortraitService.INTENT_EXTRA_IMMERSIVE_MODE_REQUESTED_SOURCE;
import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_APP_GRID_VISIBILITY_CHANGE;
-import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_COLLAPSE_NOTIFICATION;
-import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_COLLAPSE_RECENTS;
+import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_COLLAPSE_APPLICATION;
import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_FG_TASK_VIEW_READY;
import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_HIDE_SYSTEM_BAR_FOR_IMMERSIVE;
import static com.android.car.caruiportrait.common.service.CarUiPortraitService.MSG_IMMERSIVE_MODE_CHANGE;
@@ -35,18 +33,15 @@
import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_CALM_MODE_STARTED;
import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_COLLAPSE_MSG;
import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_DRIVE_STATE_CHANGED;
-import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_HOME_INTENT;
import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_HOME_SCREEN_LAYOUT_CHANGED;
import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_IMMERSIVE_REQUEST;
import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_MEDIA_INTENT;
-import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_PANEL_STATE_CHANGE_END;
import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_SUW_STATE_CHANGED;
-import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_TASK_INFO_CHANGED;
import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_TASK_MOVED_TO_FRONT;
-import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_TASK_REMOVED;
import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.createReason;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -57,7 +52,6 @@
import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Configuration;
-import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
@@ -71,7 +65,6 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
-import android.util.SparseArray;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.SurfaceView;
@@ -95,9 +88,11 @@
import com.android.car.carlauncher.homescreen.audio.media.MediaIntentRouter;
import com.android.car.carlauncher.taskstack.TaskStackChangeListeners;
import com.android.car.portraitlauncher.R;
+import com.android.car.portraitlauncher.calmmode.PortraitCalmModeActivity;
import com.android.car.portraitlauncher.common.CarUiPortraitServiceManager;
import com.android.car.portraitlauncher.common.UserEventReceiver;
import com.android.car.portraitlauncher.panel.TaskViewPanel;
+import com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
@@ -156,10 +151,9 @@
private static final boolean DBG = Build.IS_DEBUGGABLE;
/** Identifiers for panels. */
- private static final int APP_GRID = 1;
- private static final int APPLICATION = 2;
- private static final int BACKGROUND = 3;
- private static final int FULLSCREEN = 4;
+ private static final int APPLICATION = 1;
+ private static final int BACKGROUND = 2;
+ private static final int FULLSCREEN = 3;
private static final long IMMERSIVE_MODE_REQUEST_TIMEOUT = 500;
private static final String SAVED_BACKGROUND_APP_COMPONENT_NAME =
"SAVED_BACKGROUND_APP_COMPONENT_NAME";
@@ -167,6 +161,7 @@
ActivityTaskManager.getService();
private final UserEventReceiver mUserEventReceiver = new UserEventReceiver();
private final Configuration mConfiguration = new Configuration();
+
private int mStatusBarHeight;
private FrameLayout mContainer;
private LinearLayout mControlBarView;
@@ -179,18 +174,18 @@
private int mNavBarHeight;
private boolean mIsSUWInProgress;
private TaskCategoryManager mTaskCategoryManager;
- private TaskInfo mCurrentTaskInRootTaskView;
+ private ActivityManager.RunningTaskInfo mCurrentTaskInRootTaskView;
private boolean mIsNotificationCenterOnTop;
private boolean mIsRecentsOnTop;
private boolean mIsAppGridOnTop;
private boolean mIsCalmMode;
private TaskInfoCache mTaskInfoCache;
- private TaskViewPanel mAppGridTaskViewPanel;
private TaskViewPanel mRootTaskViewPanel;
- private SparseArray<ActivityManager.RunningTaskInfo> mRunningTaskInfoRecords;
private final IntentHandler mMediaIntentHandler = new IntentHandler() {
@Override
public void handleIntent(Intent intent) {
+ logIfDebuggable("handleIntent mCurrentTaskInRootTaskView: " + mCurrentTaskInRootTaskView
+ + ", incoming intent =" + intent);
if (TaskCategoryManager.isMediaApp(mCurrentTaskInRootTaskView)
&& mRootTaskViewPanel.isOpen()) {
mRootTaskViewPanel.closePanel(createReason(ON_MEDIA_INTENT, intent.getComponent()));
@@ -243,18 +238,8 @@
public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
logIfDebuggable("On task created, task = " + taskId);
if (componentName != null) {
- logIfDebuggable("On task created, task = " + taskId
- + " componentName " + componentName);
- }
- }
-
- @Override
- public void onTaskFocusChanged(int taskId, boolean focused) {
- super.onTaskFocusChanged(taskId, focused);
- logIfDebuggable("On task focus changed, task = " + taskId);
- boolean hostFocused = taskId == getTaskId() && focused;
- if (hostFocused && mTaskViewControllerWrapper != null) {
- mTaskViewControllerWrapper.showEmbeddedTasks(new int[]{BACKGROUND, FULLSCREEN});
+ logIfDebuggable(
+ "On task created, task = " + taskId + " componentName " + componentName);
}
}
@@ -266,69 +251,16 @@
if (!mRootTaskViewPanel.isReady()) {
logIfDebuggable("Root Task View is not ready yet.");
if (!TaskCategoryManager.isHomeIntent(taskInfo)
- && !mTaskCategoryManager.isBackgroundApp(taskInfo)
- && !mTaskCategoryManager.isAppGridActivity(taskInfo)) {
+ && !mTaskCategoryManager.isBackgroundApp(taskInfo)) {
cacheTask(taskInfo);
}
return;
}
- adjustFullscreenSpacing(mTaskCategoryManager.isFullScreenActivity(taskInfo));
-
- mIsNotificationCenterOnTop = mTaskCategoryManager.isNotificationActivity(taskInfo);
- mIsRecentsOnTop = mTaskCategoryManager.isRecentsActivity(taskInfo);
- mIsAppGridOnTop = mTaskCategoryManager.isAppGridActivity(taskInfo);
-
- if (mTaskCategoryManager.isBackgroundApp(taskInfo)) {
- mTaskCategoryManager.setCurrentBackgroundApp(taskInfo.baseActivity);
- mIsCalmMode = mTaskCategoryManager.isCalmModeActivity(taskInfo);
- int windowInsetsType = WindowInsets.Type.systemBars();
- if (mIsCalmMode) {
-
- if (mRootTaskViewPanel.isOpen()) {
- mRootTaskViewPanel.closePanel(createReason(ON_CALM_MODE_STARTED,
- taskInfo.taskId, getVisibleActivity(taskInfo)));
- }
- if (mAppGridTaskViewPanel.isOpen()) {
- mRootTaskViewPanel.closePanel(createReason(ON_CALM_MODE_STARTED,
- taskInfo.taskId, getVisibleActivity(taskInfo)));
- }
- setControlBarVisibility(/* isVisible = */ false, /* animate = */ true);
- windowInsetsType = WindowInsets.Type.navigationBars();
- } else {
- setControlBarVisibility(/* isVisible = */ true, /* animate = */ true);
- }
- notifySystemUI(MSG_HIDE_SYSTEM_BAR_FOR_IMMERSIVE, windowInsetsType);
- return;
- }
-
- if (shouldTaskShowOnRootTaskView(taskInfo)) {
- logIfDebuggable("Opening in root task view: " + taskInfo);
- mRootTaskViewPanel.setComponentName(getVisibleActivity(taskInfo));
- mCurrentTaskInRootTaskView = taskInfo;
- // Open immersive mode if there is unhandled immersive mode request.
- if (shouldOpenFullScreenPanel(taskInfo)) {
- mRootTaskViewPanel.openFullScreenPanel(/* animated= */ true,
- /* showToolBar= */ true, mNavBarHeight,
- createReason(ON_TASK_MOVED_TO_FRONT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- setUnhandledImmersiveModeRequest(/* componentName= */ null, /* timestamp= */ 0,
- /* requested= */ false);
- } else if (mAppGridTaskViewPanel.isOpen()) {
- // Animate the root task view to expand on top of the app grid task view.
- mRootTaskViewPanel.expandPanel(
- createReason(ON_TASK_MOVED_TO_FRONT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- } else {
- // Open the root task view.
- mRootTaskViewPanel.openPanel(/* animated = */ true,
- createReason(ON_TASK_MOVED_TO_FRONT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- }
- } else {
- logIfDebuggable("Not showing task in rootTaskView");
- }
+ TaskViewPanelStateChangeReason reason = createReason(ON_TASK_MOVED_TO_FRONT,
+ taskInfo.taskId, getVisibleActivity(taskInfo));
+ handleTaskStackChange(taskInfo, reason);
}
/**
@@ -340,23 +272,6 @@
public void onTaskRemoved(int taskId) throws RemoteException {
super.onTaskRemoved(taskId);
logIfDebuggable("onTaskRemoved taskId=" + taskId);
- // Notification center dies during the transition to deferred SUW, don't respond to it.
- if (mRootTaskViewPanel == null || mIsSUWInProgress) {
- return;
- }
-
- // It's possible that mTaskViewControllerWrapper still thinks it has one task but
- // that task is actually removed. This typically happens on deferred SUW quits.
- ActivityManager.RunningTaskInfo taskInfo = mTaskViewControllerWrapper.getRootTaskInfo();
- boolean isRootTaskViewEmpty = taskInfo == null || (mCurrentTaskInRootTaskView != null
- && mCurrentTaskInRootTaskView.taskId == taskId);
-
- // Hide the root task view panel if it is empty.
- if (isRootTaskViewEmpty && mRootTaskViewPanel.isVisible()) {
- logIfDebuggable("Close panel as no task is available.");
- mRootTaskViewPanel.closePanel(/* animated= */ false,
- createReason(ON_TASK_REMOVED, taskId));
- }
}
/**
@@ -377,117 +292,98 @@
logIfDebuggable("On Activity restart attempt, task = " + taskInfo.taskId + ", cmp ="
+ taskInfo.baseActivity + " wasVisible = " + wasVisible);
- if (taskInfo.baseIntent == null || taskInfo.baseIntent.getComponent() == null) {
- return;
- }
- // TODO(b/314398373): find out if CTS can be satisfied without this.
- if (TaskCategoryManager.isMediaApp(taskInfo) || mAppGridTaskViewPanel.isOpen()) {
- mTaskViewControllerWrapper.updateTaskVisibility(/* visibility= */ true,
- APPLICATION);
- } else if (mTaskCategoryManager.isAppGridActivity(taskInfo)) {
- mTaskViewControllerWrapper.updateTaskVisibility(/* visibility= */ true, APP_GRID);
- } else if (!wasVisible) {
- logIfDebuggable("return for not visible");
- return;
- }
-
- adjustFullscreenSpacing(mTaskCategoryManager.isFullScreenActivity(taskInfo));
-
- if (mTaskCategoryManager.isBackgroundApp(taskInfo)) {
- return;
- }
-
- if (mIsSUWInProgress) {
- return;
- }
-
- logIfDebuggable("Update UI state on app restart attempt");
- if (mTaskCategoryManager.isAppGridActivity(taskInfo)) {
- if (mRootTaskViewPanel.isAnimating()) {
- mRootTaskViewPanel.closePanel(/* animated = */ false,
- createReason(ON_ACTIVITY_RESTART_ATTEMPT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- }
-
- // If the new task is an app grid then toggle the app grid panel:
- // 1 - Close the app grid panel if it is open.
- // 2 - Open the app grid panel if it is closed:
- // a) If the root task view is open on top of the app grid then use a fade
- // animation to hide the root task view panel and show the app grid panel.
- // b) Otherwise, simply open the app grid panel.
- if (mAppGridTaskViewPanel.isOpen()) {
- mAppGridTaskViewPanel.closePanel(
- createReason(ON_ACTIVITY_RESTART_ATTEMPT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- } else if (mRootTaskViewPanel.isOpen()) {
- mAppGridTaskViewPanel.fadeInPanel(
- createReason(ON_ACTIVITY_RESTART_ATTEMPT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- mRootTaskViewPanel.fadeOutPanel(
- createReason(ON_ACTIVITY_RESTART_ATTEMPT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- } else if (mRootTaskViewPanel.isFullScreen()) {
- mAppGridTaskViewPanel.openPanel(
- createReason(ON_ACTIVITY_RESTART_ATTEMPT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- mRootTaskViewPanel.closePanel(
- createReason(ON_ACTIVITY_RESTART_ATTEMPT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- } else {
- mAppGridTaskViewPanel.openPanel(
- createReason(ON_ACTIVITY_RESTART_ATTEMPT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- }
- } else if (shouldTaskShowOnRootTaskView(taskInfo)) {
- mRootTaskViewPanel.setComponentName(getVisibleActivity(taskInfo));
- if (mAppGridTaskViewPanel.isAnimating() && mAppGridTaskViewPanel.isOpen()) {
- mAppGridTaskViewPanel.openPanel(/* animated = */ false,
- createReason(ON_ACTIVITY_RESTART_ATTEMPT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- }
-
- // If the new task should be launched in the root task view panel:
- // 1 - Close the root task view panel if it is open and the task is notification
- // center. Make sure the app grid panel is closed already in case we are
- // interrupting a running animation.
- // 2 - Open the root task view panel if it is closed:
- // a) If there is a fresh unhandled immersive mode request, open the root task
- // view panel to full screen.
- // b) If the app grid panel is already open then use an expand animation
- // to open the root task view on top of the app grid task view.
- // c) Otherwise, simply open the root task view panel.
- if (mRootTaskViewPanel.isOpen()
- && mTaskCategoryManager.isNotificationActivity(taskInfo)) {
- if (mAppGridTaskViewPanel.isOpen()) {
- mAppGridTaskViewPanel.closePanel(/* animated = */ false,
- createReason(ON_ACTIVITY_RESTART_ATTEMPT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- }
- mRootTaskViewPanel.closePanel(
- createReason(ON_ACTIVITY_RESTART_ATTEMPT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- } else {
- if (shouldOpenFullScreenPanel(taskInfo)) {
- mRootTaskViewPanel.openFullScreenPanel(/* animated= */ true,
- /* showToolBar= */ true, mNavBarHeight,
- createReason(ON_ACTIVITY_RESTART_ATTEMPT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- setUnhandledImmersiveModeRequest(/* componentName= */ null,
- /* timestamp= */ 0, /* requested= */ false);
- } else if (mAppGridTaskViewPanel.isOpen()) {
- mRootTaskViewPanel.expandPanel(
- createReason(ON_ACTIVITY_RESTART_ATTEMPT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- } else {
- mRootTaskViewPanel.openPanel(
- createReason(ON_ACTIVITY_RESTART_ATTEMPT, taskInfo.taskId,
- getVisibleActivity(taskInfo)));
- }
- }
- }
+ TaskViewPanelStateChangeReason reason = createReason(ON_ACTIVITY_RESTART_ATTEMPT,
+ taskInfo.taskId, getVisibleActivity(taskInfo));
+ handleTaskStackChange(taskInfo, reason);
}
};
+
+ private void handleCalmMode(ActivityManager.RunningTaskInfo taskInfo,
+ @NonNull TaskViewPanelStateChangeReason reason) {
+ if (!ON_TASK_MOVED_TO_FRONT.equals(reason.getReason())) {
+ logIfDebuggable(
+ "Skip handling calm mode since the reason is not " + ON_TASK_MOVED_TO_FRONT);
+ return;
+ }
+ if (mTaskCategoryManager.isFullScreenActivity(taskInfo)) {
+ logIfDebuggable(
+ "Skip handling calm mode if new activity is full screen activity");
+ return;
+ }
+
+ boolean wasCalmMode = mIsCalmMode;
+ mIsCalmMode = mTaskCategoryManager.isCalmModeActivity(taskInfo);
+
+ if (wasCalmMode && !mIsCalmMode) {
+ exitCalmMode();
+ } else if (!wasCalmMode && mIsCalmMode) {
+ enterCalmMode(taskInfo);
+ }
+ }
+
+ private void exitCalmMode() {
+ logIfDebuggable("Exiting calm mode");
+ PortraitCalmModeActivity.dismissCalmMode(getApplicationContext());
+ setControlBarVisibility(/* isVisible = */ true, /* animate = */ true);
+ notifySystemUI(MSG_HIDE_SYSTEM_BAR_FOR_IMMERSIVE, WindowInsets.Type.systemBars());
+ }
+
+ private void enterCalmMode(ActivityManager.RunningTaskInfo taskInfo) {
+ logIfDebuggable("Entering calm mode");
+ if (mRootTaskViewPanel.isVisible()) {
+ mRootTaskViewPanel.closePanel(
+ createReason(ON_CALM_MODE_STARTED, taskInfo.taskId,
+ getVisibleActivity(taskInfo)));
+ }
+ setControlBarVisibility(/* isVisible = */ false, /* animate = */ true);
+ int windowInsetsType = WindowInsets.Type.navigationBars();
+ notifySystemUI(MSG_HIDE_SYSTEM_BAR_FOR_IMMERSIVE, windowInsetsType);
+ }
+
+ private void handleSystemBarButton(boolean isPanelVisible) {
+ notifySystemUI(MSG_APP_GRID_VISIBILITY_CHANGE, boolToInt(
+ isPanelVisible && mTaskCategoryManager.isAppGridActivity(
+ mCurrentTaskInRootTaskView)));
+ notifySystemUI(MSG_NOTIFICATIONS_VISIBILITY_CHANGE, boolToInt(
+ isPanelVisible && mTaskCategoryManager.isNotificationActivity(
+ mCurrentTaskInRootTaskView)));
+ notifySystemUI(MSG_RECENTS_VISIBILITY_CHANGE, boolToInt(
+ isPanelVisible && mTaskCategoryManager.isRecentsActivity(
+ mCurrentTaskInRootTaskView)));
+ }
+
+ private void handleFullScreenPanel(ActivityManager.RunningTaskInfo taskInfo) {
+ adjustFullscreenSpacing(mTaskCategoryManager.isFullScreenActivity(taskInfo));
+ }
+
+ private void handleTaskStackChange(ActivityManager.RunningTaskInfo taskInfo,
+ TaskViewPanelStateChangeReason reason) {
+
+ mIsNotificationCenterOnTop = mTaskCategoryManager.isNotificationActivity(taskInfo);
+ mIsRecentsOnTop = mTaskCategoryManager.isRecentsActivity(taskInfo);
+ mIsAppGridOnTop = mTaskCategoryManager.isAppGridActivity(taskInfo);
+ if (mTaskCategoryManager.isBackgroundApp(taskInfo)) {
+ mTaskCategoryManager.setCurrentBackgroundApp(taskInfo.baseActivity);
+ }
+
+ handleFullScreenPanel(taskInfo);
+ handleCalmMode(taskInfo, reason);
+
+ if (!shouldUpdateApplicationPanelState(taskInfo)) {
+ return;
+ }
+
+ mCurrentTaskInRootTaskView = taskInfo;
+
+ if (shouldOpenFullScreenPanel(taskInfo)) {
+ mRootTaskViewPanel.openFullScreenPanel(/* animated= */ true, /* showToolBar= */ true,
+ mNavBarHeight, reason);
+ } else {
+ mRootTaskViewPanel.openPanel(reason);
+ }
+ }
+
private CarUiPortraitServiceManager mCarUiPortraitServiceManager;
private CarUiPortraitDriveStateController mCarUiPortraitDriveStateController;
private boolean mReceivedNewIntent;
@@ -561,7 +457,6 @@
setContentView(R.layout.car_ui_portrait_launcher);
registerUserEventReceiver();
- mRunningTaskInfoRecords = new SparseArray<>();
mTaskCategoryManager = new TaskCategoryManager(getApplicationContext());
if (savedInstanceState != null) {
String savedBackgroundAppName = savedInstanceState.getString(
@@ -585,8 +480,6 @@
setHomeScreenBottomPadding(mNavBarHeight);
mContainer.addOnLayoutChangeListener(mHomeScreenLayoutChangeListener);
- mAppGridTaskViewPanel = findViewById(R.id.app_grid_panel);
-
mRootTaskViewPanel = findViewById(R.id.application_panel);
mStatusBarHeight = getResources().getDimensionPixelSize(
@@ -626,8 +519,7 @@
if (mTaskViewControllerWrapper == null) {
mTaskViewControllerWrapper = new RemoteCarTaskViewControllerWrapperImpl(
- /* activity= */ this,
- this::onTaskViewControllerReady);
+ /* activity= */ this, this::onTaskViewControllerReady);
}
}
@@ -639,14 +531,14 @@
protected void onNewIntent(@NonNull Intent intent) {
super.onNewIntent(intent);
mReceivedNewIntent = true;
- // This is done to handle the case where 'close' is tapped on ActivityBlockingActivity and
- // it navigates to the home app. It assumes that the currently display task will be
- // replaced with the home.
- // In this case, ABA is actually displayed inside launch-root-task. By closing the
- // root task view panel we make sure the app goes to the background.
- mRootTaskViewPanel.closePanel(createReason(ON_HOME_INTENT));
- // Close app grid to account for home key event
- mAppGridTaskViewPanel.closePanel(createReason(ON_HOME_INTENT));
+ // No state change on application panel. If there is no task in application panel, user will
+ // see a surface view.
+
+ mTaskViewControllerWrapper.updateAllowListedActivities(BACKGROUND,
+ mTaskCategoryManager.getBackgroundActivitiesList());
+ mTaskViewControllerWrapper.updateAllowListedActivities(FULLSCREEN,
+ mTaskCategoryManager.getFullScreenActivitiesList());
+ mTaskViewControllerWrapper.showEmbeddedTasks(new int[]{BACKGROUND, FULLSCREEN});
}
private void registerUserEventReceiver() {
@@ -673,21 +565,13 @@
for (String providerClassName : getResources().getStringArray(
R.array.config_homeCardModuleClasses)) {
try {
- long reflectionStartTime = System.currentTimeMillis();
HomeCardModule cardModule = Class.forName(providerClassName).asSubclass(
HomeCardModule.class).getDeclaredConstructor().newInstance();
cardModule.setViewModelProvider(new ViewModelProvider(/* owner= */ this));
homeCardModules.add(cardModule);
- if (DBG) {
- long reflectionTime = System.currentTimeMillis() - reflectionStartTime;
- logIfDebuggable(
- "Initialization of HomeCardModule class " + providerClassName
- + " took " + reflectionTime + " ms");
- }
- } catch (IllegalAccessException | InstantiationException
- | ClassNotFoundException | InvocationTargetException
- | NoSuchMethodException e) {
+ } catch (IllegalAccessException | InstantiationException | ClassNotFoundException
+ | InvocationTargetException | NoSuchMethodException e) {
Log.w(TAG, "Unable to create HomeCardProvider class " + providerClassName, e);
}
}
@@ -704,10 +588,9 @@
}
}
- private void collapseRecentsPanel() {
- if (mIsRecentsOnTop) {
- mRootTaskViewPanel.closePanel(createReason(ON_COLLAPSE_MSG));
- }
+ private void collapseAppPanel() {
+ logIfDebuggable("On collapse app panel");
+ mRootTaskViewPanel.closePanel(createReason(ON_COLLAPSE_MSG));
}
@Override
@@ -719,11 +602,10 @@
return;
}
initializeCards();
- Drawable background =
- getResources().getDrawable(R.drawable.control_bar_background, getTheme());
+ Drawable background = getResources().getDrawable(R.drawable.control_bar_background,
+ getTheme());
mControlBarView.setBackground(background);
mRootTaskViewPanel.post(() -> mRootTaskViewPanel.refresh(getTheme()));
- mAppGridTaskViewPanel.post(() -> mAppGridTaskViewPanel.refresh(getTheme()));
updateBackgroundTaskViewInsets();
}
@@ -751,13 +633,6 @@
@Override
protected void onResume() {
super.onResume();
- mTaskViewControllerWrapper.updateAllowListedActivities(BACKGROUND,
- mTaskCategoryManager.getBackgroundActivitiesList());
- mTaskViewControllerWrapper.updateAllowListedActivities(FULLSCREEN,
- mTaskCategoryManager.getFullScreenActivitiesList());
-
- mTaskViewControllerWrapper.showEmbeddedTasks(new int[]{BACKGROUND, FULLSCREEN});
-
// the showEmbeddedTasks will make the task visible which will lead to opening of the panel
// and that should be skipped for application panel when the home intent is sent. Because
// that leads to CTS failures.
@@ -778,44 +653,53 @@
mTaskViewControllerWrapper.updateAllowListedActivities(FULLSCREEN, List.of());
}
- private boolean shouldTaskShowOnRootTaskView(TaskInfo taskInfo) {
+ @Override
+ @SuppressLint("MissingSuperCall")
+ public void onBackPressed() {
+ // ignore back presses
+ }
+
+ private boolean shouldUpdateApplicationPanelState(TaskInfo taskInfo) {
if (mIsSUWInProgress) {
- logIfDebuggable("Skip root task view state change during suw");
+ logIfDebuggable("Skip application panel state change during suw");
return false;
}
- if (taskInfo.baseIntent == null || taskInfo.baseIntent.getComponent() == null) {
- logIfDebuggable("Should not show on root task view since base intent is null");
+ if (taskInfo.baseIntent.getComponent() == null) {
+ logIfDebuggable("Should not show on application panel since base intent is null");
return false;
}
- // fullscreen activities will open in a separate task view which will show on top most
- // z-layer, that should not change the state of the root task view.
+ // Fullscreen activities will open in a separate task view which will show on the top most
+ // z-layer, that should not change the state of the application panel.
if (mTaskCategoryManager.isFullScreenActivity(taskInfo)) {
- logIfDebuggable("Should not show on root task view since task is full screen activity");
+ logIfDebuggable(
+ "Should not show on application panel since task is full screen activity");
return false;
}
- if (mTaskCategoryManager.isAppGridActivity(taskInfo)) {
- logIfDebuggable("Don't open app grid activity on the root task view");
- return false;
- }
-
+ // Background activities will open in a separate task view which will show on the bottom
+ // most z-layer, that should not change the state of the application panel.
if (mTaskCategoryManager.isBackgroundApp(taskInfo)) {
- logIfDebuggable("Should not show on root task view since task is background activity");
- // we don't want to change the state of the root task view when background
- // task are launched or brought to front.
+ logIfDebuggable(
+ "Should not show on application panel since task is background activity");
return false;
}
- // Any task that does NOT meet all the below criteria should be ignored.
- // 1. displayAreaFeatureId should be FEATURE_DEFAULT_TASK_CONTAINER
- // 2. should be visible
- // 3. for the current user ONLY. System user launches some tasks on cluster that should
- // not affect the state of the foreground DA
- // 4. any task that is manually defined to be ignored
- return taskInfo.displayAreaFeatureId == FEATURE_DEFAULT_TASK_CONTAINER
- && taskInfo.userId == ActivityManager.getCurrentUser()
- && !mTaskCategoryManager.shouldIgnoreOpeningForegroundDA(taskInfo);
+ // Should not trigger application panel state change for the task that is not on current
+ // user.
+ // TODO(b/336850810): update logic for ABA if necessary.
+ if (taskInfo.userId != ActivityManager.getCurrentUser()) {
+ logIfDebuggable(
+ "Should not show on application panel since task is not on current user");
+ return false;
+ }
+
+ // Should not trigger application panel state change for the task that is not allowed.
+ if (mTaskCategoryManager.shouldIgnoreForApplicationPanel(taskInfo)) {
+ logIfDebuggable("Should not show on application panel since task is not on allowed");
+ return false;
+ }
+ return true;
}
private void cacheTask(ActivityManager.RunningTaskInfo taskInfo) {
@@ -861,19 +745,24 @@
// TODO(b/275633095): Add test to verify the region is set correctly in each mode
private void updateObscuredTouchRegion() {
+ logIfDebuggable("updateObscuredTouchRegion, mIsSUWInProgress=" + mIsSUWInProgress);
Rect controlBarRect = new Rect();
mControlBarView.getBoundsOnScreen(controlBarRect);
- Rect appPanelGripBarRect = new Rect();
- mAppGridTaskViewPanel.getGripBarBounds(appPanelGripBarRect);
-
Rect rootPanelGripBarRect = new Rect();
mRootTaskViewPanel.getGripBarBounds(rootPanelGripBarRect);
- Rect navigationBarRect = new Rect(controlBarRect.left, controlBarRect.bottom,
- controlBarRect.right, mContainer.getMeasuredHeight());
+ // Make the system bar bounds outside of display, so eventually the background and
+ // fullscreen taskviews don't block the SUW
+ int homescreenHeight = getWindow().getDecorView().getHeight();
+ Rect navigationBarRect = new Rect(controlBarRect.left, /* top= */
+ mIsSUWInProgress ? homescreenHeight : homescreenHeight - mNavBarHeight,
+ controlBarRect.right,
+ mIsSUWInProgress ? homescreenHeight + mNavBarHeight : homescreenHeight);
- Rect statusBarRect = new Rect(controlBarRect.left, /* top= */ 0, controlBarRect.right,
+ Rect statusBarRect = new Rect(controlBarRect.left, /* top= */
+ mIsSUWInProgress ? -mStatusBarHeight : 0,
+ controlBarRect.right,
mIsSUWInProgress ? 0 : mStatusBarHeight);
Region obscuredTouchRegion = new Region();
@@ -881,20 +770,20 @@
obscuredTouchRegion.union(controlBarRect);
}
obscuredTouchRegion.union(navigationBarRect);
- logIfDebuggable("Update ObscuredTouchRegion for root and app grid" + obscuredTouchRegion);
mTaskViewControllerWrapper.setObscuredTouchRegion(obscuredTouchRegion, APPLICATION);
- mTaskViewControllerWrapper.setObscuredTouchRegion(obscuredTouchRegion, APP_GRID);
if (!mIsSUWInProgress) {
- obscuredTouchRegion.union(appPanelGripBarRect);
obscuredTouchRegion.union(rootPanelGripBarRect);
obscuredTouchRegion.union(statusBarRect);
} else if (mRootTaskViewPanel.isVisible()) {
obscuredTouchRegion.union(rootPanelGripBarRect);
- obscuredTouchRegion.union(appPanelGripBarRect);
}
- logIfDebuggable(
- "Update ObscuredTouchRegion for background and fullscreen" + obscuredTouchRegion);
+ logIfDebuggable("ObscuredTouchRegion, rootPanelGripBarRect = "
+ + rootPanelGripBarRect + ", navigationBarRect = "
+ + navigationBarRect + ", statusBarRect = "
+ + statusBarRect + ", controlBarRect = "
+ + controlBarRect);
+
mTaskViewControllerWrapper.setObscuredTouchRegion(obscuredTouchRegion, BACKGROUND);
mTaskViewControllerWrapper.setObscuredTouchRegion(obscuredTouchRegion, FULLSCREEN);
}
@@ -907,15 +796,13 @@
int bottomOverlap = mControlBarView.getTop();
if (mRootTaskViewPanel.isVisible()) {
bottomOverlap = mRootTaskViewPanel.getTop();
- } else if (mAppGridTaskViewPanel.isVisible()) {
- bottomOverlap = mAppGridTaskViewPanel.getTop();
}
Rect appAreaBounds = new Rect();
mBackgroundAppArea.getBoundsOnScreen(appAreaBounds);
- Rect bottomInsets = new Rect(appAreaBounds.left, bottomOverlap,
- appAreaBounds.right, appAreaBounds.bottom);
+ Rect bottomInsets = new Rect(appAreaBounds.left, bottomOverlap, appAreaBounds.right,
+ appAreaBounds.bottom);
Rect topInsets = new Rect(appAreaBounds.left, appAreaBounds.top, appAreaBounds.right,
mStatusBarHeight);
@@ -932,11 +819,8 @@
Rect appAreaBounds = new Rect();
mFullScreenAppArea.getBoundsOnScreen(appAreaBounds);
- Rect bottomInsets = new Rect(
- appAreaBounds.left,
- appAreaBounds.height() - mNavBarHeight,
- appAreaBounds.right,
- appAreaBounds.bottom);
+ Rect bottomInsets = new Rect(appAreaBounds.left, appAreaBounds.height() - mNavBarHeight,
+ appAreaBounds.right, appAreaBounds.bottom);
Rect topInsets = new Rect(appAreaBounds.left, appAreaBounds.top, appAreaBounds.right,
mStatusBarHeight);
@@ -953,9 +837,6 @@
if (mRootTaskViewPanel != null) {
mRootTaskViewPanel.setInsets(insets);
}
- if (mAppGridTaskViewPanel != null) {
- mAppGridTaskViewPanel.setInsets(insets);
- }
mTaskViewControllerWrapper.updateWindowBounds();
}
@@ -964,10 +845,6 @@
mRootTaskViewPanel.onParentDimensionChanged();
}
- if (mAppGridTaskViewPanel != null) {
- mAppGridTaskViewPanel.onParentDimensionChanged();
- }
-
updateTaskViewInsets();
mTaskViewControllerWrapper.updateWindowBounds();
}
@@ -1004,8 +881,7 @@
};
mTaskViewControllerWrapper.createCarRootTaskView(callback, getDisplayId(),
- getMainExecutor(),
- mTaskCategoryManager.getBackgroundActivitiesList());
+ getMainExecutor(), mTaskCategoryManager.getBackgroundActivitiesList());
}
private void startBackgroundActivities() {
@@ -1014,8 +890,8 @@
? CarLauncherUtils.getMapsIntent(getApplicationContext())
: (new Intent()).setComponent(mTaskCategoryManager.getCurrentBackgroundApp());
- Intent failureRecoveryIntent =
- BackgroundPanelBaseActivity.createIntent(getApplicationContext());
+ Intent failureRecoveryIntent = BackgroundPanelBaseActivity.createIntent(
+ getApplicationContext());
Intent[] intents = {failureRecoveryIntent, backgroundIntent};
@@ -1066,82 +942,23 @@
private void setControlBarVisibility(boolean isVisible, boolean animate) {
float translationY = isVisible ? 0 : mContainer.getHeight() - mControlBarView.getTop();
if (animate) {
- mControlBarView.animate().translationY(translationY).withEndAction(
- () -> {
- // TODO (b/316344351): Investigate why control bar does not re-appear
- // without requestLayout()
- mControlBarView.requestLayout();
- updateObscuredTouchRegion();
- });
+ mControlBarView.animate().translationY(translationY).withEndAction(() -> {
+ // TODO (b/316344351): Investigate why control bar does not re-appear
+ // without requestLayout()
+ mControlBarView.requestLayout();
+ updateObscuredTouchRegion();
+ });
} else {
mControlBarView.setTranslationY(translationY);
updateObscuredTouchRegion();
}
}
- private void setUpAppGridTaskView() {
- mAppGridTaskViewPanel.setTag("AppGridPanel");
-
- TaskViewControllerWrapper.TaskViewCallback callback =
- new TaskViewControllerWrapper.TaskViewCallback() {
- @Override
- public void onTaskViewCreated(@NonNull SurfaceView taskView) {
- logIfDebuggable("App grid Task View is created");
- taskView.setZOrderOnTop(false);
- mAppGridTaskViewPanel.setTaskView(taskView);
- mTaskViewControllerWrapper.setTaskView(taskView, APP_GRID);
- mTaskViewControllerWrapper.updateWindowBounds();
- }
-
- @Override
- public void onTaskViewInitialized() {
- logIfDebuggable("App grid Task View is ready");
- mAppGridTaskViewPanel.setReady(/* isReady= */ true);
- onTaskViewReadinessUpdated();
- startActivity(CarLauncherUtils.getAppsGridIntent());
- }
-
- @Override
- public void onTaskViewReleased() {
- logIfDebuggable("AppGrid Task View is released");
- mTaskViewControllerWrapper.setTaskView(/* taskView= */ null, APP_GRID);
- mAppGridTaskViewPanel.setReady(/* isReady= */ false);
- }
- };
-
- mTaskViewControllerWrapper.createCarRootTaskView(callback, getDisplayId(),
- getMainExecutor(),
- List.of(mTaskCategoryManager.getAppGridActivity()));
-
- mAppGridTaskViewPanel.setOnStateChangeListener(new TaskViewPanel.OnStateChangeListener() {
- @Override
- public void onStateChangeStart(TaskViewPanel.State oldState,
- TaskViewPanel.State newState, boolean animated) {
- boolean isVisible = newState.isVisible();
- notifySystemUI(MSG_APP_GRID_VISIBILITY_CHANGE, boolToInt(isVisible));
- if (newState.isVisible() && newState != oldState) {
- mTaskViewControllerWrapper.setWindowBounds(
- mAppGridTaskViewPanel.getTaskViewBounds(newState), APP_GRID);
- }
- }
-
- @Override
- public void onStateChangeEnd(TaskViewPanel.State oldState,
- TaskViewPanel.State newState, boolean animated) {
- updateObscuredTouchRegion();
- updateBackgroundTaskViewInsets();
-
- if (!newState.isVisible() && oldState != newState) {
- mTaskViewControllerWrapper.setWindowBounds(
- mAppGridTaskViewPanel.getTaskViewBounds(newState), APP_GRID);
- }
- }
- });
- }
private boolean isAllTaskViewsReady() {
- return mRootTaskViewPanel.isReady() && mAppGridTaskViewPanel.isReady()
- && mIsBackgroundTaskViewReady && mIsFullScreenTaskViewReady;
+ return mRootTaskViewPanel.isReady()
+ && mIsBackgroundTaskViewReady
+ && mIsFullScreenTaskViewReady;
}
/**
@@ -1167,7 +984,6 @@
// lists.
mTaskCategoryManager.refresh();
setUpBackgroundTaskView();
- setUpAppGridTaskView();
setUpFullScreenTaskView();
}
@@ -1182,17 +998,13 @@
Rect controlBarBounds = new Rect();
mControlBarView.getBoundsOnScreen(controlBarBounds);
boolean isControlBarVisible = mControlBarView.getVisibility() == View.VISIBLE;
- logIfDebuggable("Control bar:"
- + "( visible: " + isControlBarVisible
- + ", bounds:" + controlBarBounds
- + ")");
-
+ logIfDebuggable("Control bar:" + "( visible: " + isControlBarVisible + ", bounds:"
+ + controlBarBounds + ")");
mTaskInfoCache.startCachedTasks();
}
private void setUpRootTaskView() {
mRootTaskViewPanel.setTag("RootPanel");
- mRootTaskViewPanel.setTaskViewBackgroundColor(Color.BLACK);
mRootTaskViewPanel.setOnStateChangeListener(new TaskViewPanel.OnStateChangeListener() {
@Override
public void onStateChangeStart(TaskViewPanel.State oldState,
@@ -1200,46 +1012,21 @@
boolean isFullScreen = newState.isFullScreen();
if (!mIsSUWInProgress) {
setControlBarVisibility(!isFullScreen, animated);
- notifySystemUI(MSG_HIDE_SYSTEM_BAR_FOR_IMMERSIVE, isFullScreen
- ? WindowInsets.Type.navigationBars()
- : WindowInsets.Type.systemBars());
- }
-
- boolean isVisible = newState.isVisible();
- // Deselect the app grid button as soon as we know the root task view panel will
- // be shown on top.
- if (isVisible) {
- notifySystemUI(MSG_APP_GRID_VISIBILITY_CHANGE, boolToInt(false));
- }
-
- if (mCurrentTaskInRootTaskView != null && isVisible) {
- mTaskViewControllerWrapper.updateTaskVisibility(/* visibility= */ true,
- APPLICATION);
- }
-
- // Update the notification button's selection state.
- if (mIsNotificationCenterOnTop && isVisible) {
- notifySystemUI(MSG_NOTIFICATIONS_VISIBILITY_CHANGE, boolToInt(true));
- } else {
- notifySystemUI(MSG_NOTIFICATIONS_VISIBILITY_CHANGE, boolToInt(false));
- }
-
- // Update the Recents button's selection state.
- if (mIsRecentsOnTop && isVisible) {
- notifySystemUI(MSG_RECENTS_VISIBILITY_CHANGE, boolToInt(true));
- } else {
- notifySystemUI(MSG_RECENTS_VISIBILITY_CHANGE, boolToInt(false));
+ notifySystemUI(MSG_HIDE_SYSTEM_BAR_FOR_IMMERSIVE,
+ isFullScreen ? WindowInsets.Type.navigationBars()
+ : WindowInsets.Type.systemBars());
}
if (newState.isVisible() && newState != oldState) {
mTaskViewControllerWrapper.setWindowBounds(
mRootTaskViewPanel.getTaskViewBounds(newState), APPLICATION);
}
+ handleSystemBarButton(newState.isVisible());
}
@Override
- public void onStateChangeEnd(TaskViewPanel.State oldState,
- TaskViewPanel.State newState, boolean animated) {
+ public void onStateChangeEnd(TaskViewPanel.State oldState, TaskViewPanel.State newState,
+ boolean animated) {
updateObscuredTouchRegion();
// Hide the control bar after the animation if in full screen.
if (newState.isFullScreen()) {
@@ -1252,23 +1039,8 @@
updateBackgroundTaskViewInsets();
}
- // Hide the app grid task view behind the root task view.
- if (newState.isVisible()) {
- mAppGridTaskViewPanel.closePanel(/* animated = */ false,
- createReason(ON_PANEL_STATE_CHANGE_END));
- mTaskViewControllerWrapper.moveToBack(APP_GRID);
- } else if (mCurrentTaskInRootTaskView != null && oldState.isVisible()) {
- // hide the window of the task running in the root task view.
- logIfDebuggable("hiding the window for task: " + mCurrentTaskInRootTaskView);
- if (!mAppGridTaskViewPanel.isVisible()) {
- mTaskViewControllerWrapper.moveToBack(APP_GRID);
- }
- mTaskViewControllerWrapper.moveToBack(APPLICATION);
- }
- if (!newState.isVisible() && oldState != newState) {
- mTaskViewControllerWrapper.setWindowBounds(
- mRootTaskViewPanel.getTaskViewBounds(newState), APPLICATION);
- }
+ mTaskViewControllerWrapper.setWindowBounds(
+ mRootTaskViewPanel.getTaskViewBounds(newState), APPLICATION);
}
});
@@ -1286,27 +1058,6 @@
}
@Override
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- ActivityManager.RunningTaskInfo previousTaskInfoRecord =
- mRunningTaskInfoRecords.get(
- taskInfo.taskId);
- mRunningTaskInfoRecords.put(taskInfo.taskId, taskInfo);
- logIfDebuggable("ON_TASK_INFO_CHANGED, taskInfo =" + taskInfo);
- logIfDebuggable("ON_TASK_INFO_CHANGED, previousTaskInfoRecord ="
- + previousTaskInfoRecord);
-
- // Open the application panel only if the task info change from invisible
- // to visible. This is only for opening CarSettings from QC panel, due to
- // the launch flag, this action doesn't trigger onTaskMoveToFront. Normal
- // apps should trigger the application panel open action w/
- // onTaskMoveToFront, but not here.
- if (taskInfo.isVisible() && previousTaskInfoRecord != null
- && !previousTaskInfoRecord.isVisible()) {
- mRootTaskViewPanel.openPanel(createReason(ON_TASK_INFO_CHANGED));
- }
- }
-
- @Override
public void onTaskViewInitialized() {
logIfDebuggable("Root Task View is ready");
mRootTaskViewPanel.setReady(true);
@@ -1365,24 +1116,20 @@
return;
}
- // Ignore the immersive mode request for app grid, since it's not in root task view panel.
- // Handle the app grid task in TaskStackListener.
- if (mTaskCategoryManager.isAppGridActivity(componentName)) {
- return;
- }
-
if (mCarUiPortraitDriveStateController.isDrivingStateMoving()
&& mRootTaskViewPanel.isFullScreen()) {
mRootTaskViewPanel.openPanel(createReason(ON_DRIVE_STATE_CHANGED, componentName));
+ logIfDebuggable("Quit immersive mode due to drive mode");
return;
}
// Only handles the immersive mode request here if requesting component has the same package
// name as the current top task.
- if (!isPackageVisibleOnRootTask(componentName) || mIsAppGridOnTop) {
+ if (!isPackageVisibleOnApplicationPanel(componentName)) {
// Save the component and timestamp of the latest immersive mode request, in case any
// race condition with TaskStackListener.
setUnhandledImmersiveModeRequest(componentName, System.currentTimeMillis(), requested);
+ logIfDebuggable("Set unhandle since the task is not at front");
return;
}
@@ -1394,7 +1141,7 @@
}
}
- private boolean isPackageVisibleOnRootTask(ComponentName componentName) {
+ private boolean isPackageVisibleOnApplicationPanel(ComponentName componentName) {
TaskInfo taskInfo = mCurrentTaskInRootTaskView;
logIfDebuggable("Top task in launch root task is" + taskInfo);
if (taskInfo == null) {
@@ -1403,8 +1150,8 @@
ComponentName visibleComponentName = getVisibleActivity(taskInfo);
- return visibleComponentName != null
- && componentName.getPackageName().equals(visibleComponentName.getPackageName());
+ return visibleComponentName != null && componentName.getPackageName().equals(
+ visibleComponentName.getPackageName());
}
private ComponentName getVisibleActivity(TaskInfo taskInfo) {
@@ -1426,13 +1173,12 @@
private ComponentName getComponentNameFromBundle(Bundle bundle) {
String cmpString = bundle.getString(INTENT_EXTRA_IMMERSIVE_MODE_REQUESTED_SOURCE);
- return (cmpString == null)
- ? null
- : ComponentName.unflattenFromString(cmpString);
+ return (cmpString == null) ? null : ComponentName.unflattenFromString(cmpString);
}
void notifySystemUI(int key, int value) {
if (mCarUiPortraitServiceManager != null) {
+ logIfDebuggable("NotifySystemUI, key=" + key + ", value=" + value);
mCarUiPortraitServiceManager.notifySystemUI(key, value);
}
}
@@ -1463,11 +1209,8 @@
boolean hideNavBar = intToBool(msg.arg1);
mRootTaskViewPanel.setToolBarViewVisibility(hideNavBar);
break;
- case MSG_COLLAPSE_NOTIFICATION:
- collapseNotificationPanel();
- break;
- case MSG_COLLAPSE_RECENTS:
- collapseRecentsPanel();
+ case MSG_COLLAPSE_APPLICATION:
+ collapseAppPanel();
break;
default:
super.handleMessage(msg);
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/homeactivities/RemoteCarTaskViewControllerWrapperImpl.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/homeactivities/RemoteCarTaskViewControllerWrapperImpl.java
index 170ad7c..7241d64 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/homeactivities/RemoteCarTaskViewControllerWrapperImpl.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/homeactivities/RemoteCarTaskViewControllerWrapperImpl.java
@@ -68,7 +68,8 @@
@Override
public void onDisconnected(
- CarTaskViewController carTaskViewController) {}
+ CarTaskViewController carTaskViewController) {
+ }
});
});
}
@@ -113,6 +114,7 @@
@Override
public void setWindowBounds(Rect taskViewBounds, int taskViewId) {
+ logIfDebuggable("Set window bounds " + taskViewId + ", to bounds" + taskViewBounds);
RemoteCarTaskView targetTaskView = (RemoteCarTaskView) getTaskView(taskViewId);
if (targetTaskView == null) {
return;
@@ -130,6 +132,8 @@
@Override
void setObscuredTouchRegion(Region obscuredTouchRegion, int taskViewId) {
+ logIfDebuggable(
+ "Set ObscuredTouchRegion " + taskViewId + ", " + obscuredTouchRegion.getBounds());
RemoteCarTaskView targetTaskView = (RemoteCarTaskView) getTaskView(taskViewId);
if (targetTaskView == null) {
return;
@@ -139,7 +143,7 @@
@Override
void showEmbeddedTasks(int[] taskViewIds) {
- for (int key: taskViewIds) {
+ for (int key : taskViewIds) {
SurfaceView surfaceView = mTaskViewsMap.get(key);
if (surfaceView instanceof RemoteCarTaskView remoteCarTaskView) {
logIfDebuggable("showEmbeddedTasks, TaskView key=" + key);
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/homeactivities/TaskCategoryManager.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/homeactivities/TaskCategoryManager.java
index c0c9aa4..e9ca594 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/homeactivities/TaskCategoryManager.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/homeactivities/TaskCategoryManager.java
@@ -210,7 +210,20 @@
}
boolean isAppGridActivity(TaskInfo taskInfo) {
- return mAppGridActivityComponent.equals(taskInfo.baseActivity);
+ return mAppGridActivityComponent.equals(getVisibleActivity(taskInfo));
+ }
+
+ private ComponentName getVisibleActivity(TaskInfo taskInfo) {
+ if (taskInfo == null) {
+ return null;
+ }
+ if (taskInfo.topActivity != null) {
+ return taskInfo.topActivity;
+ } else if (taskInfo.baseActivity != null) {
+ return taskInfo.baseActivity;
+ } else {
+ return taskInfo.baseIntent.getComponent();
+ }
}
ComponentName getAppGridActivity() {
@@ -230,7 +243,7 @@
}
boolean isNotificationActivity(TaskInfo taskInfo) {
- return mNotificationActivityComponent.equals(taskInfo.baseActivity);
+ return mNotificationActivityComponent.equals(getVisibleActivity(taskInfo));
}
boolean isRecentsActivity(TaskInfo taskInfo) {
@@ -241,9 +254,9 @@
return mCalmModeComponent.equals(taskInfo.baseActivity);
}
- boolean shouldIgnoreOpeningForegroundDA(TaskInfo taskInfo) {
- return taskInfo.baseIntent != null && mIgnoreOpeningRootTaskViewComponentsSet.contains(
- taskInfo.baseIntent.getComponent());
+ boolean shouldIgnoreForApplicationPanel(TaskInfo taskInfo) {
+ return mIgnoreOpeningRootTaskViewComponentsSet.contains(
+ taskInfo.baseIntent.getComponent());
}
public void onDestroy() {
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/BackgroundSurfaceView.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/BackgroundSurfaceView.java
index 286cbec..de4c623 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/BackgroundSurfaceView.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/BackgroundSurfaceView.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
@@ -33,6 +32,7 @@
/**
* A {@link SurfaceView} that acts as a solid black background of {@link TaskViewPanel}. It
* avoids user to see the background app when an activity in RootTaskView fades out.
+ * TODO(b/336858361): improve the UI for this surfaceview.
*/
public class BackgroundSurfaceView extends SurfaceView {
@@ -41,6 +41,7 @@
// The color used in the surface view.
private int mColor;
+ private int mTextColor;
// The Text at the center of the surface view.
private String mText;
@@ -72,6 +73,7 @@
private void setupSurfaceView() {
mColor = getResources().getColor(R.color.car_background, getContext().getTheme());
+ mTextColor = getResources().getColor(R.color.car_on_background, getContext().getTheme());
getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
@@ -101,7 +103,7 @@
if (mText != null) {
Paint paint = new Paint();
- paint.setColor(Color.WHITE);
+ paint.setColor(mTextColor);
paint.setTextSize(20);
paint.setTextAlign(Paint.Align.CENTER);
float xPos = (canvas.getWidth() / 2f);
@@ -119,6 +121,12 @@
drawSurface(getHolder());
}
+ /** Sets the text that shows on the surface view */
+ public void setText(String text) {
+ mText = text;
+ drawSurface(getHolder());
+ }
+
/** Sets the fixed color and centered text on the surface view */
public void setFixedColorAndText(int color, String text) {
mText = text;
@@ -129,6 +137,8 @@
public void refresh(Resources.Theme theme) {
if (!mUseFixedColor) {
mColor = getResources().getColor(R.color.car_background, theme);
+ mTextColor = getResources().getColor(R.color.car_on_background,
+ getContext().getTheme());
drawSurface(getHolder());
}
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/TaskViewPanel.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/TaskViewPanel.java
index c7fbe3b..dfacd4e 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/TaskViewPanel.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/TaskViewPanel.java
@@ -16,12 +16,14 @@
package com.android.car.portraitlauncher.panel;
+import static android.provider.Settings.Global.ANIMATOR_DURATION_SCALE;
+import static android.provider.Settings.Global.getFloat;
+
import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_GRIP_BAR_CLICKED;
import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_GRIP_BAR_DRAG;
import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.ON_PANEL_READY;
import static com.android.car.portraitlauncher.panel.TaskViewPanelStateChangeReason.createReason;
-import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.app.TaskInfo;
import android.content.ComponentName;
@@ -39,6 +41,7 @@
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.car.portraitlauncher.R;
@@ -77,16 +80,13 @@
private final boolean mIsFullScreen;
/** Whether the panel should display the toolbar. */
private boolean mHasToolBar;
- /** Whether the panel should show the background surfaceView. */
- private boolean mHasBackgroundSurfaceView;
public State(boolean hasGripBar, boolean isVisible, boolean isFullScreen,
- boolean hasToolBar, boolean hasBackgroundSurfaceView) {
+ boolean hasToolBar) {
mHasGripBar = hasGripBar;
mIsVisible = isVisible;
mIsFullScreen = isFullScreen;
mHasToolBar = hasToolBar;
- mHasBackgroundSurfaceView = hasBackgroundSurfaceView;
}
boolean hasGripBar() {
@@ -97,10 +97,6 @@
return mHasToolBar;
}
- boolean hasBackgroundSurfaceView() {
- return mHasBackgroundSurfaceView;
- }
-
/** Whether the panel in this state has any visible parts. */
public boolean isVisible() {
return mIsVisible;
@@ -119,7 +115,7 @@
}
/** Notifies the listener when the panel state changes. */
- public interface OnStateChangeListener{
+ public interface OnStateChangeListener {
/**
* Called right before the panel state changes.
*
@@ -133,6 +129,7 @@
* Called right after the panel state changes.
*
* If the transition is animated, this method would be called after the animation.
+ *
* @param oldState The state from which the transition started.
* @param newState The final state of the panel after the transition.
* @param animated If the transition is animated.
@@ -225,14 +222,11 @@
mPanelTopMargin = (int) getResources().getDimension(R.dimen.panel_default_top_margin);
mOpenState = new State(/* hasGripBar = */ true, /* isVisible = */ true,
- /* isFullScreen */false, /* hasToolBar = */ false,
- /* hasBackgroundSurfaceView = */ false);
+ /* isFullScreen */false, /* hasToolBar = */ false);
mCloseState = new State(/* hasGripBar = */ true, /* isVisible = */ false,
- /* isFullScreen */false, /* hasToolBar = */ false,
- /* hasBackgroundSurfaceView = */ false);
+ /* isFullScreen */false, /* hasToolBar = */ false);
mFullScreenState = new State(/* hasGripBar = */ false, /* isVisible = */ true,
- /* isFullScreen */true, /* hasToolBar = */ true,
- /* hasBackgroundSurfaceView = */ true);
+ /* isFullScreen */true, /* hasToolBar = */ true);
mCurrentTask = null;
}
@@ -246,7 +240,6 @@
mTaskViewContainer = findViewById(R.id.task_view_container);
mTaskViewOverlay = findViewById(R.id.task_view_overlay);
mBackgroundSurfaceView = findViewById(R.id.surface_view);
- mBackgroundSurfaceView.setZOrderOnTop(false);
mActiveState = mCloseState;
setupGripBar();
@@ -280,14 +273,15 @@
/** Transitions the panel into the open state. */
public void openPanel(boolean animated, TaskViewPanelStateChangeReason reason) {
PanelAnimator animator =
- animated ? new OpenPanelAnimator(this, mOpenState.mBounds) : null;
+ animated ? new OpenPanelAnimator(this, mOpenState.mBounds,
+ getAnimationScale(getContext())) : null;
setActiveState(mOpenState, animator, reason);
}
/** Transitions the panel into the open state with overlay and centered icon. */
public void openPanelWithIcon(TaskViewPanelStateChangeReason reason) {
PanelAnimator animator = new OpenPanelWithIconAnimator(this, mOpenState.mBounds,
- mTaskViewOverlay);
+ mTaskViewOverlay, getAnimationScale(getContext()));
setActiveState(mOpenState, animator, reason);
}
@@ -299,7 +293,8 @@
/** Transitions the panel into the close state. */
public void closePanel(boolean animated, TaskViewPanelStateChangeReason reason) {
PanelAnimator animator =
- animated ? new ClosePanelAnimator(this, mCloseState.mBounds) : null;
+ animated ? new ClosePanelAnimator(this, mCloseState.mBounds,
+ getAnimationScale(getContext())) : null;
setActiveState(mCloseState, animator, reason);
}
@@ -308,20 +303,23 @@
public void expandPanel(TaskViewPanelStateChangeReason reason) {
Point origin = new Point(mOpenState.mBounds.centerX(), mOpenState.mBounds.centerY());
PanelAnimator animator =
- new ExpandPanelAnimator(/* panel= */ this, origin, mOpenState.mBounds, mGripBar);
+ new ExpandPanelAnimator(/* panel= */ this, origin, mOpenState.mBounds, mGripBar,
+ getAnimationScale(getContext()));
setActiveState(mOpenState, animator, reason);
}
/** Transitions the panel into the open state using the fade-in animation. */
public void fadeInPanel(TaskViewPanelStateChangeReason reason) {
setActiveState(mOpenState,
- new FadeInPanelAnimator(/* panel= */ this, mTaskView, mOpenState.mBounds), reason);
+ new FadeInPanelAnimator(/* panel= */ this, mTaskView, mOpenState.mBounds,
+ getAnimationScale(getContext())), reason);
}
/** Transitions the panel into the close state using the fade-out animation. */
public void fadeOutPanel(TaskViewPanelStateChangeReason reason) {
PanelAnimator animator = new FadeOutPanelAnimator(/* panel= */ this, mTaskViewOverlay,
- mTaskView, mCloseState.mBounds, mCloseState.mBounds.top);
+ mTaskView, mCloseState.mBounds, mCloseState.mBounds.top,
+ getAnimationScale(getContext()));
setActiveState(mCloseState, animator, reason);
}
@@ -366,7 +364,7 @@
/** Updates the {@code TaskView} used in the panel. */
public void setTaskView(SurfaceView surfaceView) {
- mTaskView = surfaceView;
+ mTaskView = surfaceView;
mTaskViewContainer.addView(mTaskView);
onParentDimensionChanged();
}
@@ -396,7 +394,8 @@
@SuppressLint("ClickableViewAccessibility")
private void setupGripBar() {
mGripBar.setOnTouchListener(new OnPanelDragListener(getContext()) {
- @Override void onClick() {
+ @Override
+ void onClick() {
closePanel(createReason(ON_GRIP_BAR_CLICKED));
}
@@ -432,6 +431,9 @@
if (state.hasGripBar()) {
bounds.top += mGripBar.getHeight();
}
+ if (state.hasToolBar()) {
+ bounds.top += mToolBarView.getHeight();
+ }
return bounds;
}
@@ -514,15 +516,17 @@
+ " to " + toState + " with reason code = " + reason);
if (mActiveAnimator != null) {
- logIfDebuggable("cancelling the old animation");
+ // Should try to avoid cancelling panel animation, might cause flicker.
+ Log.e(TAG, "Cancelling the old panel animation");
mActiveAnimator.cancel();
mActiveAnimator = null;
mGripBar.setVisibility(mActiveState.hasGripBar() ? VISIBLE : GONE);
mToolBarView.setVisibility(GONE);
- mBackgroundSurfaceView.setVisibility(GONE);
}
- boolean animated = animator != null;
+ float animationScale = getAnimationScale(getContext());
+ logIfDebuggable("animationScale = " + animationScale);
+ boolean animated = animator != null && animationScale != 0f;
onStateChangeStart(fromState, toState, animated);
mActiveState = toState;
@@ -543,16 +547,12 @@
post(() -> animator.animate(() -> {
mGripBar.setVisibility(mActiveState.hasGripBar() ? VISIBLE : GONE);
mToolBarView.setVisibility(mActiveState.hasToolBar() ? VISIBLE : GONE);
- mBackgroundSurfaceView.setVisibility(
- mActiveState.hasBackgroundSurfaceView() ? VISIBLE : GONE);
updateBounds(mActiveState.mBounds);
onStateChangeEnd(fromState, mActiveState, /* animated= */ true);
}));
} else {
mGripBar.setVisibility(mActiveState.hasGripBar() ? VISIBLE : GONE);
- mToolBarView.setVisibility(mActiveState.hasToolBar() ? VISIBLE : GONE);
- mBackgroundSurfaceView.setVisibility(
- mActiveState.hasBackgroundSurfaceView() ? VISIBLE : GONE);
+ mToolBarView.setVisibility(mActiveState.hasToolBar() ? VISIBLE : GONE);
updateBounds(mActiveState.mBounds);
mTaskViewOverlay.setVisibility(GONE);
onStateChangeEnd(fromState, mActiveState, /* animated= */ false);
@@ -564,6 +564,7 @@
mOnStateChangeListener.onStateChangeStart(fromState, toState, animated);
}
}
+
private void onStateChangeEnd(State fromState, State toState, boolean animated) {
mActiveAnimator = null;
if (mOnStateChangeListener != null) {
@@ -592,6 +593,11 @@
private FullScreenPanelAnimator createFullScreenPanelAnimator() {
Point offset = new Point(mOpenState.mBounds.left, mOpenState.mBounds.top);
Rect bounds = mFullScreenState.mBounds;
- return new FullScreenPanelAnimator(this, bounds, offset, mTaskViewOverlay);
+ return new FullScreenPanelAnimator(this, bounds, offset, mTaskViewOverlay,
+ getAnimationScale(getContext()));
+ }
+
+ private static float getAnimationScale(Context context) {
+ return getFloat(context.getContentResolver(), ANIMATOR_DURATION_SCALE, /* def= */ 1.0f);
}
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/TaskViewPanelStateChangeReason.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/TaskViewPanelStateChangeReason.java
index 0f2fdbe..1e1b9c2 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/TaskViewPanelStateChangeReason.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/TaskViewPanelStateChangeReason.java
@@ -23,6 +23,7 @@
/**
* Reasons why {@link TaskViewPanel}'s {@link TaskViewPanel.State} changes.
+ * TODO(b/338091566): clean this class for better API structure.
*/
public final class TaskViewPanelStateChangeReason {
public static final String ON_ACTIVITY_RESTART_ATTEMPT = "ON_ACTIVITY_RESTART_ATTEMPT";
@@ -91,6 +92,13 @@
+ "}";
}
+ /**
+ * Returns the {@link Reason}.
+ */
+ public String getReason() {
+ return mReason;
+ }
+
@StringDef({ON_ACTIVITY_RESTART_ATTEMPT,
ON_COLLAPSE_MSG,
ON_DRIVE_STATE_CHANGED,
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/ClosePanelAnimator.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/ClosePanelAnimator.java
index c2bd3d8..afd048a 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/ClosePanelAnimator.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/ClosePanelAnimator.java
@@ -37,19 +37,21 @@
/**
* A {@code PanelAnimator} to animate the panel into the close state.
*
- * @param panel The panel on which the animator acts.
- * @param bounds The final bounds that the panel should animate to.
+ * @param panel The panel on which the animator acts.
+ * @param bounds The final bounds that the panel should animate to.
+ * @param animationScale Scaling factor for Animator-based animations.
*/
- public ClosePanelAnimator(ViewGroup panel, Rect bounds) {
- super(panel);
+ public ClosePanelAnimator(ViewGroup panel, Rect bounds, float animationScale) {
+ super(panel, animationScale);
mBounds = bounds;
+ mDuration = getScaledDuration(DURATION);
}
@Override
public void animate(Runnable endAction) {
mAnimation = new BoundsAnimation(mPanel, mBounds, endAction);
mAnimation.setInterpolator(INTERPOLATOR);
- mAnimation.setDuration(DURATION);
+ mAnimation.setDuration(mDuration);
mPanel.startAnimation(mAnimation);
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/ExpandPanelAnimator.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/ExpandPanelAnimator.java
index a2582bd..9ada1f5 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/ExpandPanelAnimator.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/ExpandPanelAnimator.java
@@ -43,16 +43,19 @@
/**
* A {@code PanelAnimator} to animate the panel into the open state using the expand animation.
*
- * @param panel The panel that should animate
- * @param origin The origin of the expand animation within the panel's parent
- * @param bounds The final bounds of the panel within its parent
- * @param gripBar The grip bar of the panel.
+ * @param panel The panel that should animate
+ * @param origin The origin of the expand animation within the panel's parent
+ * @param bounds The final bounds of the panel within its parent
+ * @param gripBar The grip bar of the panel.
+ * @param animationScale Scaling factor for Animator-based animations.
*/
- public ExpandPanelAnimator(ViewGroup panel, Point origin, Rect bounds, View gripBar) {
- super(panel);
+ public ExpandPanelAnimator(ViewGroup panel, Point origin, Rect bounds, View gripBar,
+ float animationScale) {
+ super(panel, animationScale);
mBounds = bounds;
mGripBar = gripBar;
mOrigin = origin;
+ mDuration = getScaledDuration(DURATION);
}
@Override
@@ -70,7 +73,7 @@
.scaleY(FINAL_SCALE)
.translationX(/* value= */ 0)
.translationY(/* value= */ 0)
- .setDuration(DURATION)
+ .setDuration(mDuration)
.setInterpolator(INTERPOLATOR)
.withEndAction(endAction);
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/FadeInPanelAnimator.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/FadeInPanelAnimator.java
index ca0c740..574a370 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/FadeInPanelAnimator.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/FadeInPanelAnimator.java
@@ -40,14 +40,18 @@
/**
* A {@code PanelAnimator} to animate the panel into the open state using the fade-in animation.
*
- * @param panel The panel that should animate
- * @param taskView The task view of the panel.
- * @param toBounds The final bounds of the panel within its parent
+ * @param panel The panel that should animate
+ * @param taskView The task view of the panel.
+ * @param toBounds The final bounds of the panel within its parent
+ * @param animationScale Scaling factor for Animator-based animations.
*/
- public FadeInPanelAnimator(ViewGroup panel, View taskView, Rect toBounds) {
- super(panel);
+ public FadeInPanelAnimator(ViewGroup panel, View taskView, Rect toBounds,
+ float animationScale) {
+ super(panel, animationScale);
mTaskView = taskView;
mBounds = toBounds;
+ mDuration = getScaledDuration(DURATION);
+ mStartDelay = getScaledDuration(DELAY);
}
@Override
@@ -58,9 +62,9 @@
mViewPropertyAnimator = mTaskView.animate()
.scaleX(FINAL_SCALE)
.scaleY(FINAL_SCALE)
- .setDuration(DURATION)
+ .setDuration(mDuration)
.setInterpolator(INTERPOLATOR)
- .setStartDelay(DELAY)
+ .setStartDelay(mStartDelay)
.withEndAction(endAction);
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/FadeOutPanelAnimator.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/FadeOutPanelAnimator.java
index dbdd364..7438016 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/FadeOutPanelAnimator.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/FadeOutPanelAnimator.java
@@ -51,21 +51,24 @@
/**
* A {@code PanelAnimator} to animate the panel into the open state using the fade-in animation.
*
- * @param panel The panel that should animate
- * @param overlay The overlay view that covers the {@code TaskView}. Used to visually fade out
- * the {@code TaskView}.
- * @param taskView The task view of the panel.
- * @param toBounds The final bounds of the panel within its parent
+ * @param panel The panel that should animate
+ * @param overlay The overlay view that covers the {@code TaskView}. Used to visually
+ * fade out
+ * the {@code TaskView}.
+ * @param taskView The task view of the panel.
+ * @param toBounds The final bounds of the panel within its parent
* @param offScreenYPosition Y value that can be applied to a panel to get it off the screen.
- * This is needed to hide panels during the animation.
+ * This is needed to hide panels during the animation.
+ * @param animationScale Scaling factor for Animator-based animations.
*/
public FadeOutPanelAnimator(ViewGroup panel, TaskViewPanelOverlay overlay, View taskView,
- Rect toBounds, float offScreenYPosition) {
- super(panel);
+ Rect toBounds, float offScreenYPosition, float animationScale) {
+ super(panel, animationScale);
mOverlay = overlay;
mTaskView = taskView;
mBounds = toBounds;
mOffScreenYPosition = offScreenYPosition;
+ mDuration = getScaledDuration(OVERLAY_FADE_IN_DURATION);
}
@Override
@@ -76,9 +79,10 @@
// panel.
// This is necessary since we cannot fade the task views.
mOverlayAnimator = mOverlay.animate().alpha(FADE_IN_ALPHA)
- .setDuration(OVERLAY_FADE_IN_DURATION)
+ .setDuration(mDuration)
.withEndAction(() -> {
- mPanel.animate().alpha(FADE_OUT_ALPHA).setDuration(PANEL_FADE_OUT_DURATION)
+ mPanel.animate().alpha(FADE_OUT_ALPHA).setDuration(
+ getScaledDuration(PANEL_FADE_OUT_DURATION))
.withEndAction(() -> {
mOverlay.setVisibility(GONE);
mTaskView.setScaleX(INITIAL_SCALE);
@@ -90,7 +94,7 @@
});
// Scale the task view and hide it at the end.
mTaskViewAnimator = mTaskView.animate().scaleX(FINAL_SCALE).scaleY(FINAL_SCALE)
- .setDuration(OVERLAY_FADE_IN_DURATION).setInterpolator(INTERPOLATOR)
+ .setDuration(mDuration).setInterpolator(INTERPOLATOR)
.withEndAction(() -> {
// Restore the initial scale
mTaskView.setScaleX(INITIAL_SCALE);
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/FullScreenPanelAnimator.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/FullScreenPanelAnimator.java
index f76e9ad..33ab123 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/FullScreenPanelAnimator.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/FullScreenPanelAnimator.java
@@ -49,18 +49,21 @@
/**
* A {@code PanelAnimator} to animate the panel into the full screen state.
*
- * @param panel The panel that should animate
- * @param bounds The final bounds of the panel within its parent
- * @param initialOffset The initial top left corner of the panel in its parent.
- * @param overlay The overlay view that covers the {@code TaskView}. Used to display
- * the application icon during animation.
+ * @param panel The panel that should animate
+ * @param bounds The final bounds of the panel within its parent
+ * @param initialOffset The initial top left corner of the panel in its parent.
+ * @param overlay The overlay view that covers the {@code TaskView}. Used to display
+ * the application icon during animation.
+ * @param animationScale Scaling factor for Animator-based animations.
*/
public FullScreenPanelAnimator(ViewGroup panel, Rect bounds, Point initialOffset,
- TaskViewPanelOverlay overlay) {
- super(panel);
+ TaskViewPanelOverlay overlay, float animationScale) {
+ super(panel, animationScale);
mBounds = bounds;
mInitialOffset = initialOffset;
mOverlay = overlay;
+ mStartDelay = getScaledDuration(OVERLAY_FADE_OUT_START_DELAY);
+ mDuration = getScaledDuration(OVERLAY_FADE_OUT_DURATION);
}
@Override
@@ -74,8 +77,8 @@
mAnimation = new BoundsAnimation(mPanel, mBounds, () -> {
mOverlayAnimator = mOverlay.animate().alpha(OVERLAY_FADE_OUT_END_ALPHA)
- .setStartDelay(OVERLAY_FADE_OUT_START_DELAY)
- .setDuration(OVERLAY_FADE_OUT_DURATION)
+ .setStartDelay(mStartDelay)
+ .setDuration(mDuration)
.withEndAction(() -> {
mOverlay.hide();
mOverlay.setAlpha(OVERLAY_FADE_OUT_START_ALPHA);
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/OpenPanelAnimator.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/OpenPanelAnimator.java
index a630a5a..32f4f0f 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/OpenPanelAnimator.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/OpenPanelAnimator.java
@@ -36,19 +36,21 @@
/**
* A {@code PanelAnimator} to animate the panel into the open state.
*
- * @param panel The panel on which the animator acts.
- * @param bounds The final bounds that the panel should animate to.
+ * @param panel The panel on which the animator acts.
+ * @param bounds The final bounds that the panel should animate to.
+ * @param animationScale Scaling factor for Animator-based animations.
*/
- public OpenPanelAnimator(ViewGroup panel, Rect bounds) {
- super(panel);
+ public OpenPanelAnimator(ViewGroup panel, Rect bounds, float animationScale) {
+ super(panel, animationScale);
mBounds = bounds;
+ mDuration = getScaledDuration(DURATION);
}
@Override
public void animate(Runnable endAction) {
mAnimation = new BoundsAnimation(mPanel, mBounds, endAction);
mAnimation.setInterpolator(INTERPOLATOR);
- mAnimation.setDuration(DURATION);
+ mAnimation.setDuration(mDuration);
mPanel.startAnimation(mAnimation);
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/OpenPanelWithIconAnimator.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/OpenPanelWithIconAnimator.java
index 5cefa01..70aeb35 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/OpenPanelWithIconAnimator.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/OpenPanelWithIconAnimator.java
@@ -41,15 +41,18 @@
/**
* A {@code PanelAnimator} to animate the panel into the open state.
*
- * @param panel The panel on which the animator acts.
- * @param bounds The final bounds that the panel should animate to.
- * @param overlay The overlay view that covers the {@code TaskView}. Used to cover
- * the panel during animation.
+ * @param panel The panel on which the animator acts.
+ * @param bounds The final bounds that the panel should animate to.
+ * @param overlay The overlay view that covers the {@code TaskView}. Used to cover
+ * the panel during animation.
+ * @param animationScale Scaling factor for Animator-based animations.
*/
public OpenPanelWithIconAnimator(ViewGroup panel, Rect bounds,
- TaskViewPanelOverlay overlay) {
- super(panel, bounds);
+ TaskViewPanelOverlay overlay, float animationScale) {
+ super(panel, bounds, animationScale);
mOverlay = overlay;
+ mDuration = getScaledDuration(OVERLAY_FADE_OUT_DURATION);
+ mStartDelay = getScaledDuration(OVERLAY_FADE_OUT_START_DELAY);
}
@Override
@@ -61,8 +64,8 @@
mOverlayAnimator = mOverlay
.animate()
.alpha(OVERLAY_FADE_OUT_END_ALPHA)
- .setStartDelay(OVERLAY_FADE_OUT_START_DELAY)
- .setDuration(OVERLAY_FADE_OUT_DURATION)
+ .setStartDelay(mStartDelay)
+ .setDuration(mDuration)
.withEndAction(() -> {
mOverlay.hide();
mOverlay.setAlpha(OVERLAY_FADE_OUT_START_ALPHA);
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/PanelAnimator.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/PanelAnimator.java
index 0b2aa26..5556656 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/PanelAnimator.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/panel/animation/PanelAnimator.java
@@ -27,8 +27,17 @@
/** The panel on which the animation will be applied. */
protected ViewGroup mPanel;
- protected PanelAnimator(ViewGroup panel) {
+ /** The duration for this animator. */
+ protected long mStartDelay = 0;
+ /** the startDelay for this animator. */
+ protected long mDuration = 0;
+
+ /** Scaling factor for Animator-based animations.*/
+ private float mAnimationScale = 1f;
+
+ protected PanelAnimator(ViewGroup panel, float animationScale) {
mPanel = panel;
+ mAnimationScale = animationScale;
}
/**
@@ -65,4 +74,8 @@
/* y2= */ 1f, /* x3= */ 1f, /* y3= */ 1f);
return new PathInterpolator(path);
}
+
+ protected long getScaledDuration(long duration) {
+ return mAnimationScale == 1f ? duration : (long) (duration * mAnimationScale);
+ }
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/recents/PortraitCarRecentsActivity.java b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/recents/PortraitCarRecentsActivity.java
index 4bfee84..65f8661 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/recents/PortraitCarRecentsActivity.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/src/com/android/car/portraitlauncher/recents/PortraitCarRecentsActivity.java
@@ -41,14 +41,4 @@
mRecentTasksViewModel.removeHiddenTaskProvider(mPortraitHiddenTaskProvider);
super.onDestroy();
}
-
- @Override
- protected void onResume() {
- if (OPEN_RECENT_TASK_ACTION.equals(getIntent().getAction())) {
- // no-op: This action results in collapsing the panel displaying Recents which is
- // handled by SystemUI.
- return;
- }
- super.onResume();
- }
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/tests/Android.bp b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/tests/Android.bp
index 5bf453e..606cba2 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/tests/Android.bp
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitLauncher/tests/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_system_experience",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/Android.bp b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/Android.bp
index 5a6e7ef..b434c87 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/Android.bp
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/Android.bp
@@ -26,10 +26,10 @@
static_libs: [
"CarSystemUI-core",
"car-portrait-ui-common",
+ "androidx.car.app_app",
],
libs: [
- "android.car-system-stubs",
"android.car",
],
@@ -75,13 +75,13 @@
resource_dirs: ["res"],
libs: [
- "android.car-system-stubs",
"android.car",
],
static_libs: [
"CarSystemUI-tests",
"car-portrait-ui-common",
+ "androidx.car.app_app",
],
plugins: ["dagger2-compiler"],
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_background.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_background.xml
index 7104440..ff3d0ce 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_background.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/fan_speed_seek_bar_background.xml
@@ -22,7 +22,7 @@
</shape>
</item>
<item
- android:gravity="left"
+ android:gravity="start"
android:width="@dimen/hvac_panel_button_dimen">
<selector>
<item android:state_selected="true">
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background.xml
index 9bf7325..42d614a 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background.xml
@@ -27,11 +27,8 @@
</item>
<item
- android:id="@+id/nav_bar_button_unselected">
- <ripple android:color="@color/car_ui_ripple_color">
- <item android:drawable="@drawable/nav_bar_button_background_unselected" />
- </ripple>
- </item>
+ android:id="@+id/nav_bar_button_unselected"
+ android:drawable="@color/transparent"/>
<transition
android:drawable="@drawable/nav_bar_button_background_unselected_to_selected_anim"
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background_selected_to_unselected_anim.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background_selected_to_unselected_anim.xml
index b4c1167..0fbcff9 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background_selected_to_unselected_anim.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background_selected_to_unselected_anim.xml
@@ -38,7 +38,7 @@
android:duration="300"
android:startOffset="0"
android:valueFrom="@color/car_nav_icon_background_color_selected"
- android:valueTo="@color/car_nav_icon_background_color"
+ android:valueTo="@color/transparent"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.526,0 0,1 1.0,1.0" />
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background_unselected_to_selected_anim.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background_unselected_to_selected_anim.xml
index c8912ea..cbb4ec6 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background_unselected_to_selected_anim.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/nav_bar_button_background_unselected_to_selected_anim.xml
@@ -38,7 +38,7 @@
android:propertyName="fillColor"
android:duration="300"
android:startOffset="0"
- android:valueFrom="@color/car_nav_icon_background_color"
+ android:valueFrom="@color/transparent"
android:valueTo="@color/car_nav_icon_background_color_selected"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/privacy_chip_inactive_background_pill.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/privacy_chip_inactive_background_pill.xml
new file mode 100644
index 0000000..ad7d8b7
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/drawable/privacy_chip_inactive_background_pill.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <item>
+ <aapt:attr name="android:drawable">
+ <shape android:shape="rectangle">
+ <solid android:color="@color/system_bar_background_pill_color"/>
+ <corners android:radius="@dimen/system_bar_pill_radius"/>
+ </shape>
+ </aapt:attr>
+ </item>
+</layer-list>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/auth_credential_password_view.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/auth_credential_password_view.xml
index 488ece5..d888eff 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/auth_credential_password_view.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/auth_credential_password_view.xml
@@ -61,6 +61,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
+ <LinearLayout
+ android:id="@+id/customized_view_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
</LinearLayout>
<LinearLayout
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/auth_credential_pattern_view.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/auth_credential_pattern_view.xml
index 8d04647..7dfd94f 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/auth_credential_pattern_view.xml
@@ -61,6 +61,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
+ <LinearLayout
+ android:id="@+id/customized_view_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
</LinearLayout>
<LinearLayout
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/auth_credential_pin_view.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/auth_credential_pin_view.xml
index 5f327c0..fa333a1 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/auth_credential_pin_view.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/auth_credential_pin_view.xml
@@ -61,6 +61,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
+ <LinearLayout
+ android:id="@+id/customized_view_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
</LinearLayout>
<LinearLayout
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar_dock.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar_dock.xml
new file mode 100644
index 0000000..5fda563
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar_dock.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project.
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.systemui.car.systembar.CarUiPortraitSystemBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/system_bar_background"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:id="@+id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center">
+
+ <com.android.systemui.car.hvac.CarUiPortraitTemperatureControlView
+ android:id="@+id/driver_hvac"
+ style="@style/TemperatureControlView"
+ systemui:hvacAreaId="49">
+ <include layout="@layout/adjustable_temperature_view"/>
+ </com.android.systemui.car.hvac.CarUiPortraitTemperatureControlView>
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <com.android.systemui.car.systembar.CarUiPortraitAppGridButton
+ android:id="@+id/grid_nav"
+ style="@style/SystemBarButtonWithDock"
+ systemui:componentNames="com.android.car.portraitlauncher/com.android.car.carlauncher.AppGridActivity"
+ systemui:icon="@drawable/car_ic_apps"
+ systemui:highlightWhenSelected="false"
+ systemui:intent="intent:#Intent;action=com.android.car.carlauncher.ACTION_APP_GRID;package=com.android.car.portraitlauncher;launchFlags=0x24000000;end"
+ systemui:clearBackStack="false" />
+
+ <com.android.systemui.car.systembar.element.layout.CarSystemBarFrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/dock_container_margin"
+ systemui:controller="com.android.systemui.car.systembar.CarUiPortraitDockViewControllerWrapper">
+ <com.android.car.docklib.view.DockView
+ android:id="@+id/dock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </com.android.systemui.car.systembar.element.layout.CarSystemBarFrameLayout>
+
+ <!-- TODO(b/255887799): Use AssistantButton instead of CarUiAssistantButton once is fixed. -->
+ <com.android.systemui.car.systembar.CarUiAssistantButton
+ android:id="@+id/assist"
+ style="@style/SystemBarButtonWithDock"
+ systemui:icon="@drawable/car_ic_mic"
+ systemui:highlightWhenSelected="true"
+ systemui:useDefaultAppIconForRole="true"/>
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <com.android.systemui.car.hvac.CarUiPortraitTemperatureControlView
+ android:id="@+id/passenger_hvac"
+ style="@style/TemperatureControlView"
+ systemui:hvacAreaId="68">
+ <include layout="@layout/adjustable_temperature_view"/>
+ </com.android.systemui.car.hvac.CarUiPortraitTemperatureControlView>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/occlusion_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:layoutDirection="ltr"
+ android:visibility="gone">
+ <com.android.systemui.car.hvac.CarUiPortraitTemperatureControlView
+ android:id="@+id/driver_hvac"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:layout_gravity="start"
+ android:gravity="start|center_vertical"
+ systemui:hvacAreaId="49">
+ <include layout="@layout/adjustable_temperature_view"/>
+ </com.android.systemui.car.hvac.CarUiPortraitTemperatureControlView>
+
+ <com.android.systemui.car.hvac.CarUiPortraitTemperatureControlView
+ android:id="@+id/passenger_hvac"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:layout_weight="1"
+ android:gravity="end|center_vertical"
+ systemui:hvacAreaId="68">
+ <include layout="@layout/adjustable_temperature_view"/>
+ </com.android.systemui.car.hvac.CarUiPortraitTemperatureControlView>
+ </LinearLayout>
+</com.android.systemui.car.systembar.CarUiPortraitSystemBarView>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar.xml
index d24bdb9..ae2173e 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar.xml
@@ -28,15 +28,13 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <LinearLayout
- android:id="@+id/qc_entry_points_container"
+ <include layout="@layout/qc_status_icons_horizontal"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_marginStart="@dimen/car_quick_controls_entry_points_start_margin"
- android:orientation="horizontal"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/clock"
- android:layout_alignParentStart="true"/>
+ android:layout_alignParentStart="true" />
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar_dock.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar_dock.xml
new file mode 100644
index 0000000..5dfdc90
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar_dock.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="utf-8" ?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.systemui.car.systembar.CarUiPortraitSystemBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/car_top_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include
+ layout="@layout/qc_status_icons_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/car_quick_controls_entry_points_start_margin"
+ android:layout_centerVertical="true"
+ android:layout_toStartOf="@+id/clock"
+ android:layout_alignParentStart="true" />
+
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:paddingStart="@dimen/car_padding_2"
+ android:paddingEnd="@dimen/car_padding_2"
+ android:elevation="@dimen/clock_elevation"
+ android:singleLine="true"
+ style="@style/TextAppearance.TopSystemBar.Text"
+ systemui:amPmStyle="normal" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:layout_alignParentEnd="true"
+ android:layout_toEndOf="@+id/clock"
+ android:orientation="horizontal"
+ android:gravity="end|center_vertical">
+
+ <include
+ layout="@layout/camera_privacy_chip"
+ android:layout_width="@dimen/top_system_bar_icon_size"
+ android:layout_height="@dimen/top_system_bar_icon_size"
+ android:layout_marginStart="@dimen/top_system_bar_icon_horizontal_margin"
+ android:layout_marginEnd="@dimen/top_system_bar_icon_horizontal_margin" />
+
+ <include
+ layout="@layout/mic_privacy_chip"
+ android:layout_width="@dimen/top_system_bar_icon_size"
+ android:layout_height="@dimen/top_system_bar_icon_size"
+ android:layout_marginStart="@dimen/top_system_bar_icon_horizontal_margin"
+ android:layout_marginEnd="@dimen/top_system_bar_icon_horizontal_margin" />
+
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/read_only_sensor_text"
+ android:layout_width="@dimen/statusbar_sensor_text_width"
+ android:layout_height="match_parent"
+ android:layout_marginHorizontal="@dimen/top_system_bar_icon_horizontal_margin"
+ android:layout_gravity="center"
+ systemui:controller="com.android.systemui.car.statusicon.ui.StatusBarSensorInfoController" />
+
+ <com.android.systemui.car.systembar.CarUiPortraitNotificationButton
+ android:id="@+id/standalone_notifications"
+ android:contentDescription="@string/system_bar_notifications_label"
+ style="@style/CarTopSystemBarButton"
+ android:background="@drawable/status_icon_background"
+ android:layout_toLeftOf="@id/user_name"
+ systemui:componentNames="com.android.car.notification/.CarNotificationCenterActivity"
+ systemui:packages="com.android.car.notification"
+ systemui:highlightWhenSelected="true"
+ systemui:clearBackStack="false"
+ systemui:intent="intent:#Intent;component=com.android.car.notification/.CarNotificationCenterActivity;launchFlags=0x24000000;end"
+ systemui:longIntent="intent:#Intent;action=com.android.car.bugreport.action.START_AUDIO_FIRST;component=com.android.car.bugreport/.BugReportActivity;end">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center">
+
+ <ImageView
+ android:id="@+id/notification_icon"
+ android:layout_width="@dimen/top_system_bar_icon_drawing_size"
+ android:layout_height="@dimen/top_system_bar_icon_drawing_size"
+ android:layout_gravity="center"
+ android:src="@drawable/car_ic_notification"
+ android:tint="@color/system_bar_icon_color_with_selection" />
+
+ </LinearLayout>
+
+ </com.android.systemui.car.systembar.CarUiPortraitNotificationButton>
+
+ <com.android.systemui.car.systembar.CarTopSystemBarButton
+ android:id="@+id/user_name"
+ style="@style/CarTopSystemBarButton"
+ android:background="@drawable/status_icon_background"
+ systemui:intent="intent:#Intent;component=com.android.car.settings/.profiles.ProfileSwitcherActivity;launchFlags=0x24000000;end">
+
+ <ImageView
+ android:id="@+id/user_avatar"
+ android:layout_gravity="center"
+ android:layout_width="@dimen/top_system_bar_icon_drawing_size"
+ android:layout_height="@dimen/top_system_bar_icon_drawing_size"
+ android:src="@drawable/car_ic_user_icon"
+ android:tint="@color/system_bar_icon_color_with_selection" />
+
+ </com.android.systemui.car.systembar.CarTopSystemBarButton>
+
+ </LinearLayout>
+
+ </RelativeLayout>
+
+</com.android.systemui.car.systembar.CarUiPortraitSystemBarView>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar_unprovisioned.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar_unprovisioned.xml
index 3aeb72e..5db5a47 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar_unprovisioned.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar_unprovisioned.xml
@@ -28,15 +28,13 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <LinearLayout
- android:id="@+id/qc_entry_points_container"
+ <include layout="@layout/qc_status_icons_horizontal"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_marginStart="@dimen/car_quick_controls_entry_points_start_margin"
- android:orientation="horizontal"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/clock"
- android:layout_alignParentStart="true"/>
+ android:layout_alignParentStart="true" />
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/qc_status_icons_horizontal.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/qc_status_icons_horizontal.xml
new file mode 100644
index 0000000..f693aae
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/qc_status_icons_horizontal.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
+ android:id="@+id/bluetooth_panel_button"
+ android:orientation="horizontal"
+ android:gravity="center"
+ style="@style/TopBarButton"
+ systemui:panelLayoutRes="@layout/qc_bluetooth_panel"
+ systemui:disabledWhileUnprovisioned="true"
+ systemui:systemBarDisable2Flags="quickSettings">
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/bluetooth_status_icon"
+ android:layout_width="@dimen/top_system_bar_qc_icon_drawing_size"
+ android:layout_height="@dimen/top_system_bar_qc_icon_drawing_size"
+ android:layout_gravity="center"
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.BluetoothStatusIconController"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
+ android:id="@+id/connectivity_panel_button"
+ android:orientation="horizontal"
+ android:gravity="center"
+ style="@style/TopBarButton"
+ systemui:panelLayoutRes="@layout/qc_connectivity_panel"
+ systemui:disabledWhileUnprovisioned="true"
+ systemui:systemBarDisable2Flags="quickSettings">
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/connectivity_status_icon"
+ android:layout_width="@dimen/top_system_bar_qc_icon_drawing_size"
+ android:layout_height="@dimen/top_system_bar_qc_icon_drawing_size"
+ android:layout_gravity="center"
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.SignalStatusIconController"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
+ android:id="@+id/display_panel_button"
+ android:orientation="horizontal"
+ android:gravity="center"
+ style="@style/TopBarButton"
+ systemui:panelLayoutRes="@layout/qc_display_panel"
+ systemui:disabledWhileUnprovisioned="true"
+ systemui:systemBarDisable2Flags="quickSettings">
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/display_status_icon"
+ android:layout_width="@dimen/top_system_bar_qc_icon_drawing_size"
+ android:layout_height="@dimen/top_system_bar_qc_icon_drawing_size"
+ android:layout_gravity="center"
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.DisplayStatusIconController"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
+</LinearLayout>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml
index 49874af..a538006 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml
@@ -67,4 +67,26 @@
<!-- 1 left -->
<!-- 2 right -->
<integer name="config_showDisplayCompatToolbarOnSystemBar">0</integer>
+
+ <!-- A list of components that are shown on Dock by default -->
+ <string-array name="config_defaultDockApps" translatable="false">
+ <item>com.android.vending/com.google.android.finsky.carmainactivity.MainActivity</item>
+ <item>com.google.android.bluetooth/com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService</item>
+ <item>com.android.car.dialer/com.android.car.dialer.ui.TelecomActivity</item>
+ </string-array>
+
+ <!-- A list of packages that are excluded from being shown on Dock -->
+ <string-array name="config_packagesExcludedFromDock" translatable="false">
+ <item>com.android.car.portraitlauncher</item>
+ <item>com.android.car.notification</item>
+ <item>android.car.usb.handler</item>
+ </string-array>
+
+ <!-- Blocking activity feature flag -->
+ <bool name="config_enableAppBlockingActivities">false</bool>
+
+ <string-array name="config_backgroundActivities" translatable="false">
+ <item>com.google.android.apps.maps/com.google.android.maps.MapsActivity</item>
+ <item>com.android.car.portraitlauncher/com.android.car.portraitlauncher.homeactivities.BackgroundPanelBaseActivity</item>
+ </string-array>
</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/dimens.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/dimens.xml
index 6739fd1..a6fcf5c 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/dimens.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/dimens.xml
@@ -61,7 +61,7 @@
<item name="hvac_heat_or_cool_off_alpha" format="float" type="dimen">0.3</item>
<dimen name="system_bar_icon_drawing_size">56dp</dimen>
- <dimen name="system_bar_button_size">88dp</dimen>
+ <dimen name="system_bar_button_size">72dp</dimen>
<!-- Margin between the system bar buttons -->
<dimen name="system_bar_button_margin">16dp</dimen>
<!-- Padding between the system bar button and the icon within it -->
@@ -73,7 +73,6 @@
<dimen name="system_bar_minimize_icon_height">17dp</dimen>
<dimen name="system_bar_minimize_icon_width">28dp</dimen>
-
<dimen name="user_avatar_background_radius">13dp</dimen>
<dimen name="privacy_chip_horizontal_padding">0dp</dimen>
@@ -120,4 +119,7 @@
<dimen name="pin_pad_key_height">108dp</dimen>
<dimen name="pin_pad_key_margin">10dp</dimen>
<dimen name="lock_pattern_dot_size">32dp</dimen>
+
+ <dimen name="dock_container_margin">@*android:dimen/car_padding_3</dimen>
+ <dimen name="dock_item_size">80dp</dimen>
</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/styles.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/styles.xml
index cfdfd39..3ae0d53 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/styles.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/styles.xml
@@ -34,6 +34,13 @@
<item name="selectedAlpha">1.0</item>
</style>
+ <!-- TODO(b/304320644): update SystemBarButton -->
+ <style name="SystemBarButtonWithDock" parent="SystemBarButton">
+ <item name="android:layout_marginStart">0dp</item>
+ <item name="android:layout_marginEnd">0dp</item>
+ <item name="android:layout_centerVertical">true</item>
+ </style>
+
<style name="CarTopSystemBarButton">
<item name="android:layout_width">@dimen/top_system_bar_icon_size</item>
<item name="android:layout_height">@dimen/top_system_bar_icon_size</item>
@@ -67,7 +74,8 @@
<item name="android:textColor">@color/car_on_surface_variant</item>
</style>
- <style name="QuickControlEntryPointButton">
+ <style name="TopBarButton">
+ <item name="android:layout_marginStart">@dimen/top_system_bar_icon_horizontal_margin</item>
<item name="android:layout_marginEnd">@dimen/top_system_bar_icon_horizontal_margin</item>
<item name="android:background">@drawable/status_icon_background</item>
<item name="android:layout_width">@dimen/top_system_bar_icon_size</item>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaController.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaController.java
index 6f80d76..d7df512 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaController.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaController.java
@@ -16,7 +16,7 @@
package com.android.systemui.car.displayarea;
-import static com.android.car.caruiportrait.common.service.CarUiPortraitService.INTENT_EXTRA_COLLAPSE_NOTIFICATION_PANEL;
+import static com.android.car.caruiportrait.common.service.CarUiPortraitService.INTENT_EXTRA_COLLAPSE_APPLICATION_PANEL;
import static com.android.car.caruiportrait.common.service.CarUiPortraitService.INTENT_EXTRA_HIDE_SYSTEM_BAR_FOR_IMMERSIVE_MODE;
import static com.android.car.caruiportrait.common.service.CarUiPortraitService.INTENT_EXTRA_IMMERSIVE_MODE_REQUESTED_SOURCE;
import static com.android.car.caruiportrait.common.service.CarUiPortraitService.INTENT_EXTRA_IS_IMMERSIVE_MODE_REQUESTED;
@@ -210,7 +210,7 @@
@Override
public void animateCollapsePanels(int flags, boolean force) {
Intent intent = new Intent(REQUEST_FROM_SYSTEM_UI);
- intent.putExtra(INTENT_EXTRA_COLLAPSE_NOTIFICATION_PANEL, true);
+ intent.putExtra(INTENT_EXTRA_COLLAPSE_APPLICATION_PANEL, true);
mApplicationContext.sendBroadcastAsUser(intent,
new UserHandle(ActivityManager.getCurrentUser()));
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/dock/BackgroundExcludedItemsProvider.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/dock/BackgroundExcludedItemsProvider.java
new file mode 100644
index 0000000..b9f4bc2
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/dock/BackgroundExcludedItemsProvider.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.dock;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+
+import com.android.car.docklib.ExcludedItemsProvider;
+import com.android.systemui.R;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * {@link ExcludedItemsProvider}] that excludes packages and components that are shown in the
+ * background panel.
+ */
+public class BackgroundExcludedItemsProvider implements ExcludedItemsProvider {
+ // todo(b/280647032): Functionality copied from TaskCategoryManager
+
+ private static final String STUB_GEO_DATA = "geo:0.0,0,0";
+ private final Context mContext;
+ private final Set<ComponentName> mBackgroundActivities = new HashSet<>();
+ private final ApplicationInstallUninstallReceiver mApplicationInstallUninstallReceiver;
+
+ public BackgroundExcludedItemsProvider(Context context) {
+ mContext = context;
+ mApplicationInstallUninstallReceiver = registerApplicationInstallUninstallReceiver();
+ updateBackgroundActivityMap();
+ }
+
+ @Override
+ public boolean isPackageExcluded(@NonNull String pkg) {
+ return false;
+ }
+
+ @Override
+ public boolean isComponentExcluded(@NonNull ComponentName component) {
+ return mBackgroundActivities.stream().anyMatch(cn -> cn.equals(component));
+ }
+
+ /** Responsible to unregister all receivers and performs necessary cleanup. */
+ public void destroy() {
+ mContext.unregisterReceiver(mApplicationInstallUninstallReceiver);
+ }
+
+ private void updateBackgroundActivityMap() {
+ mBackgroundActivities.clear();
+ Intent intent = new Intent(CarContext.ACTION_NAVIGATE, Uri.parse(STUB_GEO_DATA));
+ List<ResolveInfo> result = mContext.getPackageManager().queryIntentActivitiesAsUser(
+ intent, PackageManager.MATCH_ALL, mContext.getUser());
+
+ for (ResolveInfo info : result) {
+ if (info == null || info.activityInfo == null
+ || info.activityInfo.getComponentName() == null) {
+ continue;
+ }
+ mBackgroundActivities.add(info.getComponentInfo().getComponentName());
+ }
+
+ mBackgroundActivities.addAll(convertToComponentNames(mContext.getResources()
+ .getStringArray(R.array.config_backgroundActivities)));
+ }
+
+ private ApplicationInstallUninstallReceiver registerApplicationInstallUninstallReceiver() {
+ ApplicationInstallUninstallReceiver
+ installUninstallReceiver = new ApplicationInstallUninstallReceiver();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(installUninstallReceiver, filter);
+ return installUninstallReceiver;
+ }
+
+ private static ArraySet<ComponentName> convertToComponentNames(String[] componentStrings) {
+ ArraySet<ComponentName> componentNames = new ArraySet<>(componentStrings.length);
+ for (int i = componentStrings.length - 1; i >= 0; i--) {
+ componentNames.add(ComponentName.unflattenFromString(componentStrings[i]));
+ }
+ return componentNames;
+ }
+
+ private class ApplicationInstallUninstallReceiver extends BroadcastReceiver {
+ @MainThread
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getData() == null) {
+ return;
+ }
+ String packageName = intent.getData().getSchemeSpecificPart();
+ String action = intent.getAction();
+ if (TextUtils.isEmpty(packageName) && TextUtils.isEmpty(action)) {
+ // Ignoring empty announcements
+ return;
+ }
+ updateBackgroundActivityMap();
+ }
+ }
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/hvac/CarUiPortraitTemperatureControlView.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/hvac/CarUiPortraitTemperatureControlView.java
index 5c1946b..bc03006 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/hvac/CarUiPortraitTemperatureControlView.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/hvac/CarUiPortraitTemperatureControlView.java
@@ -164,6 +164,13 @@
}
/**
+ * Set the {@link OnClickListener} for the temperature TextView.
+ */
+ public void setTemperatureTextClickListener(OnClickListener onClickListener) {
+ mTempTextView.setOnClickListener(onClickListener);
+ }
+
+ /**
* Updates the temperature view logic on the UI thread.
*/
protected void updateTemperatureViewUiThread() {
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitAppGridButton.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitAppGridButton.java
index 887de41..b12d270 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitAppGridButton.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitAppGridButton.java
@@ -64,10 +64,18 @@
@Override
protected OnClickListener getButtonClickListener(Intent toSend) {
- return mRecentsButtonStateProvider.getButtonClickListener(toSend,
- super::getButtonClickListener);
+ return v -> {
+ if (mIsAppGridActive) {
+ collapseApplicationPanel();
+ return;
+ }
+ mRecentsButtonStateProvider.getButtonClickListener(toSend,
+ super::getButtonClickListener).onClick(v);
+ };
}
+
+
@Override
protected void updateImage(AlphaOptimizedImageView icon) {
mRecentsButtonStateProvider.updateImage(icon, super::updateImage);
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitDockViewControllerWrapper.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitDockViewControllerWrapper.java
new file mode 100644
index 0000000..663ba52
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitDockViewControllerWrapper.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.systembar;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.car.docklib.DockViewController;
+import com.android.car.docklib.ExcludedItemsProvider;
+import com.android.car.docklib.view.DockView;
+import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.dock.BackgroundExcludedItemsProvider;
+import com.android.systemui.car.systembar.element.CarSystemBarElementController;
+import com.android.systemui.car.systembar.element.CarSystemBarElementStateController;
+import com.android.systemui.car.systembar.element.CarSystemBarElementStatusBarDisableController;
+import com.android.systemui.car.systembar.element.layout.CarSystemBarFrameLayout;
+import com.android.systemui.settings.UserFileManager;
+import com.android.systemui.settings.UserTracker;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+import java.io.File;
+import java.util.Set;
+
+public class CarUiPortraitDockViewControllerWrapper extends DockViewControllerWrapper {
+ private BackgroundExcludedItemsProvider mBackgroundExcludedItemsProvider;
+
+ @AssistedInject
+ CarUiPortraitDockViewControllerWrapper(
+ @Assisted CarSystemBarFrameLayout view,
+ CarSystemBarElementStatusBarDisableController disableController,
+ CarSystemBarElementStateController stateController,
+ Context context, UserTracker userTracker,
+ UserFileManager userFileManager,
+ CarServiceProvider carServiceProvider) {
+ super(view, disableController, stateController, context, userTracker, userFileManager,
+ carServiceProvider);
+ }
+
+ @AssistedFactory
+ public interface Factory extends
+ CarSystemBarElementController.Factory<CarSystemBarFrameLayout,
+ CarUiPortraitDockViewControllerWrapper> {}
+
+ @Override
+ protected DockViewController createDockViewController(DockView dockView, Context userContext,
+ File dataFile) {
+ return new DockViewController(dockView, userContext, dataFile) {
+ @Override
+ public void destroy() {
+ super.destroy();
+ mBackgroundExcludedItemsProvider.destroy();
+ mBackgroundExcludedItemsProvider = null;
+ }
+
+ @NonNull
+ @Override
+ public Set<ExcludedItemsProvider> getExcludedItemsProviders() {
+ if (mBackgroundExcludedItemsProvider == null) {
+ mBackgroundExcludedItemsProvider =
+ new BackgroundExcludedItemsProvider(userContext);
+ }
+ Set<ExcludedItemsProvider> providers = super.getExcludedItemsProviders();
+ providers.add(mBackgroundExcludedItemsProvider);
+ return providers;
+ }
+ };
+ }
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitNotificationButton.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitNotificationButton.java
index 2a092dd..0e95ffa 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitNotificationButton.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitNotificationButton.java
@@ -17,12 +17,32 @@
package com.android.systemui.car.systembar;
import android.content.Context;
+import android.content.Intent;
import android.util.AttributeSet;
/** The button used to show the notification in the system bar. */
public class CarUiPortraitNotificationButton extends CarUiPortraitSystemBarButton {
+ private boolean mIsSelected;
+
public CarUiPortraitNotificationButton(Context context,
AttributeSet attrs) {
super(context, attrs);
}
+
+ @Override
+ public void setSelected(boolean selected) {
+ super.setSelected(selected);
+ mIsSelected = selected;
+ }
+
+ @Override
+ protected OnClickListener getButtonClickListener(Intent toSend) {
+ return v -> {
+ if (mIsSelected) {
+ collapseApplicationPanel();
+ return;
+ }
+ super.getButtonClickListener(toSend).onClick(v);
+ };
+ }
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitSystemBarButton.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitSystemBarButton.java
index 743f2f4..219274b 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitSystemBarButton.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitSystemBarButton.java
@@ -16,15 +16,19 @@
package com.android.systemui.car.systembar;
+import static com.android.car.caruiportrait.common.service.CarUiPortraitService.INTENT_EXTRA_COLLAPSE_APPLICATION_PANEL;
import static com.android.car.caruiportrait.common.service.CarUiPortraitService.INTENT_EXTRA_FG_TASK_VIEW_READY;
import static com.android.car.caruiportrait.common.service.CarUiPortraitService.REQUEST_FROM_LAUNCHER;
+import static com.android.car.caruiportrait.common.service.CarUiPortraitService.REQUEST_FROM_SYSTEM_UI;
+import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.os.Build;
+import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.Toast;
@@ -44,11 +48,12 @@
// this is static so that we can save its state when configuration changes
private static boolean sTaskViewReady = false;
+ private final Context mContext;
public CarUiPortraitSystemBarButton(Context context, AttributeSet attrs) {
super(context, attrs);
logIfDebuggable("CarUiPortraitSystemBarButton");
-
+ mContext = context;
// disable button by default
super.setDisabled(/* disabled= */ true, getDisabledRunnable(context));
@@ -96,4 +101,11 @@
return () -> Toast.makeText(context, R.string.task_view_not_ready_message,
Toast.LENGTH_LONG).show();
}
+
+ protected void collapseApplicationPanel() {
+ Intent intent = new Intent(REQUEST_FROM_SYSTEM_UI);
+ intent.putExtra(INTENT_EXTRA_COLLAPSE_APPLICATION_PANEL, /* value= */ true);
+ mContext.getApplicationContext().sendBroadcastAsUser(intent,
+ new UserHandle(ActivityManager.getCurrentUser()));
+ }
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitSystemBarModule.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitSystemBarModule.java
index 9f9f76d..dd28972 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitSystemBarModule.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitSystemBarModule.java
@@ -21,10 +21,14 @@
import com.android.systemui.car.dagger.CarSysUIDynamicOverride;
import com.android.systemui.car.displayarea.CarDisplayAreaController;
+import com.android.systemui.car.systembar.element.CarSystemBarElementController;
import com.android.systemui.dagger.SysUISingleton;
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
/**
* Dagger injection module for {@link CarSystemBar} in CarUiPortraitSystemUI.
@@ -57,4 +61,11 @@
return new CarUiPortraitButtonSelectionStateController(context);
}
+
+ /** Injects CarUiPortraitDockViewControllerWrapper */
+ @Binds
+ @IntoMap
+ @ClassKey(CarUiPortraitDockViewControllerWrapper.class)
+ public abstract CarSystemBarElementController.Factory bindPortraitDockViewControllerWrapper(
+ CarUiPortraitDockViewControllerWrapper.Factory factory);
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitSystemBarView.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitSystemBarView.java
new file mode 100644
index 0000000..1bc95f3
--- /dev/null
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiPortraitSystemBarView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.systembar;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.car.dockutil.Flags;
+import com.android.systemui.R;
+import com.android.systemui.car.hvac.CarUiPortraitTemperatureControlView;
+import com.android.systemui.car.hvac.HvacView;
+
+/**
+ * A custom system bar for the automotive use case.
+ * <p>
+ * The system bar in the automotive use case is more like a list of shortcuts, rendered
+ * in a linear layout.
+ */
+public class CarUiPortraitSystemBarView extends CarSystemBarView {
+ public CarUiPortraitSystemBarView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ void setupHvacButton() {
+ super.setupHvacButton();
+ HvacView driverHvacView = findViewById(R.id.driver_hvac);
+ HvacView passengerHvacView = findViewById(R.id.passenger_hvac);
+
+ if (Flags.dockFeature()) {
+ if (driverHvacView instanceof CarUiPortraitTemperatureControlView) {
+ ((CarUiPortraitTemperatureControlView) driverHvacView)
+ .setTemperatureTextClickListener(this::onHvacClick);
+ }
+ if (passengerHvacView instanceof CarUiPortraitTemperatureControlView) {
+ ((CarUiPortraitTemperatureControlView) passengerHvacView)
+ .setTemperatureTextClickListener(this::onHvacClick);
+ }
+ }
+ }
+}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiRecentsButtonStateProvider.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiRecentsButtonStateProvider.java
index 46e5e73..5ce7214 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiRecentsButtonStateProvider.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/systembar/CarUiRecentsButtonStateProvider.java
@@ -16,7 +16,7 @@
package com.android.systemui.car.systembar;
-import static com.android.car.caruiportrait.common.service.CarUiPortraitService.INTENT_EXTRA_COLLAPSE_RECENTS_PANEL;
+import static com.android.car.caruiportrait.common.service.CarUiPortraitService.INTENT_EXTRA_COLLAPSE_APPLICATION_PANEL;
import static com.android.car.caruiportrait.common.service.CarUiPortraitService.REQUEST_FROM_SYSTEM_UI;
import android.app.ActivityManager;
@@ -45,7 +45,7 @@
protected boolean toggleRecents() {
if (getIsRecentsActive()) {
Intent intent = new Intent(REQUEST_FROM_SYSTEM_UI);
- intent.putExtra(INTENT_EXTRA_COLLAPSE_RECENTS_PANEL, true);
+ intent.putExtra(INTENT_EXTRA_COLLAPSE_APPLICATION_PANEL, true);
mContext.getApplicationContext().sendBroadcastAsUser(intent,
new UserHandle(ActivityManager.getCurrentUser()));
return true;
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/wmshell/CarUiPortraitWMShellModule.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/wmshell/CarUiPortraitWMShellModule.java
index 8aff2b7..a022712 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/wmshell/CarUiPortraitWMShellModule.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/wmshell/CarUiPortraitWMShellModule.java
@@ -21,7 +21,7 @@
import android.view.IWindowManager;
import com.android.systemui.car.CarServiceProvider;
-import com.android.systemui.car.taskview.CarFullscreenTaskMonitorListener;
+import com.android.systemui.car.wm.CarFullscreenTaskMonitorListener;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.wm.CarUiPortraitDisplaySystemBarsController;
import com.android.systemui.wm.DisplaySystemBarsController;
diff --git a/car_product/car_ui_portrait/car_ui_portrait_hardware.xml b/car_product/car_ui_portrait/car_ui_portrait_hardware.xml
index 1438a43..97c2d28 100644
--- a/car_product/car_ui_portrait/car_ui_portrait_hardware.xml
+++ b/car_product/car_ui_portrait/car_ui_portrait_hardware.xml
@@ -22,6 +22,8 @@
in split screen mode by default, instead of full screen. Unlike Android's multi-window mode,
where users can choose how to display apps, the device determines how apps are shown.-->
<feature name="android.software.car.splitscreen_multitasking"/>
+ <feature name="android.hardware.screen.portrait"/>
<!-- Disallow third-party IME apps. -->
<unavailable-feature name="android.software.input_methods"/>
+ <unavailable-feature name="android.hardware.screen.landscape"/>
</permissions>
diff --git a/car_product/car_ui_portrait/rro/CarUIPortraitCommon/res/values-night/color_palettes.xml b/car_product/car_ui_portrait/rro/CarUIPortraitCommon/res/values-night/color_palettes.xml
index 04322ea..72dd4b1 100644
--- a/car_product/car_ui_portrait/rro/CarUIPortraitCommon/res/values-night/color_palettes.xml
+++ b/car_product/car_ui_portrait/rro/CarUIPortraitCommon/res/values-night/color_palettes.xml
@@ -34,25 +34,38 @@
<color name="car_tertiary_container">@color/car_tertiary_30</color>
<color name="car_on_tertiary_container">@color/car_tertiary_90</color>
+ <!-- text colors, auto specific -->
+ <color name="car_text_primary">@color/car_neutral_95</color>
+ <color name="car_text_secondary">@color/car_neutral_variant_70</color>
+
<!-- error colors -->
- <color name="car_error">@color/car_error_80</color>
- <color name="car_on_error">@color/car_error_20</color>
- <color name="car_error_container">@color/car_error_30</color>
- <color name="car_on_error_container">@color/car_error_80</color>
+ <color name="car_error">@color/car_red_80</color>
+ <color name="car_on_error">@color/car_red_20</color>
+ <color name="car_error_container">@color/car_red_30</color>
+ <color name="car_on_error_container">@color/car_red_90</color>
<!-- background colors -->
<color name="car_background">@color/car_neutral_10</color>
<color name="car_on_background">@color/car_neutral_90</color>
<!-- surface colors -->
- <color name="car_surface">@color/car_neutral_10</color>
+ <color name="car_surface">@color/car_neutral_2</color>
<color name="car_on_surface">@color/car_neutral_90</color>
+ <color name="car_surface_dim">@color/car_neutral_6</color>
+ <color name="car_surface_bright">@color/car_neutral_24</color>
+ <color name="car_inverse_surface">@color/car_neutral_90</color>
+ <color name="car_inverse_on_surface">@color/car_neutral_20</color>
+ <color name="car_surface_container">@color/car_neutral_12</color>
+ <color name="car_surface_container_low">@color/car_neutral_10</color>
+ <color name="car_surface_container_lowest">@color/car_neutral_4</color>
+ <color name="car_surface_container_high">@color/car_neutral_17</color>
+ <color name="car_surface_container_highest">@color/car_neutral_22</color>
<color name="car_surface_variant">@color/car_neutral_variant_30</color>
<color name="car_on_surface_variant">@color/car_neutral_variant_80</color>
<color name="car_outline">@color/car_neutral_variant_60</color>
- <color name="car_shadow">@color/car_neutral_variant_0</color>
- <color name="car_inverse_surface">@color/car_neutral_90</color>
- <color name="car_inverse_on_surface">@color/car_neutral_20</color>
+ <color name="car_outline_variant">@color/car_neutral_variant_30</color>
+ <color name="car_scrim">@color/car_neutral_0</color>
+ <color name="car_shadow">@color/car_neutral_0</color>
<color name="car_inverse_primary">@color/car_primary_40</color>
<color name="car_surface_1">#222429</color>
<color name="car_surface_2">#272930</color>
@@ -61,64 +74,37 @@
<color name="car_surface_5">#30333e</color>
<!-- red colors -->
- <color name="car_red_color">@color/car_error_80</color>
- <color name="car_red_on_color">#690002</color>
- <color name="car_red_color_container">#940005</color>
- <color name="car_red_on_color_container">@color/car_error_90</color>
+ <color name="car_red_color">@color/car_red_70</color>
+ <color name="car_red_on_color">@color/car_red_10</color>
+ <color name="car_red_color_container">@color/car_red_30</color>
+ <color name="car_red_on_color_container">@color/car_red_90</color>
<color name="car_red_tint">#dc362e</color>
<color name="car_decline_red">#d77163</color>
<color name="car_on_decline_red">#1c0f0d</color>
<!-- blue colors -->
- <color name="car_blue_color">#acc7ff</color>
- <color name="car_blue_on_color">#002e6c</color>
- <color name="car_blue_color_container">#004397</color>
- <color name="car_blue_on_color_container">#d6e2ff</color>
+ <color name="car_blue_color">@color/car_blue_70</color>
+ <color name="car_blue_on_color">@color/car_blue_10</color>
+ <color name="car_blue_color_container">@color/car_blue_30</color>
+ <color name="car_blue_on_color_container">@color/car_blue_90</color>
<color name="car_blue_tint">#4285f4</color>
<!-- green colors -->
- <color name="car_green_color">#5cdf7b</color>
- <color name="car_green_on_color">#003912</color>
- <color name="car_green_color_container">#00531e</color>
- <color name="car_green_on_color_container">#7afd95</color>
+ <color name="car_green_color">@color/car_green_70</color>
+ <color name="car_green_on_color">@color/car_green_10</color>
+ <color name="car_green_color_container">@color/car_green_30</color>
+ <color name="car_green_on_color_container">@color/car_green_90</color>
<color name="car_green_tint">#37be5f</color>
<color name="car_confirm_green">#75b885</color>
<color name="car_on_confirm_green">#0f1811</color>
<!-- yellow colors -->
- <color name="car_yellow_color">#fbbc04</color>
- <color name="car_yellow_on_color">#402d00</color>
- <color name="car_yellow_color_container">#5c4200</color>
- <color name="car_yellow_on_color_container">#ffdf9c</color>
+ <color name="car_yellow_color">@color/car_yellow_70</color>
+ <color name="car_yellow_on_color">@color/car_yellow_10</color>
+ <color name="car_yellow_color_container">@color/car_yellow_30</color>
+ <color name="car_yellow_on_color_container">@color/car_yellow_90</color>
<color name="car_yellow_tint">#fbbc04</color>
- <!-- android dark colors -->
- <color name="car_color_accent_primary">@color/car_primary_90</color>
- <color name="car_color_accent_primary_variant">@color/car_primary_70</color>
- <color name="car_color_accent_secondary">@color/car_secondary_90</color>
- <color name="car_color_accent_secondary_variant">@color/car_secondary_70</color>
- <color name="car_color_accent_tertiary">@color/car_tertiary_90</color>
- <color name="car_color_accent_tertiary_variant">@color/car_tertiary_70</color>
- <color name="car_text_color_primary">@color/car_neutral_95</color>
- <color name="car_text_color_secondary">@color/car_neutral_variant_80</color>
- <color name="car_text_color_tertiary">@color/car_neutral_variant_60</color>
- <color name="car_text_color_primary_inverse">@color/car_neutral_10</color>
- <color name="car_text_color_secondary_inverse">@color/car_neutral_30</color>
- <color name="car_text_color_tertiary_inverse">@color/car_neutral_50</color>
- <color name="car_color_background">@color/car_neutral_10</color>
- <color name="car_color_background_floating">@color/car_neutral_10</color>
- <color name="car_color_surface">@color/car_neutral_20</color>
- <color name="car_color_surface_variant">@color/car_neutral_30</color>
- <color name="car_color_surface_highlight">@color/car_neutral_35</color>
- <color name="car_surface_header">@color/car_neutral_30</color>
- <color name="car_under_surface">@color/car_neutral_0</color>
- <color name="car_off_state">@color/car_neutral_20</color>
- <color name="car_accent_surface">@color/car_primary_95</color>
- <color name="car_text_primary_on_accent">@color/car_neutral_10</color>
- <color name="car_text_secondary_on_accent">@color/car_neutral_variant_30</color>
- <color name="car_volume_background">@color/car_neutral_25</color>
- <color name="car_scrim">@color/car_neutral_80</color>
-
<!-- control highlight colors -->
<color name="car_control_highlight">#99d9e2ff</color>
diff --git a/car_product/car_ui_portrait/rro/CarUIPortraitCommon/res/values/color_palettes.xml b/car_product/car_ui_portrait/rro/CarUIPortraitCommon/res/values/color_palettes.xml
index 90475d2..bab97f3 100644
--- a/car_product/car_ui_portrait/rro/CarUIPortraitCommon/res/values/color_palettes.xml
+++ b/car_product/car_ui_portrait/rro/CarUIPortraitCommon/res/values/color_palettes.xml
@@ -19,40 +19,53 @@
<!-- primary colors -->
<color name="car_primary">@color/car_primary_40</color>
<color name="car_on_primary">@color/car_primary_100</color>
- <color name="car_primary_container">@color/car_primary_90</color>
+ <color name="car_primary_container">@color/car_primary_88</color>
<color name="car_on_primary_container">@color/car_primary_10</color>
<!-- secondary colors -->
<color name="car_secondary">@color/car_secondary_40</color>
<color name="car_on_secondary">@color/car_secondary_100</color>
- <color name="car_secondary_container">@color/car_secondary_90</color>
+ <color name="car_secondary_container">@color/car_secondary_88</color>
<color name="car_on_secondary_container">@color/car_secondary_10</color>
<!-- tertiary colors -->
<color name="car_tertiary">@color/car_tertiary_40</color>
<color name="car_on_tertiary">@color/car_tertiary_100</color>
- <color name="car_tertiary_container">@color/car_tertiary_90</color>
+ <color name="car_tertiary_container">@color/car_tertiary_88</color>
<color name="car_on_tertiary_container">@color/car_tertiary_10</color>
+ <!-- text colors, auto specific -->
+ <color name="car_text_primary">@color/car_neutral_5</color>
+ <color name="car_text_secondary">@color/car_neutral_variant_30</color>
+
<!-- error colors -->
- <color name="car_error">@color/car_error_40</color>
- <color name="car_on_error">@color/car_error_100</color>
- <color name="car_error_container">@color/car_error_90</color>
- <color name="car_on_error_container">@color/car_error_10</color>
+ <color name="car_error">@color/car_red_40</color>
+ <color name="car_on_error">@color/car_red_100</color>
+ <color name="car_error_container">@color/car_red_90</color>
+ <color name="car_on_error_container">@color/car_red_10</color>
<!-- background colors -->
- <color name="car_background">@color/car_neutral_99</color>
+ <color name="car_background">@color/car_neutral_98</color>
<color name="car_on_background">@color/car_neutral_10</color>
<!-- surface colors -->
- <color name="car_surface">@color/car_neutral_99</color>
+ <color name="car_surface">@color/car_neutral_98</color>
<color name="car_on_surface">@color/car_neutral_10</color>
- <color name="car_surface_variant">@color/car_neutral_variant_90</color>
- <color name="car_on_surface_variant">@color/car_neutral_variant_30</color>
- <color name="car_outline">@color/car_neutral_variant_50</color>
- <color name="car_shadow">@color/car_neutral_variant_0</color>
+ <color name="car_surface_dim">@color/car_neutral_87</color>
+ <color name="car_surface_bright">@color/car_neutral_98</color>
<color name="car_inverse_surface">@color/car_neutral_20</color>
<color name="car_inverse_on_surface">@color/car_neutral_95</color>
+ <color name="car_surface_container">@color/car_neutral_92</color>
+ <color name="car_surface_container_low">@color/car_neutral_95</color>
+ <color name="car_surface_container_lowest">@color/car_neutral_100</color>
+ <color name="car_surface_container_high">@color/car_neutral_94</color>
+ <color name="car_surface_container_highest">@color/car_neutral_88</color>
+ <color name="car_surface_variant">@color/car_neutral_variant_85</color>
+ <color name="car_on_surface_variant">@color/car_neutral_variant_30</color>
+ <color name="car_outline">@color/car_neutral_variant_50</color>
+ <color name="car_outline_variant">@color/car_neutral_variant_80</color>
+ <color name="car_scrim">@color/car_neutral_0</color>
+ <color name="car_shadow">@color/car_neutral_0</color>
<color name="car_inverse_primary">@color/car_primary_80</color>
<color name="car_surface_1">#f2f3fd</color>
<color name="car_surface_2">#eaeefb</color>
@@ -61,64 +74,37 @@
<color name="car_surface_5">#dce4f8</color>
<!-- red colors -->
- <color name="car_red_color">#b91b19</color>
- <color name="car_red_on_color">@color/car_primary_100</color>
- <color name="car_red_color_container">@color/car_error_90</color>
- <color name="car_red_on_color_container">@color/car_error_10</color>
+ <color name="car_red_color">@color/car_red_40</color>
+ <color name="car_red_on_color">@color/car_red_100</color>
+ <color name="car_red_color_container">@color/car_red_90</color>
+ <color name="car_red_on_color_container">@color/car_red_10</color>
<color name="car_red_tint">#dc362e</color>
<color name="car_decline_red">#b91b19</color>
<color name="car_on_decline_red">@color/car_primary_100</color>
<!-- blue colors -->
- <color name="car_blue_color">#005ac5</color>
- <color name="car_blue_on_color">@color/car_primary_100</color>
- <color name="car_blue_color_container">#d6e2ff</color>
- <color name="car_blue_on_color_container">#001a43</color>
+ <color name="car_blue_color">@color/car_blue_40</color>
+ <color name="car_blue_on_color">@color/car_blue_100</color>
+ <color name="car_blue_color_container">@color/car_blue_90</color>
+ <color name="car_blue_on_color_container">@color/car_blue_10</color>
<color name="car_blue_tint">#4285f4</color>
<!-- green colors -->
- <color name="car_green_color">#006e2b</color>
- <color name="car_green_on_color">@color/car_primary_100</color>
- <color name="car_green_color_container">#7afd95</color>
- <color name="car_green_on_color_container">#002107</color>
+ <color name="car_green_color">@color/car_green_40</color>
+ <color name="car_green_on_color">@color/car_green_100</color>
+ <color name="car_green_color_container">@color/car_green_90</color>
+ <color name="car_green_on_color_container">@color/car_green_10</color>
<color name="car_green_tint">#37be5f</color>
<color name="car_confirm_green">#418544</color>
<color name="car_on_confirm_green">@color/car_primary_100</color>
<!-- yellow colors -->
- <color name="car_yellow_color">#7a5900</color>
- <color name="car_yellow_on_color">@color/car_primary_100</color>
- <color name="car_yellow_color_container">#ffdf9c</color>
- <color name="car_yellow_on_color_container">#261900</color>
+ <color name="car_yellow_color">@color/car_yellow_40</color>
+ <color name="car_yellow_on_color">@color/car_yellow_100</color>
+ <color name="car_yellow_color_container">@color/car_yellow_90</color>
+ <color name="car_yellow_on_color_container">@color/car_yellow_10</color>
<color name="car_yellow_tint">#fbbc04</color>
- <!-- android light colors -->
- <color name="car_color_accent_primary">@color/car_primary_90</color>
- <color name="car_color_accent_primary_variant">@color/car_primary_40</color>
- <color name="car_color_accent_secondary">@color/car_secondary_90</color>
- <color name="car_color_accent_secondary_variant">@color/car_secondary_40</color>
- <color name="car_color_accent_tertiary">@color/car_tertiary_90</color>
- <color name="car_color_accent_tertiary_variant">@color/car_tertiary_40</color>
- <color name="car_text_color_primary">@color/car_neutral_10</color>
- <color name="car_text_color_secondary">@color/car_neutral_variant_30</color>
- <color name="car_text_color_tertiary">@color/car_neutral_variant_50</color>
- <color name="car_text_color_primary_inverse">@color/car_neutral_95</color>
- <color name="car_text_color_secondary_inverse">@color/car_neutral_80</color>
- <color name="car_text_color_tertiary_inverse">@color/car_neutral_60</color>
- <color name="car_color_background">@color/car_neutral_93</color>
- <color name="car_color_background_floating">#fbf9fe</color>
- <color name="car_color_surface">#fbf9fe</color>
- <color name="car_color_surface_variant">@color/car_neutral_90</color>
- <color name="car_color_surface_highlight">@color/car_neutral_variant_100</color>
- <color name="car_surface_header">@color/car_neutral_90</color>
- <color name="car_under_surface">@color/car_neutral_0</color>
- <color name="car_off_state">@color/car_neutral_20</color>
- <color name="car_accent_surface">@color/car_secondary_95</color>
- <color name="car_text_primary_on_accent">@color/car_neutral_10</color>
- <color name="car_text_secondary_on_accent">@color/car_neutral_variant_30</color>
- <color name="car_volume_background">@color/car_neutral_25</color>
- <color name="car_scrim">@color/car_neutral_80</color>
-
<!-- control highlight colors -->
<color name="car_control_highlight">#99003fa4</color>
diff --git a/car_product/car_ui_portrait/rro/CarUIPortraitCommon/res/values/color_tonal_palettes.xml b/car_product/car_ui_portrait/rro/CarUIPortraitCommon/res/values/color_tonal_palettes.xml
index 197f2d6..d243500 100644
--- a/car_product/car_ui_portrait/rro/CarUIPortraitCommon/res/values/color_tonal_palettes.xml
+++ b/car_product/car_ui_portrait/rro/CarUIPortraitCommon/res/values/color_tonal_palettes.xml
@@ -18,55 +18,88 @@
<!--TODO: Convert to use design-tokens -->
<!-- primary colors -->
- <color name="car_primary_100">#ffffff</color>
- <color name="car_primary_99">#fdfbff</color>
- <color name="car_primary_95">#edf0ff</color>
- <color name="car_primary_93">#e5eaff</color>
- <color name="car_primary_90">#d9e2ff</color>
- <color name="car_primary_80">#b0c5ff</color>
- <color name="car_primary_70">#86a9ff</color>
- <color name="car_primary_60">#588cff</color>
- <color name="car_primary_50">#3670e9</color>
- <color name="car_primary_40">#0856cf</color>
- <color name="car_primary_30">#003fa4</color>
- <color name="car_primary_25">#00358d</color>
- <color name="car_primary_20">#002b76</color>
- <color name="car_primary_10">#00184a</color>
<color name="car_primary_0">#000000</color>
+ <color name="car_primary_2">#000810</color>
+ <color name="car_primary_4">#00101a</color>
+ <color name="car_primary_5">#00131e</color>
+ <color name="car_primary_6">#001521</color>
+ <color name="car_primary_10">#001e2d</color>
+ <color name="car_primary_12">#002233</color>
+ <color name="car_primary_17">#002d42</color>
+ <color name="car_primary_20">#00344b</color>
+ <color name="car_primary_22">#003951</color>
+ <color name="car_primary_24">#003e57</color>
+ <color name="car_primary_30">#004c6b</color>
+ <color name="car_primary_40">#1f6587</color>
+ <color name="car_primary_50">#3e7ea1</color>
+ <color name="car_primary_60">#5a98bc</color>
+ <color name="car_primary_70">#76b2d8</color>
+ <color name="car_primary_80">#91cef5</color>
+ <color name="car_primary_85">#a5dbff</color>
+ <color name="car_primary_87">#b2e0ff</color>
+ <color name="car_primary_88">#b9e2ff</color>
+ <color name="car_primary_90">#c5e7ff</color>
+ <color name="car_primary_92">#d2ecff</color>
+ <color name="car_primary_94">#def0ff</color>
+ <color name="car_primary_95">#e4f3ff</color>
+ <color name="car_primary_98">#f5faff</color>
+ <color name="car_primary_100">#ffffff</color>
<!-- secondary colors -->
- <color name="car_secondary_100">#ffffff</color>
- <color name="car_secondary_99">#fdfbff</color>
- <color name="car_secondary_95">#edf0ff</color>
- <color name="car_secondary_93">#e5eaff</color>
- <color name="car_secondary_90">#dde2fa</color>
- <color name="car_secondary_80">#c0c6dd</color>
- <color name="car_secondary_70">#a5abc1</color>
- <color name="car_secondary_60">#8a90a6</color>
- <color name="car_secondary_50">#70768b</color>
- <color name="car_secondary_40">#585e71</color>
- <color name="car_secondary_30">#404659</color>
- <color name="car_secondary_25">#353b4d</color>
- <color name="car_secondary_20">#2a3042</color>
- <color name="car_secondary_10">#151b2c</color>
<color name="car_secondary_0">#000000</color>
+ <color name="car_secondary_2">#000810</color>
+ <color name="car_secondary_4">#00101a</color>
+ <color name="car_secondary_5">#01131d</color>
+ <color name="car_secondary_6">#031520</color>
+ <color name="car_secondary_10">#0a1e28</color>
+ <color name="car_secondary_12">#0f222d</color>
+ <color name="car_secondary_17">#1a2c37</color>
+ <color name="car_secondary_20">#20333e</color>
+ <color name="car_secondary_22">#253743</color>
+ <color name="car_secondary_24">#293b47</color>
+ <color name="car_secondary_30">#374955</color>
+ <color name="car_secondary_40">#4f616d</color>
+ <color name="car_secondary_50">#677987</color>
+ <color name="car_secondary_60">#8093a1</color>
+ <color name="car_secondary_70">#9baebc</color>
+ <color name="car_secondary_80">#b6c9d8</color>
+ <color name="car_secondary_85">#c4d7e6</color>
+ <color name="car_secondary_87">#c9ddec</color>
+ <color name="car_secondary_88">#ccdfee</color>
+ <color name="car_secondary_90">#d2e5f4</color>
+ <color name="car_secondary_92">#d7ebfa</color>
+ <color name="car_secondary_94">#def0ff</color>
+ <color name="car_secondary_95">#e4f3ff</color>
+ <color name="car_secondary_98">#f5faff</color>
+ <color name="car_secondary_100">#ffffff</color>
<!-- tertiary colors -->
- <color name="car_tertiary_100">#ffffff</color>
- <color name="car_tertiary_99">#fcfcfc</color>
- <color name="car_tertiary_95">#ffebfb</color>
- <color name="car_tertiary_93">#ffe2fc</color>
- <color name="car_tertiary_90">#fed7f9</color>
- <color name="car_tertiary_80">#e1bbdd</color>
- <color name="car_tertiary_70">#c4a0c1</color>
- <color name="car_tertiary_60">#a886a5</color>
- <color name="car_tertiary_50">#8d6c8b</color>
- <color name="car_tertiary_40">#735571</color>
- <color name="car_tertiary_30">#5a3d59</color>
- <color name="car_tertiary_25">#4d324d</color>
- <color name="car_tertiary_20">#422742</color>
- <color name="car_tertiary_10">#2b122c</color>
<color name="car_tertiary_0">#000000</color>
+ <color name="car_tertiary_2">#090320</color>
+ <color name="car_tertiary_4">#100927</color>
+ <color name="car_tertiary_5">#130b2a</color>
+ <color name="car_tertiary_6">#150e2d</color>
+ <color name="car_tertiary_10">#1e1735</color>
+ <color name="car_tertiary_12">#221b3a</color>
+ <color name="car_tertiary_17">#2c2545</color>
+ <color name="car_tertiary_20">#332c4c</color>
+ <color name="car_tertiary_22">#373050</color>
+ <color name="car_tertiary_24">#3c3455</color>
+ <color name="car_tertiary_30">#4a4263</color>
+ <color name="car_tertiary_40">#62597c</color>
+ <color name="car_tertiary_50">#7b7296</color>
+ <color name="car_tertiary_60">#958bb1</color>
+ <color name="car_tertiary_70">#b0a6cd</color>
+ <color name="car_tertiary_80">#cbc1e9</color>
+ <color name="car_tertiary_85">#dacff8</color>
+ <color name="car_tertiary_87">#dfd4fd</color>
+ <color name="car_tertiary_88">#e2d7ff</color>
+ <color name="car_tertiary_90">#e7deff</color>
+ <color name="car_tertiary_92">#ede4ff</color>
+ <color name="car_tertiary_94">#f2ebff</color>
+ <color name="car_tertiary_95">#f5eeff</color>
+ <color name="car_tertiary_98">#fdf7ff</color>
+ <color name="car_tertiary_100">#ffffff</color>
<!-- error colors -->
<color name="car_error_100">#ffffff</color>
@@ -84,38 +117,170 @@
<color name="car_error_0">#000000</color>
<!-- neutral colors -->
- <color name="car_neutral_100">#ffffff</color>
- <color name="car_neutral_99">#fefbff</color>
- <color name="car_neutral_95">#f2f0f5</color>
- <color name="car_neutral_93">#eceaef</color>
- <color name="car_neutral_90">#e4e2e6</color>
- <color name="car_neutral_80">#c8c6ca</color>
- <color name="car_neutral_70">#acabaf</color>
- <color name="car_neutral_60">#919094</color>
- <color name="car_neutral_50">#77767a</color>
- <color name="car_neutral_40">#5e5e62</color>
- <color name="car_neutral_35">#525256</color>
- <color name="car_neutral_30">#46464a</color>
- <color name="car_neutral_25">#3b3b3e</color>
- <color name="car_neutral_20">#303033</color>
- <color name="car_neutral_10">#1b1b1e</color>
<color name="car_neutral_0">#000000</color>
+ <color name="car_neutral_2">#04080b</color>
+ <color name="car_neutral_4">#0a0f12</color>
+ <color name="car_neutral_5">#0d1115</color>
+ <color name="car_neutral_6">#0f1417</color>
+ <color name="car_neutral_10">#181c1f</color>
+ <color name="car_neutral_12">#1c2024</color>
+ <color name="car_neutral_17">#262b2e</color>
+ <color name="car_neutral_20">#2c3134</color>
+ <color name="car_neutral_22">#313539</color>
+ <color name="car_neutral_24">#353a3d</color>
+ <color name="car_neutral_30">#43474b</color>
+ <color name="car_neutral_40">#5a5f63</color>
+ <color name="car_neutral_50">#73787c</color>
+ <color name="car_neutral_60">#8d9195</color>
+ <color name="car_neutral_70">#a8acb0</color>
+ <color name="car_neutral_80">#c3c7cb</color>
+ <color name="car_neutral_85">#d1d5d9</color>
+ <color name="car_neutral_87">#d7dadf</color>
+ <color name="car_neutral_88">#d9dde2</color>
+ <color name="car_neutral_90">#dfe3e7</color>
+ <color name="car_neutral_92">#e5e8ed</color>
+ <color name="car_neutral_94">#ebeef3</color>
+ <color name="car_neutral_95">#eef1f6</color>
+ <color name="car_neutral_98">#f6fafe</color>
+ <color name="car_neutral_100">#ffffff</color>
<!-- neutral variant colors -->
- <color name="car_neutral_variant_100">#ffffff</color>
- <color name="car_neutral_variant_99">#fdfbff</color>
- <color name="car_neutral_variant_95">#f0f0fa</color>
- <color name="car_neutral_variant_93">#eaeaf5</color>
- <color name="car_neutral_variant_90">#e2e2ec</color>
- <color name="car_neutral_variant_80">#c5c6d0</color>
- <color name="car_neutral_variant_70">#a9aab4</color>
- <color name="car_neutral_variant_60">#8f909a</color>
- <color name="car_neutral_variant_50">#75767f</color>
- <color name="car_neutral_variant_40">#5c5e67</color>
- <color name="car_neutral_variant_30">#44464e</color>
- <color name="car_neutral_variant_25">#393b43</color>
- <color name="car_neutral_variant_20">#2e3038</color>
- <color name="car_neutral_variant_10">#191b23</color>
<color name="car_neutral_variant_0">#000000</color>
+ <color name="car_neutral_variant_2">#03080c</color>
+ <color name="car_neutral_variant_4">#080f13</color>
+ <color name="car_neutral_variant_5">#0b1216</color>
+ <color name="car_neutral_variant_6">#0d1419</color>
+ <color name="car_neutral_variant_10">#161c21</color>
+ <color name="car_neutral_variant_12">#1a2025</color>
+ <color name="car_neutral_variant_17">#242b30</color>
+ <color name="car_neutral_variant_20">#2b3136</color>
+ <color name="car_neutral_variant_22">#2f363b</color>
+ <color name="car_neutral_variant_24">#333a3f</color>
+ <color name="car_neutral_variant_30">#41484d</color>
+ <color name="car_neutral_variant_40">#585f65</color>
+ <color name="car_neutral_variant_50">#71787e</color>
+ <color name="car_neutral_variant_60">#8b9297</color>
+ <color name="car_neutral_variant_70">#a5acb2</color>
+ <color name="car_neutral_variant_80">#c1c7ce</color>
+ <color name="car_neutral_variant_85">#cfd5dc</color>
+ <color name="car_neutral_variant_87">#d4dbe1</color>
+ <color name="car_neutral_variant_88">#d7dee4</color>
+ <color name="car_neutral_variant_90">#dde3ea</color>
+ <color name="car_neutral_variant_92">#e2e9ef</color>
+ <color name="car_neutral_variant_94">#e8eff5</color>
+ <color name="car_neutral_variant_95">#ebf1f8</color>
+ <color name="car_neutral_variant_98">#f5faff</color>
+ <color name="car_neutral_variant_100">#ffffff</color>
+ <!-- red colors -->
+ <color name="car_red_0">#000000</color>
+ <color name="car_red_2">#1a0000</color>
+ <color name="car_red_4">#280001</color>
+ <color name="car_red_5">#2d0001</color>
+ <color name="car_red_6">#310001</color>
+ <color name="car_red_10">#410001</color>
+ <color name="car_red_12">#490002</color>
+ <color name="car_red_17">#5c0003</color>
+ <color name="car_red_20">#690004</color>
+ <color name="car_red_22">#710005</color>
+ <color name="car_red_24">#790005</color>
+ <color name="car_red_30">#930008</color>
+ <color name="car_red_40">#ba1b19</color>
+ <color name="car_red_50">#de372f</color>
+ <color name="car_red_60">#ff5448</color>
+ <color name="car_red_70">#ff897c</color>
+ <color name="car_red_80">#ffb4aa</color>
+ <color name="car_red_85">#ffc7c0</color>
+ <color name="car_red_87">#ffcfc9</color>
+ <color name="car_red_88">#ffd3cd</color>
+ <color name="car_red_90">#ffdad5</color>
+ <color name="car_red_92">#ffe2de</color>
+ <color name="car_red_94">#ffe9e6</color>
+ <color name="car_red_95">#ffedea</color>
+ <color name="car_red_98">#fff8f7</color>
+ <color name="car_red_100">#ffffff</color>
+
+ <!-- green colors -->
+ <color name="car_green_0">#000000</color>
+ <color name="car_green_2">#000a01</color>
+ <color name="car_green_4">#001203</color>
+ <color name="car_green_5">#001504</color>
+ <color name="car_green_6">#001804</color>
+ <color name="car_green_10">#002107</color>
+ <color name="car_green_12">#002609</color>
+ <color name="car_green_17">#00320f</color>
+ <color name="car_green_20">#003912</color>
+ <color name="car_green_22">#003e14</color>
+ <color name="car_green_24">#004316</color>
+ <color name="car_green_30">#00531d</color>
+ <color name="car_green_40">#006e29</color>
+ <color name="car_green_50">#1d893b</color>
+ <color name="car_green_60">#3ea453</color>
+ <color name="car_green_70">#5bbf6b</color>
+ <color name="car_green_80">#77dc83</color>
+ <color name="car_green_85">#84ea90</color>
+ <color name="car_green_87">#8af095</color>
+ <color name="car_green_88">#8df398</color>
+ <color name="car_green_90">#92f99d</color>
+ <color name="car_green_92">#98ffa2</color>
+ <color name="car_green_94">#b8ffba</color>
+ <color name="car_green_95">#c6ffc6</color>
+ <color name="car_green_98">#ebffe7</color>
+ <color name="car_green_100">#ffffff</color>
+
+ <!-- yellow colors -->
+ <color name="car_yellow_0">#000000</color>
+ <color name="car_yellow_2">#0e0600</color>
+ <color name="car_yellow_4">#180c00</color>
+ <color name="car_yellow_5">#1b0e00</color>
+ <color name="car_yellow_6">#1f1000</color>
+ <color name="car_yellow_10">#2a1800</color>
+ <color name="car_yellow_12">#2f1b00</color>
+ <color name="car_yellow_17">#3d2500</color>
+ <color name="car_yellow_20">#462b00</color>
+ <color name="car_yellow_22">#4c2e00</color>
+ <color name="car_yellow_24">#523200</color>
+ <color name="car_yellow_30">#643f00</color>
+ <color name="car_yellow_40">#845400</color>
+ <color name="car_yellow_50">#a56b00</color>
+ <color name="car_yellow_60">#c78200</color>
+ <color name="car_yellow_70">#eb9a00</color>
+ <color name="car_yellow_80">#ffb958</color>
+ <color name="car_yellow_85">#ffcb8a</color>
+ <color name="car_yellow_87">#ffd29c</color>
+ <color name="car_yellow_88">#ffd6a5</color>
+ <color name="car_yellow_90">#ffddb6</color>
+ <color name="car_yellow_92">#ffe4c6</color>
+ <color name="car_yellow_94">#ffebd5</color>
+ <color name="car_yellow_95">#ffeedd</color>
+ <color name="car_yellow_98">#fff8f4</color>
+ <color name="car_yellow_100">#ffffff</color>
+
+ <!-- blue colors -->
+ <color name="car_blue_0">#000000</color>
+ <color name="car_blue_2">#00061c</color>
+ <color name="car_blue_4">#000c2a</color>
+ <color name="car_blue_5">#000f30</color>
+ <color name="car_blue_6">#001135</color>
+ <color name="car_blue_10">#001945</color>
+ <color name="car_blue_12">#001d4d</color>
+ <color name="car_blue_17">#002662</color>
+ <color name="car_blue_20">#002c6f</color>
+ <color name="car_blue_22">#003178</color>
+ <color name="car_blue_24">#003581</color>
+ <color name="car_blue_30">#00419c</color>
+ <color name="car_blue_40">#0057cc</color>
+ <color name="car_blue_50">#1f70f5</color>
+ <color name="car_blue_60">#578cff</color>
+ <color name="car_blue_70">#86a9ff</color>
+ <color name="car_blue_80">#b0c6ff</color>
+ <color name="car_blue_85">#c5d4ff</color>
+ <color name="car_blue_87">#cdd9ff</color>
+ <color name="car_blue_88">#d1dcff</color>
+ <color name="car_blue_90">#d9e2ff</color>
+ <color name="car_blue_92">#e1e7ff</color>
+ <color name="car_blue_94">#eaedff</color>
+ <color name="car_blue_95">#eef0ff</color>
+ <color name="car_blue_98">#faf8ff</color>
+ <color name="car_blue_100">#ffffff</color>
</resources>
diff --git a/car_product/car_ui_portrait/rro/CarUIPortraitCommon/src/com/android/car/caruiportrait/common/service/CarUiPortraitService.java b/car_product/car_ui_portrait/rro/CarUIPortraitCommon/src/com/android/car/caruiportrait/common/service/CarUiPortraitService.java
index 1ba5550..63c76f8 100644
--- a/car_product/car_ui_portrait/rro/CarUIPortraitCommon/src/com/android/car/caruiportrait/common/service/CarUiPortraitService.java
+++ b/car_product/car_ui_portrait/rro/CarUIPortraitCommon/src/com/android/car/caruiportrait/common/service/CarUiPortraitService.java
@@ -85,13 +85,9 @@
public static final String INTENT_EXTRA_LAUNCHER_READY =
"INTENT_EXTRA_LAUNCHER_READY";
- // key name for the intent's extra that tells if notification panel should be collapsed.
- public static final String INTENT_EXTRA_COLLAPSE_NOTIFICATION_PANEL =
- "INTENT_EXTRA_COLLAPSE_NOTIFICATION_PANEL";
-
- // key name for the intent's extra that tells if recents panel should be collapsed.
- public static final String INTENT_EXTRA_COLLAPSE_RECENTS_PANEL =
- "INTENT_EXTRA_COLLAPSE_RECENTS_PANEL";
+ // key name for the intent's extra that tells if application panel should be collapsed.
+ public static final String INTENT_EXTRA_COLLAPSE_APPLICATION_PANEL =
+ "INTENT_EXTRA_COLLAPSE_APPLICATION_PANEL";
// Keeps track of all current registered clients.
private final ArrayList<Messenger> mClients = new ArrayList<Messenger>();
@@ -160,12 +156,7 @@
/**
* Command to service to collapse notification panel if open.
*/
- public static final int MSG_COLLAPSE_NOTIFICATION = 12;
-
- /**
- * Command to service to collapse recents panel.
- */
- public static final int MSG_COLLAPSE_RECENTS = 13;
+ public static final int MSG_COLLAPSE_APPLICATION = 12;
private boolean mIsSystemInImmersiveMode;
private String mImmersiveModeSource;
@@ -265,12 +256,8 @@
notifyClients(MSG_SUW_IN_PROGRESS, boolToInt(isSuwInProgress));
}
- if (intent.hasExtra(INTENT_EXTRA_COLLAPSE_NOTIFICATION_PANEL)) {
- notifyClients(MSG_COLLAPSE_NOTIFICATION, 1);
- }
-
- if (intent.hasExtra(INTENT_EXTRA_COLLAPSE_RECENTS_PANEL)) {
- notifyClients(MSG_COLLAPSE_RECENTS, 1);
+ if (intent.hasExtra(INTENT_EXTRA_COLLAPSE_APPLICATION_PANEL)) {
+ notifyClients(MSG_COLLAPSE_APPLICATION, 1);
}
}
};
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/incall_dialpad_fragment.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/incall_dialpad_fragment.xml
index 981f16d..53a5626 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/incall_dialpad_fragment.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/layout/incall_dialpad_fragment.xml
@@ -30,24 +30,13 @@
android:layout_alignParentTop="true"
style="@style/TextAppearance.DialNumber" />
- <Chronometer
- android:id="@+id/call_state"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:focusable="true"
- android:singleLine="true"
- android:layout_marginTop="@dimen/in_call_state_margin"
- android:layout_centerHorizontal="true"
- android:layout_below="@id/title"
- style="@style/TextAppearance.InCallState" />
-
<com.android.car.ui.FocusArea
android:id="@+id/dialpad_focus_area"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/in_call_dialpad_focus_area_margin"
android:layout_centerHorizontal="true"
- android:layout_below="@+id/call_state">
+ android:layout_below="@+id/title">
<fragment
android:id="@+id/dialpad_fragment"
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/xml/overlays.xml
index 5184091..b34be01 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitDialerRRO/res/xml/overlays.xml
@@ -33,7 +33,6 @@
<item target="id/background_image" value="@id/background_image" />
<item target="id/call_action_id" value="@id/call_action_id" />
<item target="id/call_button" value="@id/call_button" />
- <item target="id/call_state" value="@id/call_state" />
<item target="id/connect_bluetooth_button" value="@id/connect_bluetooth_button" />
<item target="id/contact_details_favorite_button" value="@id/contact_details_favorite_button" />
<item target="id/contact_details_sms_button" value="@id/contact_details_sms_button" />
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/drawable/media_item_divider.xml
similarity index 61%
copy from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
copy to car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/drawable/media_item_divider.xml
index a6ac85e..0313523 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/drawable/media_item_divider.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -13,11 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<ImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/statusbar_sensor_text_width"
- android:layout_height="@*android:dimen/status_bar_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:tag="@string/qc_icon_tag"/>
\ No newline at end of file
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/listview_background_shape">
+ <stroke android:width="0dp" android:color="@color/car_ui_text_color_primary" />
+ <solid android:color="@color/car_ui_text_color_primary" />
+</shape>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/layout/browse_custom_action.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/layout/browse_custom_action.xml
new file mode 100644
index 0000000..03d3e32
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/layout/browse_custom_action.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/browse_item_custom_action_divider"
+ android:layout_width="1dp"
+ android:layout_height="@dimen/media_browse_list_item_icons_size"
+ android:src="@drawable/media_item_divider"
+ android:layout_marginStart="@dimen/media_browse_list_item_icon_margin"
+ android:layout_marginEnd="@dimen/media_browse_list_item_icon_margin" />
+
+ <ImageView
+ android:id="@+id/browse_item_custom_action"
+ android:layout_width="@dimen/media_browse_list_item_icons_size"
+ android:layout_height="@dimen/media_browse_list_item_icons_size"
+ android:scaleType="fitCenter"
+ android:tint="@color/car_on_background"
+ android:layout_marginStart="@dimen/media_browse_list_item_icon_margin"
+ android:layout_marginEnd="@dimen/media_browse_list_item_icon_margin"
+ />
+
+</LinearLayout>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/layout/media_browse_header_item.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/layout/media_browse_header_item.xml
index 57b5141..87468b9 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/layout/media_browse_header_item.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/layout/media_browse_header_item.xml
@@ -20,7 +20,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/media_browse_header_action_margin"
- android:background="@drawable/car_ui_toolbar_background"
android:orientation="horizontal">
<LinearLayout
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/xml/overlays.xml
index 91003cb..1c98aa1 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitMediaRRO/res/xml/overlays.xml
@@ -85,6 +85,8 @@
<item target="id/ui_content_end_guideline" value="@id/ui_content_end_guideline"/>
<item target="id/ui_content_start_guideline" value="@id/ui_content_start_guideline"/>
<item target="id/ui_content_top_guideline" value="@id/ui_content_top_guideline"/>
+ <item target="id/browse_item_custom_action_divider" value="@id/browse_item_custom_action_divider"/>
+ <item target="id/browse_item_custom_action" value="@id/browse_item_custom_action"/>
<item target="layout/fragment_error" value="@layout/fragment_error"/>
<item target="layout/browse_mini_bar" value="@layout/browse_mini_bar"/>
@@ -95,6 +97,7 @@
<item target="layout/media_browse_header_item" value="@layout/media_browse_header_item"/>
<item target="layout/media_browse_list_icons_item" value="@layout/media_browse_list_icons_item"/>
<item target="layout/media_browse_list_item" value="@layout/media_browse_list_item"/>
+ <item target="layout/browse_custom_action" value="@layout/browse_custom_action"/>
<item target="string/menu_item_app_selector_title" value="@string/menu_item_app_selector_title"/>
<item target="string/menu_item_sound_settings_title" value="@string/menu_item_sound_settings_title"/>
@@ -113,4 +116,6 @@
<item target="style/QueueListItemTitlesContainerStyle" value="@style/QueueListItemTitlesContainerStyle"/>
<item target="style/QueueListItemTitleStyle" value="@style/QueueListItemTitleStyle"/>
<item target="style/TextAppearance.NoContent" value="@style/TextAppearance.NoContent"/>
+
+ <item target="drawable/media_item_divider" value="@drawable/media_item_divider"/>
</overlay>
diff --git a/car_product/car_ui_portrait/rro/android/Android.bp b/car_product/car_ui_portrait/rro/android/Android.bp
index 8a1cde5..fa14525 100644
--- a/car_product/car_ui_portrait/rro/android/Android.bp
+++ b/car_product/car_ui_portrait/rro/android/Android.bp
@@ -21,7 +21,6 @@
resource_dirs: ["res"],
certificate: "platform",
manifest: "AndroidManifest.xml",
- system_ext_specific: true,
static_libs: [
"car-portrait-ui-common",
],
diff --git a/car_product/car_ui_portrait/rro/android/res/values/dimens.xml b/car_product/car_ui_portrait/rro/android/res/values/dimens.xml
index b72ee24..691a0ae 100644
--- a/car_product/car_ui_portrait/rro/android/res/values/dimens.xml
+++ b/car_product/car_ui_portrait/rro/android/res/values/dimens.xml
@@ -92,4 +92,9 @@
<!-- Make the dots in lock pattern thicker in Car -->
<dimen name="lock_pattern_dot_size">32dp</dimen>
+
+ <!-- Intent Resolver -->
+ <dimen name="car_activity_resolver_width">832dp</dimen>
+ <dimen name="car_activity_resolver_corner_radius">48dp</dimen>
+ <dimen name="car_activity_resolver_list_max_height">516dp</dimen>
</resources>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/styles_device_default.xml b/car_product/car_ui_portrait/rro/android/res/values/styles_device_default.xml
index 7ad10f8..521c869 100644
--- a/car_product/car_ui_portrait/rro/android/res/values/styles_device_default.xml
+++ b/car_product/car_ui_portrait/rro/android/res/values/styles_device_default.xml
@@ -195,12 +195,6 @@
<item name="android:background">@drawable/car_button_background</item>
</style>
- <style name="Widget.DeviceDefault.CompoundButton.RadioButton"
- parent="android:Widget.Material.CompoundButton.RadioButton">
- <item name="android:textColor">?android:attr/textColorPrimary</item>
- <item name="android:stateListAnimator">@null</item>
- </style>
-
<style name="Widget.DeviceDefault.ActionButton" parent="android:Widget.Material.ActionButton">
<item name="android:textColor">@color/car_on_background</item>
</style>
@@ -224,12 +218,6 @@
<item name="android:textColor">@color/car_on_background</item>
</style>
- <style name="Widget.DeviceDefault.CompoundButton.CheckBox"
- parent="android:Widget.Material.CompoundButton.CheckBox">
- <item name="android:button">@drawable/car_checkbox</item>
- <item name="android:textColor">?android:attr/textColorPrimary</item>
- </style>
-
<style name="Widget.DeviceDefault.CompoundButton.Switch"
parent="android:Widget.Material.CompoundButton.Switch">
<item name="android:thumb">@drawable/car_switch_thumb</item>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/system_colors.xml b/car_product/car_ui_portrait/rro/android/res/values/system_colors.xml
index f631c34..b60a5e2 100644
--- a/car_product/car_ui_portrait/rro/android/res/values/system_colors.xml
+++ b/car_product/car_ui_portrait/rro/android/res/values/system_colors.xml
@@ -15,7 +15,7 @@
-->
<resources>
<color name="system_accent1_0">@color/car_primary_100</color>
- <color name="system_accent1_10">@color/car_primary_99</color>
+ <color name="system_accent1_10">@color/car_primary_98</color>
<color name="system_accent1_50">@color/car_primary_95</color>
<color name="system_accent1_100">@color/car_primary_90</color>
<color name="system_accent1_200">@color/car_primary_80</color>
@@ -29,7 +29,7 @@
<color name="system_accent1_1000">@color/car_primary_0</color>
<color name="system_accent2_0">@color/car_secondary_100</color>
- <color name="system_accent2_10">@color/car_secondary_99</color>
+ <color name="system_accent2_10">@color/car_secondary_98</color>
<color name="system_accent2_50">@color/car_secondary_95</color>
<color name="system_accent2_100">@color/car_secondary_90</color>
<color name="system_accent2_200">@color/car_secondary_80</color>
@@ -43,7 +43,7 @@
<color name="system_accent2_1000">@color/car_secondary_0</color>
<color name="system_accent3_0">@color/car_tertiary_100</color>
- <color name="system_accent3_10">@color/car_tertiary_99</color>
+ <color name="system_accent3_10">@color/car_tertiary_98</color>
<color name="system_accent3_50">@color/car_tertiary_95</color>
<color name="system_accent3_100">@color/car_tertiary_90</color>
<color name="system_accent3_200">@color/car_tertiary_80</color>
@@ -57,7 +57,7 @@
<color name="system_accent3_1000">@color/car_tertiary_0</color>
<color name="system_neutral1_0">@color/car_neutral_100</color>
- <color name="system_neutral1_10">@color/car_neutral_99</color>
+ <color name="system_neutral1_10">@color/car_neutral_98</color>
<color name="system_neutral1_50">@color/car_neutral_95</color>
<color name="system_neutral1_100">@color/car_neutral_90</color>
<color name="system_neutral1_200">@color/car_neutral_80</color>
@@ -71,7 +71,7 @@
<color name="system_neutral1_1000">@color/car_neutral_0</color>
<color name="system_neutral2_0">@color/car_neutral_variant_100</color>
- <color name="system_neutral2_10">@color/car_neutral_variant_99</color>
+ <color name="system_neutral2_10">@color/car_neutral_variant_98</color>
<color name="system_neutral2_50">@color/car_neutral_variant_95</color>
<color name="system_neutral2_100">@color/car_neutral_variant_90</color>
<color name="system_neutral2_200">@color/car_neutral_variant_80</color>
diff --git a/car_product/car_ui_portrait/rro/android/res/values/themes_device_defaults.xml b/car_product/car_ui_portrait/rro/android/res/values/themes_device_defaults.xml
index 8e1dcbd..c99866c 100644
--- a/car_product/car_ui_portrait/rro/android/res/values/themes_device_defaults.xml
+++ b/car_product/car_ui_portrait/rro/android/res/values/themes_device_defaults.xml
@@ -61,6 +61,12 @@
</style>
<style name="Theme.DeviceDefault.Dialog" parent="android:Theme.Material.Dialog">
+ <item name="android:textColorPrimary">@color/car_on_background</item>
+ <item name="android:textColorPrimaryInverse">@color/car_background</item>
+ <item name="android:textColorSecondary">@color/car_secondary</item>
+ <item name="android:textColorSecondaryInverse">@color/car_on_secondary</item>
+ <item name="android:textColorTertiary">@color/car_secondary</item>
+ <item name="android:textColorTertiaryInverse">@color/car_on_secondary</item>
<item name="android:textAppearanceLarge">@*android:style/TextAppearance.DeviceDefault.Large</item>
<item name="android:textAppearanceMedium">@*android:style/TextAppearance.DeviceDefault.Medium</item>
<item name="android:textAppearanceSmall">@*android:style/TextAppearance.DeviceDefault.Small</item>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/colors.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/colors.xml
index 74977c1..41d38ef 100644
--- a/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/colors.xml
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/values/colors.xml
@@ -15,9 +15,10 @@
~ limitations under the License.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <color name="car_ui_preference_primary_switch_background_color">@color/car_primary_container</color>
- <color name="car_ui_toolbar_nav_icon_color">@color/car_ui_text_color_primary</color>
- <color name="car_ui_preference_primary_title">@color/car_on_surface</color>
<color name="car_ui_preference_primary_summary">@color/car_on_surface_variant</color>
+ <color name="car_ui_preference_primary_switch_background_color">@color/car_primary_container</color>
+ <color name="car_ui_preference_primary_title">@color/car_on_surface</color>
<color name="car_ui_preference_two_action_divider_color">@color/car_outline</color>
+ <color name="car_ui_toolbar_nav_icon_color">@color/car_ui_text_color_primary</color>
+ <color name="car_ui_toolbar_search_hint_text_color">@color/car_ui_text_color_primary</color>
</resources>
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/res/xml/overlays.xml
index cbc3c59..3e19237 100644
--- a/car_product/car_ui_portrait/rro/car-ui-customizations/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/res/xml/overlays.xml
@@ -48,10 +48,10 @@
<item target="color/car_ui_text_color_primary" value="@color/car_ui_text_color_primary" />
<item target="color/car_ui_text_color_secondary" value="@color/car_ui_text_color_secondary" />
+ <item target="color/car_ui_toolbar_search_hint_text_color" value="@color/car_ui_toolbar_search_hint_text_color" />
<item target="color/car_ui_toolbar_tab_item_selector" value="@color/car_ui_toolbar_tab_item_selector" />
<item target="color/car_ui_preference_two_action_divider_color" value="@color/car_ui_preference_two_action_divider_color" />
-
<item target="drawable/car_ui_list_item_background" value="@drawable/car_ui_list_item_background" />
<item target="drawable/car_ui_toolbar_background" value="@drawable/car_ui_toolbar_background" />
<item target="drawable/car_ui_toolbar_menu_item_divider" value="@drawable/car_ui_toolbar_menu_item_divider" />
diff --git a/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk b/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk
index ead7198..b48385e 100644
--- a/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk
+++ b/car_product/car_ui_portrait/rro/car_ui_portrait_rro.mk
@@ -94,4 +94,7 @@
PRODUCT_PROPERTY_OVERRIDES += \
- ro.boot.vendor.overlay.theme=$(subst $(space),,$(PORTRAIT_RRO_PACKAGES))
\ No newline at end of file
+ ro.boot.vendor.overlay.theme=$(subst $(space),,$(PORTRAIT_RRO_PACKAGES))
+
+PRODUCT_PRODUCT_PROPERTIES += \
+ persist.wm.debug.shell_transit=0
diff --git a/car_product/distant_display/OWNERS b/car_product/distant_display/OWNERS
index 50e90e6..e7d5f87 100644
--- a/car_product/distant_display/OWNERS
+++ b/car_product/distant_display/OWNERS
@@ -1,4 +1,3 @@
# Car Distant Display Reference OWNERS
babakbo@google.com
priyanksingh@google.com
-igorr@google.com
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/Android.bp b/car_product/distant_display/apps/CarDistantDisplayPanoManager/Android.bp
new file mode 100644
index 0000000..f35efd8
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/Android.bp
@@ -0,0 +1,67 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+ name: "CarDistantDisplayPanoManager",
+
+ srcs: ["src/**/*.kt"],
+
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "androidx.activity_activity-ktx",
+ "androidx.appcompat_appcompat",
+ "androidx.core_core-ktx",
+ "androidx.lifecycle_lifecycle-livedata-ktx",
+ "androidx.lifecycle_lifecycle-viewmodel-ktx",
+ "androidx.legacy_legacy-support-v4",
+ "car-ui-lib",
+
+ // App Cards
+ "car-app-card-host",
+
+ // Kotlin
+ "kotlin-stdlib",
+ "kotlinx-coroutines-android",
+
+ "libprotobuf-java-lite",
+ ],
+
+ required: ["allowed_privapp_com.android.car.pano.manager"],
+
+ optimize: {
+ enabled: false,
+ },
+
+ dex_preopt: {
+ enabled: false,
+ },
+
+ manifest: "AndroidManifest.xml",
+
+ platform_apis: true,
+
+ certificate: "platform",
+}
+
+prebuilt_etc {
+ name: "allowed_privapp_com.android.car.pano.manager",
+ sub_dir: "permissions",
+ src: "com.android.car.pano.manager.xml",
+ filename_from_src: true,
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/AndroidManifest.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/AndroidManifest.xml
new file mode 100644
index 0000000..1211e95
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/AndroidManifest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.car.pano.manager">
+ <!-- Needed to get current user -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"
+ tools:ignore="ProtectedPermissions"/>
+ <!-- Needed for context#registerReceiverForAllUsers -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
+ tools:ignore="ProtectedPermissions"/>
+ <!-- Allow the host to connect to app card content providers -->
+ <uses-permission android:name="android.car.permission.BIND_APP_CARD_PROVIDER"
+ tools:ignore="ProtectedPermissions"/>
+ <!-- Needed to query 3p app card content providers -->
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
+ tools:ignore="QueryAllPackagesPermission" />
+
+ <application
+ android:icon="@drawable/ic_pano_manager"
+ android:label="@string/app_name"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.CarUi.NoToolbar"
+ android:roundIcon="@drawable/ic_pano_manager">
+ <activity
+ android:name=".MainActivity"
+ android:exported="true"
+ android:label="@string/app_name"
+ android:resizeableActivity="true"
+ android:showForAllUsers="true"
+ android:theme="@style/Theme.CarUi.NoToolbar">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/com.android.car.pano.manager.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/com.android.car.pano.manager.xml
new file mode 100644
index 0000000..ec6eb57
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/com.android.car.pano.manager.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<permissions>
+ <privapp-permissions package="com.android.car.pano.manager">
+ <!-- Needed to get current user -->
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <!-- Needed for context#registerReceiverForAllUsers -->
+ <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+ <!-- Allow the host to connect to app card content providers -->
+ <permission name="android.car.permission.BIND_APP_CARD_PROVIDER"/>
+ <!-- Needed to query 3p app card content providers -->
+ <permission name="android.permission.QUERY_ALL_PACKAGES"/>
+ </privapp-permissions>
+</permissions>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/add_replace_button_bg.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/add_replace_button_bg.xml
new file mode 100644
index 0000000..d63d320
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/add_replace_button_bg.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="false">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/add_replace_button_bg_radius"/>
+ <solid android:color="@color/add_replace_button_bg"/>
+ </shape>
+ </item>
+ <item android:state_pressed="true">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/add_replace_button_bg_radius"/>
+ <solid android:color="@color/add_replace_button_pressed_bg"/>
+ </shape>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/app_card_bg.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/app_card_bg.xml
new file mode 100644
index 0000000..9e67071
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/app_card_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/app_card_bg_radius" />
+ <solid android:color="@color/app_card_bg" />
+</shape>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/app_card_selected_bg.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/app_card_selected_bg.xml
new file mode 100644
index 0000000..ce399ac
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/app_card_selected_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/app_card_bg_radius" />
+ <solid android:color="@color/app_card_bg" />
+ <stroke android:width="@dimen/app_card_selected_bg_stroke_width" android:color="@color/app_card_selected_bg_stroke" />
+</shape>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/close_button_bg.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/close_button_bg.xml
new file mode 100644
index 0000000..5f37c8e
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/close_button_bg.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="false">
+ <shape android:shape="oval">
+ <corners android:radius="@dimen/close_button_bg_radius"/>
+ <solid android:color="@color/close_button_bg"/>
+ </shape>
+ </item>
+ <item android:state_pressed="true">
+ <shape android:shape="oval">
+ <corners android:radius="@dimen/close_button_bg_radius"/>
+ <solid android:color="@color/close_button_pressed_bg"/>
+ </shape>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/ic_close.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/ic_close.xml
new file mode 100644
index 0000000..a4bba4b
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/ic_close.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="37dp"
+ android:height="36dp"
+ android:viewportWidth="37"
+ android:viewportHeight="36">
+ <path
+ android:pathData="M28.803,9.617L26.688,7.502L18.303,15.887L9.918,7.502L7.803,9.617L16.188,18.002L7.803,26.387L9.918,28.502L18.303,20.117L26.688,28.502L28.803,26.387L20.418,18.002L28.803,9.617Z"
+ android:fillColor="#E0E3E3"/>
+</vector>
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/ic_pano_manager.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/ic_pano_manager.xml
new file mode 100644
index 0000000..7d33b21
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/ic_pano_manager.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="90dp"
+ android:height="89dp"
+ android:viewportWidth="90"
+ android:viewportHeight="89">
+ <path
+ android:pathData="M45.038,88.984C69.525,88.984 89.376,69.133 89.376,44.646C89.376,20.159 69.525,0.308 45.038,0.308C20.551,0.308 0.7,20.159 0.7,44.646C0.7,69.133 20.551,88.984 45.038,88.984Z"
+ android:fillColor="#1A73E8"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M20.79,56.662C21.283,57.169 21.88,57.422 22.579,57.422C22.908,57.422 23.628,57.215 24.738,56.8C25.89,56.34 27.411,55.856 29.303,55.349C31.195,54.843 33.416,54.382 35.966,53.967C38.449,53.526 41.252,53.296 44.377,53.278C44.326,52.777 44.3,52.269 44.3,51.754C44.3,50.859 44.379,49.982 44.532,49.131C41.04,49.144 37.568,49.42 34.115,49.96C30.619,50.513 27.185,51.457 23.813,52.793V32.641C27.185,33.93 30.619,34.852 34.115,35.404C37.652,35.957 41.209,36.234 44.787,36.234C48.365,36.234 51.902,35.957 55.398,35.404C58.935,34.852 62.39,33.93 65.762,32.641V38.633C67.135,39.338 68.383,40.252 69.463,41.333V30.706C69.463,29.923 69.217,29.278 68.723,28.771C68.271,28.219 67.695,27.942 66.996,27.942C66.667,27.942 65.926,28.173 64.775,28.633C63.665,29.048 62.163,29.508 60.272,30.015C58.38,30.522 56.138,31.006 53.547,31.466C50.998,31.881 48.077,32.088 44.787,32.088C41.497,32.088 38.557,31.881 35.966,31.466C33.416,31.006 31.195,30.522 29.303,30.015C27.411,29.508 25.89,29.048 24.738,28.633C23.628,28.173 22.908,27.942 22.579,27.942C21.88,27.942 21.283,28.219 20.79,28.771C20.337,29.278 20.111,29.923 20.111,30.706V54.658C20.111,55.441 20.337,56.109 20.79,56.662Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M57.696,63.471L57.414,60.708C56.624,60.478 55.871,60.152 55.156,59.729C54.442,59.307 53.783,58.809 53.181,58.233L50.811,59.384L48.835,56.276L50.98,54.55C50.754,53.782 50.641,52.996 50.641,52.19C50.641,51.346 50.754,50.54 50.98,49.772L48.835,48.046L50.811,44.88L53.181,46.031C53.821,45.494 54.479,45.015 55.156,44.593C55.871,44.171 56.624,43.844 57.414,43.614L57.696,40.794H61.083L61.421,43.614C62.211,43.844 62.945,44.171 63.622,44.593C64.337,45.015 65.015,45.494 65.654,46.031L68.025,44.88L70,48.046L67.855,49.772C68.081,50.54 68.194,51.346 68.194,52.19C68.194,52.996 68.081,53.782 67.855,54.55L70,56.276L68.025,59.384L65.654,58.233C65.052,58.809 64.394,59.307 63.679,59.729C62.964,60.152 62.211,60.478 61.421,60.708L61.083,63.471H57.696ZM59.389,58.003C60.97,58.003 62.324,57.427 63.453,56.276C64.582,55.125 65.146,53.744 65.146,52.132C65.146,50.521 64.582,49.139 63.453,47.988C62.324,46.837 60.97,46.262 59.389,46.262C57.809,46.262 56.455,46.837 55.326,47.988C54.197,49.139 53.633,50.521 53.633,52.132C53.633,53.744 54.197,55.125 55.326,56.276C56.455,57.427 57.809,58.003 59.389,58.003Z"
+ android:fillColor="#ffffff"/>
+</vector>
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/ic_touch_app.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/ic_touch_app.xml
new file mode 100644
index 0000000..a766a5d
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/ic_touch_app.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="42dp"
+ android:height="43dp"
+ android:viewportWidth="42"
+ android:viewportHeight="43">
+ <group>
+ <clip-path
+ android:pathData="M0,0.5h42v42h-42z"/>
+ <path
+ android:pathData="M19.206,39C18.36,39 17.544,38.825 16.756,38.475C15.998,38.125 15.356,37.615 14.831,36.944L5.338,24.869L6.256,23.906C6.694,23.41 7.233,23.119 7.875,23.031C8.546,22.915 9.158,23.017 9.712,23.337L13.781,25.569V9.994C13.781,9.615 13.913,9.294 14.175,9.031C14.467,8.74 14.817,8.594 15.225,8.594C15.633,8.594 15.969,8.74 16.231,9.031C16.494,9.294 16.625,9.615 16.625,9.994V30.425L10.85,27.231L17.106,35.194C17.369,35.515 17.675,35.763 18.025,35.938C18.404,36.083 18.798,36.156 19.206,36.156H28.35C29.4,36.156 30.29,35.792 31.019,35.063C31.777,34.304 32.156,33.385 32.156,32.306V25C32.156,24.329 31.923,23.76 31.456,23.294C30.99,22.827 30.421,22.594 29.75,22.594H20.125V19.75H29.75C31.208,19.75 32.448,20.26 33.469,21.281C34.49,22.302 35,23.542 35,25V32.306C35,34.173 34.344,35.763 33.031,37.075C31.748,38.358 30.188,39 28.35,39H19.206ZM8.488,13.887C8.137,13.333 7.875,12.721 7.7,12.05C7.525,11.379 7.438,10.694 7.438,9.994C7.438,7.835 8.196,6.012 9.712,4.525C11.229,3.008 13.067,2.25 15.225,2.25C17.354,2.25 19.177,3.008 20.694,4.525C22.21,6.042 22.969,7.879 22.969,10.038C22.969,10.708 22.881,11.379 22.706,12.05C22.531,12.721 22.269,13.333 21.919,13.887L19.469,12.488C19.673,12.108 19.833,11.715 19.95,11.306C20.067,10.898 20.125,10.46 20.125,9.994C20.125,8.623 19.644,7.471 18.681,6.537C17.719,5.575 16.552,5.094 15.181,5.094C13.84,5.094 12.688,5.575 11.725,6.537C10.762,7.5 10.281,8.652 10.281,9.994C10.281,10.46 10.34,10.898 10.456,11.306C10.573,11.715 10.733,12.108 10.938,12.488L8.488,13.887Z"
+ android:fillColor="#E0E3E3"/>
+ </group>
+</vector>
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/ic_widgets.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/ic_widgets.xml
new file mode 100644
index 0000000..2b90163
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/ic_widgets.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="42dp"
+ android:height="42dp"
+ android:viewportWidth="42"
+ android:viewportHeight="42">
+ <group>
+ <clip-path
+ android:pathData="M0,0h42v42h-42z"/>
+ <path
+ android:pathData="M29.575,21.875L20.125,12.425L29.575,2.975L39.025,12.425L29.575,21.875ZM5.25,18.638V5.294H18.638V18.638H5.25ZM23.362,36.75V23.362H36.706V36.75H23.362ZM5.25,36.75V23.362H18.638V36.75H5.25ZM8.094,15.794H15.794V8.137H8.094V15.794ZM29.663,18.025L35.131,12.556L29.663,7.088L24.194,12.556L29.663,18.025ZM26.206,33.906H33.862V26.206H26.206V33.906ZM8.094,33.906H15.794V26.206H8.094V33.906Z"
+ android:fillColor="#E0E3E3"/>
+ </group>
+</vector>
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/image_outline.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/image_outline.xml
new file mode 100644
index 0000000..7c4263a
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/drawable/image_outline.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/app_card_image_radius" />
+</shape>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/activity_main.xml
similarity index 64%
copy from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
copy to car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/activity_main.xml
index a6ac85e..1815129 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/activity_main.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -11,13 +12,10 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ ~ limitations under the License
-->
-
-<ImageView
+<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/statusbar_sensor_text_width"
- android:layout_height="@*android:dimen/status_bar_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:tag="@string/qc_icon_tag"/>
\ No newline at end of file
+ android:id="@+id/fragment_container_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/image_app_card.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/image_app_card.xml
new file mode 100644
index 0000000..19b6308
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/image_app_card.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/card"
+ android:layout_width="@dimen/app_card_width"
+ android:layout_height="@dimen/app_card_height"
+ android:padding="@dimen/app_card_padding"
+ android:layout_marginStart="@dimen/app_card_margin_horizontal"
+ android:layout_marginEnd="@dimen/app_card_margin_horizontal"
+ android:background="@drawable/app_card_bg">
+ <TextView
+ android:id="@+id/header_text"
+ android:layout_width="@dimen/app_card_header_text_width"
+ android:layout_height="@dimen/app_card_header_text_height"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:textAppearance="@style/HeaderText" />
+ <ImageView
+ android:id="@+id/header_icon"
+ android:layout_width="@dimen/app_card_header_icon_size"
+ android:layout_height="@dimen/app_card_header_icon_size"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginStart="@dimen/app_card_header_icon_margin_start" />
+ <ImageView
+ android:id="@+id/image"
+ android:layout_width="@dimen/app_card_image_size"
+ android:layout_height="@dimen/app_card_image_size"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/header_text"
+ android:layout_marginStart="@dimen/app_card_image_margin_start"
+ android:layout_marginTop="@dimen/app_card_image_margin_top"
+ android:background="@drawable/image_outline"
+ android:clipToOutline="true" />
+ <TextView
+ android:id="@+id/primary_text"
+ android:layout_width="@dimen/app_card_primary_text_width"
+ android:layout_height="@dimen/app_card_image_primary_text_height"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/image"
+ android:layout_marginTop="@dimen/app_card_image_primary_text_margin_top"
+ android:textAppearance="@style/PrimaryText" />
+ <TextView
+ android:id="@+id/secondary_text"
+ android:layout_width="@dimen/app_card_secondary_text_width"
+ android:layout_height="@dimen/app_card_secondary_text_height"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/primary_text"
+ android:textAppearance="@style/SecondaryText" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/picker_fragment.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/picker_fragment.xml
new file mode 100644
index 0000000..883790f
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/picker_fragment.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/root_padding"
+ android:background="@color/root_bg">
+
+ <Button
+ android:id="@+id/done_button"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/done_button_height"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true"
+ android:layout_marginTop="@dimen/done_button_margin_top"
+ android:layout_marginEnd="@dimen/done_button_margin_end"
+ android:paddingStart="@dimen/done_button_padding_horizontal"
+ android:paddingEnd="@dimen/done_button_padding_horizontal"
+ android:paddingTop="@dimen/done_button_padding_vertical"
+ android:paddingBottom="@dimen/done_button_padding_vertical"
+ android:background="@drawable/add_replace_button_bg"
+ android:textAppearance="@style/HomeText"
+ android:text="@string/done_button" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/all_app_cards"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/selected_app_cards_height"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:orientation="horizontal" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/progress_buttons_app_card.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/progress_buttons_app_card.xml
new file mode 100644
index 0000000..a8b02e6
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/progress_buttons_app_card.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/card"
+ android:layout_width="@dimen/app_card_width"
+ android:layout_height="@dimen/app_card_height"
+ android:padding="@dimen/app_card_padding"
+ android:layout_marginStart="@dimen/app_card_margin_horizontal"
+ android:layout_marginEnd="@dimen/app_card_margin_horizontal"
+ android:background="@drawable/app_card_bg">
+ <TextView
+ android:id="@+id/header_text"
+ android:layout_width="@dimen/app_card_header_text_width"
+ android:layout_height="@dimen/app_card_header_text_height"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:textAppearance="@style/HeaderText" />
+ <ImageView
+ android:id="@+id/header_icon"
+ android:layout_width="@dimen/app_card_header_icon_size"
+ android:layout_height="@dimen/app_card_header_icon_size"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginStart="@dimen/app_card_header_icon_margin_start" />
+ <TextView
+ android:id="@+id/primary_text"
+ android:layout_width="@dimen/app_card_primary_text_width"
+ android:layout_height="@dimen/app_card_progress_primary_text_height"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/header_text"
+ android:layout_marginTop="@dimen/app_card_progress_primary_text_margin_top"
+ android:textAppearance="@style/PrimaryText.Progress" />
+ <TextView
+ android:id="@+id/secondary_text"
+ android:layout_width="@dimen/app_card_secondary_text_width"
+ android:layout_height="@dimen/app_card_secondary_text_height"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/primary_text"
+ android:textAppearance="@style/SecondaryText" />
+ <ProgressBar
+ android:id="@+id/progress_bar"
+ style="@android:style/Widget.ProgressBar.Horizontal"
+ android:layout_width="@dimen/app_card_progress_bar_width"
+ android:layout_height="@dimen/app_card_progress_bar_height"
+ android:layout_below="@id/secondary_text"
+ android:layout_alignParentStart="true"
+ android:layout_marginTop="@dimen/app_card_progress_bar_margin_top"
+ android:progressBackgroundTint="@color/app_card_progress_bg_tint"
+ android:progressTint="@color/app_card_progress_tint" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/reorder_fragment.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/reorder_fragment.xml
new file mode 100644
index 0000000..70d3ec7
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/layout/reorder_fragment.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/root_padding"
+ android:background="@color/root_bg">
+
+ <ImageButton
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/close_button_size"
+ android:layout_height="@dimen/close_button_size"
+ android:layout_marginStart="@dimen/close_button_margin_start"
+ android:layout_marginTop="@dimen/close_button_margin_top"
+ android:src="@drawable/ic_close"
+ android:background="@drawable/close_button_bg"
+ android:layout_alignParentStart="true" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/selected_app_cards"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/selected_app_cards_height"
+ android:layout_marginStart="@dimen/selected_app_cards_margin_start"
+ android:layout_marginTop="@dimen/selected_app_cards_margin_top"
+ android:layout_below="@id/close_button"
+ android:layout_centerHorizontal="true"
+ android:orientation="horizontal" />
+
+ <ImageView
+ android:id="@+id/touch_app"
+ android:layout_width="@dimen/touch_app_image_size"
+ android:layout_height="@dimen/touch_app_image_size"
+ android:layout_below="@id/selected_app_cards"
+ android:layout_toEndOf="@id/close_button"
+ android:layout_marginStart="@dimen/touch_app_image_margin_start"
+ android:layout_marginTop="@dimen/touch_app_image_margin_top"
+ android:src="@drawable/ic_touch_app" />
+
+ <TextView
+ android:id="@+id/reorder_text"
+ android:layout_width="@dimen/reorder_text_width"
+ android:layout_height="@dimen/reorder_text_height"
+ android:layout_alignTop="@id/touch_app"
+ android:layout_toEndOf="@id/touch_app"
+ android:layout_marginStart="@dimen/reorder_text_margin_start"
+ android:textAppearance="@style/HomeText"
+ android:text="@string/reorder_text" />
+
+ <Button
+ android:id="@+id/add_replace_button"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/add_replace_button_height"
+ android:layout_toEndOf="@id/reorder_text"
+ android:layout_below="@id/selected_app_cards"
+ android:layout_marginTop="@dimen/add_replace_button_margin_top"
+ android:layout_marginStart="@dimen/add_replace_button_margin_start"
+ android:paddingStart="@dimen/add_replace_button_padding_start"
+ android:paddingEnd="@dimen/add_replace_button_padding_end"
+ android:paddingTop="@dimen/add_replace_button_padding_vertical"
+ android:paddingBottom="@dimen/add_replace_button_padding_vertical"
+ android:background="@drawable/add_replace_button_bg"
+ android:drawableStart="@drawable/ic_widgets"
+ android:drawablePadding="@dimen/add_replace_button_padding_drawable"
+ android:textAppearance="@style/HomeText"
+ android:text="@string/add_replace_button" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/colors.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/colors.xml
new file mode 100644
index 0000000..3c84b9d
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/colors.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<resources>
+ <color name="root_bg">#000000</color>
+
+ <!-- App Cards -->
+ <color name="app_card_image_tint">#919191</color>
+
+ <!-- Drawable colors -->
+ <color name="add_replace_button_bg">#474747</color>
+ <color name="close_button_bg">#474747</color>
+ <color name="add_replace_button_pressed_bg">#302e2e</color>
+ <color name="close_button_pressed_bg">#302e2e</color>
+ <color name="app_card_bg">#1D1B20</color>
+ <color name="app_card_selected_bg_stroke">@android:color/white</color>
+ <color name="app_card_progress_tint">#FFFFFF</color>
+ <color name="app_card_progress_bg_tint">#625B71</color>
+</resources>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/config.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/config.xml
new file mode 100644
index 0000000..f16e011
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<resources>
+ <!-- App Card Configs -->
+ <integer name="app_card_api_level">1</integer>
+ <integer name="app_card_update_rate_ms">1000</integer>
+ <integer name="app_card_fast_update_rate_ms">500</integer>
+ <integer name="app_card_image_size_px">500</integer>
+ <integer name="app_card_button_image_size_px">10</integer>
+ <integer name="app_card_header_image_size_px">100</integer>
+ <integer name="app_card_min_buttons">0</integer>
+ <bool name="app_card_is_interactable">false</bool>
+</resources>
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/dimens.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/dimens.xml
new file mode 100644
index 0000000..c83d507
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/dimens.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<resources>
+ <dimen name="add_replace_button_bg_radius">80dp</dimen>
+ <dimen name="close_button_bg_radius">80dp</dimen>
+ <dimen name="app_card_bg_radius">24dp</dimen>
+ <dimen name="app_card_selected_bg_stroke_width">3dp</dimen>
+ <dimen name="app_card_image_radius">13dp</dimen>
+ <dimen name="root_padding">16dp</dimen>
+ <dimen name="close_button_size">53dp</dimen>
+ <dimen name="close_button_margin_start">29dp</dimen>
+ <dimen name="close_button_margin_top">39dp</dimen>
+ <dimen name="selected_app_cards_height">350dp</dimen>
+ <dimen name="selected_app_cards_margin_start">18dp</dimen>
+ <dimen name="selected_app_cards_margin_top">53dp</dimen>
+ <dimen name="touch_app_image_size">28dp</dimen>
+ <dimen name="touch_app_image_margin_start">18dp</dimen>
+ <dimen name="touch_app_image_margin_top">49dp</dimen>
+ <dimen name="reorder_text_width">210dp</dimen>
+ <dimen name="reorder_text_height">29dp</dimen>
+ <dimen name="reorder_text_margin_start">11dp</dimen>
+ <dimen name="done_button_height">53dp</dimen>
+ <dimen name="done_button_margin_top">42dp</dimen>
+ <dimen name="done_button_margin_end">28dp</dimen>
+ <dimen name="done_button_padding_horizontal">18dp</dimen>
+ <dimen name="done_button_padding_vertical">15dp</dimen>
+ <dimen name="add_replace_button_height">53dp</dimen>
+ <dimen name="add_replace_button_margin_top">33dp</dimen>
+ <dimen name="add_replace_button_margin_start">235dp</dimen>
+ <dimen name="add_replace_button_padding_start">16dp</dimen>
+ <dimen name="add_replace_button_padding_end">16dp</dimen>
+ <dimen name="add_replace_button_padding_vertical">13dp</dimen>
+ <dimen name="add_replace_button_padding_drawable">11dp</dimen>
+ <dimen name="app_card_width">280dp</dimen>
+ <dimen name="app_card_height">350dp</dimen>
+ <dimen name="app_card_padding">16dp</dimen>
+ <dimen name="app_card_margin_horizontal">9dp</dimen>
+ <dimen name="app_card_header_text_width">212dp</dimen>
+ <dimen name="app_card_header_text_height">24dp</dimen>
+ <dimen name="app_card_header_icon_size">21dp</dimen>
+ <dimen name="app_card_header_icon_margin_start">15dp</dimen>
+ <dimen name="app_card_image_size">176dp</dimen>
+ <dimen name="app_card_image_margin_start">37dp</dimen>
+ <dimen name="app_card_image_margin_top">32dp</dimen>
+ <dimen name="app_card_primary_text_width">248dp</dimen>
+ <dimen name="app_card_image_primary_text_height">33dp</dimen>
+ <dimen name="app_card_progress_primary_text_height">58dp</dimen>
+ <dimen name="app_card_image_primary_text_margin_top">19dp</dimen>
+ <dimen name="app_card_progress_primary_text_margin_top">74dp</dimen>
+ <dimen name="app_card_secondary_text_width">248dp</dimen>
+ <dimen name="app_card_secondary_text_height">22dp</dimen>
+ <dimen name="app_card_progress_bar_width">251dp</dimen>
+ <dimen name="app_card_progress_bar_height">3dp</dimen>
+ <dimen name="app_card_progress_bar_margin_top">89dp</dimen>
+</resources>
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/strings.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/strings.xml
new file mode 100644
index 0000000..0d1849e
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<resources>
+ <string name="app_name">Pano Manager</string>
+ <string name="reorder_text">Drag and drop to reorder</string>
+ <string name="add_replace_button">Add or replace</string>
+ <string name="done_button">Done</string>
+</resources>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/styles.xml b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/styles.xml
new file mode 100644
index 0000000..12efaad
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/res/values/styles.xml
@@ -0,0 +1,56 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources>
+ <style name="BaseTextAppearance" parent="@*android:style/TextAppearance.DeviceDefault.Small">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:textFontWeight">400</item>
+ <item name="android:textStyle">normal</item>
+ </style>
+
+ <style name="HomeText" parent="BaseTextAppearance">
+ <item name="android:lineHeight">29sp</item>
+ <item name="android:textSize">19sp</item>
+ <item name="android:textColor">#FFFFFF</item>
+ </style>
+
+ <style name="HeaderText" parent="BaseTextAppearance">
+ <item name="android:fontFamily">noto-sans</item>
+ <item name="android:textSize">21sp</item>
+ <item name="android:textColor">#E0E3E3</item>
+ <item name="android:maxLines">1</item>
+ <item name="android:ellipsize">end</item>
+ </style>
+
+ <style name="PrimaryText" parent="BaseTextAppearance">
+ <item name="android:fontFamily">noto-sans</item>
+ <item name="android:textSize">24sp</item>
+ <item name="android:textColor">#FFFFFF</item>
+ <item name="android:maxLines">1</item>
+ <item name="android:ellipsize">end</item>
+ </style>
+
+ <style name="PrimaryText.Progress" parent="PrimaryText">
+ <item name="android:textSize">43sp</item>
+ </style>
+
+ <style name="SecondaryText" parent="BaseTextAppearance">
+ <item name="android:fontFamily">noto-sans</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">#E0E3E3</item>
+ <item name="android:maxLines">1</item>
+ <item name="android:ellipsize">end</item>
+ </style>
+</resources>
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/AppCardServiceManager.kt b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/AppCardServiceManager.kt
new file mode 100644
index 0000000..1b6c202
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/AppCardServiceManager.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pano.manager
+
+import android.app.Application
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.os.Message
+import android.os.Messenger
+import android.util.Log
+import androidx.lifecycle.AndroidViewModel
+import com.android.car.appcard.AppCardContext
+
+/** Used to communicate with AppCardService */
+class AppCardServiceManager(application: Application) : AndroidViewModel(application) {
+ private val applicationContext = application
+ private val handler = IncomingHandler(Looper.getMainLooper())
+ private var messenger = Messenger(handler)
+ private var serviceMessenger: Messenger? = null
+ private var sentKeyList = listOf<String>()
+ private val serviceConnection: ServiceConnection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+ serviceMessenger = Messenger(service)
+ askServiceToSendContextUpdates()
+ askServiceToSendOrderUpdates()
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ serviceMessenger = null
+ }
+
+ override fun onBindingDied(name: ComponentName?) {
+ bind()
+ }
+ }
+
+ init {
+ bind()
+ }
+
+ override fun onCleared() {
+ super.onCleared()
+ unregisterOrderListener()
+ unregisterContextListener()
+ unbind()
+ }
+
+ fun bind() {
+ val launchIntent = Intent()
+ launchIntent.action = SERVICE_ACTION
+ launchIntent.setPackage(SERVICE_PKG)
+ applicationContext.bindService(launchIntent, serviceConnection, Context.BIND_AUTO_CREATE)
+ }
+
+ fun unbind() {
+ applicationContext.unbindService(serviceConnection)
+ }
+
+ fun registerContextListener(listener: AppCardContextListener) {
+ handler.appCardContextListener = listener
+ handler.latestAppCardContext?.let {
+ listener.update(it)
+ }
+ askServiceToSendContextUpdates()
+ }
+
+ fun registerOrderListener(listener: AppCardOrderListener) {
+ handler.appCardOrderListener = listener
+ handler.latestAppCardOrder?.let {
+ listener.update(it)
+ // We only want to receive app card order once
+ handler.appCardOrderListener = null
+ }
+ askServiceToSendOrderUpdates()
+ }
+
+ fun sendAppCardOrder(appCards: List<String>) {
+ if (appCards == sentKeyList) {
+ return
+ }
+ serviceMessenger?.let {
+ val bundle = Bundle()
+ bundle.putStringArrayList(KEY_APP_CARD_ORDER, ArrayList(appCards))
+ val h = null
+ val msg = Message.obtain(h, MessageType.MSG_SEND_APP_CARD_UPDATE.type, bundle)
+ it.send(msg)
+ sentKeyList = appCards
+ }
+ }
+
+ private fun askServiceToSendContextUpdates() {
+ serviceMessenger?.send(getMessageWithHostId(MessageType.MSG_REGISTER_CONTEXT_LISTENER))
+ }
+
+ private fun askServiceToSendOrderUpdates() {
+ serviceMessenger?.send(getMessageWithHostId(MessageType.MSG_REGISTER_APP_CARD_LISTENER))
+ }
+
+ private fun unregisterOrderListener() {
+ serviceMessenger?.send(getMessageWithHostId(MessageType.MSG_UNREGISTER_APP_CARD_LISTENER))
+ }
+
+ private fun unregisterContextListener() {
+ serviceMessenger?.send(getMessageWithHostId(MessageType.MSG_UNREGISTER_CONTEXT_LISTENER))
+ }
+
+ private fun getMessageWithHostId(type: MessageType): Message {
+ val h = null
+ val msg = Message.obtain(h, type.type)
+ val bundle = Bundle()
+ bundle.putString(KEY_APP_CARD_HOST_ID, applicationContext.packageName)
+ msg.obj = bundle
+ msg.replyTo = messenger
+ return msg
+ }
+
+ private class IncomingHandler(looper: Looper) : Handler(looper) {
+ var appCardContextListener: AppCardContextListener? = null
+ var appCardOrderListener: AppCardOrderListener? = null
+ var latestAppCardContext: AppCardContext? = null
+ var latestAppCardOrder: List<String>? = null
+
+ override fun handleMessage(msg: Message) {
+ super.handleMessage(msg)
+ val msgType = MessageType.fromInt(msg.what)
+ logIfDebuggable("handleMessage: ${msgType.type}")
+ when (msgType) {
+ MessageType.MSG_SEND_CONTEXT_UPDATE -> {
+ AppCardContext.fromBundle(msg.obj as Bundle)?.let {
+ latestAppCardContext = it
+ appCardContextListener?.update(it)
+ }
+ }
+
+ MessageType.MSG_SEND_APP_CARD_UPDATE -> {
+ val bundle = msg.obj as Bundle
+ val order = bundle.getStringArrayList(KEY_APP_CARD_ORDER)
+ order?.let {
+ latestAppCardOrder = it
+ appCardOrderListener?.update(it)
+ // We only want to receive app card order once
+ appCardOrderListener = null
+ }
+ }
+
+ else -> return
+ }
+ }
+ }
+
+ interface AppCardContextListener {
+ fun update(appCardContext: AppCardContext)
+ }
+
+ interface AppCardOrderListener {
+ fun update(order: List<String>)
+ }
+
+ internal enum class MessageType(val type: Int) {
+ MSG_REGISTER_CONTEXT_LISTENER(0),
+ MSG_REGISTER_APP_CARD_LISTENER(1),
+ MSG_SEND_APP_CARD_UPDATE(2),
+ MSG_SEND_CONTEXT_UPDATE(3),
+ MSG_UNREGISTER_CONTEXT_LISTENER(4),
+ MSG_UNREGISTER_APP_CARD_LISTENER(5);
+
+ companion object {
+ fun fromInt(value: Int) = MessageType.entries.first { it.type == value }
+ }
+ }
+
+ companion object {
+ private const val KEY_APP_CARD_ORDER = "appCardOrder"
+ private const val KEY_APP_CARD_HOST_ID = "appCardHostId"
+ private const val TAG = "AppCardServiceManager"
+ private const val SERVICE_ACTION = "com.android.systemui.car.appcard.AppCardService"
+ private const val SERVICE_PKG = "com.android.systemui"
+
+ private fun logIfDebuggable(msg: String) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, msg)
+ }
+ }
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/AppCardType.kt b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/AppCardType.kt
new file mode 100644
index 0000000..24b1467
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/AppCardType.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pano.manager
+
+enum class AppCardType(val type: Int) {
+ IMAGE(1),
+ PROGRESS(2);
+
+ companion object {
+ fun fromInt(value: Int) = AppCardType.entries.first { it.type == value }
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/AppCardViewModel.kt b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/AppCardViewModel.kt
new file mode 100644
index 0000000..9089094
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/AppCardViewModel.kt
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pano.manager
+
+import android.app.Application
+import android.util.Log
+import android.widget.Toast
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MutableLiveData
+import com.android.car.appcard.AppCard
+import com.android.car.appcard.AppCardContentProvider
+import com.android.car.appcard.AppCardContext
+import com.android.car.appcard.host.AppCardComponentContainer
+import com.android.car.appcard.host.AppCardContainer
+import com.android.car.appcard.host.AppCardHost
+import com.android.car.appcard.host.AppCardListener
+import com.android.car.appcard.host.ApplicationIdentifier
+import com.android.car.pano.manager.picker.PickerAppCardViewAdapter
+import com.android.car.pano.manager.reorder.ReorderAppCardTouchHelper
+import com.android.internal.widget.helper.ItemTouchHelper
+import java.util.Collections
+import java.util.Optional
+
+/** An [AndroidViewModel] that registers itself as an [AppCardListener] */
+class AppCardViewModel(application: Application) : AndroidViewModel(application), AppCardListener,
+ ReorderAppCardTouchHelper.AppCardMoveHelperContract, PickerAppCardViewAdapter.Selector {
+ private var appCardHost: AppCardHost
+ private var appCardContext: AppCardContext? = null
+ var touchHelper: ReorderAppCardTouchHelper.AppCardTouchHelperContract? = null
+ private var inMoveState = false
+
+ val selectedAppCards = MutableLiveData<MutableList<AppCardContainer>>().apply {
+ value = mutableListOf()
+ }
+ val unselectedAppCards = MutableLiveData<MutableList<AppCardContainer>>().apply {
+ value = mutableListOf()
+ }
+ private val limitToast =
+ Toast.makeText(application, MAX_TOAST_TEXT, Toast.LENGTH_SHORT)
+
+ init {
+ appCardHost = AppCardHost(
+ application,
+ application.resources.getInteger(R.integer.app_card_update_rate_ms),
+ application.resources.getInteger(R.integer.app_card_fast_update_rate_ms),
+ ContextCompat.getMainExecutor(application)
+ )
+
+ // Register view model as [AppCardListener] so that changes to app cards can
+ // propagate to all views that use this view model
+ appCardHost.registerListener(listener = this)
+ }
+
+ fun setInitialList(initialOrder: List<String>) {
+ synchronized(lock = selectedAppCards) {
+ initialOrder.forEach { key ->
+ // Add [EmptyAppCard] for initial list so that they are updated in place once actual app
+ // card is received
+ getAppCardIdentifier(key)?.let {
+ val emptyAppCard = EmptyAppCard(it.second)
+ selectedAppCards.value?.add(AppCardContainer(it.first, emptyAppCard))
+ }
+ }
+ appCardContext?.let {
+ refreshSelected()
+ }
+ }
+ }
+
+ fun setAppCardContext(newAppCardContext: AppCardContext) {
+ synchronized(lock = selectedAppCards) {
+ appCardContext = newAppCardContext
+ refreshSelected()
+ }
+ }
+
+ override fun onCleared() {
+ super.onCleared()
+
+ removeSelectedAppCards()
+ removeUnselectedAppCards()
+ appCardHost.destroy()
+ }
+
+ /**
+ * A new [AppCardContainer] has been received, either update an existing view
+ * or create a new view representing the [AppCardContainer]
+ */
+ override fun onAppCardReceived(appCard: AppCardContainer) {
+ synchronized(lock = selectedAppCards) {
+ Log.d(TAG, "onAppCardReceived: ${appCard.appCard.id}")
+ if (!onAppCardReceivedInternal(selectedAppCards, appCard, shouldAdd = false)) {
+ onAppCardReceivedInternal(unselectedAppCards, appCard, shouldAdd = true)
+ }
+ }
+ }
+
+ private fun onAppCardReceivedInternal(
+ list: MutableLiveData<MutableList<AppCardContainer>>,
+ appCard: AppCardContainer,
+ shouldAdd: Boolean,
+ ): Boolean {
+ var updated = false
+ list.value?.forEach {
+ if (getKey(it) == getKey(appCard)) {
+ it.appCard = appCard.appCard
+ updated = true
+ }
+ }
+ if (!updated && shouldAdd) {
+ list.value?.add(appCard)
+ }
+
+ if (!inMoveState && (updated || shouldAdd)) {
+ list.value = list.value
+ }
+
+ return updated || shouldAdd
+ }
+
+ private fun getKey(appCard: AppCardContainer) = "${appCard.appId} : ${appCard.appCard.id}"
+
+ private fun getKey(component: AppCardComponentContainer) =
+ "${component.appId} : ${component.appCardId}"
+
+ private fun getAppCardIdentifier(key: String): Pair<ApplicationIdentifier, String>? {
+ val split = key.split(KEY_DELIMITER)
+ if (split.size != 3) {
+ return null
+ }
+ return Pair(ApplicationIdentifier(split[1], split[0]), split[2])
+ }
+
+ /** A new [AppCardComponentContainer] has been received, update an existing view */
+ override fun onComponentReceived(component: AppCardComponentContainer) {
+ synchronized(lock = selectedAppCards) {
+ if (!onComponentReceivedInternal(selectedAppCards, component)) {
+ onComponentReceivedInternal(unselectedAppCards, component)
+ }
+ }
+ }
+
+ private fun onComponentReceivedInternal(
+ list: MutableLiveData<MutableList<AppCardContainer>>,
+ component: AppCardComponentContainer,
+ ): Boolean {
+ var updated = false
+ list.value?.forEach {
+ if (getKey(it) == getKey(component)) {
+ updated = true
+ it.appCard.updateComponent(component.component)
+ }
+ }
+
+ if (!inMoveState && updated) {
+ list.value = list.value
+ }
+
+ return updated
+ }
+
+ /** A package has been removed from the system, remove all associated views */
+ override fun onProviderRemoved(packageName: String, authority: String?) {
+ synchronized(lock = selectedAppCards) {
+ onProviderRemovedInternal(selectedAppCards, packageName, authority)
+ onProviderRemovedInternal(unselectedAppCards, packageName, authority)
+ }
+ }
+
+ private fun onProviderRemovedInternal(
+ list: MutableLiveData<MutableList<AppCardContainer>>,
+ packageName: String,
+ authority: String?,
+ ) {
+ var removeIndex: Int? = null
+ list.value?.forEachIndexed { index, it ->
+ val identifier = it.appId
+ val isSamePackage = identifier.containsPackage(packageName)
+
+ var isSameAuthority = true
+ authority?.let {
+ isSameAuthority = identifier.containsAuthority(authority)
+ }
+
+ if (isSamePackage && isSameAuthority) {
+ removeIndex = index
+ return@forEachIndexed
+ }
+ }
+ removeIndex?.let {
+ list.value?.removeAt(it)
+ if (!inMoveState) {
+ list.value = list.value
+ }
+ }
+ }
+
+ /** A package has been added tp the system, refresh [AppCard]s if needed */
+ override fun onProviderAdded(packageName: String, authority: String?) {
+ Log.d(TAG, "onPackageAdded: $packageName")
+ refreshAll()
+ }
+
+ /**
+ * An error has occurred when communicating with an [AppCardContentProvider]
+ * Handle error
+ */
+ override fun onPackageCommunicationError(
+ identifier: ApplicationIdentifier,
+ throwable: Throwable,
+ ) {
+ Log.d(TAG, "onPackageCommunicationError: $identifier $throwable")
+ }
+
+ /**
+ * Ask [AppCardHost] to connect to new [AppCardContentProvider] and then retrieve new [AppCard]s
+ */
+ fun refreshSelected() {
+ appCardHost.refreshCompatibleApplication()
+
+ appCardContext?.let { context ->
+ selectedAppCards.value?.forEach {
+ appCardHost.requestAppCard(context, it.appId, it.appCard.id)
+ }
+ }
+ }
+
+ fun refreshAll() {
+ appCardHost.refreshCompatibleApplication()
+
+ appCardContext?.let {
+ appCardHost.getAllAppCards(it)
+ }
+ }
+
+ fun removeUnselectedAppCards() {
+ synchronized(lock = selectedAppCards) {
+ unselectedAppCards.value?.forEach {
+ appCardHost.notifyAppCardRemoved(
+ it.appId,
+ it.appCard.id
+ )
+ }
+
+ unselectedAppCards.value?.clear()
+ unselectedAppCards.value = mutableListOf()
+ }
+ }
+
+ fun removeSelectedAppCards() {
+ synchronized(lock = selectedAppCards) {
+ selectedAppCards.value?.forEach {
+ appCardHost.notifyAppCardRemoved(
+ it.appId,
+ it.appCard.id
+ )
+ }
+
+ selectedAppCards.value?.clear()
+ selectedAppCards.value = mutableListOf()
+ }
+ }
+
+ override fun onRowMoved(from: Int, to: Int) {
+ synchronized(lock = selectedAppCards) {
+ selectedAppCards.value?.let {
+ Collections.swap(it, from, to)
+ touchHelper?.setAppCards(it, Optional.of(from), Optional.of(to))
+ }
+ }
+ }
+
+ override fun stateChanged(actionState: Int) {
+ inMoveState = actionState == ItemTouchHelper.ACTION_STATE_DRAG
+ }
+
+ override fun shouldToggleSelect(appCardContainer: AppCardContainer): Boolean {
+ synchronized(lock = selectedAppCards) {
+ val key = getKey(appCardContainer)
+ var selectedIndex = Optional.empty<Int>()
+
+ selectedAppCards.value?.forEachIndexed { index, container ->
+ if (key == getKey(container)) {
+ selectedIndex = Optional.of(index)
+ return@forEachIndexed
+ }
+ }
+
+ if (selectedIndex.isEmpty) {
+ unselectedAppCards.value?.forEachIndexed { index, container ->
+ if (key == getKey(container)) {
+ selectedIndex = Optional.of(index)
+ return@forEachIndexed
+ }
+ }
+
+ if (selectedIndex.isPresent) {
+ selectedAppCards.value?.size?.let { size ->
+ if (size < MAX_SELECTED_CARDS) {
+ unselectedAppCards.value?.removeAt(selectedIndex.get())?.let {
+ selectedAppCards.value?.add(it)
+ return true
+ }
+ } else {
+ limitToast.show()
+ }
+ }
+ }
+ } else {
+ selectedAppCards.value?.removeAt(selectedIndex.get())?.let {
+ unselectedAppCards.value?.add(0, it)
+
+ return true
+ }
+ }
+ return false
+ }
+ }
+
+ companion object {
+ private const val TAG = "AppCardViewModel"
+ private const val KEY_DELIMITER = " : "
+ private const val MAX_SELECTED_CARDS = 3
+ private const val MAX_TOAST_TEXT = "Maximum app card limit reached"
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/EmptyAppCard.kt b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/EmptyAppCard.kt
new file mode 100644
index 0000000..a292a74
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/EmptyAppCard.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pano.manager
+
+import com.android.car.appcard.AppCard
+import com.android.car.appcard.component.Component
+
+/** Empty [AppCard] that is only used as a placeholder for initial list of app cards */
+class EmptyAppCard(id: String) : AppCard(id) {
+ override fun toByteArray() = ByteArray(0)
+
+ override fun updateComponent(component: Component) {
+ // no-op
+ }
+
+ override fun verifyUniquenessOfComponentIds() = false
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/MainActivity.kt b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/MainActivity.kt
new file mode 100644
index 0000000..d412649
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/MainActivity.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pano.manager
+
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.commit
+import com.android.car.appcard.AppCardContext
+import com.android.car.pano.manager.reorder.ReorderFragment
+
+class MainActivity : AppCompatActivity() {
+ private val appCardViewModel: AppCardViewModel by viewModels()
+ private val appCardServiceManager: AppCardServiceManager by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ appCardServiceManager.registerContextListener(object :
+ AppCardServiceManager.AppCardContextListener {
+ override fun update(appCardContext: AppCardContext) {
+ appCardViewModel.setAppCardContext(appCardContext)
+ }
+ })
+ appCardServiceManager.registerOrderListener(object :
+ AppCardServiceManager.AppCardOrderListener {
+ override fun update(order: List<String>) {
+ appCardViewModel.setInitialList(order)
+ }
+ })
+
+ supportFragmentManager.commit {
+ setReorderingAllowed(true)
+ add(R.id.fragment_container_view, ReorderFragment::class.java, savedInstanceState)
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+
+ appCardViewModel.refreshAll()
+ }
+
+ companion object {
+ private const val TAG = "MainActivity"
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/holder/ImageAppCardViewHolder.kt b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/holder/ImageAppCardViewHolder.kt
new file mode 100644
index 0000000..f64baa7
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/holder/ImageAppCardViewHolder.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pano.manager.holder
+
+import android.content.Context
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.view.View
+import android.widget.ImageView
+import android.widget.RelativeLayout
+import android.widget.TextView
+import androidx.core.graphics.drawable.toBitmap
+import androidx.recyclerview.widget.RecyclerView
+import com.android.car.appcard.ImageAppCard
+import com.android.car.appcard.component.Image
+import com.android.car.appcard.host.AppCardContainer
+import com.android.car.pano.manager.R
+import com.android.car.pano.manager.picker.PickerAppCardViewAdapter
+
+class ImageAppCardViewHolder(
+ context: Context,
+ private val view: View,
+ selector: PickerAppCardViewAdapter.Selector?,
+) : RecyclerView.ViewHolder(view) {
+ private val packageManager = context.packageManager
+ private val headerTextView = view.requireViewById<TextView>(R.id.header_text)
+ private val headerIconView = view.requireViewById<ImageView>(R.id.header_icon)
+ private val imageView = view.requireViewById<ImageView>(R.id.image)
+ private val primaryTextView = view.requireViewById<TextView>(R.id.primary_text)
+ private val secondaryTextView = view.requireViewById<TextView>(R.id.secondary_text)
+ private val imageTintColor = context.resources.getColor(R.color.app_card_image_tint, null)
+ private var isSelected = false
+ private val selectedBackground = context.resources.getDrawable(
+ R.drawable.app_card_selected_bg,
+ null
+ )
+ private val clearedBackground = context.resources.getDrawable(
+ R.drawable.app_card_bg,
+ null
+ )
+ private lateinit var appCardContainer: AppCardContainer
+
+ init {
+ selector?.let {
+ view.setOnClickListener { _ ->
+ if (it.shouldToggleSelect(appCardContainer)) {
+ isSelected = !isSelected
+ updateSelectedBackground()
+ }
+ }
+ }
+ }
+
+ fun bind(container: AppCardContainer, selected: Boolean) {
+ isSelected = selected
+ appCardContainer = container
+ val appCard = appCardContainer.appCard as ImageAppCard
+
+ updateSelectedBackground()
+
+ headerTextView.apply {
+ appCard.header?.title?.let {
+ text = it
+ visibility = View.VISIBLE
+ } ?: run {
+ visibility = View.INVISIBLE
+ }
+ }
+
+ headerIconView.apply {
+ appCard.header?.logo?.let {
+ setImageBitmap(it.imageData)
+ colorFilter = if (it.colorFilter == Image.ColorFilter.TINT) {
+ PorterDuffColorFilter(imageTintColor, PorterDuff.Mode.SRC_IN)
+ } else {
+ null
+ }
+ } ?: packageManager?.getApplicationIcon(appCardContainer.appId.packageName)?.toBitmap()?.let {
+ setImageBitmap(it)
+ }
+ }
+
+ imageView.apply {
+ appCard.image?.let {
+ setImageBitmap(it.imageData)
+ colorFilter = if (it.colorFilter == Image.ColorFilter.TINT) {
+ PorterDuffColorFilter(imageTintColor, PorterDuff.Mode.SRC_IN)
+ } else {
+ null
+ }
+ }
+ }
+
+ primaryTextView.apply {
+ appCard.primaryText?.let {
+ text = it
+ visibility = View.VISIBLE
+ } ?: run {
+ visibility = View.INVISIBLE
+ }
+ }
+
+ secondaryTextView.apply {
+ appCard.secondaryText?.let {
+ text = it
+ visibility = View.VISIBLE
+ } ?: run {
+ visibility = View.INVISIBLE
+ }
+ }
+ }
+
+ private fun updateSelectedBackground() {
+ view.findViewById<RelativeLayout>(R.id.card)?.background =
+ if (isSelected) selectedBackground else clearedBackground
+ }
+
+ companion object {
+ private const val TAG = "ImageAppCardViewHolder"
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/holder/ProgressAppCardViewHolder.kt b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/holder/ProgressAppCardViewHolder.kt
new file mode 100644
index 0000000..c49dd93
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/holder/ProgressAppCardViewHolder.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pano.manager.holder
+
+import android.content.Context
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.view.View
+import android.widget.ImageView
+import android.widget.ProgressBar
+import android.widget.RelativeLayout
+import android.widget.TextView
+import androidx.core.graphics.drawable.toBitmap
+import androidx.recyclerview.widget.RecyclerView
+import com.android.car.appcard.ImageAppCard
+import com.android.car.appcard.component.Image
+import com.android.car.appcard.host.AppCardContainer
+import com.android.car.pano.manager.R
+import com.android.car.pano.manager.picker.PickerAppCardViewAdapter
+
+class ProgressAppCardViewHolder(
+ context: Context,
+ private val view: View,
+ selector: PickerAppCardViewAdapter.Selector?,
+) : RecyclerView.ViewHolder(view) {
+ private val packageManager = context.packageManager
+ private val headerTextView = view.requireViewById<TextView>(R.id.header_text)
+ private val headerIconView = view.requireViewById<ImageView>(R.id.header_icon)
+ private val progressBarView = view.requireViewById<ProgressBar>(R.id.progress_bar).apply {
+ isIndeterminate = false
+ }
+ private val primaryTextView = view.requireViewById<TextView>(R.id.primary_text)
+ private val secondaryTextView = view.requireViewById<TextView>(R.id.secondary_text)
+ private val imageTintColor =
+ context.resources.getColor(R.color.app_card_image_tint, null)
+ private var isSelected = false
+ private val selectedBackground = context.resources.getDrawable(
+ R.drawable.app_card_selected_bg,
+ null
+ )
+ private val clearedBackground = context.resources.getDrawable(
+ R.drawable.app_card_bg,
+ null
+ )
+ private lateinit var appCardContainer: AppCardContainer
+
+ init {
+ selector?.let {
+ view.setOnClickListener { _ ->
+ if (it.shouldToggleSelect(appCardContainer)) {
+ isSelected = !isSelected
+ updateSelectedBackground()
+ }
+ }
+ }
+ }
+
+ fun bind(container: AppCardContainer, selected: Boolean) {
+ isSelected = selected
+ appCardContainer = container
+ val appCard = appCardContainer.appCard as ImageAppCard
+
+ updateSelectedBackground()
+
+ headerTextView.apply {
+ appCard.header?.title?.let {
+ text = it
+ } ?: run {
+ visibility = View.INVISIBLE
+ }
+ }
+
+ headerIconView.apply {
+ appCard.header?.logo?.let {
+ setImageBitmap(it.imageData)
+ colorFilter = if (it.colorFilter == Image.ColorFilter.TINT) {
+ PorterDuffColorFilter(imageTintColor, PorterDuff.Mode.SRC_IN)
+ } else {
+ null
+ }
+ } ?: packageManager?.getApplicationIcon(appCardContainer.appId.packageName)?.toBitmap()?.let {
+ setImageBitmap(it)
+ }
+ }
+
+ progressBarView.apply {
+ appCard.progressBar?.let {
+ min = it.min
+ max = it.max
+ progress = it.progress
+ visibility = View.VISIBLE
+ } ?: run {
+ visibility = View.INVISIBLE
+ }
+ }
+
+ primaryTextView.apply {
+ appCard.primaryText?.let {
+ text = it
+ visibility = View.VISIBLE
+ } ?: run {
+ visibility = View.INVISIBLE
+ }
+ }
+
+ secondaryTextView.apply {
+ appCard.secondaryText?.let {
+ text = it
+ visibility = View.VISIBLE
+ } ?: run {
+ visibility = View.INVISIBLE
+ }
+ }
+ }
+
+ private fun updateSelectedBackground() {
+ view.findViewById<RelativeLayout>(R.id.card)?.background =
+ if (isSelected) selectedBackground else clearedBackground
+ }
+
+ companion object {
+ private const val TAG = "ProgressAppCardViewHolder"
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/picker/PickerAppCardViewAdapter.kt b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/picker/PickerAppCardViewAdapter.kt
new file mode 100644
index 0000000..da0d80d
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/picker/PickerAppCardViewAdapter.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pano.manager.picker
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.android.car.appcard.ImageAppCard
+import com.android.car.appcard.host.AppCardContainer
+import com.android.car.pano.manager.AppCardServiceManager
+import com.android.car.pano.manager.AppCardType
+import com.android.car.pano.manager.R
+import com.android.car.pano.manager.holder.ImageAppCardViewHolder
+import com.android.car.pano.manager.holder.ProgressAppCardViewHolder
+
+class PickerAppCardViewAdapter(
+ private val context: Context,
+ private val inflater: LayoutInflater,
+ private val appCardServiceManager: AppCardServiceManager,
+ private val selector: Selector,
+) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
+ private val selectedAppCards: MutableList<AppCardContainer> = mutableListOf()
+ private val unselectedAppCards: MutableList<AppCardContainer> = mutableListOf()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ val type = AppCardType.fromInt(viewType)
+ val attachToRoot = false
+ return when (type) {
+ AppCardType.IMAGE -> {
+ val view = inflater.inflate(
+ R.layout.image_app_card,
+ parent,
+ attachToRoot
+ )
+ ImageAppCardViewHolder(context, view, selector)
+ }
+
+ AppCardType.PROGRESS -> {
+ val view = inflater.inflate(
+ R.layout.progress_buttons_app_card,
+ parent,
+ attachToRoot
+ )
+ ProgressAppCardViewHolder(context, view, selector)
+ }
+ }
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ val appCard = if (position >= selectedAppCards.size) {
+ unselectedAppCards[position - selectedAppCards.size].appCard as ImageAppCard
+ } else {
+ selectedAppCards[position].appCard as ImageAppCard
+ }
+
+ appCard.image?.let {
+ return AppCardType.IMAGE.type
+ } ?: run {
+ return AppCardType.PROGRESS.type
+ }
+ }
+
+ override fun getItemCount(): Int = unselectedAppCards.size + selectedAppCards.size
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ val type = AppCardType.fromInt(holder.itemViewType)
+ var selected = false
+ val card = if (position >= selectedAppCards.size) {
+ unselectedAppCards[position - selectedAppCards.size]
+ } else {
+ selected = true
+ selectedAppCards[position]
+ }
+
+ when (type) {
+ AppCardType.IMAGE -> {
+ (holder as ImageAppCardViewHolder).bind(card, selected)
+ }
+
+ AppCardType.PROGRESS -> {
+ (holder as ProgressAppCardViewHolder).bind(card, selected)
+ }
+ }
+ }
+
+ fun setAppCards(
+ newSelected: MutableList<AppCardContainer>,
+ newUnselected: MutableList<AppCardContainer>,
+ ) {
+ selectedAppCards.clear()
+ unselectedAppCards.clear()
+ newSelected.forEach {
+ // Ignore [EmptyAppCard] for recyclerview
+ if (it.appCard is ImageAppCard) {
+ selectedAppCards.add(it)
+ }
+ }
+ newUnselected.forEach {
+ // Ignore [EmptyAppCard] for recyclerview
+ if (it.appCard is ImageAppCard) {
+ unselectedAppCards.add(it)
+ }
+ }
+
+ notifyDataSetChanged()
+
+ val keyList = mutableListOf<String>()
+ selectedAppCards.forEach { appCardContainer ->
+ keyList.add("${appCardContainer.appId} : ${appCardContainer.appCard.id}")
+ }
+ appCardServiceManager.sendAppCardOrder(keyList)
+ }
+
+ interface Selector {
+ fun shouldToggleSelect(appCardContainer: AppCardContainer): Boolean
+ }
+
+ companion object {
+ private const val TAG = "PickerAppCardViewAdapter"
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/picker/PickerFragment.kt b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/picker/PickerFragment.kt
new file mode 100644
index 0000000..92161cd
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/picker/PickerFragment.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pano.manager.picker
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.Button
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.fragment.app.commit
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.car.pano.manager.AppCardServiceManager
+import com.android.car.pano.manager.AppCardViewModel
+import com.android.car.pano.manager.R
+import com.android.car.pano.manager.reorder.ReorderFragment
+
+class PickerFragment : Fragment(R.layout.picker_fragment) {
+ private lateinit var inflater: LayoutInflater
+ private lateinit var listView: RecyclerView
+ private lateinit var adapter: PickerAppCardViewAdapter
+ private val appCardViewModel: AppCardViewModel by activityViewModels()
+ private val appCardServiceManager: AppCardServiceManager by activityViewModels()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ view.requireViewById<Button>(R.id.done_button).setOnClickListener {
+ activity?.supportFragmentManager?.commit {
+ setReorderingAllowed(true)
+ val args = null
+ add(R.id.fragment_container_view, ReorderFragment::class.java, args)
+ }
+ }
+
+ listView = view.requireViewById(R.id.all_app_cards)
+ inflater = LayoutInflater.from(activity)
+ adapter = PickerAppCardViewAdapter(context!!, inflater, appCardServiceManager, appCardViewModel)
+ appCardViewModel.touchHelper = null
+
+ val owner = this
+ appCardViewModel.unselectedAppCards.observe(owner) {
+ appCardViewModel.selectedAppCards.value?.let { selected ->
+ adapter.setAppCards(selected, it)
+ }
+ }
+ appCardViewModel.selectedAppCards.observe(owner) {
+ appCardViewModel.unselectedAppCards.value?.let { unselected ->
+ adapter.setAppCards(it, unselected)
+ }
+ }
+
+ listView.adapter = adapter
+ val reverseLayout = false
+ listView.layoutManager = LinearLayoutManager(
+ context,
+ LinearLayoutManager.HORIZONTAL,
+ reverseLayout
+ )
+ }
+
+ override fun onStart() {
+ super.onStart()
+
+ appCardViewModel.refreshAll()
+ }
+
+ override fun onStop() {
+ super.onStop()
+
+ appCardViewModel.removeUnselectedAppCards()
+ }
+
+ companion object {
+ private const val TAG = "PickerFragment"
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/reorder/ReorderAppCardTouchHelper.kt b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/reorder/ReorderAppCardTouchHelper.kt
new file mode 100644
index 0000000..c702804
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/reorder/ReorderAppCardTouchHelper.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pano.manager.reorder
+
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.RecyclerView
+import com.android.car.appcard.host.AppCardContainer
+import java.util.Optional
+
+class ReorderAppCardTouchHelper(
+ private val touchHelper: AppCardTouchHelperContract,
+ private val moveHelper: AppCardMoveHelperContract,
+) : ItemTouchHelper.Callback() {
+ private var prevTo = -1
+
+ override fun getMovementFlags(list: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
+ val dragFlag = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
+ val swipeFlags = 0
+ return makeMovementFlags(dragFlag, swipeFlags)
+ }
+
+ override fun onMove(
+ list: RecyclerView,
+ viewHolder: RecyclerView.ViewHolder,
+ target: RecyclerView.ViewHolder
+ ): Boolean {
+ var from = viewHolder.bindingAdapterPosition
+ val to = target.bindingAdapterPosition
+ // When moving items from start to end, when in an interim state sometimes $from is -1, hence,
+ // we should change $from to previous $to for correct behavior
+ if (from < to && from == RecyclerView.NO_POSITION) {
+ from = prevTo
+ }
+ if (from == to) {
+ return false
+ }
+ moveHelper.onRowMoved(from, to)
+ prevTo = to
+ return true
+ }
+
+ override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
+ moveHelper.stateChanged(actionState)
+ if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
+ touchHelper.onRowSelected(viewHolder)
+ }
+ super.onSelectedChanged(viewHolder, actionState)
+ }
+
+ override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
+ super.clearView(recyclerView, viewHolder)
+ touchHelper.onRowClear(viewHolder)
+ }
+
+ override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
+ // no-op
+ }
+
+ override fun isLongPressDragEnabled(): Boolean {
+ return true
+ }
+
+ override fun isItemViewSwipeEnabled(): Boolean {
+ return false
+ }
+
+ interface AppCardMoveHelperContract {
+ fun onRowMoved(from: Int, to: Int)
+ fun stateChanged(actionState: Int)
+ }
+
+ interface AppCardTouchHelperContract {
+ fun onRowSelected(myViewHolder: RecyclerView.ViewHolder?)
+ fun onRowClear(myViewHolder: RecyclerView.ViewHolder?)
+ fun setAppCards(newList: MutableList<AppCardContainer>, from: Optional<Int>, to: Optional<Int>)
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/reorder/ReorderAppCardViewAdapter.kt b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/reorder/ReorderAppCardViewAdapter.kt
new file mode 100644
index 0000000..c1b1e79
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/reorder/ReorderAppCardViewAdapter.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pano.manager.reorder
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.RelativeLayout
+import androidx.recyclerview.widget.RecyclerView
+import com.android.car.appcard.ImageAppCard
+import com.android.car.appcard.host.AppCardContainer
+import com.android.car.pano.manager.AppCardServiceManager
+import com.android.car.pano.manager.AppCardType
+import com.android.car.pano.manager.R
+import com.android.car.pano.manager.holder.ImageAppCardViewHolder
+import com.android.car.pano.manager.holder.ProgressAppCardViewHolder
+import java.util.Optional
+
+class ReorderAppCardViewAdapter(
+ private val context: Context,
+ private val inflater: LayoutInflater,
+ private val appCardServiceManager: AppCardServiceManager,
+) : RecyclerView.Adapter<RecyclerView.ViewHolder>(),
+ ReorderAppCardTouchHelper.AppCardTouchHelperContract {
+ private val selectedBackground = context.resources.getDrawable(
+ R.drawable.app_card_selected_bg,
+ null
+ )
+ private val clearedBackground = context.resources.getDrawable(
+ R.drawable.app_card_bg,
+ null
+ )
+ private val appCards: MutableList<AppCardContainer> = mutableListOf()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ val type = AppCardType.fromInt(viewType)
+ val attachToRoot = false
+ return when (type) {
+ AppCardType.IMAGE -> {
+ val view = inflater.inflate(
+ R.layout.image_app_card,
+ parent,
+ attachToRoot
+ )
+ ImageAppCardViewHolder(context, view, selector = null)
+ }
+
+ AppCardType.PROGRESS -> {
+ val view = inflater.inflate(
+ R.layout.progress_buttons_app_card,
+ parent,
+ attachToRoot
+ )
+ ProgressAppCardViewHolder(context, view, selector = null)
+ }
+ }
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ val appCard = appCards[position].appCard as ImageAppCard
+ appCard.image?.let {
+ return AppCardType.IMAGE.type
+ } ?: run {
+ return AppCardType.PROGRESS.type
+ }
+ }
+
+ override fun getItemCount(): Int = appCards.size
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ val type = AppCardType.fromInt(holder.itemViewType)
+ when (type) {
+ AppCardType.IMAGE -> {
+ (holder as ImageAppCardViewHolder).bind(appCards[position], selected = false)
+ }
+
+ AppCardType.PROGRESS -> {
+ (holder as ProgressAppCardViewHolder).bind(appCards[position], selected = false)
+ }
+ }
+ }
+
+ override fun setAppCards(
+ newList: MutableList<AppCardContainer>,
+ from: Optional<Int>,
+ to: Optional<Int>
+ ) {
+ appCards.clear()
+ newList.forEach {
+ // Ignore [EmptyAppCard] for recyclerview
+ if (it.appCard is ImageAppCard) {
+ appCards.add(it)
+ }
+ }
+ if (from.isEmpty && to.isEmpty) {
+ notifyDataSetChanged()
+ } else {
+ notifyItemMoved(from.get(), to.get())
+
+ val keyList = mutableListOf<String>()
+ appCards.forEach { appCardContainer ->
+ keyList.add("${appCardContainer.appId} : ${appCardContainer.appCard.id}")
+ }
+ appCardServiceManager.sendAppCardOrder(keyList)
+ }
+ }
+
+ override fun onRowSelected(myViewHolder: RecyclerView.ViewHolder?) {
+ myViewHolder?.itemView?.findViewById<RelativeLayout>(R.id.card)?.background = selectedBackground
+ }
+
+ override fun onRowClear(myViewHolder: RecyclerView.ViewHolder?) {
+ myViewHolder?.itemView?.findViewById<RelativeLayout>(R.id.card)?.background = clearedBackground
+ }
+
+ companion object {
+ private const val TAG = "ReorderAppCardViewAdapter"
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/reorder/ReorderFragment.kt b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/reorder/ReorderFragment.kt
new file mode 100644
index 0000000..a2e67c5
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplayPanoManager/src/com/android/car/pano/manager/reorder/ReorderFragment.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pano.manager.reorder
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.Button
+import android.widget.ImageButton
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.fragment.app.commit
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.LinearSnapHelper
+import androidx.recyclerview.widget.RecyclerView
+import com.android.car.pano.manager.AppCardServiceManager
+import com.android.car.pano.manager.AppCardViewModel
+import com.android.car.pano.manager.R
+import com.android.car.pano.manager.picker.PickerFragment
+import java.util.Optional
+
+class ReorderFragment : Fragment(R.layout.reorder_fragment) {
+ private lateinit var inflater: LayoutInflater
+ private lateinit var listView: RecyclerView
+ private lateinit var adapter: ReorderAppCardViewAdapter
+ private val appCardViewModel: AppCardViewModel by activityViewModels()
+ private val appCardServiceManager: AppCardServiceManager by activityViewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ inflater = LayoutInflater.from(context)
+ context?.let { adapter = ReorderAppCardViewAdapter(it, inflater, appCardServiceManager) }
+ appCardViewModel.touchHelper = adapter
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ listView = view.requireViewById(R.id.selected_app_cards)
+ view.requireViewById<ImageButton>(R.id.close_button).setOnClickListener {
+ activity?.finish()
+ }
+ view.requireViewById<Button>(R.id.add_replace_button).setOnClickListener {
+ activity?.supportFragmentManager?.commit {
+ setReorderingAllowed(true)
+ val args = null
+ add(R.id.fragment_container_view, PickerFragment::class.java, args)
+ }
+ }
+
+ val snapHelper = LinearSnapHelper()
+ snapHelper.attachToRecyclerView(listView)
+
+ val owner = this
+ appCardViewModel.selectedAppCards.observe(owner) {
+ adapter.setAppCards(it, from = Optional.empty(), to = Optional.empty())
+ }
+
+ val callback = ReorderAppCardTouchHelper(adapter, appCardViewModel)
+ val touchHelper = ItemTouchHelper(callback)
+ touchHelper.attachToRecyclerView(listView)
+
+ listView.adapter = adapter
+ val reverseLayout = false
+ listView.layoutManager = LinearLayoutManager(
+ context,
+ LinearLayoutManager.HORIZONTAL,
+ reverseLayout
+ )
+ }
+
+ override fun onStart() {
+ super.onStart()
+
+ appCardViewModel.refreshSelected()
+ }
+
+ companion object {
+ private const val TAG = "PickerFragment"
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/Android.bp b/car_product/distant_display/apps/CarDistantDisplaySystemUI/Android.bp
index 6affab6..d1e6e57 100644
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/Android.bp
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/Android.bp
@@ -19,12 +19,20 @@
android_app {
name: "CarDistantDisplaySystemUI",
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
resource_dirs: ["res"],
static_libs: [
"CarSystemUI-core",
+ "displaycompat-service",
+
+ // Kotlin
+ "kotlin-stdlib",
+ "kotlinx-coroutines-android",
],
libs: [
@@ -61,3 +69,54 @@
"allowed_privapp_com.android.carsystemui",
],
}
+
+android_library_import {
+ name: "displaycompat-service",
+ aars: [
+ "displaycompat-service.aar",
+ ],
+ sdk_version: "34",
+}
+
+android_library {
+ name: "DistantDisplaySystemUI-tests",
+
+ manifest: "tests/AndroidManifest.xml",
+ resource_dirs: [
+ "tests/res",
+ "res",
+ ],
+ srcs: [
+ "tests/src/**/*.java",
+ "src/**/*.java",
+ "src/**/*.kt",
+ "src/**/I*.aidl",
+ ],
+
+ static_libs: [
+ "CarSystemUI-tests",
+ "displaycompat-service",
+ ],
+
+ libs: [
+ "android.car",
+ "android.car-system-stubs",
+ ],
+ aaptflags: [
+ "--extra-packages",
+ "com.android.systemui",
+ ],
+
+ plugins: ["dagger2-compiler"],
+
+ lint: {
+ test: true,
+ },
+
+ // TODO(b/218518451) re-enable errorprone.
+ errorprone: {
+ enabled: false,
+ },
+ // TODO(b/319708040): re-enable use_resource_processor
+ use_resource_processor: false,
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/AndroidManifest.xml b/car_product/distant_display/apps/CarDistantDisplaySystemUI/AndroidManifest.xml
index ea3903f..bb40040 100644
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/AndroidManifest.xml
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/AndroidManifest.xml
@@ -21,6 +21,7 @@
package="com.android.systemui"
android:sharedUserId="android.uid.systemui"
coreApp="true">
+
<!-- Permission to assign Activity to TDA -->
<uses-permission android:name="android.car.permission.CONTROL_CAR_APP_LAUNCH"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
@@ -41,12 +42,37 @@
<!-- Permission to get outside temperature -->
<uses-permission android:name="android.car.permission.CAR_EXTERIOR_ENVIRONMENT"/>
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+ <uses-permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY"/>
<protected-broadcast android:name="REQUEST_FROM_SYSTEM_UI" />
<application
tools:replace="android:appComponentFactory"
android:appComponentFactory="com.android.systemui.SystemUIAppComponentFactory">
<activity
+ android:name=".car.distantdisplay.activity.DistantDisplayCompanionActivity"
+ android:documentLaunchMode="always"
+ android:excludeFromRecents="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+ android:exported="false"
+ android:showForAllUsers="true"
+ android:theme="@android:style/Theme.NoTitleBar">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".car.distantdisplay.activity.DistantDisplayGameController"
+ android:documentLaunchMode="always"
+ android:excludeFromRecents="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+ android:exported="true"
+ android:showForAllUsers="true"
+ android:theme="@android:style/Theme.NoTitleBar">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ </intent-filter>
+ </activity>
+ <activity
android:name=".car.distantdisplay.activity.NavigationTaskViewWallpaperActivity"
android:exported="true"
android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
@@ -84,5 +110,16 @@
<action android:name="com.android.systemui.car.intent.action.DISTANT_DISPLAY"/>
</intent-filter>
</receiver>
+ <service
+ android:name="com.android.systemui.car.appcard.AppCardService"
+ android:enabled="true"
+ android:singleUser="true"
+ android:permission="android.car.permission.BIND_APP_CARD_PROVIDER"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.systemui.car.appcard.AppCardService" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </service>
</application>
</manifest>
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/displaycompat-service.aar b/car_product/distant_display/apps/CarDistantDisplaySystemUI/displaycompat-service.aar
new file mode 100644
index 0000000..6eb1950
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/displaycompat-service.aar
Binary files differ
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/drawable/dpad_image.xml b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/drawable/dpad_image.xml
new file mode 100644
index 0000000..f67b4a3
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/drawable/dpad_image.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="470dp"
+ android:height="470dp"
+ android:viewportWidth="470"
+ android:viewportHeight="470">
+ <path
+ android:pathData="M235,235m-235,0a235,235 0,1 1,470 0a235,235 0,1 1,-470 0"
+ android:strokeAlpha="0.2"
+ android:fillColor="#32363C"
+ android:fillAlpha="0.2"/>
+ <path
+ android:pathData="M90.85,182.39C83.29,182.39 77.16,188.52 77.16,196.08L77.16,273.92C77.16,281.48 83.29,287.61 90.85,287.61L182.39,287.61L182.39,379.15C182.39,386.71 188.52,392.84 196.08,392.84L273.92,392.84C281.48,392.84 287.61,386.71 287.61,379.15L287.61,287.61L379.15,287.61C386.71,287.61 392.84,281.48 392.84,273.92L392.84,196.08C392.84,188.52 386.71,182.39 379.15,182.39L287.61,182.39L287.61,90.86C287.61,83.29 281.48,77.17 273.92,77.17L196.08,77.17C188.52,77.17 182.39,83.29 182.39,90.86L182.39,182.39L90.85,182.39Z"
+ android:fillColor="#1F1F1F"
+ android:fillType="evenOdd"/>
+ <group>
+ <clip-path
+ android:pathData="M67.16,67.84h335v335h-335z"/>
+ <clip-path
+ android:pathData="M90.85,182.39C83.29,182.39 77.16,188.52 77.16,196.08L77.16,273.92C77.16,281.48 83.29,287.61 90.85,287.61L182.39,287.61L182.39,379.15C182.39,386.71 188.52,392.84 196.08,392.84L273.92,392.84C281.48,392.84 287.61,386.71 287.61,379.15L287.61,287.61L379.15,287.61C386.71,287.61 392.84,281.48 392.84,273.92L392.84,196.08C392.84,188.52 386.71,182.39 379.15,182.39L287.61,182.39L287.61,90.86C287.61,83.29 281.48,77.17 273.92,77.17L196.08,77.17C188.52,77.17 182.39,83.29 182.39,90.86L182.39,182.39L90.85,182.39Z"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M77.16,196.08L86.29,196.08L77.16,196.08ZM90.85,182.39L90.85,191.51L90.85,182.39ZM77.16,273.92L68.04,273.92L77.16,273.92ZM90.85,287.61L90.85,278.49H90.85L90.85,287.61ZM182.39,287.61L191.51,287.61L191.51,278.49L182.39,278.49L182.39,287.61ZM182.39,379.15L191.51,379.15L191.51,379.15L182.39,379.15ZM196.08,392.84L196.08,383.71L196.08,392.84ZM273.92,392.84L273.92,401.96L273.92,392.84ZM287.61,379.15L278.49,379.15L278.49,379.15L287.61,379.15ZM287.61,287.61L287.61,278.49L278.49,278.49L278.49,287.61L287.61,287.61ZM379.15,287.61L379.15,296.74L379.15,287.61ZM287.61,182.39L278.49,182.39L278.49,191.51L287.61,191.51L287.61,182.39ZM287.61,90.86L296.74,90.86L287.61,90.86ZM273.92,77.17L273.92,68.04L273.92,77.17ZM196.08,77.17L196.08,86.29L196.08,77.17ZM182.39,90.86L191.51,90.86L182.39,90.86ZM182.39,182.39L182.39,191.51L191.51,191.51L191.51,182.39L182.39,182.39ZM86.29,196.08C86.29,193.56 88.33,191.51 90.85,191.51L90.85,173.26C78.25,173.26 68.04,183.48 68.04,196.08L86.29,196.08ZM86.29,273.92L86.29,196.08L68.04,196.08L68.04,273.92L86.29,273.92ZM90.85,278.49C88.33,278.49 86.29,276.44 86.29,273.92L68.04,273.92C68.04,286.52 78.25,296.74 90.85,296.74L90.85,278.49ZM182.39,278.49L90.85,278.49L90.85,296.74L182.39,296.74L182.39,278.49ZM191.51,379.15L191.51,287.61L173.26,287.61L173.26,379.15L191.51,379.15ZM196.08,383.71C193.56,383.71 191.51,381.67 191.51,379.15L173.26,379.15C173.26,391.75 183.48,401.96 196.08,401.96L196.08,383.71ZM273.92,383.71L196.08,383.71L196.08,401.96L273.92,401.96L273.92,383.71ZM278.49,379.15C278.49,381.67 276.44,383.71 273.92,383.71L273.92,401.96C286.52,401.96 296.74,391.75 296.74,379.15L278.49,379.15ZM278.49,287.61L278.49,379.15L296.74,379.15L296.74,287.61L278.49,287.61ZM379.15,278.49L287.61,278.49L287.61,296.74L379.15,296.74L379.15,278.49ZM383.71,273.92C383.71,276.44 381.67,278.49 379.15,278.49L379.15,296.74C391.75,296.74 401.96,286.52 401.96,273.92L383.71,273.92ZM383.71,196.08L383.71,273.92L401.96,273.92L401.96,196.08L383.71,196.08ZM379.15,191.51C381.67,191.51 383.71,193.56 383.71,196.08L401.96,196.08C401.96,183.48 391.75,173.26 379.15,173.26L379.15,191.51ZM287.61,191.51L379.15,191.51L379.15,173.26L287.61,173.26L287.61,191.51ZM278.49,90.86L278.49,182.39L296.74,182.39L296.74,90.86L278.49,90.86ZM273.92,86.29C276.44,86.29 278.49,88.33 278.49,90.86L296.74,90.86C296.74,78.25 286.52,68.04 273.92,68.04L273.92,86.29ZM196.08,86.29L273.92,86.29L273.92,68.04L196.08,68.04L196.08,86.29ZM191.51,90.86C191.51,88.33 193.56,86.29 196.08,86.29L196.08,68.04C183.48,68.04 173.26,78.25 173.26,90.86L191.51,90.86ZM191.51,182.39L191.51,90.86L173.26,90.86L173.26,182.39L191.51,182.39ZM90.85,191.51L182.39,191.51L182.39,173.26L90.85,173.26L90.85,191.51Z"
+ android:fillColor="#1F1F1F"/>
+ </group>
+ <path
+ android:pathData="M90.85,182.39C83.29,182.39 77.16,188.52 77.16,196.08L77.16,273.92C77.16,281.48 83.29,287.61 90.85,287.61L182.39,287.61L182.39,379.15C182.39,386.71 188.52,392.84 196.08,392.84L273.92,392.84C281.48,392.84 287.61,386.71 287.61,379.15L287.61,287.61L379.15,287.61C386.71,287.61 392.84,281.48 392.84,273.92L392.84,196.08C392.84,188.52 386.71,182.39 379.15,182.39L287.61,182.39L287.61,90.86C287.61,83.29 281.48,77.17 273.92,77.17L196.08,77.17C188.52,77.17 182.39,83.29 182.39,90.86L182.39,182.39L90.85,182.39Z"
+ android:fillType="evenOdd">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:startX="235"
+ android:startY="77.17"
+ android:endX="235"
+ android:endY="235"
+ android:type="linear">
+ <item android:offset="0" android:color="#FF797C7F"/>
+ <item android:offset="1" android:color="#FF363738"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:pathData="M90.85,182.39C83.29,182.39 77.16,188.52 77.16,196.08L77.16,273.92C77.16,281.48 83.29,287.61 90.85,287.61L182.39,287.61L182.39,379.15C182.39,386.71 188.52,392.84 196.08,392.84L273.92,392.84C281.48,392.84 287.61,386.71 287.61,379.15L287.61,287.61L379.15,287.61C386.71,287.61 392.84,281.48 392.84,273.92L392.84,196.08C392.84,188.52 386.71,182.39 379.15,182.39L287.61,182.39L287.61,90.86C287.61,83.29 281.48,77.17 273.92,77.17L196.08,77.17C188.52,77.17 182.39,83.29 182.39,90.86L182.39,182.39L90.85,182.39Z"
+ android:fillType="evenOdd">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:startX="235"
+ android:startY="77.17"
+ android:endX="235"
+ android:endY="235"
+ android:type="linear">
+ <item android:offset="0" android:color="#FF797C7F"/>
+ <item android:offset="1" android:color="#FF363738"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:pathData="M98.21,182.39C86.59,182.39 77.16,191.81 77.16,203.43L77.16,266.57C77.16,278.19 86.59,287.61 98.21,287.61L173.26,287.61C178.3,287.61 182.39,291.7 182.39,296.74L182.39,371.79C182.39,383.42 191.81,392.84 203.43,392.84L266.57,392.84C278.19,392.84 287.61,383.42 287.61,371.79L287.61,296.74C287.61,291.7 291.7,287.61 296.74,287.61L371.79,287.61C383.41,287.61 392.84,278.19 392.84,266.57L392.84,203.43C392.84,191.81 383.41,182.39 371.79,182.39L296.74,182.39C291.7,182.39 287.61,178.3 287.61,173.26L287.61,98.21C287.61,86.59 278.19,77.17 266.57,77.17L203.43,77.17C191.81,77.17 182.39,86.59 182.39,98.21L182.39,173.26C182.39,178.3 178.3,182.39 173.26,182.39L98.21,182.39Z"
+ android:fillType="evenOdd">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:startX="235"
+ android:startY="77.17"
+ android:endX="235"
+ android:endY="392.84"
+ android:type="linear">
+ <item android:offset="0" android:color="#FF505152"/>
+ <item android:offset="0.09" android:color="#FF363738"/>
+ <item android:offset="0.3" android:color="#FF363738"/>
+ <item android:offset="0.36" android:color="#FF505152"/>
+ <item android:offset="0.41" android:color="#FF363738"/>
+ <item android:offset="1" android:color="#FF363738"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:pathData="M101.72,192.91C93.97,192.91 87.69,199.19 87.69,206.94L87.69,263.06C87.69,270.81 93.97,277.09 101.72,277.09L179.22,277.09C186.78,277.09 192.91,283.22 192.91,290.78L192.91,368.29C192.91,376.03 199.19,382.32 206.94,382.32L263.06,382.32C270.81,382.32 277.09,376.03 277.09,368.29L277.09,290.78C277.09,283.22 283.22,277.09 290.78,277.09L368.29,277.09C376.03,277.09 382.31,270.81 382.31,263.06L382.31,206.94C382.31,199.19 376.03,192.91 368.29,192.91L290.78,192.91C283.22,192.91 277.09,186.78 277.09,179.22L277.09,101.72C277.09,93.97 270.81,87.69 263.06,87.69L206.94,87.69C199.19,87.69 192.91,93.97 192.91,101.72L192.91,179.22C192.91,186.78 186.78,192.91 179.22,192.91L101.72,192.91Z"
+ android:fillType="evenOdd">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:startX="241.14"
+ android:startY="87.69"
+ android:endX="247.28"
+ android:endY="382.31"
+ android:type="linear">
+ <item android:offset="0.07" android:color="#FF373839"/>
+ <item android:offset="0.61" android:color="#FF474849"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:pathData="M235,235m-40.34,0a40.34,40.34 0,1 1,80.67 0a40.34,40.34 0,1 1,-80.67 0"
+ android:strokeAlpha="0.5"
+ android:fillAlpha="0.5">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:startX="235"
+ android:startY="194.67"
+ android:endX="235"
+ android:endY="275.34"
+ android:type="linear">
+ <item android:offset="0" android:color="#9E050505"/>
+ <item android:offset="1" android:color="#00FFFFFF"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:pathData="M219.9,336.72L250.1,336.72C251.9,336.72 252.99,338.72 252.01,340.24L236.91,363.57C236.01,364.96 233.98,364.96 233.08,363.57L217.99,340.24C217,338.72 218.09,336.72 219.9,336.72Z"
+ android:strokeAlpha="0.4"
+ android:fillColor="#1F1F1F"
+ android:fillAlpha="0.4"/>
+ <path
+ android:pathData="M136.79,221.45L136.79,248.55C136.79,250.31 134.87,251.41 133.35,250.52L110.32,236.97C108.82,236.09 108.82,233.92 110.32,233.04L133.35,219.49C134.87,218.59 136.79,219.69 136.79,221.45Z"
+ android:strokeAlpha="0.4"
+ android:fillColor="#1F1F1F"
+ android:fillAlpha="0.4"/>
+ <path
+ android:pathData="M248.55,133.29H221.45C219.68,133.29 218.59,131.37 219.48,129.85L233.03,106.82C233.91,105.32 236.08,105.32 236.96,106.82L250.51,129.85C251.41,131.37 250.31,133.29 248.55,133.29Z"
+ android:strokeAlpha="0.4"
+ android:fillColor="#1F1F1F"
+ android:fillAlpha="0.4"/>
+ <path
+ android:pathData="M326.19,248.55L326.19,221.45C326.19,219.69 328.11,218.59 329.63,219.49L352.67,233.04C354.16,233.92 354.16,236.09 352.67,236.97L329.63,250.52C328.11,251.41 326.19,250.31 326.19,248.55Z"
+ android:strokeAlpha="0.4"
+ android:fillColor="#1F1F1F"
+ android:fillAlpha="0.4"/>
+</vector>
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/drawable/gamepad_image.xml b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/drawable/gamepad_image.xml
new file mode 100644
index 0000000..b1da93c
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/drawable/gamepad_image.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="470dp"
+ android:height="470dp"
+ android:viewportWidth="470"
+ android:viewportHeight="470">
+ <path
+ android:pathData="M235,235m-235,0a235,235 0,1 1,470 0a235,235 0,1 1,-470 0"
+ android:strokeAlpha="0.2"
+ android:fillColor="#32363C"
+ android:fillAlpha="0.2"/>
+ <path
+ android:pathData="M235,108.73m-63.13,0a63.13,63.13 0,1 1,126.27 0a63.13,63.13 0,1 1,-126.27 0"
+ android:fillColor="#1F1F1F"/>
+ <path
+ android:pathData="M235,108.73m-52.61,0a52.61,52.61 0,1 1,105.22 0a52.61,52.61 0,1 1,-105.22 0">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:startX="235"
+ android:startY="56.12"
+ android:endX="235"
+ android:endY="161.34"
+ android:type="linear">
+ <item android:offset="0" android:color="#9EBDC1C6"/>
+ <item android:offset="1" android:color="#005F6368"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:pathData="M235,108.73m-43.84,0a43.84,43.84 0,1 1,87.69 0a43.84,43.84 0,1 1,-87.69 0"
+ android:fillColor="#4C4E51"/>
+ <path
+ android:pathData="M232.58,120.99V110.33L224.87,98.08H230.02L234.6,105.72H234.85L239.27,98.08H244.49L236.9,110.33V120.99H232.58Z"
+ android:fillColor="#2F2F31"/>
+ <path
+ android:pathData="M108.73,235m-63.13,0a63.13,63.13 0,1 1,126.27 0a63.13,63.13 0,1 1,-126.27 0"
+ android:fillColor="#1F1F1F"/>
+ <path
+ android:pathData="M108.73,235m-52.61,0a52.61,52.61 0,1 1,105.22 0a52.61,52.61 0,1 1,-105.22 0">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:startX="108.73"
+ android:startY="182.39"
+ android:endX="108.73"
+ android:endY="287.61"
+ android:type="linear">
+ <item android:offset="0" android:color="#9EBDC1C6"/>
+ <item android:offset="1" android:color="#005F6368"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:pathData="M108.73,235m-43.84,0a43.84,43.84 0,1 1,87.69 0a43.84,43.84 0,1 1,-87.69 0"
+ android:fillColor="#4C4E51"/>
+ <path
+ android:pathData="M98.02,222.82H103.37L107.82,230.3H108.07L112.49,222.82H117.86L110.92,233.76L118.44,245.73H113.06L108.07,237.47H107.82L102.82,245.73H97.45L104.94,233.76L98.02,222.82Z"
+ android:fillColor="#2F2F31"/>
+ <path
+ android:pathData="M235,361.27m-63.13,0a63.13,63.13 0,1 1,126.27 0a63.13,63.13 0,1 1,-126.27 0"
+ android:fillColor="#1F1F1F"/>
+ <path
+ android:pathData="M235,361.27m-52.61,0a52.61,52.61 0,1 1,105.22 0a52.61,52.61 0,1 1,-105.22 0">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:startX="235"
+ android:startY="308.66"
+ android:endX="235"
+ android:endY="413.88"
+ android:type="linear">
+ <item android:offset="0" android:color="#9EBDC1C6"/>
+ <item android:offset="1" android:color="#005F6368"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:pathData="M235,361.27m-43.84,0a43.84,43.84 0,1 1,87.69 0a43.84,43.84 0,1 1,-87.69 0"
+ android:fillColor="#4C4E51"/>
+ <path
+ android:pathData="M223.12,371.39L231.7,348.47H236.66L245.26,371.39H240.5L238.58,365.91H229.81L227.89,371.39H223.12ZM237.17,361.95L235.15,356.19L234.32,353.43H234.07L233.23,356.19L231.22,361.95H237.17Z"
+ android:fillColor="#2F2F31"/>
+ <path
+ android:pathData="M361.27,235m-63.13,0a63.13,63.13 0,1 1,126.27 0a63.13,63.13 0,1 1,-126.27 0"
+ android:fillColor="#1F1F1F"/>
+ <path
+ android:pathData="M361.26,235m-52.61,0a52.61,52.61 0,1 1,105.22 0a52.61,52.61 0,1 1,-105.22 0">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:startX="361.26"
+ android:startY="182.39"
+ android:endX="361.26"
+ android:endY="287.61"
+ android:type="linear">
+ <item android:offset="0" android:color="#9EBDC1C6"/>
+ <item android:offset="1" android:color="#005F6368"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:pathData="M361.27,235m-43.84,0a43.84,43.84 0,1 1,87.69 0a43.84,43.84 0,1 1,-87.69 0"
+ android:fillColor="#4C4E51"/>
+ <path
+ android:pathData="M354.14,245.73V222.82H363.1C364.42,222.82 365.61,223.09 366.65,223.65C367.72,224.18 368.56,224.9 369.18,225.79C369.8,226.69 370.11,227.69 370.11,228.8C370.11,229.99 369.81,231.01 369.21,231.84C368.64,232.67 367.91,233.3 367.04,233.73V233.98C368.21,234.39 369.15,235.06 369.85,236C370.58,236.92 370.94,238.04 370.94,239.36C370.94,240.66 370.6,241.79 369.92,242.75C369.25,243.69 368.35,244.43 367.2,244.96C366.07,245.47 364.81,245.73 363.42,245.73H354.14ZM358.46,235.87V241.79H363.26C363.98,241.79 364.59,241.66 365.08,241.41C365.57,241.15 365.95,240.8 366.2,240.35C366.46,239.9 366.59,239.4 366.59,238.85C366.59,238.27 366.46,237.76 366.2,237.31C365.95,236.86 365.56,236.51 365.05,236.26C364.54,236 363.9,235.87 363.13,235.87H358.46ZM358.46,232.16H362.78C363.44,232.16 364,232.04 364.44,231.81C364.91,231.55 365.27,231.21 365.5,230.78C365.76,230.36 365.88,229.9 365.88,229.41C365.88,228.9 365.77,228.44 365.53,228.03C365.3,227.63 364.96,227.31 364.51,227.07C364.06,226.82 363.53,226.69 362.91,226.69H358.46V232.16Z"
+ android:fillColor="#2F2F31"/>
+</vector>
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/layout/car_distant_display_companion_activity.xml b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/layout/car_distant_display_companion_activity.xml
new file mode 100644
index 0000000..3c73b37
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/layout/car_distant_display_companion_activity.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/activity_companion_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textAppearance="@style/TextAppearance.Car.Body.Medium"
+ android:layout_marginBottom="24dp"/>
+ <Button
+ android:id="@+id/move_back_button"
+ android:text="@string/distant_display_companion_button_text"
+ android:drawableEnd="@drawable/ic_sys_ui_bring_back"
+ android:drawablePadding="16dp"
+ android:maxWidth="@null"
+ style="@style/ButtonStyle"/>
+</LinearLayout>
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/layout/car_distant_display_game_controller.xml b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/layout/car_distant_display_game_controller.xml
new file mode 100644
index 0000000..46f2475
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/layout/car_distant_display_game_controller.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:background="@color/car_background">
+ <TextView
+ android:id="@+id/game_companion_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textAppearance="@style/TextAppearance.Car.Body.Small"
+ android:layout_marginBottom="24dp"/>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal">
+ <ImageView
+ android:id="@+id/game_dpad"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/dpad_image" />
+ <Button
+ android:id="@+id/game_pause_button"
+ android:text="Pause"
+ style="@style/ButtonStyle"
+ android:layout_marginHorizontal="24dp"/>
+ <ImageView
+ android:id="@+id/game_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/gamepad_image" />
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/layout/car_top_system_bar.xml b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/layout/car_top_system_bar.xml
index 4e4850b..d45a291 100644
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/layout/car_top_system_bar.xml
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/layout/car_top_system_bar.xml
@@ -30,13 +30,11 @@
android:layout_weight="1"
android:layoutDirection="ltr">
- <LinearLayout
- android:id="@+id/qc_entry_points_container"
+ <include layout="@layout/qc_status_icons_horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
- android:layout_alignParentStart="true"
- />
+ android:layout_alignParentStart="true" />
<FrameLayout
android:id="@+id/clock_container"
@@ -77,23 +75,27 @@
android:contentDescription="@string/system_bar_mic_privacy_chip"
/>
- <FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
android:id="@+id/distant_display_nav_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/user_name_container"
- style="@style/QuickControlEntryPointButton">
- <ImageView
+ style="@style/TopBarButton"
+ systemui:controller="com.android.systemui.car.statusicon.ui.DistantDisplayStatusIconPanelController"
+ systemui:panelLayoutRes="@layout/qc_distant_display_panel"
+ systemui:panelWidthRes="@dimen/car_profile_quick_controls_panel_width"
+ systemui:gravity="top|end"
+ systemui:disabledWhileUnprovisioned="true">
+ <com.android.systemui.car.statusicon.StatusIconView
android:id="@+id/distant_display_nav"
android:layout_height="match_parent"
android:layout_width="@dimen/distant_display_button_size"
android:scaleType="center"
android:layout_gravity="center"
- android:tint="@color/system_bar_icon_color_with_selection"
- android:background="@drawable/status_icon_background"
- android:src="@drawable/ic_sys_ui_send_to_distant_display"/>
- </FrameLayout>
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.DistantDisplayStatusIconController"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
<FrameLayout
android:id="@+id/user_name_container"
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/layout/car_top_system_bar_dock.xml b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/layout/car_top_system_bar_dock.xml
new file mode 100644
index 0000000..0f93cc8
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/layout/car_top_system_bar_dock.xml
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<com.android.systemui.car.systembar.CarSystemBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/car_top_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/status_bar_background"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layoutDirection="ltr">
+
+ <include layout="@layout/qc_status_icons_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true" />
+
+ <FrameLayout
+ android:id="@+id/clock_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/car_padding_2"
+ android:paddingEnd="@dimen/car_padding_2"
+ android:layout_centerInParent="true">
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:elevation="5dp"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.SystemBar.Clock"
+ systemui:amPmStyle="gone"
+ />
+ </FrameLayout>
+
+ <include layout="@layout/read_only_status_icons"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerVertical="true"
+ android:layout_toRightOf="@id/clock_container"/>
+
+ <com.android.systemui.car.systembar.CarSystemBarButton
+ android:id="@+id/notifications"
+ android:contentDescription="@string/system_bar_notifications_label"
+ android:layout_width="@dimen/car_quick_controls_entry_points_button_width"
+ android:layout_height="match_parent"
+ style="@style/TopBarButton"
+ android:layout_toLeftOf="@id/distant_display_nav_container"
+ systemui:longIntent="intent:#Intent;action=com.android.car.bugreport.action.START_AUDIO_FIRST;component=com.android.car.bugreport/.BugReportActivity;end">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center">
+ <ImageView
+ android:id="@+id/notification_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:src="@drawable/car_ic_notification_dock"
+ android:tint="@color/system_bar_icon_color_with_selection" />
+ </LinearLayout>
+ </com.android.systemui.car.systembar.CarSystemBarButton>
+
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
+ android:id="@+id/distant_display_nav_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_toLeftOf="@id/camera_privacy_chip"
+ style="@style/TopBarButton"
+ systemui:controller="com.android.systemui.car.statusicon.ui.DistantDisplayStatusIconPanelController"
+ systemui:panelLayoutRes="@layout/qc_distant_display_panel"
+ systemui:panelWidthRes="@dimen/car_profile_quick_controls_panel_width"
+ systemui:gravity="top|end"
+ systemui:disabledWhileUnprovisioned="true">
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/distant_display_nav"
+ android:layout_height="match_parent"
+ android:layout_width="@dimen/distant_display_button_size"
+ android:scaleType="center"
+ android:layout_gravity="center"
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.DistantDisplayStatusIconController"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
+
+ <include layout="@layout/camera_privacy_chip"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerVertical="true"
+ android:layout_toLeftOf="@id/mic_privacy_chip" />
+
+ <include layout="@layout/mic_privacy_chip"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerVertical="true"
+ android:layout_toLeftOf="@id/user_name_container"
+ android:contentDescription="@string/system_bar_mic_privacy_chip" />
+
+ <FrameLayout
+ android:id="@+id/user_name_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true">
+ <com.android.systemui.car.systembar.CarSystemBarButton
+ android:id="@+id/user_name"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ style="@style/TopBarButton"
+ android:gravity="center">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:layout_marginStart="@dimen/car_padding_2"
+ android:layout_marginEnd="@dimen/car_padding_2"
+ android:gravity="center_vertical">
+ <ImageView
+ android:id="@+id/user_avatar"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:src="@drawable/car_ic_users_icon"
+ android:tint="@color/system_bar_icon_color_with_selection"
+ android:layout_marginEnd="@dimen/system_bar_user_icon_padding"
+ android:contentDescription="@string/system_bar_user_avatar" />
+ <TextView
+ android:id="@+id/user_name_text"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:textAppearance="@style/TextAppearance.SystemBar.Username"
+ android:singleLine="true"
+ android:maxWidth="@dimen/car_system_bar_user_name_max_width"
+ android:layout_marginEnd="@dimen/system_bar_user_icon_padding"
+ android:contentDescription="@string/system_bar_user_name_text" />
+ </LinearLayout>
+ </com.android.systemui.car.systembar.CarSystemBarButton>
+ </FrameLayout>
+ </RelativeLayout>
+</com.android.systemui.car.systembar.CarSystemBarView>
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/values/config.xml b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/values/config.xml
index 8316adb..ff86e90 100644
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/values/config.xml
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/values/config.xml
@@ -20,23 +20,26 @@
com.android.systemui.CarDistantDisplaySystemUIFactory
</string>
- <!-- Classes to start in addition to those started for CarSystemUI. -->
- <string-array name="config_systemUIServiceComponentsInclude" translatable="false">
- <item>com.android.systemui.car.systembar.CarSystemBar</item>
- <item>com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier
- </item>
- <item>com.android.systemui.car.window.SystemUIOverlayWindowManager</item>
- <item>com.android.systemui.car.toast.CarToastUI</item>
- <item>com.android.systemui.car.volume.VolumeUI</item>
- <item>com.android.systemui.car.cluster.ClusterDisplayController</item>
- <item>com.android.systemui.car.distantdisplay.activity.window.ActivityWindowManager</item>
- </string-array>
-
<!-- display Id for distant display. -->
<integer name="config_distantDisplayId">3</integer>
- <string-array name="config_navigationActivities" translatable="false">
- <item>com.google.android.apps.maps/com.google.android.maps.MapsActivity</item>
+ <string-array name="config_restrictedActivities" translatable="false">
+ <item>com.android.systemui/com.android.systemui.car.distantdisplay.activity.RootTaskViewWallpaperActivity</item>
<item>com.android.systemui/com.android.systemui.car.distantdisplay.activity.NavigationTaskViewWallpaperActivity</item>
+ <item>com.android.car.carlauncher/com.android.car.carlauncher.CarLauncher</item>
+ <item>com.android.car.carlauncher/com.android.car.carlauncher.AppGridActivity</item>
+ <item>com.android.car.settings/com.android.car.settings.FallbackHome</item>
+ <item>com.android.car.driverui/com.android.car.driverui.MainActivity</item>
+ <item>com.google.android.apps.maps/com.google.android.maps.LimitedMapsActivity</item>
+ <item>com.android.car.mapsplaceholder/com.android.car.mapsplaceholder.MapsPlaceholderActivity</item>
+ <item>com.android.systemui/com.android.systemui.car.wm.activity.ActivityBlockingActivity</item>
+ <item>com.android.car.media/com.android.car.media.MediaActivity</item>
+ <item>com.android.car.media/com.android.car.media.MediaDispatcherActivity</item>
+ <item>com.android.car.media/com.android.car.media.MediaBlockingActivity</item>
+ </string-array>
+
+ <string-array name="config_distantDisplayGameControllerPackages" translatable="false">
+ <item>com.vectorunit.cobalt.googleplay</item>
+ <item>com.vectorunit.cobalt.googleauto</item>
</string-array>
</resources>
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/values/strings.xml b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/values/strings.xml
index 0868d87..a2519d7 100644
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/values/strings.xml
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/res/values/strings.xml
@@ -16,6 +16,12 @@
-->
<resources>
- <string name="qc_send_to_pano_title">Send to pano</string>
+ <string name="qc_send_to_center_title">Send to center</string>
+ <string name="qc_send_to_right_title">Send to right</string>
<string name="qc_bring_back_to_default_display_title">Bring it back</string>
+
+ <string name="distant_display_companion_message">%s is shown on the distant display.</string>
+ <string name="distant_display_companion_message_default_app">App</string>
+ <string name="distant_display_companion_button_text">Bring it back</string>
+ <string name="distant_display_game_companion_message">Use this screen to control %s on the distant display</string>
</resources>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/SystemUIBinder.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/SystemUIBinder.java
index 5c28479..aefd93d 100644
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/SystemUIBinder.java
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/SystemUIBinder.java
@@ -19,7 +19,6 @@
import com.android.systemui.car.distantdisplay.activity.window.ActivityWindowModule;
import com.android.systemui.car.distantdisplay.activity.window.DistantDisplayActivityWindowManager;
import com.android.systemui.car.qc.DistantDisplayQuickControlsModule;
-import com.android.systemui.car.systembar.DistantDisplaySystemBarModule;
import dagger.Binds;
import dagger.Module;
@@ -27,8 +26,7 @@
import dagger.multibindings.IntoMap;
/** Binder for AAECarSystemUI specific {@link CoreStartable} modules and components. */
-@Module(includes = {ActivityWindowModule.class, DistantDisplaySystemBarModule.class,
- DistantDisplayQuickControlsModule.class})
+@Module(includes = {ActivityWindowModule.class, DistantDisplayQuickControlsModule.class})
abstract class SystemUIBinder extends CarSystemUIBinder {
@Binds
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/appcard/AppCardService.kt b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/appcard/AppCardService.kt
new file mode 100644
index 0000000..f706733
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/appcard/AppCardService.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.appcard
+
+import android.app.Service
+import android.content.Intent
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.os.Message
+import android.os.Messenger
+import android.util.Log
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+/** Service used to communicate between multiple app card hosts */
+class AppCardService : Service() {
+ private val handler = IncomingHandler(Looper.getMainLooper())
+ private val messenger = Messenger(handler)
+
+ override fun onCreate() {
+ logIfDebuggable("onCreate")
+ super.onCreate()
+ }
+
+ override fun onRebind(intent: Intent?) {
+ logIfDebuggable("onRebind")
+ super.onRebind(intent)
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ logIfDebuggable("onStartCommand")
+ super.onStartCommand(intent, flags, startId)
+ return START_STICKY
+ }
+
+ override fun onDestroy() {
+ logIfDebuggable("onDestroy")
+ super.onDestroy()
+ }
+
+ override fun onUnbind(intent: Intent?): Boolean {
+ logIfDebuggable("onUnbind")
+ return super.onUnbind(intent)
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ logIfDebuggable("onBind")
+ return messenger.binder
+ }
+
+ private class IncomingHandler(looper: Looper) : Handler(looper) {
+ var contextListeners = mutableMapOf<String, Messenger>()
+ var appCardListeners = mutableMapOf<String, Messenger>()
+ var latestContext: Any? = null
+ var latestOrder: Any? = null
+
+ override fun handleMessage(msg: Message) {
+ super.handleMessage(msg)
+ val msgType = MessageType.fromInt(msg.what)
+ logIfDebuggable("handleMessage: ${msgType.type}")
+ when (msgType) {
+ MessageType.MSG_REGISTER_CONTEXT_LISTENER -> {
+ getHostId(msg)?.let { id ->
+ contextListeners[id] = msg.replyTo
+ val handler = null
+ latestContext?.let {
+ val message = Message.obtain(
+ handler,
+ MessageType.MSG_SEND_CONTEXT_UPDATE.type,
+ it
+ )
+ msg.replyTo.send(message)
+ }
+ }
+ }
+
+ MessageType.MSG_REGISTER_APP_CARD_LISTENER -> {
+ getHostId(msg)?.let { id ->
+ appCardListeners[id] = msg.replyTo
+ val handler = null
+ latestOrder?.let {
+ val message = Message.obtain(
+ handler,
+ MessageType.MSG_SEND_APP_CARD_UPDATE.type,
+ it
+ )
+ msg.replyTo.send(message)
+ }
+ }
+ }
+
+ MessageType.MSG_SEND_APP_CARD_UPDATE -> {
+ latestOrder = msg.obj
+ val handler = null
+ val message = Message.obtain(handler, msg.what, msg.obj)
+ appCardListeners.values.forEach { it.send(message) }
+ }
+
+ MessageType.MSG_SEND_CONTEXT_UPDATE -> {
+ latestContext = msg.obj
+ val handler = null
+ val message = Message.obtain(handler, msg.what, msg.obj)
+ contextListeners.values.forEach { it.send(message) }
+ }
+
+ MessageType.MSG_UNREGISTER_CONTEXT_LISTENER -> {
+ getHostId(msg)?.let {
+ contextListeners.remove(it)
+ }
+ }
+
+ MessageType.MSG_UNREGISTER_APP_CARD_LISTENER -> {
+ getHostId(msg)?.let {
+ appCardListeners.remove(it)
+ }
+ }
+ }
+ }
+
+ private fun getHostId(msg: Message): String? {
+ val bundle = msg.obj as Bundle
+ return bundle.getString(KEY_APP_CARD_HOST_ID)
+ }
+ }
+
+ public override fun dump(fd: FileDescriptor?, writer: PrintWriter, args: Array<String?>?) {
+ writer.println("AppCardService dump:")
+ writer.println("- cached app card context: " + handler.latestContext)
+ writer.println("- app card context listeners: " + handler.contextListeners)
+ writer.println("- cached app card order: " + handler.latestOrder)
+ writer.println("- app card context listeners: " + handler.appCardListeners)
+ }
+
+ internal enum class MessageType(val type: Int) {
+ MSG_REGISTER_CONTEXT_LISTENER(0),
+ MSG_REGISTER_APP_CARD_LISTENER(1),
+ MSG_SEND_APP_CARD_UPDATE(2),
+ MSG_SEND_CONTEXT_UPDATE(3),
+ MSG_UNREGISTER_CONTEXT_LISTENER(4),
+ MSG_UNREGISTER_APP_CARD_LISTENER(5);
+
+ companion object {
+ fun fromInt(value: Int) = MessageType.entries.first { it.type == value }
+ }
+ }
+
+ companion object {
+ private const val TAG = "AppCardService"
+ private const val KEY_APP_CARD_HOST_ID = "appCardHostId"
+
+ private fun logIfDebuggable(msg: String) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, msg)
+ }
+ }
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/DistantDisplayCompanionActivity.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/DistantDisplayCompanionActivity.java
new file mode 100644
index 0000000..1828bfe
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/DistantDisplayCompanionActivity.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.distantdisplay.activity;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.car.distantdisplay.common.TaskViewController;
+
+import javax.inject.Inject;
+
+public class DistantDisplayCompanionActivity extends Activity {
+ private static final String KEY_COMPANION_MOVED_PACKAGE_NAME =
+ "key_companion_moved_package_name";
+
+ private final Context mContext;
+ private final TaskViewController mTaskViewController;
+
+ /**
+ * Create new intent for the DistantDisplayCompanionActivity with the moved package name
+ * provided as an extra.
+ **/
+ public static Intent createIntent(Context context, String movedPackageName) {
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(context, DistantDisplayCompanionActivity.class));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (movedPackageName != null) {
+ intent.putExtra(KEY_COMPANION_MOVED_PACKAGE_NAME, movedPackageName);
+ }
+ return intent;
+ }
+
+ @Inject
+ public DistantDisplayCompanionActivity(Context context, TaskViewController taskViewController) {
+ mContext = context;
+ mTaskViewController = taskViewController;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.car_distant_display_companion_activity);
+
+ TextView textView = findViewById(R.id.activity_companion_message);
+ if (textView != null) {
+ textView.setText(
+ mContext.getString(R.string.distant_display_companion_message, getAppString()));
+ }
+
+ Button moveBackButton = findViewById(R.id.move_back_button);
+ if (moveBackButton != null) {
+ moveBackButton.setOnClickListener((v) -> {
+ mTaskViewController.moveTaskFromDistantDisplay();
+ });
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ finish();
+ }
+
+ private String getAppString() {
+ if (!getIntent().hasExtra(KEY_COMPANION_MOVED_PACKAGE_NAME)) {
+ return getDefaultAppString();
+ }
+ String packageString = getIntent().getStringExtra(KEY_COMPANION_MOVED_PACKAGE_NAME);
+ if (TextUtils.isEmpty(packageString)) {
+ return getDefaultAppString();
+ }
+ PackageManager pm = mContext.getPackageManager();
+ ApplicationInfo info;
+ try {
+ info = pm.getApplicationInfo(packageString, /* flags= */ 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return getDefaultAppString();
+ }
+ return String.valueOf(pm.getApplicationLabel(info));
+ }
+
+ private String getDefaultAppString() {
+ return mContext.getString(R.string.distant_display_companion_message_default_app);
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/DistantDisplayGameController.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/DistantDisplayGameController.java
new file mode 100644
index 0000000..63d354b
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/DistantDisplayGameController.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.distantdisplay.activity;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.MotionEvent;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.systemui.R;
+
+import javax.inject.Inject;
+
+public class DistantDisplayGameController extends Activity {
+ // Intent extra key to specify the activity the was moved to the distant display for which these
+ // extended controls are associated with.
+ private static final String EXTENDED_CONTROLS_ASSOCIATED_PACKAGE_NAME_KEY =
+ "extended_controls_associated_package_name_key";
+
+ private final Context mContext;
+
+ /**
+ * Create new intent for the DistantDisplayGameController with the moved package name
+ * provided as an extra.
+ **/
+ public static Intent createIntent(Context context, String movedPackageName) {
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(context, DistantDisplayGameController.class));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (movedPackageName != null) {
+ intent.putExtra(EXTENDED_CONTROLS_ASSOCIATED_PACKAGE_NAME_KEY, movedPackageName);
+ }
+ return intent;
+ }
+
+ @Inject
+ public DistantDisplayGameController(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (!Process.myUserHandle().isSystem()) {
+ Intent intent = createIntent(mContext, getPackageNameExtra());
+ mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
+ finish();
+ return;
+ }
+
+ setContentView(R.layout.car_distant_display_game_controller);
+
+ TextView textView = findViewById(R.id.game_companion_message);
+ if (textView != null) {
+ textView.setText(mContext.getString(R.string.distant_display_game_companion_message,
+ getAppString()));
+ }
+
+ ImageView dPad = findViewById(R.id.game_dpad);
+ if (dPad != null) {
+ dPad.setOnTouchListener((v, event) -> {
+ if (event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ return false;
+ }
+ String direction = getDpadDirection(v.getWidth(), v.getHeight(), event.getX(),
+ event.getY());
+ if (!TextUtils.isEmpty(direction)) {
+ Toast.makeText(mContext, direction, Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ });
+ }
+
+ ImageView gameButtons = findViewById(R.id.game_buttons);
+ if (gameButtons != null) {
+ gameButtons.setOnTouchListener((v, event) -> {
+ if (event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ return false;
+ }
+ String button = getGameButton(v.getWidth(), v.getHeight(), event.getX(),
+ event.getY());
+ if (!TextUtils.isEmpty(button)) {
+ Toast.makeText(mContext, button, Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ });
+ }
+
+ Button pauseButton = findViewById(R.id.game_pause_button);
+ if (pauseButton != null) {
+ pauseButton.setOnClickListener(v -> {
+ Toast.makeText(mContext, "Pressed Pause", Toast.LENGTH_SHORT).show();
+ });
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ finish();
+ }
+
+ private String getAppString() {
+ String packageString = getPackageNameExtra();
+ if (TextUtils.isEmpty(packageString)) {
+ return getDefaultAppString();
+ }
+ PackageManager pm = mContext.getPackageManager();
+ ApplicationInfo info;
+ try {
+ info = pm.getApplicationInfo(packageString, /* flags= */ 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return getDefaultAppString();
+ }
+ return String.valueOf(pm.getApplicationLabel(info));
+ }
+
+ @Nullable
+ private String getPackageNameExtra() {
+ return getIntent().getStringExtra(EXTENDED_CONTROLS_ASSOCIATED_PACKAGE_NAME_KEY);
+ }
+
+ private String getDefaultAppString() {
+ return mContext.getString(R.string.distant_display_companion_message_default_app);
+ }
+
+ // TODO(b/333732791): use individual button elements to avoid slicing single images this way
+ private String getDpadDirection(int width, int height, float x, float y) {
+ double startRatio = 0.168;
+ double endRatio = 0.835;
+ double midRatio1 = 0.385;
+ double midRatio2 = 0.615;
+ int leftBound = (int) (width * startRatio);
+ int rightBound = (int) (width * endRatio);
+ int topBound = (int) (height * startRatio);
+ int bottomBound = (int) (height * endRatio);
+ int midXBound1 = (int) (width * midRatio1);
+ int midXBound2 = (int) (width * midRatio2);
+ int midYBound1 = (int) (height * midRatio1);
+ int midYBound2 = (int) (height * midRatio2);
+
+ Rect leftArrow = new Rect(leftBound, midYBound1, midXBound1, midYBound2);
+ Rect rightArrow = new Rect(midXBound2, midYBound1, rightBound, midYBound2);
+ Rect topArrow = new Rect(midXBound1, topBound, midXBound2, midYBound1);
+ Rect bottomArrow = new Rect(midXBound1, midYBound2, midXBound2, bottomBound);
+
+ if (isPointWithinRect(leftArrow, x, y)) {
+ return "Pressed LEFT";
+ }
+ if (isPointWithinRect(rightArrow, x, y)) {
+ return "Pressed RIGHT";
+ }
+ if (isPointWithinRect(topArrow, x, y)) {
+ return "Pressed UP";
+ }
+ if (isPointWithinRect(bottomArrow, x, y)) {
+ return "Pressed DOWN";
+ }
+ return "";
+ }
+
+ private boolean isPointWithinRect(Rect rect, float x, float y) {
+ return x > rect.left && x < rect.right && y > rect.top && y < rect.bottom;
+ }
+
+ // TODO(b/333732791): use individual button elements to avoid slicing single images this way
+ private String getGameButton(int width, int height, float x, float y) {
+ double centerRatio = 0.5;
+ double firstQuarterRatio = 0.23;
+ double lastQuarterRatio = 1 - firstQuarterRatio;
+ int buttonRadius = (int) (0.113 * width); // assumes width == height
+
+ Pair<Integer, Integer> xButtonCenter = Pair.create((int) (width * firstQuarterRatio),
+ (int) (height * centerRatio));
+ Pair<Integer, Integer> yButtonCenter = Pair.create((int) (width * centerRatio),
+ (int) (height * firstQuarterRatio));
+ Pair<Integer, Integer> bButtonCenter = Pair.create((int) (width * lastQuarterRatio),
+ (int) (height * centerRatio));
+ Pair<Integer, Integer> aButtonCenter = Pair.create((int) (width * centerRatio),
+ (int) (height * lastQuarterRatio));
+
+ if (isPointWithinCircle(xButtonCenter.first, xButtonCenter.second, buttonRadius, x, y)) {
+ return "Pressed X";
+ }
+ if (isPointWithinCircle(yButtonCenter.first, yButtonCenter.second, buttonRadius, x, y)) {
+ return "Pressed Y";
+ }
+ if (isPointWithinCircle(bButtonCenter.first, bButtonCenter.second, buttonRadius, x, y)) {
+ return "Pressed B";
+ }
+ if (isPointWithinCircle(aButtonCenter.first, aButtonCenter.second, buttonRadius, x, y)) {
+ return "Pressed A";
+ }
+
+ return "";
+ }
+
+ private boolean isPointWithinCircle(int centerX, int centerY, int radius, float x, float y) {
+ return Math.pow((x - centerX), 2) + Math.pow((y - centerY), 2) < Math.pow(radius, 2);
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/MoveTaskReceiver.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/MoveTaskReceiver.java
index 585514b..26f7015 100644
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/MoveTaskReceiver.java
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/MoveTaskReceiver.java
@@ -45,6 +45,7 @@
public static final String TAG = "MoveTaskReceiver";
public static final String MOVE_ACTION = "com.android.systemui.car.intent.action.MOVE_TASK";
public static final String MOVE_TO_DISTANT_DISPLAY = "to_dd";
+ public static final String MOVE_TO_DISTANT_DISPLAY_PASSENGER = "to_dd_passenger";
public static final String MOVE_FROM_DISTANT_DISPLAY = "from_dd";
/**
@@ -69,7 +70,8 @@
}
String data = intent.getStringExtra("move");
- if (data.equals(MOVE_TO_DISTANT_DISPLAY) || data.equals(MOVE_FROM_DISTANT_DISPLAY)) {
+ if (data.equals(MOVE_TO_DISTANT_DISPLAY) || data.equals(MOVE_FROM_DISTANT_DISPLAY)
+ || data.equals(MOVE_TO_DISTANT_DISPLAY_PASSENGER)) {
mOnChangeDisplayForTask.onTaskDisplayChangeRequest(data);
}
}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/window/ActivityWindowModule.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/window/ActivityWindowModule.java
index 3ed224b..e6446a7 100644
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/window/ActivityWindowModule.java
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/activity/window/ActivityWindowModule.java
@@ -15,8 +15,11 @@
*/
package com.android.systemui.car.distantdisplay.activity.window;
+import android.app.Activity;
import android.os.Handler;
+import com.android.systemui.car.distantdisplay.activity.DistantDisplayCompanionActivity;
+import com.android.systemui.car.distantdisplay.activity.DistantDisplayGameController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
@@ -44,6 +47,18 @@
public abstract ActivityWindowController bindActivityWindowController(
ActivityWindowController activityWindowController);
+ /** Inject into DistantDisplayCompanionActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(DistantDisplayCompanionActivity.class)
+ public abstract Activity bindActivityBlockingActivity(DistantDisplayCompanionActivity activity);
+
+ /** Inject into DistantDisplayGameController. */
+ @Binds
+ @IntoMap
+ @ClassKey(DistantDisplayGameController.class)
+ public abstract Activity bindGameControllerActivity(DistantDisplayGameController activity);
+
@Provides
@SysUISingleton
static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/systembar/DistantDisplayController.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/common/DistantDisplayController.java
similarity index 63%
rename from car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/systembar/DistantDisplayController.java
rename to car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/common/DistantDisplayController.java
index 3848c19..731525f 100644
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/systembar/DistantDisplayController.java
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/common/DistantDisplayController.java
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.car.systembar;
+package com.android.systemui.car.distantdisplay.common;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
@@ -24,21 +26,27 @@
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
+import android.util.ArraySet;
import android.util.Log;
import android.view.Display;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.car.qc.QCItem;
import com.android.systemui.R;
-import com.android.systemui.car.distantdisplay.common.DistantDisplayQcItem;
-import com.android.systemui.car.distantdisplay.common.TaskViewController;
import com.android.systemui.car.distantdisplay.util.AppCategoryDetector;
import com.android.systemui.car.qc.DistantDisplayControlsUpdateListener;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.settings.UserTracker;
+import com.google.android.car.distantdisplay.service.DistantDisplayService;
+
+import java.util.ArrayList;
+import java.util.List;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Inject;
@@ -56,13 +64,15 @@
private final Context mContext;
private final UserTracker mUserTracker;
private final TaskViewController mTaskViewController;
+ private final List<ComponentName> mRestrictedActivities;
private final Drawable mDistantDisplayDrawable;
private final Drawable mDefaultDisplayDrawable;
+ @GuardedBy("mStatusChangeListeners")
+ private final Set<StatusChangeListener> mStatusChangeListeners = new ArraySet<>();
@Nullable
- private String mTopPackageOnDefaultDisplay;
+ private ComponentName mTopActivityOnDefaultDisplay;
@Nullable
- private String mTopPackageOnDistantDisplay;
- private StatusChangeListener mStatusChangeListener;
+ private ComponentName mTopActivityOnDistantDisplay;
private DistantDisplayControlsUpdateListener mDistantDisplayControlsUpdateListener;
/**
@@ -73,24 +83,24 @@
/**
* Callback triggered for display changes of the task.
*/
- void onDisplayChanged(int displayId);
+ default void onDisplayChanged(int displayId) {}
/**
* Callback triggered for visibility changes.
*/
- void onVisibilityChanged(boolean visible);
+ default void onVisibilityChanged(boolean visible) {}
}
private final TaskViewController.Callback mTaskViewControllerCallback =
new TaskViewController.Callback() {
@Override
- public void topAppOnDisplayChanged(int displayId, String packageName) {
+ public void topAppOnDisplayChanged(int displayId, ComponentName componentName) {
if (displayId == Display.DEFAULT_DISPLAY) {
- mTopPackageOnDefaultDisplay = packageName;
+ mTopActivityOnDefaultDisplay = componentName;
updateButtonState();
} else if (displayId == mDistantDisplayId) {
- mTopPackageOnDistantDisplay = packageName;
+ mTopActivityOnDistantDisplay = componentName;
updateButtonState();
}
}
@@ -128,6 +138,13 @@
R.drawable.ic_distant_display_nav, /* theme= */ null);
mDefaultDisplayDrawable = mContext.getResources().getDrawable(
R.drawable.ic_default_display_nav, /* theme= */ null);
+ mRestrictedActivities = new ArrayList<>();
+ String[] ddRestrictedActivities = mContext.getResources().getStringArray(
+ R.array.config_restrictedActivities);
+ for (String ddRestrictedActivity : ddRestrictedActivities) {
+ mRestrictedActivities.add(
+ ComponentName.unflattenFromString(ddRestrictedActivity));
+ }
mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class);
mMediaSessionManager.addOnActiveSessionsChangedListener(/* notificationListener= */ null,
userTracker.getUserHandle(),
@@ -137,19 +154,22 @@
}
/**
- * Sets a listener that needs to be notified of controls change available in status panel.
+ * Adds a listener that needs to be notified of controls change available in status panel.
**/
- public void setDistantDisplayControlStatusInfoListener(
- StatusChangeListener statusChangeListener) {
- mStatusChangeListener = statusChangeListener;
- updateButtonState();
+ public void addDistantDisplayControlStatusInfoListener(StatusChangeListener listener) {
+ synchronized (mStatusChangeListeners) {
+ mStatusChangeListeners.add(listener);
+ notifyStatusChangeListener(listener);
+ }
}
/**
* Removes a DistantDisplayControlStatusInfoListener.
**/
- public void removeDistantDisplayControlStatusInfoListener() {
- mStatusChangeListener = null;
+ public void removeDistantDisplayControlStatusInfoListener(StatusChangeListener listener) {
+ synchronized (mStatusChangeListeners) {
+ mStatusChangeListeners.remove(listener);
+ }
}
public void setDistantDisplayControlsUpdateListener(
@@ -191,33 +211,61 @@
* @return Controls to move content between display
* {@link com.android.systemui.car.distantdisplay.common.DistantDisplayQcItem}
*/
- public DistantDisplayQcItem getControls() {
- if (isVideoApp(mTopPackageOnDistantDisplay)) {
- return new DistantDisplayQcItem.Builder()
+ public List<DistantDisplayQcItem> getControls() {
+ List<DistantDisplayQcItem> items = new ArrayList<>();
+ if (canMoveBetweenDisplays(mTopActivityOnDistantDisplay)) {
+ DistantDisplayQcItem item1 = new DistantDisplayQcItem.Builder()
.setTitle(mContext.getString(R.string.qc_bring_back_to_default_display_title))
.setIcon(mDefaultDisplayDrawable)
- .setActionHandler((item, context, intent) ->
- mTaskViewController.moveTaskFromDistantDisplay())
+ .setActionHandler(createActionHandler(DistantDisplayService.State.DEFAULT))
.build();
- } else if (isVideoApp(mTopPackageOnDefaultDisplay)) {
- return new DistantDisplayQcItem.Builder()
- .setTitle(mContext.getString(R.string.qc_send_to_pano_title))
+ items.add(item1);
+ return items;
+ } else if (canMoveBetweenDisplays(mTopActivityOnDefaultDisplay)) {
+ DistantDisplayQcItem item1 = new DistantDisplayQcItem.Builder()
+ .setTitle(mContext.getString(R.string.qc_send_to_center_title))
.setIcon(mDistantDisplayDrawable)
- .setActionHandler((item, context, intent) ->
- mTaskViewController.moveTaskToDistantDisplay())
+ .setActionHandler(createActionHandler(DistantDisplayService.State.DRIVER_DD))
.build();
+ DistantDisplayQcItem item2 = new DistantDisplayQcItem.Builder()
+ .setTitle(mContext.getString(R.string.qc_send_to_right_title))
+ .setIcon(mDistantDisplayDrawable)
+ .setActionHandler(createActionHandler(DistantDisplayService.State.PASSENGER_DD))
+ .build();
+ items.add(item1);
+ items.add(item2);
+ return items;
} else {
return null;
}
}
+ private QCItem.ActionHandler createActionHandler(DistantDisplayService.State state) {
+ return new QCItem.ActionHandler() {
+ @Override
+ public void onAction(@NonNull QCItem item, @NonNull Context context,
+ @NonNull Intent intent) {
+ switch (state) {
+ case DEFAULT -> mTaskViewController.moveTaskFromDistantDisplay();
+ case DRIVER_DD -> mTaskViewController.moveTaskToDistantDisplay();
+ case PASSENGER_DD -> mTaskViewController.moveTaskToRightDistantDisplay();
+ }
+ }
+
+ @Override
+ public boolean isActivity() {
+ return true;
+ }
+ };
+ }
+
private Optional<MediaController> getMediaControllerFromActiveMediaSession() {
String foregroundMediaPackage;
- if (isVideoApp(mTopPackageOnDistantDisplay)) {
- foregroundMediaPackage = mTopPackageOnDistantDisplay;
- } else if (isVideoApp(mTopPackageOnDefaultDisplay)) {
- foregroundMediaPackage = mTopPackageOnDefaultDisplay;
+ if (isVideoApp(mTopActivityOnDistantDisplay)) {
+ foregroundMediaPackage = mTopActivityOnDistantDisplay.getPackageName();
+ } else if (isVideoApp(mTopActivityOnDefaultDisplay)) {
+ foregroundMediaPackage = mTopActivityOnDefaultDisplay.getPackageName();
} else {
foregroundMediaPackage = null;
}
@@ -233,23 +281,21 @@
.findFirst();
}
- private boolean isVideoApp(String packageName) {
+ private boolean canMoveBetweenDisplays(ComponentName componentName) {
+ return componentName != null && !mRestrictedActivities.contains(componentName);
+ }
+
+ private boolean isVideoApp(ComponentName componentName) {
+ if (componentName == null) return false;
+ String packageName = componentName.getPackageName();
if (packageName == null) return false;
return AppCategoryDetector.isVideoApp(mUserTracker.getUserContext().getPackageManager(),
packageName);
}
private void updateButtonState() {
- if (mStatusChangeListener == null) return;
-
- if (isVideoApp(mTopPackageOnDistantDisplay)) {
- mStatusChangeListener.onDisplayChanged(mDistantDisplayId);
- mStatusChangeListener.onVisibilityChanged(true);
- } else if (isVideoApp(mTopPackageOnDefaultDisplay)) {
- mStatusChangeListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
- mStatusChangeListener.onVisibilityChanged(true);
- } else {
- mStatusChangeListener.onVisibilityChanged(false);
+ synchronized (mStatusChangeListeners) {
+ mStatusChangeListeners.forEach(this::notifyStatusChangeListener);
}
if (mDistantDisplayControlsUpdateListener != null) {
@@ -257,6 +303,20 @@
}
}
+ private void notifyStatusChangeListener(StatusChangeListener listener) {
+ if (listener == null) return;
+
+ if (canMoveBetweenDisplays(mTopActivityOnDistantDisplay)) {
+ listener.onDisplayChanged(mDistantDisplayId);
+ listener.onVisibilityChanged(true);
+ } else if (canMoveBetweenDisplays(mTopActivityOnDefaultDisplay)) {
+ listener.onDisplayChanged(Display.DEFAULT_DISPLAY);
+ listener.onVisibilityChanged(true);
+ } else {
+ listener.onVisibilityChanged(false);
+ }
+ }
+
private static void logIfDebuggable(String message) {
Log.d(TAG, message);
}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/common/TaskViewController.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/common/TaskViewController.java
index b2ae5a1..8e6fa49 100644
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/common/TaskViewController.java
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/distantdisplay/common/TaskViewController.java
@@ -23,20 +23,27 @@
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.DisplayManager;
+import android.hardware.input.InputManager;
+import android.media.session.MediaSessionManager;
import android.os.Build;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import com.android.car.apps.common.util.IntentUtils;
import com.android.car.ui.utils.CarUxRestrictionsUtil;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.car.distantdisplay.activity.DistantDisplayCompanionActivity;
+import com.android.systemui.car.distantdisplay.activity.DistantDisplayGameController;
import com.android.systemui.car.distantdisplay.activity.MoveTaskReceiver;
import com.android.systemui.car.distantdisplay.activity.RootTaskViewWallpaperActivity;
import com.android.systemui.car.distantdisplay.util.AppCategoryDetector;
@@ -45,7 +52,12 @@
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.google.android.car.common.DisplayCompatVirtualDisplay;
+import com.google.android.car.distantdisplay.service.DistantDisplayService;
+import com.google.android.car.distantdisplay.service.DistantDisplayService.ServiceConnectedListener;
+
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -69,14 +81,21 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final UserTracker mUserTracker;
private final UserManager mUserManager;
+ private final InputManager mInputManager;
private final DisplayManager mDisplayManager;
+ private final MediaSessionManager mMediaSessionManager;
+ private final String mMediaBlockingComponentName;
+ private final List<ComponentName> mRestrictedActivities;
+ private List<String> mGameControllerPackages;
private final List<Callback> mCallbacks = new ArrayList<>();
private boolean mInitialized;
private int mDistantDisplayId;
+ private DistantDisplayService mDistantDisplayService;
private final DistantDisplayForegroundTaskMap mForegroundTasks =
new DistantDisplayForegroundTaskMap();
private int mDistantDisplayRootWallpaperTaskId = INVALID_TASK_ID;
private MoveTaskReceiver mMoveTaskReceiver;
+ private CarUxRestrictionsUtil mCarUxRestrictionsUtil;
private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener
mOnUxRestrictionsChangedListener = carUxRestrictions -> {
@@ -102,6 +121,7 @@
public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
logIfDebuggable("onTaskMovedToFront: displayId: " + taskInfo.displayId + ", " + taskInfo
+ " token: " + taskInfo.token);
+ Intent intent = taskInfo.baseIntent;
mForegroundTasks.put(taskInfo.taskId, taskInfo.displayId, taskInfo.baseIntent);
if (taskInfo.displayId == DEFAULT_DISPLAY_ID) {
notifyListeners(DEFAULT_DISPLAY_ID);
@@ -116,8 +136,6 @@
@Override
public void onTaskDisplayChanged(int taskId, int newDisplayId) {
- logIfDebuggable(
- "onTaskDisplayChanged: taskId: " + taskId + " newDisplayId: " + newDisplayId);
TaskData oldData = mForegroundTasks.get(taskId);
if (oldData != null) {
// If a task has not changed displays, do nothing. If it has truly been moved to
@@ -134,12 +152,18 @@
// Task on the default display has changed (by either a new task being added or an
// old task being moved away) - notify listeners
notifyListeners(DEFAULT_DISPLAY_ID);
+ logIfDebuggable(
+ "onTaskDisplayChanged: taskId: " + taskId + " newDisplayId: "
+ + newDisplayId);
}
if (newDisplayId == mDistantDisplayId || (oldData != null
&& oldData.mDisplayId == mDistantDisplayId)) {
// Task on the distant display has changed (by either a new task being added or an
// old task being moved away) - notify listeners
notifyListeners(mDistantDisplayId);
+ logIfDebuggable(
+ "onTaskDisplayChanged: taskId: " + taskId + " newDisplayId: "
+ + newDisplayId);
}
}
};
@@ -147,11 +171,43 @@
@Inject
public TaskViewController(Context context, BroadcastDispatcher broadcastDispatcher,
UserTracker userTracker) {
+
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mUserTracker = userTracker;
mUserManager = context.getSystemService(UserManager.class);
+ mInputManager = context.getSystemService(InputManager.class);
mDisplayManager = context.getSystemService(DisplayManager.class);
+ mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
+ mRestrictedActivities = new ArrayList<>();
+ String[] ddRestrictedActivities = mContext.getResources().getStringArray(
+ R.array.config_restrictedActivities);
+ mMediaBlockingComponentName = mContext.getResources().getString(
+ R.string.config_mediaBlockingActivity);
+ for (int i = 0; i < ddRestrictedActivities.length; i++) {
+ mRestrictedActivities.add(
+ ComponentName.unflattenFromString(ddRestrictedActivities[i]));
+ }
+ mGameControllerPackages = Arrays.asList(mContext.getResources().getStringArray(
+ R.array.config_distantDisplayGameControllerPackages));
+
+ DistantDisplayService.registerService(
+ new ServiceConnectedListener() {
+ @Override
+ public void onServiceConnected(DistantDisplayService service) {
+ Log.d(TAG, "TaskViewController onServiceConnected: " + service);
+ mDistantDisplayService = service;
+ }
+
+ @Override
+ public void onDisplayCreated(DisplayCompatVirtualDisplay virtualDisplay) {
+ Log.d(TAG,
+ "TaskViewController onDisplayCreated: "
+ + virtualDisplay.getDisplayId());
+ mDistantDisplayId = virtualDisplay.getDisplayId();
+ initialize(virtualDisplay.getDisplayId());
+ }
+ });
mDistantDisplayId = mContext.getResources().getInteger(R.integer.config_distantDisplayId);
}
@@ -167,18 +223,13 @@
mDistantDisplayId = distantDisplayId;
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackChangeLister);
- CarUxRestrictionsUtil carUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(mContext);
- carUxRestrictionsUtil.register(mOnUxRestrictionsChangedListener);
+ mCarUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(mContext);
+ mCarUxRestrictionsUtil.register(mOnUxRestrictionsChangedListener);
if (DEBUG) {
Log.i(TAG, "Setup adb debugging : ");
setupDebuggingThroughAdb();
- if (mUserManager.isUserUnlocked()) {
- // User is already unlocked - immediately launch wallpaper activity
- launchActivity(mDistantDisplayId,
- RootTaskViewWallpaperActivity.createIntent(mContext));
- }
// Register user unlock receiver for future user switches and unlocks
mBroadcastDispatcher.registerReceiver(mUserEventReceiver,
new IntentFilter(Intent.ACTION_USER_UNLOCKED),
@@ -208,6 +259,12 @@
changeDisplayForTask(MoveTaskReceiver.MOVE_TO_DISTANT_DISPLAY);
}
+ /** Move task from default display to distant display. */
+ public void moveTaskToRightDistantDisplay() {
+ if (!mInitialized) return;
+ changeDisplayForTask(MoveTaskReceiver.MOVE_TO_DISTANT_DISPLAY_PASSENGER);
+ }
+
/** Move task from distant display to default display. */
public void moveTaskFromDistantDisplay() {
if (!mInitialized) return;
@@ -228,6 +285,16 @@
}
}
+ @VisibleForTesting
+ void setDistantDisplayService(DistantDisplayService displayDisplayService) {
+ mDistantDisplayService = displayDisplayService;
+ }
+
+ @VisibleForTesting
+ int getDistantDisplayId() {
+ return mDistantDisplayId;
+ }
+
private void setupDebuggingThroughAdb() {
IntentFilter filter = new IntentFilter(MoveTaskReceiver.MOVE_ACTION);
mMoveTaskReceiver = new MoveTaskReceiver();
@@ -246,19 +313,34 @@
Log.i(TAG, "Handling movement command : " + movement);
if (movement.equals(MoveTaskReceiver.MOVE_FROM_DISTANT_DISPLAY)
&& !mForegroundTasks.isEmpty()) {
- TaskData data = mForegroundTasks.getTopTaskOnDisplay(
- mDistantDisplayId);
+ TaskData data = mForegroundTasks.getTopTaskOnDisplay(mDistantDisplayId);
if (data == null || data.mTaskId == mDistantDisplayRootWallpaperTaskId) return;
moveTaskToDisplay(data.mTaskId, DEFAULT_DISPLAY_ID);
- } else if (movement.equals(MoveTaskReceiver.MOVE_TO_DISTANT_DISPLAY)
+ mDistantDisplayService.updateState(DistantDisplayService.State.DEFAULT);
+ } else if ((movement.equals(MoveTaskReceiver.MOVE_TO_DISTANT_DISPLAY) || movement.equals(
+ MoveTaskReceiver.MOVE_TO_DISTANT_DISPLAY_PASSENGER))
&& !mForegroundTasks.isEmpty()) {
+ int uxr = mCarUxRestrictionsUtil.getCurrentRestrictions().getActiveRestrictions();
+ if (isVideoRestricted(uxr)) {
+ Log.i(TAG, "uxr restriction in effect can't move task: ");
+ return;
+ }
TaskData data = mForegroundTasks.getTopTaskOnDisplay(
DEFAULT_DISPLAY_ID);
if (data == null) return;
- String pkgName = getPackageNameFromBaseIntent(data.mBaseIntent);
- if (pkgName != null && isVideoApp(pkgName)) {
- moveTaskToDisplay(data.mTaskId, mDistantDisplayId);
+ ComponentName componentName =
+ data.mBaseIntent == null ? null : data.mBaseIntent.getComponent();
+ if (mRestrictedActivities.contains(componentName)) {
+ Log.w(TAG, "restricted activity: " + componentName);
+ return;
}
+ moveTaskToDisplay(data.mTaskId, mDistantDisplayId);
+ launchCompanionUI(componentName);
+ DistantDisplayService.State state = movement.equals(
+ MoveTaskReceiver.MOVE_TO_DISTANT_DISPLAY)
+ ? DistantDisplayService.State.DRIVER_DD
+ : DistantDisplayService.State.PASSENGER_DD;
+ mDistantDisplayService.updateState(state);
}
}
@@ -270,6 +352,30 @@
}
}
+ private void launchCompanionUI(@Nullable ComponentName componentName) {
+ String packageName = componentName != null ? componentName.getPackageName() : null;
+ Intent intent;
+ UserHandle launchUserHandle = UserHandle.SYSTEM;
+ if (isGameApp(packageName)) {
+ intent = DistantDisplayGameController.createIntent(mContext, packageName);
+ } else if (componentName != null && hasActiveMediaSession(componentName)) {
+ //TODO: b/344983836 Currently there is no reliable way to figure out if the current
+ // application supports video media
+ ComponentName mediaComponent = ComponentName.unflattenFromString(
+ mMediaBlockingComponentName);
+ intent = new Intent();
+ intent.setComponent(mediaComponent);
+ intent.putExtra(Intent.EXTRA_COMPONENT_NAME, componentName.flattenToShortString());
+ intent.putExtra(IntentUtils.EXTRA_MEDIA_BLOCKING_ACTIVITY_DISMISS_ON_PARK, false);
+ launchUserHandle = mUserTracker.getUserHandle();
+ } else {
+ intent = DistantDisplayCompanionActivity.createIntent(mContext, packageName);
+ }
+ ActivityOptions options = ActivityOptions.makeBasic()
+ .setLaunchDisplayId(DEFAULT_DISPLAY_ID);
+ mContext.startActivityAsUser(intent, options.toBundle(), launchUserHandle);
+ }
+
private boolean isVideoApp(@Nullable String packageName) {
if (packageName == null) {
Log.w(TAG, "package name is null");
@@ -282,6 +388,17 @@
packageName);
}
+ private boolean isGameApp(@Nullable String packageName) {
+ return mGameControllerPackages.contains(packageName);
+ }
+
+ private boolean hasActiveMediaSession(ComponentName componentName) {
+ return mMediaSessionManager.getActiveSessionsForUser(null,
+ mUserTracker.getUserHandle())
+ .stream().anyMatch(mediaController -> componentName.getPackageName()
+ .equals(mediaController.getPackageName()));
+ }
+
/**
* Provides a DisplayManager.
* This method will be called from the SurfaceHolderCallback to create a virtual display.
@@ -297,11 +414,20 @@
}
@Nullable
- private String getPackageNameFromBaseIntent(Intent intent) {
- if (intent == null || intent.getComponent() == null) {
+ private ComponentName getComponentNameFromBaseIntent(Intent intent) {
+ if (intent == null) {
return null;
}
- return intent.getComponent().getPackageName();
+ return intent.getComponent();
+ }
+
+ @Nullable
+ private String getPackageNameFromBaseIntent(Intent intent) {
+ ComponentName componentName = getComponentNameFromBaseIntent(intent);
+ if (componentName == null) {
+ return null;
+ }
+ return componentName.getPackageName();
}
private static void logIfDebuggable(String message) {
@@ -321,21 +447,21 @@
private void notifyListeners(int displayId) {
if (displayId != DEFAULT_DISPLAY_ID && displayId != mDistantDisplayId) return;
- String pkg;
+ ComponentName componentName;
TaskData data = mForegroundTasks.getTopTaskOnDisplay(displayId);
if (data != null) {
- pkg = getPackageNameFromBaseIntent(data.mBaseIntent);
+ componentName = getComponentNameFromBaseIntent(data.mBaseIntent);
} else {
- pkg = null;
+ componentName = null;
}
- notifyListeners(displayId, pkg);
+ notifyListeners(displayId, componentName);
}
- private void notifyListeners(int displayId, String pkg) {
+ private void notifyListeners(int displayId, ComponentName componentName) {
synchronized (mCallbacks) {
for (Callback callback : mCallbacks) {
- callback.topAppOnDisplayChanged(displayId, pkg);
+ callback.topAppOnDisplayChanged(displayId, componentName);
}
}
}
@@ -344,9 +470,9 @@
public interface Callback {
/**
* Called when the top app on a particular display changes, including the relevant
- * display id and package name. Note that this will only be called for the default and the
+ * display id and component name. Note that this will only be called for the default and the
* configured distant display ids.
*/
- void topAppOnDisplayChanged(int displayId, @Nullable String packageName);
+ void topAppOnDisplayChanged(int displayId, @Nullable ComponentName componentName);
}
}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/qc/DisplaySwitcher.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/qc/DisplaySwitcher.java
index b611084..e3128bd 100644
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/qc/DisplaySwitcher.java
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/qc/DisplaySwitcher.java
@@ -25,8 +25,10 @@
import com.android.car.qc.QCList;
import com.android.car.qc.QCRow;
import com.android.car.qc.provider.BaseLocalQCProvider;
+import com.android.systemui.car.distantdisplay.common.DistantDisplayController;
import com.android.systemui.car.distantdisplay.common.DistantDisplayQcItem;
-import com.android.systemui.car.systembar.DistantDisplayController;
+
+import java.util.List;
import javax.inject.Inject;
@@ -59,15 +61,17 @@
}
listBuilder.addRow(builder.build());
}
- DistantDisplayQcItem controls = mDistantDisplayController.getControls();
+ List<DistantDisplayQcItem> controls = mDistantDisplayController.getControls();
if (controls != null) {
- Icon icon = Icon.createWithBitmap(drawableToBitmap(controls.getIcon()));
- QCRow controlElement = new QCRow.Builder()
- .setTitle(controls.getTitle())
- .setIcon(icon)
- .build();
- controlElement.setActionHandler(controls.getActionHandler());
- listBuilder.addRow(controlElement);
+ for (DistantDisplayQcItem control : controls) {
+ Icon icon = Icon.createWithBitmap(drawableToBitmap(control.getIcon()));
+ QCRow controlElement = new QCRow.Builder()
+ .setTitle(control.getTitle())
+ .setIcon(icon)
+ .build();
+ controlElement.setActionHandler(control.getActionHandler());
+ listBuilder.addRow(controlElement);
+ }
}
return listBuilder.build();
}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/qc/DistantDisplayQuickControlsModule.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/qc/DistantDisplayQuickControlsModule.java
index b179b68..52ed5be 100644
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/qc/DistantDisplayQuickControlsModule.java
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/qc/DistantDisplayQuickControlsModule.java
@@ -17,6 +17,9 @@
package com.android.systemui.car.qc;
import com.android.car.qc.provider.BaseLocalQCProvider;
+import com.android.systemui.car.statusicon.ui.DistantDisplayStatusIconController;
+import com.android.systemui.car.statusicon.ui.DistantDisplayStatusIconPanelController;
+import com.android.systemui.car.systembar.element.CarSystemBarElementController;
import dagger.Binds;
import dagger.Module;
@@ -34,4 +37,18 @@
@ClassKey(DisplaySwitcher.class)
public abstract BaseLocalQCProvider bindDisplaySwitcher(
DisplaySwitcher displaySwitcher);
+
+ /** Injects DistantDisplayStatusIconController. */
+ @Binds
+ @IntoMap
+ @ClassKey(DistantDisplayStatusIconController.class)
+ public abstract CarSystemBarElementController.Factory bindDistantDisplayStatusIconController(
+ DistantDisplayStatusIconController.Factory distantDisplayStatusIconController);
+
+ /** Injects DistantDisplayStatusIconPanelController. */
+ @Binds
+ @IntoMap
+ @ClassKey(DistantDisplayStatusIconPanelController.class)
+ public abstract CarSystemBarElementController.Factory bindDistantDisplayPanelController(
+ DistantDisplayStatusIconPanelController.Factory distantDisplaySPanelController);
}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/statusicon/ui/DistantDisplayStatusIconController.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/statusicon/ui/DistantDisplayStatusIconController.java
index f57406a..8053443 100644
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/statusicon/ui/DistantDisplayStatusIconController.java
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/statusicon/ui/DistantDisplayStatusIconController.java
@@ -18,39 +18,37 @@
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.ImageView;
import com.android.systemui.R;
-import com.android.systemui.car.statusicon.StatusIconController;
-import com.android.systemui.car.statusicon.StatusIconPanelViewController;
-import com.android.systemui.car.systembar.DistantDisplayController;
+import com.android.systemui.car.distantdisplay.common.DistantDisplayController;
+import com.android.systemui.car.statusicon.StatusIconView;
+import com.android.systemui.car.statusicon.StatusIconViewController;
+import com.android.systemui.car.systembar.element.CarSystemBarElementStateController;
+import com.android.systemui.car.systembar.element.CarSystemBarElementStatusBarDisableController;
import com.android.systemui.dagger.qualifiers.Main;
-import javax.inject.Inject;
-import javax.inject.Provider;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
-public class DistantDisplayStatusIconController extends StatusIconController implements
+/** Controller for the distant display status icon */
+public class DistantDisplayStatusIconController extends StatusIconViewController implements
DistantDisplayController.StatusChangeListener {
public static final String TAG = DistantDisplayStatusIconController.class.getSimpleName();
private static final int DEFAULT_DISPLAY_ID = 0;
- private final Provider<StatusIconPanelViewController.Builder> mPanelControllerBuilderProvider;
private final DistantDisplayController mDistantDisplayController;
private final Drawable mDistantDisplayDrawable;
private final Drawable mDefaultDisplayDrawable;
- private ImageView mDistantDisplayButton;
private int mCurrentDisplayId;
- private StatusIconPanelViewController mDistantDisplayPanelController;
-
- @Inject
+ @AssistedInject
DistantDisplayStatusIconController(
- Provider<StatusIconPanelViewController.Builder> panelControllerBuilderProvider,
+ @Assisted StatusIconView view,
+ CarSystemBarElementStatusBarDisableController disableController,
+ CarSystemBarElementStateController stateController,
DistantDisplayController distantDisplayController,
@Main Resources resources) {
- mPanelControllerBuilderProvider = panelControllerBuilderProvider;
+ super(view, disableController, stateController);
mDistantDisplayController = distantDisplayController;
mDistantDisplayDrawable = resources.getDrawable(
R.drawable.ic_sys_ui_send_to_distant_display, /* theme= */ null);
@@ -58,25 +56,21 @@
R.drawable.ic_sys_ui_bring_back, /* theme= */ null);
}
- /**
- * Find the {@link DistantDisplayButton} from a view and sets button state.
- */
- public void addDistantDisplayButtonView(View view) {
- if (mDistantDisplayButton != null) return;
+ @AssistedFactory
+ public interface Factory extends
+ StatusIconViewController.Factory<DistantDisplayStatusIconController> {
+ }
- mDistantDisplayButton = view.findViewById(getId());
+ @Override
+ protected void onViewAttached() {
+ super.onViewAttached();
+ mDistantDisplayController.addDistantDisplayControlStatusInfoListener(this);
+ }
- if (mDistantDisplayButton == null) {
- Log.e(TAG, "Distant Display button view not found to initialize button and panel.");
- return;
- }
-
- mDistantDisplayPanelController = mPanelControllerBuilderProvider.get()
- .setGravity(Gravity.TOP | Gravity.END).build(mDistantDisplayButton,
- getPanelContentLayout(), R.dimen.car_profile_quick_controls_panel_width);
- mDistantDisplayPanelController.init();
- registerIconView(mDistantDisplayButton);
- mDistantDisplayController.setDistantDisplayControlStatusInfoListener(this);
+ @Override
+ protected void onViewDetached() {
+ super.onViewDetached();
+ mDistantDisplayController.removeDistantDisplayControlStatusInfoListener(this);
}
@Override
@@ -90,33 +84,8 @@
}
@Override
- public void onDestroy() {
- if (mDistantDisplayButton != null) {
- unregisterIconView(mDistantDisplayButton);
- }
- mDistantDisplayController.removeDistantDisplayControlStatusInfoListener();
- mDistantDisplayButton = null;
- }
-
- @Override
- protected int getId() {
- return R.id.distant_display_nav;
- }
-
- @Override
- protected int getPanelContentLayout() {
- return R.layout.qc_distant_display_panel;
- }
-
- @Override
public void onDisplayChanged(int displayId) {
mCurrentDisplayId = displayId;
updateStatus();
}
-
- @Override
- public void onVisibilityChanged(boolean visible) {
- setIconVisibility(visible);
- onStatusUpdated();
- }
}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/statusicon/ui/DistantDisplayStatusIconPanelController.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/statusicon/ui/DistantDisplayStatusIconPanelController.java
new file mode 100644
index 0000000..02a7de8
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/statusicon/ui/DistantDisplayStatusIconPanelController.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.statusicon.ui;
+
+import android.view.View;
+
+import com.android.systemui.car.distantdisplay.common.DistantDisplayController;
+import com.android.systemui.car.statusicon.StatusIconPanelViewController;
+import com.android.systemui.car.systembar.CarSystemBarPanelButtonView;
+import com.android.systemui.car.systembar.CarSystemBarPanelButtonViewController;
+import com.android.systemui.car.systembar.element.CarSystemBarElementController;
+import com.android.systemui.car.systembar.element.CarSystemBarElementStateController;
+import com.android.systemui.car.systembar.element.CarSystemBarElementStatusBarDisableController;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+import javax.inject.Provider;
+
+/** Controller for the distant display panel entry point */
+public class DistantDisplayStatusIconPanelController extends CarSystemBarPanelButtonViewController
+ implements DistantDisplayController.StatusChangeListener {
+ private final DistantDisplayController mDistantDisplayController;
+
+ @AssistedInject
+ protected DistantDisplayStatusIconPanelController(
+ @Assisted CarSystemBarPanelButtonView view,
+ CarSystemBarElementStatusBarDisableController disableController,
+ CarSystemBarElementStateController stateController,
+ Provider<StatusIconPanelViewController.Builder> statusIconPanelBuilder,
+ DistantDisplayController distantDisplayController) {
+ super(view, disableController, stateController, statusIconPanelBuilder);
+ mDistantDisplayController = distantDisplayController;
+ }
+
+ @AssistedFactory
+ public interface Factory extends
+ CarSystemBarElementController.Factory<CarSystemBarPanelButtonView,
+ DistantDisplayStatusIconPanelController> {
+ }
+
+ @Override
+ protected void onViewAttached() {
+ super.onViewAttached();
+ mDistantDisplayController.addDistantDisplayControlStatusInfoListener(this);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ super.onViewDetached();
+ mDistantDisplayController.removeDistantDisplayControlStatusInfoListener(this);
+ }
+
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ mView.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/systembar/DistantDisplayCarSystemBarController.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/systembar/DistantDisplayCarSystemBarController.java
deleted file mode 100644
index 97e99e7..0000000
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/systembar/DistantDisplayCarSystemBarController.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.car.systembar;
-
-import android.content.Context;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.car.CarServiceProvider;
-import com.android.systemui.car.statusbar.UserNameViewController;
-import com.android.systemui.car.statusicon.StatusIconPanelViewController;
-import com.android.systemui.car.statusicon.ui.DistantDisplayStatusIconController;
-import com.android.systemui.settings.UserTracker;
-
-import dagger.Lazy;
-
-import javax.inject.Provider;
-
-/** A single class which controls the navigation bar views in distant display. */
-public class DistantDisplayCarSystemBarController extends CarSystemBarController {
- private final DistantDisplayStatusIconController mDistantDisplayStatusIconController;
-
- public DistantDisplayCarSystemBarController(Context context,
- UserTracker userTracker,
- CarSystemBarViewFactory carSystemBarViewFactory,
- CarServiceProvider carServiceProvider,
- ButtonSelectionStateController buttonSelectionStateController,
- Lazy<UserNameViewController> userNameViewControllerLazy,
- Lazy<MicPrivacyChipViewController> micPrivacyChipViewControllerLazy,
- Lazy<CameraPrivacyChipViewController> cameraPrivacyChipViewControllerLazy,
- ButtonRoleHolderController buttonRoleHolderController,
- SystemBarConfigs systemBarConfigs,
- Provider<StatusIconPanelViewController.Builder> panelControllerBuilderProvider,
- DistantDisplayStatusIconController distantDisplayStatusIconController) {
- super(context, userTracker, carSystemBarViewFactory, carServiceProvider,
- buttonSelectionStateController, userNameViewControllerLazy,
- micPrivacyChipViewControllerLazy, cameraPrivacyChipViewControllerLazy,
- buttonRoleHolderController, systemBarConfigs, panelControllerBuilderProvider);
- mDistantDisplayStatusIconController = distantDisplayStatusIconController;
- }
-
- @Nullable
- @Override
- public CarSystemBarView getTopBar(boolean isSetUp) {
- CarSystemBarView topSystemBarView = super.getTopBar(isSetUp);
-
- if (topSystemBarView != null) {
- mDistantDisplayStatusIconController.addDistantDisplayButtonView(topSystemBarView);
- }
-
- return topSystemBarView;
- }
-
- @Override
- public void removeAll() {
- super.removeAll();
- mDistantDisplayStatusIconController.onDestroy();
- }
-}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/systembar/DistantDisplaySystemBarModule.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/systembar/DistantDisplaySystemBarModule.java
deleted file mode 100644
index 9bb4bbe..0000000
--- a/car_product/distant_display/apps/CarDistantDisplaySystemUI/src/com/android/systemui/car/systembar/DistantDisplaySystemBarModule.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.car.systembar;
-
-import android.content.Context;
-
-import com.android.systemui.car.CarServiceProvider;
-import com.android.systemui.car.dagger.CarSysUIDynamicOverride;
-import com.android.systemui.car.statusbar.UserNameViewController;
-import com.android.systemui.car.statusicon.StatusIconPanelViewController;
-import com.android.systemui.car.statusicon.ui.DistantDisplayStatusIconController;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.settings.UserTracker;
-
-import dagger.Lazy;
-import dagger.Module;
-import dagger.Provides;
-
-import javax.inject.Provider;
-
-/**
- * Dagger injection module for {@link CarSystemBar} in CarDistantDisplaySystemUI.
- */
-@Module(includes = {CarSystemBarModule.class})
-public class DistantDisplaySystemBarModule {
-
- @SysUISingleton
- @Provides
- @CarSysUIDynamicOverride
- static CarSystemBarController provideCarSystemBarController(Context context,
- UserTracker userTracker,
- CarSystemBarViewFactory carSystemBarViewFactory,
- CarServiceProvider carServiceProvider,
- ButtonSelectionStateController buttonSelectionStateController,
- Lazy<UserNameViewController> userNameViewControllerLazy,
- Lazy<MicPrivacyChipViewController> micPrivacyChipViewControllerLazy,
- Lazy<CameraPrivacyChipViewController> cameraPrivacyChipViewControllerLazy,
- ButtonRoleHolderController buttonRoleHolderController,
- SystemBarConfigs systemBarConfigs,
- Provider<StatusIconPanelViewController.Builder> panelControllerBuilderProvider,
- DistantDisplayStatusIconController distantDisplayStatusIconController) {
- return new DistantDisplayCarSystemBarController(context, userTracker,
- carSystemBarViewFactory, carServiceProvider, buttonSelectionStateController,
- userNameViewControllerLazy,
- micPrivacyChipViewControllerLazy, cameraPrivacyChipViewControllerLazy,
- buttonRoleHolderController, systemBarConfigs, panelControllerBuilderProvider,
- distantDisplayStatusIconController);
- }
-}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/Android.bp b/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/Android.bp
new file mode 100644
index 0000000..2778a12
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/Android.bp
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_team: "trendy_team_system_experience",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CarDistantDisplaySystemUITest",
+
+ dxflags: ["--multi-dex"],
+ platform_apis: true,
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+ static_libs: ["DistantDisplaySystemUI-tests"],
+ compile_multilib: "both",
+
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libmultiplejvmtiagentsinterferenceagent",
+ "libstaticjvmtiagent",
+ ],
+ libs: [
+ "android.test.runner",
+ "telephony-common",
+ "android.test.base",
+ ],
+ aaptflags: [
+ "--extra-packages com.android.systemui",
+ ],
+
+ certificate: "platform",
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/AndroidManifest.xml b/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/AndroidManifest.xml
new file mode 100644
index 0000000..777b46f
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:sharedUserId="android.uid.system"
+ package="com.android.systemui.tests">
+ <application
+ android:debuggable="true">
+ <uses-library android:name="android.test.runner"/>
+
+ <activity android:name="com.android.systemui.car.distantdisplay.common.TestActivity"
+ android:exported="false" />
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Tests for Distant Display"
+ android:targetPackage="com.android.systemui.tests"/>
+</manifest>
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/AndroidTest.xml b/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/AndroidTest.xml
new file mode 100644
index 0000000..6a5a208
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Distant Display Tests">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="CarDistantDisplaySystemUITest.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.systemui.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml b/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/res/layout/test_activity.xml
similarity index 68%
rename from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
rename to car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/res/layout/test_activity.xml
index a6ac85e..98ade30 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/res/layout/test_activity.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -13,11 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<ImageView
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/statusbar_sensor_text_width"
- android:layout_height="@*android:dimen/status_bar_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:tag="@string/qc_icon_tag"/>
\ No newline at end of file
+ android:id="@+id/test_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+</FrameLayout>
\ No newline at end of file
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/src/com/android/systemui/car/distantdisplay/common/TaskViewControllerTest.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/src/com/android/systemui/car/distantdisplay/common/TaskViewControllerTest.java
new file mode 100644
index 0000000..9bf0d02
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/src/com/android/systemui/car/distantdisplay/common/TaskViewControllerTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.distantdisplay.common;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.car.users.CarSystemUIUserUtil;
+import com.android.systemui.settings.UserTracker;
+
+import com.google.android.car.distantdisplay.service.DistantDisplayService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TaskViewControllerTest extends SysuiTestCase {
+
+ private static final int DEFAULT_DISPLAY_ID = 0;
+ private final UserHandle mUserHandle = UserHandle.of(1000);
+
+ private MockitoSession mSession;
+
+ @Mock
+ private UserTracker mUserTracker;
+
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+
+ @Mock
+ private DistantDisplayService mDistantDisplayService;
+
+
+ private TaskViewController mTaskViewController;
+
+ @Before
+ public void setUp() {
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .spyStatic(CarSystemUIUserUtil.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ when(mUserTracker.getUserHandle()).thenReturn(mUserHandle);
+ mTaskViewController = new TaskViewController(getContext(), mBroadcastDispatcher,
+ mUserTracker);
+ mTaskViewController.setDistantDisplayService(mDistantDisplayService);
+ // TODO: read the display id using DisplayManager
+ mTaskViewController.initialize(3);
+
+ }
+
+ private void startActivity(Context context, Intent intent) {
+ ActivityOptions activityOptions = ActivityOptions.makeBasic();
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent, activityOptions.toBundle());
+ }
+
+ @After
+ public void tearDown() {
+ if (mSession != null) {
+ mSession.finishMocking();
+ mSession = null;
+ }
+ }
+
+ @Test
+ public void launchTask_moveToDistantDisplay() throws InterruptedException {
+ testLaunchOnDefaultDisplay();
+ testMoveTaskOnDistantDisplay();
+ }
+
+ private void testLaunchOnDefaultDisplay() throws InterruptedException {
+ CountDownLatch latch = new CountDownLatch(1);
+ TaskViewController.Callback callback = createCallback(latch, DEFAULT_DISPLAY_ID);
+
+ mTaskViewController.addCallback(callback);
+
+ Intent intent = new Intent(mContext, TestActivity.class);
+ startActivity(mContext, intent);
+
+ verifyCallbackCompletion(latch, "Callback timed out on default display");
+
+ mTaskViewController.removeCallback(callback);
+ waitForIdleSync();
+ }
+
+ private void testMoveTaskOnDistantDisplay() throws InterruptedException {
+ CountDownLatch latch = new CountDownLatch(1);
+ TaskViewController.Callback callback = createCallback(latch,
+ mTaskViewController.getDistantDisplayId());
+
+ mTaskViewController.addCallback(callback);
+ mTaskViewController.moveTaskToDistantDisplay();
+
+ verifyCallbackCompletion(latch, "Callback timed out on distant display");
+ }
+
+ private TaskViewController.Callback createCallback(CountDownLatch latch,
+ int expectedDisplayId) {
+ return (displayId, componentName) -> {
+ if (componentName == null) {
+ return;
+ }
+ assertTrue(
+ componentName.getClassName().contains(TestActivity.class.getSimpleName()));
+ assertEquals(expectedDisplayId, displayId);
+ latch.countDown();
+ };
+ }
+
+ private void verifyCallbackCompletion(CountDownLatch latch, String timeoutMessage)
+ throws InterruptedException {
+ assertTrue(timeoutMessage, latch.await(5, TimeUnit.SECONDS));
+ }
+}
diff --git a/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/src/com/android/systemui/car/distantdisplay/common/TestActivity.java b/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/src/com/android/systemui/car/distantdisplay/common/TestActivity.java
new file mode 100644
index 0000000..9524a7d
--- /dev/null
+++ b/car_product/distant_display/apps/CarDistantDisplaySystemUI/tests/src/com/android/systemui/car/distantdisplay/common/TestActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.distantdisplay.common;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.systemui.R;
+
+public class TestActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.test_activity);
+ }
+}
diff --git a/car_product/distant_display/apps/car_ui_distant_display_apps.mk b/car_product/distant_display/apps/car_ui_distant_display_apps.mk
index 9c3f6ec..04a81bc 100644
--- a/car_product/distant_display/apps/car_ui_distant_display_apps.mk
+++ b/car_product/distant_display/apps/car_ui_distant_display_apps.mk
@@ -2,7 +2,7 @@
# Copyright (C) 2023 The Android Open-Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
+# you may not use this file except in compliance with the License.\
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
@@ -18,4 +18,9 @@
PRODUCT_PACKAGES += \
CarDistantDisplaySystemUI \
DesignComposeDriverUIPrebuilt \
- PaintBooth
+ PaintBooth \
+ displaycompat-service-sharedlibrary \
+ CarDistantDisplayPanoManager \
+
+PRODUCT_PACKAGES_DEBUG += \
+ LaunchOnPrivateDisplayTestApp \
diff --git a/car_product/distant_display/rro/CarServiceRRO/AndroidManifest.xml b/car_product/distant_display/rro/CarServiceRRO/AndroidManifest.xml
index 7e6c9fc..81f6b17 100644
--- a/car_product/distant_display/rro/CarServiceRRO/AndroidManifest.xml
+++ b/car_product/distant_display/rro/CarServiceRRO/AndroidManifest.xml
@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.car.updatable.rro">
<application android:hasCode="false"/>
- <overlay android:priority="10"
+ <overlay android:priority="9001"
android:targetPackage="com.android.car.updatable"
android:targetName="CarServiceCustomization"
android:resourcesMap="@xml/overlays"
diff --git a/car_product/distant_display/rro/CarServiceRRO/res/values/config.xml b/car_product/distant_display/rro/CarServiceRRO/res/values/config.xml
index 2914572..2835415 100644
--- a/car_product/distant_display/rro/CarServiceRRO/res/values/config.xml
+++ b/car_product/distant_display/rro/CarServiceRRO/res/values/config.xml
@@ -22,5 +22,32 @@
See also packages/services/Car/service/res/values/config.xml
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="instrumentClusterRendererService" translatable="false">com.android.designcompose.reference.driverui/.ClusterRenderingService</string>
+ <!-- The name of Activity who is in charge of ClusterHome. -->
+ <string name="config_clusterHomeActivity" translatable="false">com.android.car.driverui/.MainActivity</string>
+
+ <string-array translatable="false" name="config_allowed_optional_car_features">
+ <item>car_navigation_service</item>
+ <item>cluster_home_service</item>
+ <item>com.android.car.user.CarUserNoticeService</item>
+ <item>diagnostic</item>
+ <item>storage_monitoring</item>
+ <item>vehicle_map_service</item>
+ <item>car_evs_service</item>
+ <item>car_telemetry_service</item>
+ </string-array>
+
+ <!-- Assign occupant zones to seats/passengers. -->
+ <string-array translatable="false" name="config_occupant_zones">
+ <item>occupantZoneId=0,occupantType=DRIVER,seatRow=1,seatSide=driver</item>
+ </string-array>
+
+ <!-- Assign displays to occupant zones. -->
+ <string-array translatable="false" name="config_occupant_display_mapping">
+ <item>displayPort=0,displayType=MAIN,occupantZoneId=0,inputTypes=TOUCH_SCREEN|DPAD_KEYS|NAVIGATE_KEYS|ROTARY_NAVIGATION</item>
+ <item>displayPort=1,displayType=INSTRUMENT_CLUSTER,occupantZoneId=0,inputTypes=DPAD_KEYS</item>
+ <item>displayUniqueId=virtual:com.android.systemui:DistantDisplay,displayType=AUXILIARY_2,occupantZoneId=0,inputTypes=DPAD_KEYS</item>
+ </string-array>
+
+ <bool name="audioUseDynamicRouting">true</bool>
+ <bool name="audioUseCarVolumeGroupMuting">true</bool>
</resources>
diff --git a/car_product/distant_display/rro/CarServiceRRO/res/xml/car_ux_restrictions_map.xml b/car_product/distant_display/rro/CarServiceRRO/res/xml/car_ux_restrictions_map.xml
new file mode 100644
index 0000000..3346b92
--- /dev/null
+++ b/car_product/distant_display/rro/CarServiceRRO/res/xml/car_ux_restrictions_map.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+
+<UxRestrictions xmlns:car="http://schemas.android.com/apk/res-auto">
+ <!-- Map the driving states to UX restrictions here -->
+ <!-- Note - The platform takes a fully restricted approach when there is no information or if
+ the information is malformed. As a result,
+ 1. Default values for requiresDistractionOptimization is true, and uxr is fully_restricted.
+ 2. If uxr != baseline, then requiresDistractionOptimization is automatically promoted to true,
+ even if it is declared as false. Because it doesn't make sense to have an non baseline UX
+ restriction without requiresDistractionOptimization set to true.
+ So if we don't want any restrictions, requiresDistractionOptimization should be explicitly
+ marked as false and uxr should be explicitly set to baseline. -->
+
+ <RestrictionMapping physicalPort="0">
+ <DrivingState state="parked">
+ <Restrictions requiresDistractionOptimization="false" uxr="baseline"/>
+ </DrivingState>
+
+ <DrivingState state="idling">
+ <Restrictions requiresDistractionOptimization="true" uxr="no_video"/>
+ </DrivingState>
+
+ <!-- This is restrictions for moving and speed [0,5m/s) -->
+ <DrivingState state="moving" minSpeed="0" maxSpeed="5.0">
+ <Restrictions requiresDistractionOptimization="true" uxr="no_dialpad|no_filtering|limit_string_length|no_keyboard|no_video|limit_content|no_setup|no_text_message"/>
+ </DrivingState>
+
+ <!-- Restrictions for speed >=5 -->
+ <DrivingState state="moving" minSpeed="5.0">
+ <Restrictions requiresDistractionOptimization="true" uxr="no_dialpad|no_filtering|limit_string_length|no_keyboard|no_video|limit_content|no_setup|no_text_message"/>
+ </DrivingState>
+
+ </RestrictionMapping>
+
+ <!--Setting baseline restrictions for the private virtual display on Distant display -->
+ <RestrictionMapping occupantZoneId="0" displayType="6">
+ <DrivingState state="parked">
+ <Restrictions requiresDistractionOptimization="false" uxr="baseline"/>
+ </DrivingState>
+
+ <DrivingState state="idling">
+ <Restrictions requiresDistractionOptimization="false" uxr="baseline"/>
+ </DrivingState>
+
+ <DrivingState state="moving">
+ <Restrictions requiresDistractionOptimization="false" uxr="baseline"/>
+ </DrivingState>
+
+ </RestrictionMapping>
+
+ <!-- Configure restriction parameters here-->
+ <RestrictionParameters>
+ <!-- Parameters to express displayed String related restrictions -->
+ <!-- Max allowed length of general purpose strings when limit_string_length is imposed-->
+ <StringRestrictions maxLength="120"/>
+ <!-- Parameters to express content related restrictions -->
+ <!-- Max number of cumulative content items allowed to be displayed when
+ limit_content is imposed. -->
+ <!-- Maximum levels deep that the user can navigate to when limit_content is imposed. -->
+ <ContentRestrictions maxCumulativeItems="21" maxDepth="3"/>
+ </RestrictionParameters>
+
+</UxRestrictions>
+
diff --git a/car_product/distant_display/rro/CarServiceRRO/res/xml/overlays.xml b/car_product/distant_display/rro/CarServiceRRO/res/xml/overlays.xml
index 0f75649..7337776 100644
--- a/car_product/distant_display/rro/CarServiceRRO/res/xml/overlays.xml
+++ b/car_product/distant_display/rro/CarServiceRRO/res/xml/overlays.xml
@@ -15,5 +15,12 @@
~ limitations under the License.
-->
<overlay>
- <item target="string/instrumentClusterRendererService" value="@string/instrumentClusterRendererService" />
+ <item target="string/config_clusterHomeActivity" value="@string/config_clusterHomeActivity" />
+ <item target="array/config_allowed_optional_car_features" value="@array/config_allowed_optional_car_features" />
+
+ <item target="array/config_occupant_zones" value="@array/config_occupant_zones" />
+ <item target="array/config_occupant_display_mapping" value="@array/config_occupant_display_mapping" />
+ <item target="bool/audioUseDynamicRouting" value="@bool/audioUseDynamicRouting" />
+ <item target="bool/audioUseCarVolumeGroupMuting" value="@bool/audioUseCarVolumeGroupMuting" />
+ <item target="xml/car_ux_restrictions_map" value="@xml/car_ux_restrictions_map" />
</overlay>
diff --git a/car_product/distant_display/rro/DriverUiRRO/AndroidManifest.xml b/car_product/distant_display/rro/DriverUiRRO/AndroidManifest.xml
index 26bc5a1..9ac1a66 100644
--- a/car_product/distant_display/rro/DriverUiRRO/AndroidManifest.xml
+++ b/car_product/distant_display/rro/DriverUiRRO/AndroidManifest.xml
@@ -15,11 +15,11 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.designcompose.reference.driverui.rro">
+ package="com.android.car.driverui.rro">
<application android:hasCode="false"/>
<overlay android:priority="10"
android:targetName="DriverUI"
- android:targetPackage="com.android.designcompose.reference.driverui"
+ android:targetPackage="com.android.car.driverui"
android:resourcesMap="@xml/overlays"
android:isStatic="true" />
</manifest>
diff --git a/car_product/distant_display/rro/DriverUiRRO/res/values/config.xml b/car_product/distant_display/rro/DriverUiRRO/res/values/config.xml
index ef87e68..8568545 100644
--- a/car_product/distant_display/rro/DriverUiRRO/res/values/config.xml
+++ b/car_product/distant_display/rro/DriverUiRRO/res/values/config.xml
@@ -17,5 +17,11 @@
<!-- Resources to configure based on each OEM's preference. -->
<resources>
- <string name="figma_doc">heFTRzWcxPMkuSdj3oqN66</string>
+ <string name="figma_doc">a1f1PP0ZdKDWSDif9ZHLml</string>
+
+ <string-array name="initial_app_cards">
+ <item>com.example.appcard.sampleapplication : com.example.appcard.sampleapplication : clockAppCard</item>
+ <item>com.example.appcard.sample.media : com.example.appcard.sample.media : mediaAppCard</item>
+ <item>com.example.appcard.sampleapplication : com.example.appcard.sampleapplication : calendarAppCard</item>
+ </string-array>
</resources>
diff --git a/car_product/distant_display/rro/DriverUiRRO/res/xml/overlays.xml b/car_product/distant_display/rro/DriverUiRRO/res/xml/overlays.xml
index 53fe5f8..f4659e6 100644
--- a/car_product/distant_display/rro/DriverUiRRO/res/xml/overlays.xml
+++ b/car_product/distant_display/rro/DriverUiRRO/res/xml/overlays.xml
@@ -16,4 +16,5 @@
-->
<overlay>
<item target="string/figma_doc" value="@string/figma_doc"/>
+ <item target="array/initial_app_cards" value="@array/initial_app_cards"/>
</overlay>
diff --git a/car-helper-lib/Android.bp b/car_product/distant_display/rro/LaunchOnPrivateDisplayTestAppRRO/Android.bp
similarity index 69%
copy from car-helper-lib/Android.bp
copy to car_product/distant_display/rro/LaunchOnPrivateDisplayTestAppRRO/Android.bp
index a07a188..d86f8da 100644
--- a/car-helper-lib/Android.bp
+++ b/car_product/distant_display/rro/LaunchOnPrivateDisplayTestAppRRO/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 The Android Open Source Project
+// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,20 +11,17 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-
-// This library has common code which is used by other apps.
//
-// NOTE: This library should not be used within p/s/Car
+//
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_library {
- name: "car-helper-lib",
- srcs: [
- "src/**/*.java",
- ],
+runtime_resource_overlay {
+ name: "LaunchOnPrivateDisplayTestAppRRO",
resource_dirs: ["res"],
- platform_apis: true,
+ manifest: "AndroidManifest.xml",
+ sdk_version: "current",
+ product_specific: true,
}
diff --git a/car_product/distant_display/rro/LaunchOnPrivateDisplayTestAppRRO/AndroidManifest.xml b/car_product/distant_display/rro/LaunchOnPrivateDisplayTestAppRRO/AndroidManifest.xml
new file mode 100644
index 0000000..5bfab87
--- /dev/null
+++ b/car_product/distant_display/rro/LaunchOnPrivateDisplayTestAppRRO/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.launchonprivatedisplay.rro">
+ <application android:hasCode="false"/>
+ <!--Define priority between 5000-7999 so that it can take the highest priority as it is meant to be defined by Car OEMs.-->
+ <overlay android:priority="5000"
+ android:targetPackage="com.example.android.launchonprivatedisplay"
+ android:targetName="MainActivity"
+ android:resourcesMap="@xml/overlays"
+ android:isStatic="true" />
+</manifest>
diff --git a/car_product/distant_display/rro/LaunchOnPrivateDisplayTestAppRRO/res/values/config.xml b/car_product/distant_display/rro/LaunchOnPrivateDisplayTestAppRRO/res/values/config.xml
new file mode 100644
index 0000000..223bc88
--- /dev/null
+++ b/car_product/distant_display/rro/LaunchOnPrivateDisplayTestAppRRO/res/values/config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ Overlay resources to configure private displays based on each OEM's preference.
+ See also packages/services/Car/service/res/values/config.xml
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="distant_display_names">
+ <item>"Center Display"</item>
+ <item>"Cluster Display"</item>
+ <item>"Center Distant Display"</item>
+ </string-array>
+
+ <string-array name="distant_display_unique_ids">
+ <item>"local:4619827259835644672"</item>
+ <item>"local:4619827551948147201"</item>
+ <item>"virtual:com.android.systemui:DistantDisplay"</item>
+ </string-array>
+</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml b/car_product/distant_display/rro/LaunchOnPrivateDisplayTestAppRRO/res/xml/overlays.xml
similarity index 61%
copy from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
copy to car_product/distant_display/rro/LaunchOnPrivateDisplayTestAppRRO/res/xml/overlays.xml
index a6ac85e..856f1e2 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
+++ b/car_product/distant_display/rro/LaunchOnPrivateDisplayTestAppRRO/res/xml/overlays.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -13,11 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<ImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/statusbar_sensor_text_width"
- android:layout_height="@*android:dimen/status_bar_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:tag="@string/qc_icon_tag"/>
\ No newline at end of file
+<overlay>
+ <item target="array/distant_display_names" value="@array/distant_display_names" />
+ <item target="array/distant_display_unique_ids" value="@array/distant_display_unique_ids" />
+</overlay>
diff --git a/car_product/distant_display/rro/android/Android.bp b/car_product/distant_display/rro/android/Android.bp
index 68629f3..a83f9a4 100644
--- a/car_product/distant_display/rro/android/Android.bp
+++ b/car_product/distant_display/rro/android/Android.bp
@@ -21,5 +21,4 @@
resource_dirs: ["res"],
certificate: "platform",
manifest: "AndroidManifest.xml",
- system_ext_specific: true,
}
diff --git a/car_product/distant_display/rro/android/res/values/config.xml b/car_product/distant_display/rro/android/res/values/config.xml
index f69414b..d92b1fb 100644
--- a/car_product/distant_display/rro/android/res/values/config.xml
+++ b/car_product/distant_display/rro/android/res/values/config.xml
@@ -18,6 +18,6 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- The name of the package that will hold the system cluster service role. -->
<string name="config_systemAutomotiveCluster" translatable="false">
- com.android.designcompose.reference.driverui
+ com.android.car.driverui
</string>
</resources>
\ No newline at end of file
diff --git a/car_product/distant_display/rro/distant_display_rro.mk b/car_product/distant_display/rro/distant_display_rro.mk
index 9926056..5f68b33 100644
--- a/car_product/distant_display/rro/distant_display_rro.mk
+++ b/car_product/distant_display/rro/distant_display_rro.mk
@@ -12,12 +12,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-
-$(call inherit-product, packages/services/Car/car_product/car_ui_portrait/rro/car-ui-customizations/product.mk)
-$(call inherit-product, packages/services/Car/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/product.mk)
-$(call inherit-product-if-exists, vendor/auto/embedded/products/coolwhip/car-ui-lib-rros/product.mk)
-$(call inherit-product-if-exists, vendor/google/nexus_overlay/fonts/fonts.mk)
# All RROs to be included in car_ui_portrait builds.
PRODUCT_PACKAGES += \
@@ -25,5 +19,8 @@
CarServiceRRO \
DistantDisplayFrameworkResRRO \
+PRODUCT_PACKAGES_DEBUG += \
+ LaunchOnPrivateDisplayTestAppRRO \
+
PRODUCT_PRODUCT_PROPERTIES += \
car.ui.config=distantdisplay
diff --git a/car_product/overlay-visual/OWNERS b/car_product/overlay-visual/OWNERS
index 81c87ef..ceb7a02 100644
--- a/car_product/overlay-visual/OWNERS
+++ b/car_product/overlay-visual/OWNERS
@@ -1,3 +1,2 @@
# Car UI Reference OWNERS
stenning@google.com
-igorr@google.com
diff --git a/car_product/properties/bluetooth.prop b/car_product/properties/bluetooth.prop
index cd45fc2..885dbfc 100644
--- a/car_product/properties/bluetooth.prop
+++ b/car_product/properties/bluetooth.prop
@@ -35,6 +35,8 @@
# are disabled by default.
bluetooth.profile.a2dp.sink.enabled=true
bluetooth.profile.avrcp.controller.enabled=true
+bluetooth.profile.a2dp.source.enabled=true
+bluetooth.profile.avrcp.target.enabled=true
bluetooth.profile.gatt.enabled=true
bluetooth.profile.hfp.hf.enabled=true
bluetooth.profile.map.client.enabled=true
@@ -47,3 +49,7 @@
# Security Requests, that lead to pairing prompts and bond loss. This impacts
# classic, LE, and Fast Pair bonding.
bluetooth.gatt.check_encrypted_link.enabled=false
+
+# Enable art cover for a2dp source.
+persist.bluetooth.avrcpversion=avrcp16
+persist.bluetooth.avrcpcontrolversion=avrcp16
diff --git a/car_product/rro/CarResourceCommon/res-color/values/color_palettes.xml b/car_product/rro/CarResourceCommon/res-color/values/color_palettes.xml
index 464d4b1..553291b 100644
--- a/car_product/rro/CarResourceCommon/res-color/values/color_palettes.xml
+++ b/car_product/rro/CarResourceCommon/res-color/values/color_palettes.xml
@@ -33,25 +33,38 @@
<color name="car_tertiary_container">@color/car_tertiary_30</color>
<color name="car_on_tertiary_container">@color/car_tertiary_90</color>
+ <!-- text colors, auto specific -->
+ <color name="car_text_primary">@color/car_neutral_95</color>
+ <color name="car_text_secondary">@color/car_neutral_variant_70</color>
+
<!-- error colors -->
- <color name="car_error">@color/car_error_80</color>
- <color name="car_on_error">@color/car_error_20</color>
- <color name="car_error_container">@color/car_error_30</color>
- <color name="car_on_error_container">@color/car_error_80</color>
+ <color name="car_error">@color/car_red_80</color>
+ <color name="car_on_error">@color/car_red_20</color>
+ <color name="car_error_container">@color/car_red_30</color>
+ <color name="car_on_error_container">@color/car_red_90</color>
<!-- background colors -->
<color name="car_background">@color/car_neutral_10</color>
<color name="car_on_background">@color/car_neutral_90</color>
<!-- surface colors -->
- <color name="car_surface">@color/car_neutral_10</color>
+ <color name="car_surface">@color/car_neutral_2</color>
<color name="car_on_surface">@color/car_neutral_90</color>
+ <color name="car_surface_dim">@color/car_neutral_6</color>
+ <color name="car_surface_bright">@color/car_neutral_24</color>
+ <color name="car_inverse_surface">@color/car_neutral_90</color>
+ <color name="car_inverse_on_surface">@color/car_neutral_20</color>
+ <color name="car_surface_container">@color/car_neutral_12</color>
+ <color name="car_surface_container_low">@color/car_neutral_10</color>
+ <color name="car_surface_container_lowest">@color/car_neutral_4</color>
+ <color name="car_surface_container_high">@color/car_neutral_17</color>
+ <color name="car_surface_container_highest">@color/car_neutral_22</color>
<color name="car_surface_variant">@color/car_neutral_variant_30</color>
<color name="car_on_surface_variant">@color/car_neutral_variant_80</color>
<color name="car_outline">@color/car_neutral_variant_60</color>
- <color name="car_shadow">@color/car_neutral_variant_0</color>
- <color name="car_inverse_surface">@color/car_neutral_90</color>
- <color name="car_inverse_on_surface">@color/car_neutral_20</color>
+ <color name="car_outline_variant">@color/car_neutral_variant_30</color>
+ <color name="car_scrim">@color/car_neutral_0</color>
+ <color name="car_shadow">@color/car_neutral_0</color>
<color name="car_inverse_primary">@color/car_primary_40</color>
<color name="car_surface_1">#222429</color>
<color name="car_surface_2">#272930</color>
@@ -60,35 +73,35 @@
<color name="car_surface_5">#30333e</color>
<!-- red colors -->
- <color name="car_red_color">@color/car_error_80</color>
- <color name="car_red_on_color">#690002</color>
- <color name="car_red_color_container">#940005</color>
- <color name="car_red_on_color_container">@color/car_error_90</color>
+ <color name="car_red_color">@color/car_red_70</color>
+ <color name="car_red_on_color">@color/car_red_10</color>
+ <color name="car_red_color_container">@color/car_red_30</color>
+ <color name="car_red_on_color_container">@color/car_red_90</color>
<color name="car_red_tint">#dc362e</color>
<color name="car_decline_red">#d77163</color>
<color name="car_on_decline_red">#1c0f0d</color>
<!-- blue colors -->
- <color name="car_blue_color">#acc7ff</color>
- <color name="car_blue_on_color">#002e6c</color>
- <color name="car_blue_color_container">#004397</color>
- <color name="car_blue_on_color_container">#d6e2ff</color>
+ <color name="car_blue_color">@color/car_blue_70</color>
+ <color name="car_blue_on_color">@color/car_blue_10</color>
+ <color name="car_blue_color_container">@color/car_blue_30</color>
+ <color name="car_blue_on_color_container">@color/car_blue_90</color>
<color name="car_blue_tint">#4285f4</color>
<!-- green colors -->
- <color name="car_green_color">#5cdf7b</color>
- <color name="car_green_on_color">#003912</color>
- <color name="car_green_color_container">#00531e</color>
- <color name="car_green_on_color_container">#7afd95</color>
+ <color name="car_green_color">@color/car_green_70</color>
+ <color name="car_green_on_color">@color/car_green_10</color>
+ <color name="car_green_color_container">@color/car_green_30</color>
+ <color name="car_green_on_color_container">@color/car_green_90</color>
<color name="car_green_tint">#37be5f</color>
<color name="car_confirm_green">#75b885</color>
<color name="car_on_confirm_green">#0f1811</color>
<!-- yellow colors -->
- <color name="car_yellow_color">#fbbc04</color>
- <color name="car_yellow_on_color">#402d00</color>
- <color name="car_yellow_color_container">#5c4200</color>
- <color name="car_yellow_on_color_container">#ffdf9c</color>
+ <color name="car_yellow_color">@color/car_yellow_70</color>
+ <color name="car_yellow_on_color">@color/car_yellow_10</color>
+ <color name="car_yellow_color_container">@color/car_yellow_30</color>
+ <color name="car_yellow_on_color_container">@color/car_yellow_90</color>
<color name="car_yellow_tint">#fbbc04</color>
<!-- control highlight colors -->
diff --git a/car_product/rro/CarResourceCommon/res-color/values/color_tonal_palettes.xml b/car_product/rro/CarResourceCommon/res-color/values/color_tonal_palettes.xml
index d154ea3..e73c966 100644
--- a/car_product/rro/CarResourceCommon/res-color/values/color_tonal_palettes.xml
+++ b/car_product/rro/CarResourceCommon/res-color/values/color_tonal_palettes.xml
@@ -16,55 +16,88 @@
-->
<resources>
<!-- primary colors -->
- <color name="car_primary_100">#ffffff</color>
- <color name="car_primary_99">#fdfbff</color>
- <color name="car_primary_95">#edf0ff</color>
- <color name="car_primary_93">#e5eaff</color>
- <color name="car_primary_90">#d9e2ff</color>
- <color name="car_primary_80">#b0c5ff</color>
- <color name="car_primary_70">#86a9ff</color>
- <color name="car_primary_60">#588cff</color>
- <color name="car_primary_50">#3670e9</color>
- <color name="car_primary_40">#0856cf</color>
- <color name="car_primary_30">#003fa4</color>
- <color name="car_primary_25">#00358d</color>
- <color name="car_primary_20">#002b76</color>
- <color name="car_primary_10">#00184a</color>
<color name="car_primary_0">#000000</color>
+ <color name="car_primary_2">#000810</color>
+ <color name="car_primary_4">#00101a</color>
+ <color name="car_primary_5">#00131e</color>
+ <color name="car_primary_6">#001521</color>
+ <color name="car_primary_10">#001e2d</color>
+ <color name="car_primary_12">#002233</color>
+ <color name="car_primary_17">#002d42</color>
+ <color name="car_primary_20">#00344b</color>
+ <color name="car_primary_22">#003951</color>
+ <color name="car_primary_24">#003e57</color>
+ <color name="car_primary_30">#004c6b</color>
+ <color name="car_primary_40">#1f6587</color>
+ <color name="car_primary_50">#3e7ea1</color>
+ <color name="car_primary_60">#5a98bc</color>
+ <color name="car_primary_70">#76b2d8</color>
+ <color name="car_primary_80">#91cef5</color>
+ <color name="car_primary_85">#a5dbff</color>
+ <color name="car_primary_87">#b2e0ff</color>
+ <color name="car_primary_88">#b9e2ff</color>
+ <color name="car_primary_90">#c5e7ff</color>
+ <color name="car_primary_92">#d2ecff</color>
+ <color name="car_primary_94">#def0ff</color>
+ <color name="car_primary_95">#e4f3ff</color>
+ <color name="car_primary_98">#f5faff</color>
+ <color name="car_primary_100">#ffffff</color>
<!-- secondary colors -->
- <color name="car_secondary_100">#ffffff</color>
- <color name="car_secondary_99">#fdfbff</color>
- <color name="car_secondary_95">#edf0ff</color>
- <color name="car_secondary_93">#e5eaff</color>
- <color name="car_secondary_90">#dde2fa</color>
- <color name="car_secondary_80">#c0c6dd</color>
- <color name="car_secondary_70">#a5abc1</color>
- <color name="car_secondary_60">#8a90a6</color>
- <color name="car_secondary_50">#70768b</color>
- <color name="car_secondary_40">#585e71</color>
- <color name="car_secondary_30">#404659</color>
- <color name="car_secondary_25">#353b4d</color>
- <color name="car_secondary_20">#2a3042</color>
- <color name="car_secondary_10">#151b2c</color>
<color name="car_secondary_0">#000000</color>
+ <color name="car_secondary_2">#000810</color>
+ <color name="car_secondary_4">#00101a</color>
+ <color name="car_secondary_5">#01131d</color>
+ <color name="car_secondary_6">#031520</color>
+ <color name="car_secondary_10">#0a1e28</color>
+ <color name="car_secondary_12">#0f222d</color>
+ <color name="car_secondary_17">#1a2c37</color>
+ <color name="car_secondary_20">#20333e</color>
+ <color name="car_secondary_22">#253743</color>
+ <color name="car_secondary_24">#293b47</color>
+ <color name="car_secondary_30">#374955</color>
+ <color name="car_secondary_40">#4f616d</color>
+ <color name="car_secondary_50">#677987</color>
+ <color name="car_secondary_60">#8093a1</color>
+ <color name="car_secondary_70">#9baebc</color>
+ <color name="car_secondary_80">#b6c9d8</color>
+ <color name="car_secondary_85">#c4d7e6</color>
+ <color name="car_secondary_87">#c9ddec</color>
+ <color name="car_secondary_88">#ccdfee</color>
+ <color name="car_secondary_90">#d2e5f4</color>
+ <color name="car_secondary_92">#d7ebfa</color>
+ <color name="car_secondary_94">#def0ff</color>
+ <color name="car_secondary_95">#e4f3ff</color>
+ <color name="car_secondary_98">#f5faff</color>
+ <color name="car_secondary_100">#ffffff</color>
<!-- tertiary colors -->
- <color name="car_tertiary_100">#ffffff</color>
- <color name="car_tertiary_99">#fcfcfc</color>
- <color name="car_tertiary_95">#ffebfb</color>
- <color name="car_tertiary_93">#ffe2fc</color>
- <color name="car_tertiary_90">#fed7f9</color>
- <color name="car_tertiary_80">#e1bbdd</color>
- <color name="car_tertiary_70">#c4a0c1</color>
- <color name="car_tertiary_60">#a886a5</color>
- <color name="car_tertiary_50">#8d6c8b</color>
- <color name="car_tertiary_40">#735571</color>
- <color name="car_tertiary_30">#5a3d59</color>
- <color name="car_tertiary_25">#4d324d</color>
- <color name="car_tertiary_20">#422742</color>
- <color name="car_tertiary_10">#2b122c</color>
<color name="car_tertiary_0">#000000</color>
+ <color name="car_tertiary_2">#090320</color>
+ <color name="car_tertiary_4">#100927</color>
+ <color name="car_tertiary_5">#130b2a</color>
+ <color name="car_tertiary_6">#150e2d</color>
+ <color name="car_tertiary_10">#1e1735</color>
+ <color name="car_tertiary_12">#221b3a</color>
+ <color name="car_tertiary_17">#2c2545</color>
+ <color name="car_tertiary_20">#332c4c</color>
+ <color name="car_tertiary_22">#373050</color>
+ <color name="car_tertiary_24">#3c3455</color>
+ <color name="car_tertiary_30">#4a4263</color>
+ <color name="car_tertiary_40">#62597c</color>
+ <color name="car_tertiary_50">#7b7296</color>
+ <color name="car_tertiary_60">#958bb1</color>
+ <color name="car_tertiary_70">#b0a6cd</color>
+ <color name="car_tertiary_80">#cbc1e9</color>
+ <color name="car_tertiary_85">#dacff8</color>
+ <color name="car_tertiary_87">#dfd4fd</color>
+ <color name="car_tertiary_88">#e2d7ff</color>
+ <color name="car_tertiary_90">#e7deff</color>
+ <color name="car_tertiary_92">#ede4ff</color>
+ <color name="car_tertiary_94">#f2ebff</color>
+ <color name="car_tertiary_95">#f5eeff</color>
+ <color name="car_tertiary_98">#fdf7ff</color>
+ <color name="car_tertiary_100">#ffffff</color>
<!-- error colors -->
<color name="car_error_100">#ffffff</color>
@@ -82,37 +115,170 @@
<color name="car_error_0">#000000</color>
<!-- neutral colors -->
- <color name="car_neutral_100">#ffffff</color>
- <color name="car_neutral_99">#fefbff</color>
- <color name="car_neutral_95">#f2f0f5</color>
- <color name="car_neutral_93">#eceaef</color>
- <color name="car_neutral_90">#e4e2e6</color>
- <color name="car_neutral_80">#c8c6ca</color>
- <color name="car_neutral_70">#acabaf</color>
- <color name="car_neutral_60">#919094</color>
- <color name="car_neutral_50">#77767a</color>
- <color name="car_neutral_40">#5e5e62</color>
- <color name="car_neutral_35">#525256</color>
- <color name="car_neutral_30">#46464a</color>
- <color name="car_neutral_25">#3b3b3e</color>
- <color name="car_neutral_20">#303033</color>
- <color name="car_neutral_10">#1b1b1e</color>
<color name="car_neutral_0">#000000</color>
+ <color name="car_neutral_2">#04080b</color>
+ <color name="car_neutral_4">#0a0f12</color>
+ <color name="car_neutral_5">#0d1115</color>
+ <color name="car_neutral_6">#0f1417</color>
+ <color name="car_neutral_10">#181c1f</color>
+ <color name="car_neutral_12">#1c2024</color>
+ <color name="car_neutral_17">#262b2e</color>
+ <color name="car_neutral_20">#2c3134</color>
+ <color name="car_neutral_22">#313539</color>
+ <color name="car_neutral_24">#353a3d</color>
+ <color name="car_neutral_30">#43474b</color>
+ <color name="car_neutral_40">#5a5f63</color>
+ <color name="car_neutral_50">#73787c</color>
+ <color name="car_neutral_60">#8d9195</color>
+ <color name="car_neutral_70">#a8acb0</color>
+ <color name="car_neutral_80">#c3c7cb</color>
+ <color name="car_neutral_85">#d1d5d9</color>
+ <color name="car_neutral_87">#d7dadf</color>
+ <color name="car_neutral_88">#d9dde2</color>
+ <color name="car_neutral_90">#dfe3e7</color>
+ <color name="car_neutral_92">#e5e8ed</color>
+ <color name="car_neutral_94">#ebeef3</color>
+ <color name="car_neutral_95">#eef1f6</color>
+ <color name="car_neutral_98">#f6fafe</color>
+ <color name="car_neutral_100">#ffffff</color>
<!-- neutral variant colors -->
- <color name="car_neutral_variant_100">#ffffff</color>
- <color name="car_neutral_variant_99">#fdfbff</color>
- <color name="car_neutral_variant_95">#f0f0fa</color>
- <color name="car_neutral_variant_93">#eaeaf5</color>
- <color name="car_neutral_variant_90">#e2e2ec</color>
- <color name="car_neutral_variant_80">#c5c6d0</color>
- <color name="car_neutral_variant_70">#a9aab4</color>
- <color name="car_neutral_variant_60">#8f909a</color>
- <color name="car_neutral_variant_50">#75767f</color>
- <color name="car_neutral_variant_40">#5c5e67</color>
- <color name="car_neutral_variant_30">#44464e</color>
- <color name="car_neutral_variant_25">#393b43</color>
- <color name="car_neutral_variant_20">#2e3038</color>
- <color name="car_neutral_variant_10">#191b23</color>
<color name="car_neutral_variant_0">#000000</color>
+ <color name="car_neutral_variant_2">#03080c</color>
+ <color name="car_neutral_variant_4">#080f13</color>
+ <color name="car_neutral_variant_5">#0b1216</color>
+ <color name="car_neutral_variant_6">#0d1419</color>
+ <color name="car_neutral_variant_10">#161c21</color>
+ <color name="car_neutral_variant_12">#1a2025</color>
+ <color name="car_neutral_variant_17">#242b30</color>
+ <color name="car_neutral_variant_20">#2b3136</color>
+ <color name="car_neutral_variant_22">#2f363b</color>
+ <color name="car_neutral_variant_24">#333a3f</color>
+ <color name="car_neutral_variant_30">#41484d</color>
+ <color name="car_neutral_variant_40">#585f65</color>
+ <color name="car_neutral_variant_50">#71787e</color>
+ <color name="car_neutral_variant_60">#8b9297</color>
+ <color name="car_neutral_variant_70">#a5acb2</color>
+ <color name="car_neutral_variant_80">#c1c7ce</color>
+ <color name="car_neutral_variant_85">#cfd5dc</color>
+ <color name="car_neutral_variant_87">#d4dbe1</color>
+ <color name="car_neutral_variant_88">#d7dee4</color>
+ <color name="car_neutral_variant_90">#dde3ea</color>
+ <color name="car_neutral_variant_92">#e2e9ef</color>
+ <color name="car_neutral_variant_94">#e8eff5</color>
+ <color name="car_neutral_variant_95">#ebf1f8</color>
+ <color name="car_neutral_variant_98">#f5faff</color>
+ <color name="car_neutral_variant_100">#ffffff</color>
+
+ <!-- red colors -->
+ <color name="car_red_0">#000000</color>
+ <color name="car_red_2">#1a0000</color>
+ <color name="car_red_4">#280001</color>
+ <color name="car_red_5">#2d0001</color>
+ <color name="car_red_6">#310001</color>
+ <color name="car_red_10">#410001</color>
+ <color name="car_red_12">#490002</color>
+ <color name="car_red_17">#5c0003</color>
+ <color name="car_red_20">#690004</color>
+ <color name="car_red_22">#710005</color>
+ <color name="car_red_24">#790005</color>
+ <color name="car_red_30">#930008</color>
+ <color name="car_red_40">#ba1b19</color>
+ <color name="car_red_50">#de372f</color>
+ <color name="car_red_60">#ff5448</color>
+ <color name="car_red_70">#ff897c</color>
+ <color name="car_red_80">#ffb4aa</color>
+ <color name="car_red_85">#ffc7c0</color>
+ <color name="car_red_87">#ffcfc9</color>
+ <color name="car_red_88">#ffd3cd</color>
+ <color name="car_red_90">#ffdad5</color>
+ <color name="car_red_92">#ffe2de</color>
+ <color name="car_red_94">#ffe9e6</color>
+ <color name="car_red_95">#ffedea</color>
+ <color name="car_red_98">#fff8f7</color>
+ <color name="car_red_100">#ffffff</color>
+
+ <!-- green colors -->
+ <color name="car_green_0">#000000</color>
+ <color name="car_green_2">#000a01</color>
+ <color name="car_green_4">#001203</color>
+ <color name="car_green_5">#001504</color>
+ <color name="car_green_6">#001804</color>
+ <color name="car_green_10">#002107</color>
+ <color name="car_green_12">#002609</color>
+ <color name="car_green_17">#00320f</color>
+ <color name="car_green_20">#003912</color>
+ <color name="car_green_22">#003e14</color>
+ <color name="car_green_24">#004316</color>
+ <color name="car_green_30">#00531d</color>
+ <color name="car_green_40">#006e29</color>
+ <color name="car_green_50">#1d893b</color>
+ <color name="car_green_60">#3ea453</color>
+ <color name="car_green_70">#5bbf6b</color>
+ <color name="car_green_80">#77dc83</color>
+ <color name="car_green_85">#84ea90</color>
+ <color name="car_green_87">#8af095</color>
+ <color name="car_green_88">#8df398</color>
+ <color name="car_green_90">#92f99d</color>
+ <color name="car_green_92">#98ffa2</color>
+ <color name="car_green_94">#b8ffba</color>
+ <color name="car_green_95">#c6ffc6</color>
+ <color name="car_green_98">#ebffe7</color>
+ <color name="car_green_100">#ffffff</color>
+
+ <!-- yellow colors -->
+ <color name="car_yellow_0">#000000</color>
+ <color name="car_yellow_2">#0e0600</color>
+ <color name="car_yellow_4">#180c00</color>
+ <color name="car_yellow_5">#1b0e00</color>
+ <color name="car_yellow_6">#1f1000</color>
+ <color name="car_yellow_10">#2a1800</color>
+ <color name="car_yellow_12">#2f1b00</color>
+ <color name="car_yellow_17">#3d2500</color>
+ <color name="car_yellow_20">#462b00</color>
+ <color name="car_yellow_22">#4c2e00</color>
+ <color name="car_yellow_24">#523200</color>
+ <color name="car_yellow_30">#643f00</color>
+ <color name="car_yellow_40">#845400</color>
+ <color name="car_yellow_50">#a56b00</color>
+ <color name="car_yellow_60">#c78200</color>
+ <color name="car_yellow_70">#eb9a00</color>
+ <color name="car_yellow_80">#ffb958</color>
+ <color name="car_yellow_85">#ffcb8a</color>
+ <color name="car_yellow_87">#ffd29c</color>
+ <color name="car_yellow_88">#ffd6a5</color>
+ <color name="car_yellow_90">#ffddb6</color>
+ <color name="car_yellow_92">#ffe4c6</color>
+ <color name="car_yellow_94">#ffebd5</color>
+ <color name="car_yellow_95">#ffeedd</color>
+ <color name="car_yellow_98">#fff8f4</color>
+ <color name="car_yellow_100">#ffffff</color>
+
+ <!-- blue colors -->
+ <color name="car_blue_0">#000000</color>
+ <color name="car_blue_2">#00061c</color>
+ <color name="car_blue_4">#000c2a</color>
+ <color name="car_blue_5">#000f30</color>
+ <color name="car_blue_6">#001135</color>
+ <color name="car_blue_10">#001945</color>
+ <color name="car_blue_12">#001d4d</color>
+ <color name="car_blue_17">#002662</color>
+ <color name="car_blue_20">#002c6f</color>
+ <color name="car_blue_22">#003178</color>
+ <color name="car_blue_24">#003581</color>
+ <color name="car_blue_30">#00419c</color>
+ <color name="car_blue_40">#0057cc</color>
+ <color name="car_blue_50">#1f70f5</color>
+ <color name="car_blue_60">#578cff</color>
+ <color name="car_blue_70">#86a9ff</color>
+ <color name="car_blue_80">#b0c6ff</color>
+ <color name="car_blue_85">#c5d4ff</color>
+ <color name="car_blue_87">#cdd9ff</color>
+ <color name="car_blue_88">#d1dcff</color>
+ <color name="car_blue_90">#d9e2ff</color>
+ <color name="car_blue_92">#e1e7ff</color>
+ <color name="car_blue_94">#eaedff</color>
+ <color name="car_blue_95">#eef0ff</color>
+ <color name="car_blue_98">#faf8ff</color>
+ <color name="car_blue_100">#ffffff</color>
</resources>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/Android.bp b/car_product/rro/CarSystemUIPassengerRRO/Android.bp
index a3aa594..b76441b 100644
--- a/car_product/rro/CarSystemUIPassengerRRO/Android.bp
+++ b/car_product/rro/CarSystemUIPassengerRRO/Android.bp
@@ -24,6 +24,7 @@
product_specific: true,
static_libs: [
+ "car-resource-common",
"car-ui-lib-no-overlayable",
"androidx-constraintlayout_constraintlayout",
],
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml b/car_product/rro/CarSystemUIPassengerRRO/res/color/car_status_icon_color.xml
similarity index 61%
copy from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
copy to car_product/rro/CarSystemUIPassengerRRO/res/color/car_status_icon_color.xml
index a6ac85e..413339c 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/color/car_status_icon_color.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -13,11 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<ImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/statusbar_sensor_text_width"
- android:layout_height="@*android:dimen/status_bar_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:tag="@string/qc_icon_tag"/>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/car_on_primary" android:state_selected="true"/>
+ <item android:color="@color/car_on_surface"/>
+</selector>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_ic_chevron_right.xml b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_ic_chevron_right.xml
new file mode 100644
index 0000000..bb87b98
--- /dev/null
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_ic_chevron_right.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="@dimen/car_quick_controls_icon_drawable_width"
+ android:height="@dimen/car_quick_controls_icon_drawable_height"
+ android:viewportWidth="36"
+ android:viewportHeight="36">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M14.9999 9L12.8774 11.1225L19.7549 18L12.8774 24.8775L14.9999 27L23.9999 18L14.9999 9Z"/>
+</vector>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_ic_logout.xml b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_ic_logout.xml
new file mode 100644
index 0000000..3cab33e
--- /dev/null
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_ic_logout.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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="@*android:dimen/car_touch_target_size"
+ android:height="@*android:dimen/car_touch_target_size"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M5 21q-0.825 0-1.413-0.587Q3 19.825 3 19V5q0-0.825 0.587 -1.413Q4.175 3 5 3h7v2H5v14h7v2Zm11-4-1.375-1.45 2.55-2.55H9v-2h8.175l-2.55-2.55L16 7l5 5Z" />
+</vector>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_ic_power.xml b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_ic_power.xml
new file mode 100644
index 0000000..28ccdf9
--- /dev/null
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_ic_power.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="@dimen/car_quick_controls_icon_drawable_width"
+ android:height="@dimen/car_quick_controls_icon_drawable_height"
+ android:viewportWidth="36"
+ android:viewportHeight="36">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M10.62 7.50021H30V24.0002H27.12L30.105 26.9852C31.71 26.9402 33 25.6202 33 24.0002V7.50021C33 5.85021 31.65 4.50021 30 4.50021H7.62L10.62 7.50021ZM0 4.50021L3 7.50021V24.0002C3 25.6502 4.35 27.0002 6 27.0002H10.5L9 28.5002V31.5002H27L31.095 35.5952L33 33.6902L1.905 2.59521L0 4.50021ZM6 10.5002L19.5 24.0002H6V10.5002Z" />
+</vector>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_ic_settings_icon.xml b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_ic_settings_icon.xml
new file mode 100644
index 0000000..0bffee4
--- /dev/null
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_ic_settings_icon.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@*android:dimen/status_bar_system_icon_size"
+ android:height="@*android:dimen/status_bar_system_icon_size"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4C9.75,2 9.54,2.18 9.51,2.42L9.13,5.07C8.52,5.32 7.96,5.66 7.44,6.05l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46C2.21,8.95 2.27,9.22 2.46,9.37l2.11,1.65C4.53,11.34 4.5,11.67 4.5,12s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65C9.54,21.82 9.75,22 10,22h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64L19.43,12.98zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5s3.5,1.57 3.5,3.5S13.93,15.5 12,15.5z"/>
+</vector>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_quick_controls_pill_button_background.xml b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_quick_controls_pill_button_background.xml
new file mode 100644
index 0000000..d695878
--- /dev/null
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/car_quick_controls_pill_button_background.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <item>
+ <aapt:attr name="android:drawable">
+ <shape android:shape="rectangle">
+ <solid android:color="@color/car_surface_5"/>
+ <corners android:radius="@dimen/system_bar_pill_radius"/>
+ </shape>
+ </aapt:attr>
+ </item>
+ <item android:drawable="@drawable/system_bar_pill_rotary_background"/>
+</layer-list>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/drawable/hvac_decrease_button.xml b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/hvac_decrease_button.xml
index 27ca936..122d49c 100644
--- a/car_product/rro/CarSystemUIPassengerRRO/res/drawable/hvac_decrease_button.xml
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/hvac_decrease_button.xml
@@ -14,40 +14,24 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<item android:gravity="center"
- android:width="@dimen/system_bar_icon_drawing_size"
- android:height="@dimen/system_bar_icon_drawing_size">
+ android:width="@dimen/hvac_temperature_icon_size"
+ android:height="@dimen/hvac_temperature_icon_size">
<aapt:attr name="android:drawable">
- <shape android:shape="oval">
- <size
- android:width="@dimen/system_bar_icon_drawing_size"
- android:height="@dimen/system_bar_icon_drawing_size"/>
- <solid
- android:color="@color/hvac_temperature_adjust_button_color"/>
- </shape>
- </aapt:attr>
- </item>
- <item android:gravity="center"
- android:width="@dimen/system_bar_icon_drawing_size"
- android:height="@dimen/system_bar_icon_drawing_size">
- <aapt:attr name="android:drawable">
- <vector android:width="@dimen/system_bar_icon_drawing_size"
- android:height="@dimen/system_bar_icon_drawing_size"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ <vector android:width="@dimen/hvac_temperature_icon_size"
+ android:height="@dimen/hvac_temperature_icon_size"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
<path
- android:fillColor="@color/hvac_temperature_decrease_arrow_color"
- android:pathData="M14,7l-5,5 5,5V7z"/>
+ android:fillColor="@color/car_outline"
+ android:pathData="M200,520L200,440L760,440L760,520L200,520Z"/>
</vector>
</aapt:attr>
</item>
- <item>
- <aapt:attr name="android:drawable">
- <ripple android:color="?android:attr/colorControlHighlight"/>
- </aapt:attr>
- </item>
+ <item android:width="@dimen/hvac_temperature_button_size"
+ android:height="@dimen/hvac_temperature_button_size"
+ android:drawable="@drawable/system_bar_button_background"/>
</layer-list>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/drawable/hvac_increase_button.xml b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/hvac_increase_button.xml
index 727bb6d..c0875c4 100644
--- a/car_product/rro/CarSystemUIPassengerRRO/res/drawable/hvac_increase_button.xml
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/hvac_increase_button.xml
@@ -19,35 +19,20 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<item android:gravity="center"
- android:width="@dimen/system_bar_icon_drawing_size"
- android:height="@dimen/system_bar_icon_drawing_size">
+ android:width="@dimen/hvac_temperature_icon_size"
+ android:height="@dimen/hvac_temperature_icon_size">
<aapt:attr name="android:drawable">
- <shape android:shape="oval">
- <size
- android:width="@dimen/system_bar_icon_drawing_size"
- android:height="@dimen/system_bar_icon_drawing_size"/>
- <solid
- android:color="@color/hvac_temperature_adjust_button_color"/>
- </shape>
- </aapt:attr>
- </item>
- <item android:gravity="center"
- android:width="@dimen/system_bar_icon_drawing_size"
- android:height="@dimen/system_bar_icon_drawing_size">
- <aapt:attr name="android:drawable">
- <vector android:width="@dimen/system_bar_icon_drawing_size"
- android:height="@dimen/system_bar_icon_drawing_size"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ <vector android:width="@dimen/hvac_temperature_icon_size"
+ android:height="@dimen/hvac_temperature_icon_size"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
<path
- android:fillColor="@color/hvac_temperature_increase_arrow_color"
- android:pathData="M10,17l5,-5 -5,-5v10z"/>
+ android:fillColor="@color/car_outline"
+ android:pathData="M440,520L200,520L200,440L440,440L440,200L520,200L520,440L760,440L760,520L520,520L520,760L440,760L440,520Z"/>
</vector>
</aapt:attr>
</item>
- <item>
- <aapt:attr name="android:drawable">
- <ripple android:color="?android:attr/colorControlHighlight"/>
- </aapt:attr>
- </item>
+ <item android:width="@dimen/hvac_temperature_button_size"
+ android:height="@dimen/hvac_temperature_button_size"
+ android:drawable="@drawable/system_bar_button_background"/>
</layer-list>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/drawable/nav_bar_button_background.xml b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/nav_bar_button_background.xml
new file mode 100644
index 0000000..d0bdd03
--- /dev/null
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/nav_bar_button_background.xml
@@ -0,0 +1,88 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true" android:state_focused="true" android:state_pressed="true">
+ <layer-list>
+ <item>
+ <ripple android:color="@color/car_ui_ripple_color">
+ <item android:drawable="@drawable/nav_bar_button_background_selected" />
+ </ripple>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
+ <stroke android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
+ android:color="@color/car_ui_rotary_focus_pressed_stroke_color"/>
+ <corners android:radius="24dp"/>
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+ <item android:state_selected="true" android:state_focused="true">
+ <layer-list>
+ <item>
+ <ripple android:color="@color/car_ui_ripple_color">
+ <item android:drawable="@drawable/nav_bar_button_background_selected" />
+ </ripple>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
+ <stroke android:width="@dimen/car_ui_rotary_focus_stroke_width"
+ android:color="@color/car_ui_rotary_focus_stroke_color"/>
+ <corners android:radius="24dp"/>
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+ <item android:state_focused="true" android:state_pressed="true">
+ <layer-list>
+ <item>
+ <ripple android:color="@color/car_ui_ripple_color">
+ <item android:drawable="@drawable/nav_bar_button_background_unselected" />
+ </ripple>
+ </item>
+ <item>
+ <shape android:shape="oval">
+ <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
+ <stroke android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
+ android:color="@color/car_ui_rotary_focus_pressed_stroke_color"/>
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+ <item android:state_focused="true">
+ <layer-list>
+ <item>
+ <ripple android:color="@color/car_ui_ripple_color">
+ <item android:drawable="@drawable/nav_bar_button_background_unselected" />
+ </ripple>
+ </item>
+ <item>
+ <shape android:shape="oval">
+ <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
+ <stroke android:width="@dimen/car_ui_rotary_focus_stroke_width"
+ android:color="@color/car_ui_rotary_focus_stroke_color"/>
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+ <item android:state_selected="true">
+ <ripple android:color="@color/car_ui_ripple_color">
+ <item android:drawable="@drawable/nav_bar_button_background_selected" />
+ </ripple>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/drawable/nav_bar_button_background_selected.xml b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/nav_bar_button_background_selected.xml
new file mode 100644
index 0000000..64405aa
--- /dev/null
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/nav_bar_button_background_selected.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="@dimen/system_bar_button_size"
+ android:height="@dimen/system_bar_button_size"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+
+ <group
+ android:name="s"
+ android:translateX="24"
+ android:translateY="24"
+ android:scaleX="0.5"
+ android:scaleY="0.5">
+ <path
+ android:name="v"
+ android:fillColor="@color/car_primary"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData="M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c " />
+ </group>
+</vector>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/drawable/nav_bar_button_background_unselected.xml b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/nav_bar_button_background_unselected.xml
new file mode 100644
index 0000000..6a92a4e
--- /dev/null
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/drawable/nav_bar_button_background_unselected.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="@dimen/system_bar_button_size"
+ android:height="@dimen/system_bar_button_size"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+
+ <group
+ android:name="s"
+ android:translateX="24"
+ android:translateY="24"
+ android:scaleX="0.5"
+ android:scaleY="0.5">
+ <path
+ android:name="v"
+ android:fillColor="@color/car_surface"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData="M48 0.25 C48,0.25 48,0 48,0 C47.75,26 31.25,48 0,48 C0,48 0,48 0,48 C-30,48 -48,25.75 -48,-0.25 C-48,-0.25 -48,-0.25 -48,-0.25 C-47.75,-23.5 -32.25,-47.75 0.5,-48 C0.5,-48 0.5,-48 0.5,-48 C28,-47.75 47.75,-29.75 48,0.25c " />
+ </group>
+</vector>
\ No newline at end of file
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/layout/car_passenger_bottom_system_bar.xml b/car_product/rro/CarSystemUIPassengerRRO/res/layout/car_passenger_bottom_system_bar.xml
index a29e7e2..9c1a716 100644
--- a/car_product/rro/CarSystemUIPassengerRRO/res/layout/car_passenger_bottom_system_bar.xml
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/layout/car_passenger_bottom_system_bar.xml
@@ -68,7 +68,7 @@
<com.android.systemui.car.systembar.CarSystemBarButton
android:id="@+id/control_center_nav"
android:contentDescription="@string/system_bar_control_center_label"
- style="@style/ControlCenterSystemBarButton"
+ style="@style/SystemBarButton"
android:visibility="gone"
systemui:highlightWhenSelected="true"
systemui:icon="@drawable/car_ic_control_center"
@@ -90,14 +90,61 @@
</LinearLayout>
<!--TODO() b/260663404 Need to implement Privacy chip indicator -->
- <com.android.systemui.car.statusicon.ui.QuickControlsEntryPointContainer
- android:id="@+id/qc_entry_points_container"
+
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
+ android:id="@+id/passenger_qc_entry_point_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
+ android:paddingHorizontal="@dimen/car_padding_1"
+ style="@style/TopBarButton"
+ app:panelLayoutRes="@layout/car_quick_controls_panel"
+ app:disabledWhileUnprovisioned="true"
+ app:systemBarDisable2Flags="quickSettings"
app:showAsDropDown="false"
- app:panelGravity="0x00800055"/>
-
+ app:gravity="bottom|end">
+ <ImageView
+ android:id="@+id/user_icon"
+ android:layout_width="@dimen/car_quick_controls_entry_points_icon_width"
+ android:layout_height="match_parent"
+ android:adjustViewBounds="true"
+ android:src="@drawable/car_user_icon_circle_background"
+ android:scaleType="fitCenter"
+ android:layout_marginEnd="@dimen/car_quick_controls_entry_points_icon_space"/>
+ <TextView
+ android:id="@+id/user_name_text"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:visibility="gone"/>
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/media_volume_status_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:tint="@color/car_status_icon_color"
+ android:layout_marginEnd="@dimen/car_quick_controls_entry_points_icon_space"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.MediaVolumeStatusIconController"/>
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/signal_status_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:layout_marginEnd="@dimen/car_quick_controls_entry_points_icon_space"
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.SignalStatusIconController"/>
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:elevation="5dp"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.SystemBar.ClockWithSelection"
+ systemui:amPmStyle="gone"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
</RelativeLayout>
</com.android.systemui.car.systembar.CarSystemBarView>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/layout/car_qc_entry_points_button.xml b/car_product/rro/CarSystemUIPassengerRRO/res/layout/car_qc_entry_points_button.xml
deleted file mode 100644
index c3b45cf..0000000
--- a/car_product/rro/CarSystemUIPassengerRRO/res/layout/car_qc_entry_points_button.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
- android:layout_width="wrap_content"
- android:layout_height="76dp"
- android:orientation="horizontal"
- android:gravity="center_vertical"
- android:paddingTop="@dimen/car_padding_1"
- android:paddingBottom="@dimen/car_padding_1"
- android:paddingStart="@dimen/car_padding_2"
- android:paddingEnd="@dimen/car_padding_2"
- style="@style/QuickControlEntryPointButton">
-
- <ImageView
- android:id="@+id/user_icon"
- android:layout_width="@dimen/car_quick_controls_entry_points_icon_width"
- android:layout_height="match_parent"
- android:adjustViewBounds="true"
- android:src="@drawable/car_user_icon_circle_background"
- android:scaleType="fitCenter"
- android:layout_marginEnd="@dimen/car_quick_controls_entry_points_icon_space"/>
- <TextView
- android:id="@+id/user_name_text"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:visibility="gone"/>
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/car_quick_controls_entry_points_icon_space"
- android:tag="@string/qc_icon_tag"/>
- <com.android.systemui.statusbar.policy.Clock
- android:id="@+id/clock"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:elevation="5dp"
- android:singleLine="true"
- android:textAppearance="@style/TextAppearance.SystemBar.ClockWithSelection"
- systemui:amPmStyle="gone"/>
-
-</LinearLayout>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/layout/car_quick_controls_panel.xml b/car_product/rro/CarSystemUIPassengerRRO/res/layout/car_quick_controls_panel.xml
new file mode 100644
index 0000000..453f6b2
--- /dev/null
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/layout/car_quick_controls_panel.xml
@@ -0,0 +1,229 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <com.android.car.ui.FocusParkingView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <com.android.car.ui.FocusArea
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="vertical">
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/header_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/car_padding_1"
+ android:layout_marginBottom="@dimen/car_padding_2"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/qc_scroll_container">
+ <com.android.systemui.statusbar.policy.DateView
+ android:id="@+id/date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|center_vertical"
+ android:gravity="center_vertical"
+ android:singleLine="true"
+ android:textSize="@dimen/car_body3_size"
+ android:textColor="@*android:color/car_body3"
+ android:layout_marginStart="@dimen/car_padding_4"
+ android:layout_marginEnd="@dimen/car_padding_4"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/read_only_status_icons_container"
+ systemui:datePattern="EEEMMMd" />
+ <LinearLayout
+ android:id="@+id/read_only_status_icons_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end|center_vertical"
+ android:orientation="horizontal"
+ android:gravity="end|center_vertical"
+ android:layout_marginStart="@dimen/car_padding_4"
+ android:layout_marginEnd="@dimen/car_padding_4"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/date">
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:layout_width="@dimen/car_quick_controls_entry_points_icon_width"
+ android:layout_height="match_parent"
+ android:layout_marginHorizontal="@dimen/car_padding_1"
+ android:layout_gravity="center"
+ systemui:controller="com.android.systemui.car.statusicon.ui.LocationStatusIconController"/>
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:layout_width="@dimen/car_quick_controls_entry_points_icon_width"
+ android:layout_height="match_parent"
+ android:layout_marginHorizontal="@dimen/car_padding_1"
+ android:layout_gravity="center"
+ systemui:controller="com.android.systemui.car.statusicon.ui.MobileSignalStatusIconController"/>
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:layout_width="@dimen/car_quick_controls_entry_points_icon_width"
+ android:layout_height="match_parent"
+ android:layout_marginHorizontal="@dimen/car_padding_1"
+ android:layout_gravity="center"
+ systemui:controller="com.android.systemui.car.statusicon.ui.WifiSignalStatusIconController"/>
+ </LinearLayout>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <ScrollView
+ android:id="@+id/qc_scroll_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/car_padding_3"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/header_container"
+ app:layout_constraintBottom_toTopOf="@+id/footer_container">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <com.android.systemui.car.qc.SystemUIQCView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ app:remoteQCProvider="content://com.android.car.settings.qc/hotspot_row"/>
+ <com.android.systemui.car.qc.SystemUIQCView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ app:remoteQCProvider="content://com.android.car.settings.qc/brightness_slider"/>
+ <com.android.systemui.car.qc.SystemUIQCView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ app:remoteQCProvider="content://com.android.car.settings.qc/media_volume_slider_without_icon"/>
+ </LinearLayout>
+ </ScrollView>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/footer_container"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_quick_controls_panel_footer_button_view_height"
+ android:layout_marginBottom="@dimen/car_padding_2"
+ android:layout_marginStart="@dimen/car_padding_2"
+ android:layout_marginEnd="@dimen/car_padding_2"
+ android:orientation="horizontal"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+ <com.android.systemui.car.qc.QCFooterView
+ android:id="@+id/user_button"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ style="@style/QCFooterButtonViewStyle"
+ app:controller="com.android.systemui.car.qc.QCUserPickerButtonController"
+ app:layout_constraintHorizontal_weight="9"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/settings_button">
+ <ImageView
+ android:id="@+id/user_icon"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:background="@drawable/car_user_icon_circle_background"
+ android:layout_marginStart="@dimen/car_padding_1"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/user_name_text"/>
+ <TextView
+ android:id="@+id/user_name_text"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:textAppearance="@style/TextAppearance.SystemBar.Username"
+ android:singleLine="true"
+ android:autoSizeTextType="uniform"
+ android:layout_marginStart="@dimen/car_padding_2"
+ android:layout_marginEnd="@dimen/car_padding_2"
+ app:layout_constraintHorizontal_weight="1"
+ app:layout_constraintStart_toEndOf="@id/user_icon"
+ app:layout_constraintEnd_toStartOf="@+id/chevron_right"/>
+ <ImageView
+ android:id="@+id/chevron_right"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:src="@drawable/car_ic_chevron_right"
+ android:scaleType="fitCenter"
+ android:layout_marginEnd="@dimen/car_padding_1"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintStart_toEndOf="@id/user_name_text"
+ app:layout_constraintEnd_toEndOf="parent"/>
+ </com.android.systemui.car.qc.QCFooterView>
+
+ <com.android.systemui.car.qc.QCFooterView
+ android:id="@+id/settings_button"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ app:layout_constraintHorizontal_weight="2"
+ app:layout_constraintStart_toEndOf="@id/user_button"
+ app:layout_constraintEnd_toStartOf="@+id/logout_button"
+ style="@style/QCFooterButtonViewStyle"
+ app:intent="intent:#Intent;action=android.settings.SETTINGS;launchFlags=0x24000000;end">
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@drawable/car_ic_settings_icon"
+ android:scaleType="fitCenter"/>
+ </com.android.systemui.car.qc.QCFooterView>
+
+ <com.android.systemui.car.qc.QCFooterView
+ android:id="@+id/logout_button"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ app:controller="com.android.systemui.car.qc.QCLogoutButtonController"
+ app:layout_constraintHorizontal_weight="2"
+ app:layout_constraintStart_toEndOf="@id/settings_button"
+ app:layout_constraintEnd_toStartOf="@+id/screen_off_button"
+ style="@style/QCFooterButtonViewStyle">
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@drawable/car_ic_logout"
+ android:scaleType="fitCenter"/>
+ </com.android.systemui.car.qc.QCFooterView>
+
+ <com.android.systemui.car.qc.QCFooterView
+ android:id="@+id/screen_off_button"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ app:controller="com.android.systemui.car.qc.QCScreenOffButtonController"
+ app:layout_constraintHorizontal_weight="2"
+ app:layout_constraintStart_toEndOf="@id/logout_button"
+ app:layout_constraintEnd_toEndOf="parent"
+ style="@style/QCFooterButtonViewStyle">
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@drawable/car_ic_power"
+ android:scaleType="fitCenter"/>
+ </com.android.systemui.car.qc.QCFooterView>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </com.android.car.ui.FocusArea>
+</FrameLayout>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/values/attrs.xml b/car_product/rro/CarSystemUIPassengerRRO/res/values/attrs.xml
index 08f6f91..8f14714 100644
--- a/car_product/rro/CarSystemUIPassengerRRO/res/values/attrs.xml
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/values/attrs.xml
@@ -44,11 +44,6 @@
<attr name="android:textAppearance"/>
</declare-styleable>
- <declare-styleable name="QuickControlsEntryPointContainer">
- <attr name="showAsDropDown" format="boolean"/>
- <attr name="panelGravity" format="integer"/>
- </declare-styleable>
-
<declare-styleable name="Clock">
<attr name="amPmStyle" format="enum">
<enum name="normal" value="0" />
@@ -57,4 +52,83 @@
</attr>
<attr name="showDark" format="boolean" />
</declare-styleable>
+
+ <declare-styleable name="DateView">
+ <attr name="datePattern" format="string" />
+ </declare-styleable>
+
+ <!-- Custom attribute for the CarSystemBarElement interface - implementing classes must opt in
+ to consuming these attributes within their constructor and applying appropriately. -->
+ <declare-styleable name="CarSystemBarElement">
+ <!-- Class name of the CarSystemBarElementController that should be attached to this view -->
+ <attr name="controller" format="string"/>
+ <!-- Attribute for specifying the system bar disable flag of a supporting element view.
+ This corresponds to the StatusBarManager.DisableFlags. -->
+ <attr name="systemBarDisableFlags" format="integer">
+ <flag name="none" value="0"/>
+ <flag name="expand" value="1"/>
+ <flag name="notificationIcons" value="2"/>
+ <flag name="notificationAlerts" value="4"/>
+ <flag name="systemInfo" value="8"/>
+ <flag name="home" value="16"/>
+ <flag name="recent" value="32"/>
+ <flag name="back" value="64"/>
+ <flag name="clock" value="128"/>
+ <flag name="search" value="256"/>
+ <flag name="ongoingCallChip" value="512"/>
+ </attr>
+ <!-- Attribute for specifying the system bar disable flag of a supporting element view.
+ This corresponds to the StatusBarManager.Disable2Flags. -->
+ <attr name="systemBarDisable2Flags" format="integer">
+ <flag name="none" value="0"/>
+ <flag name="quickSettings" value="1"/>
+ <flag name="systemIcons" value="2"/>
+ <flag name="notificationShade" value="4"/>
+ <flag name="globalActions" value="8"/>
+ <flag name="rotateSuggestions" value="16"/>
+ </attr>
+ <!-- Attribute for specifying if this element should be disabled when in the
+ LOCK_TASK_MODE_LOCKED state -->
+ <attr name="disableForLockTaskModeLocked" format="boolean"/>
+ </declare-styleable>
+
+ <declare-styleable name="CarSystemBarPanelButtonView">
+ <!-- REQUIRED: Layout resource for the panel -->
+ <attr name="panelLayoutRes" format="reference"/>
+ <!-- Width of the panel. If not specified, will use default width value -->
+ <attr name="panelWidthRes" format="reference"/>
+ <!-- X Offset value for the panel location relative to the anchor view -->
+ <attr name="xOffset" format="integer"/>
+ <!-- Y Offset value for the panel location relative to the anchor view -->
+ <attr name="yOffset" format="integer"/>
+ <!-- Gravity of the panel relative to the anchor view - uses same flag format as android:gravity -->
+ <attr name="gravity" format="integer">
+ <flag name="top" value="0x30" />
+ <flag name="bottom" value="0x50" />
+ <flag name="left" value="0x03" />
+ <flag name="right" value="0x05" />
+ <flag name="center_vertical" value="0x10" />
+ <flag name="fill_vertical" value="0x70" />
+ <flag name="center_horizontal" value="0x01" />
+ <flag name="fill_horizontal" value="0x07" />
+ <flag name="center" value="0x11" />
+ <flag name="fill" value="0x77" />
+ <flag name="clip_vertical" value="0x80" />
+ <flag name="clip_horizontal" value="0x08" />
+ <flag name="start" value="0x00800003" />
+ <flag name="end" value="0x00800005" />
+ </attr>
+ <!-- Whether the panel should be disabled while driving - defaults to false -->
+ <attr name="disabledWhileDriving" format="boolean"/>
+ <!-- Whether the panel should be disabled while unprovisioned - defaults to false -->
+ <attr name="disabledWhileUnprovisioned" format="boolean"/>
+ <!-- Whether the panel should be shown as a dropdown relative to the anchor view or not
+ (making it relative to the screen with offsets and gravity) - defaults to true -->
+ <attr name="showAsDropDown" format="boolean"/>
+ </declare-styleable>
+
+ <declare-styleable name="SystemUIQCView">
+ <attr name="remoteQCProvider" format="string"/>
+ <attr name="localQCProvider" format="string"/>
+ </declare-styleable>
</resources>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/values/config.xml b/car_product/rro/CarSystemUIPassengerRRO/res/values/config.xml
index fa825a4..8cf8ae7 100644
--- a/car_product/rro/CarSystemUIPassengerRRO/res/values/config.xml
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/values/config.xml
@@ -14,9 +14,6 @@
~ limitations under the License.
-->
<resources>
- <string-array name="config_quickControlsEntryPointIconControllers" translatable="false">
- <item>com.android.systemui.car.statusicon.ui.QuickControlsStatusIconListController</item>
- </string-array>
<string name="config_notificationPanelViewMediator" translatable="false">
com.android.systemui.car.notification.BottomNotificationPanelViewMediator</string>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/values/dimens.xml b/car_product/rro/CarSystemUIPassengerRRO/res/values/dimens.xml
index 1bda529..ed26b9f 100644
--- a/car_product/rro/CarSystemUIPassengerRRO/res/values/dimens.xml
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/values/dimens.xml
@@ -18,6 +18,7 @@
<dimen name="car_padding_1">8dp</dimen>
<dimen name="car_padding_2">16dp</dimen>
<dimen name="car_padding_3">24dp</dimen>
+ <dimen name="car_padding_4">32dp</dimen>
<dimen name="car_quick_controls_entry_points_icon_space">12dp</dimen>
@@ -29,9 +30,10 @@
<dimen name="hvac_container_padding">16dp</dimen>
<dimen name="hvac_temperature_bar_margin">32dp</dimen>
- <dimen name="hvac_temperature_text_size">56sp</dimen>
- <dimen name="hvac_temperature_text_padding">8dp</dimen>
- <dimen name="hvac_temperature_button_size">76dp</dimen>
+ <dimen name="hvac_temperature_text_size">44sp</dimen>
+ <dimen name="hvac_temperature_text_padding">10dp</dimen>
+ <dimen name="hvac_temperature_button_size">64dp</dimen>
+ <dimen name="hvac_temperature_icon_size">36dp</dimen>
<!--These values represent MIN and MAX for hvac-->
<item name="hvac_min_value_celsius" format="float" type="dimen">16</item>
<item name="hvac_max_value_celsius" format="float" type="dimen">28</item>
@@ -43,7 +45,13 @@
<dimen name="car_quick_controls_entry_points_button_margin_start">12dp</dimen>
<dimen name="car_quick_controls_entry_points_button_margin_end">12dp</dimen>
<dimen name="car_quick_controls_entry_points_clock_text_size">28sp</dimen>
+ <dimen name="car_quick_controls_panel_footer_button_view_height">60dp</dimen>
+ <dimen name="car_quick_controls_footer_button_min_height">56dp</dimen>
+ <dimen name="car_quick_controls_panel_footer_button_view_padding">10dp</dimen>
+ <dimen name="car_quick_controls_icon_drawable_width">24dp</dimen>
+ <dimen name="car_quick_controls_icon_drawable_height">24dp</dimen>
<dimen name="car_body1_size">32sp</dimen>
+ <dimen name="car_body3_size">26sp</dimen>
<dimen name="system_bar_user_icon_drawing_size">36dp</dimen>
<dimen name="user_icon_background_size">128dp</dimen>
<dimen name="system_bar_button_size">76dp</dimen>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/values/strings.xml b/car_product/rro/CarSystemUIPassengerRRO/res/values/strings.xml
index 4a61512..9d0f86c 100644
--- a/car_product/rro/CarSystemUIPassengerRRO/res/values/strings.xml
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/values/strings.xml
@@ -19,7 +19,6 @@
<!-- Content description strings for system bar icons. Strings should be added for all drawables
to enable Talkback for accessibility. -->
<string name="system_bar_passenger_home_label">Home Screen</string>
- <string name="qc_icon_tag" translatable="false">TAG_QC_ICON</string>
<string name="system_bar_control_center_label">Control Center</string>
<!-- System bar contentDescription for Notifications-->
<string name="system_bar_notifications_label">Notifications</string>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/values/styles.xml b/car_product/rro/CarSystemUIPassengerRRO/res/values/styles.xml
index eaaa998..e58bbae 100644
--- a/car_product/rro/CarSystemUIPassengerRRO/res/values/styles.xml
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/values/styles.xml
@@ -16,14 +16,16 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="SystemBarButton">
- <item name="android:layout_height">75dp</item>
- <item name="android:layout_width">75dp</item>
- <item name="android:layout_marginEnd">30dp</item>
- <item name="unselectedAlpha">0.46</item>
- <item name="android:background">?android:attr/selectableItemBackground</item>
+ <item name="android:layout_height">64dp</item>
+ <item name="android:layout_width">64dp</item>
+ <item name="android:layout_marginEnd">32dp</item>
+ <item name="android:padding">16dp</item>
+ <item name="unselectedAlpha">1.0</item>
+ <item name="android:gravity">center</item>
+ <item name="android:background">@drawable/nav_bar_button_background</item>
</style>
- <style name="QuickControlEntryPointButton">
+ <style name="TopBarButton">
<item name="android:layout_marginTop">@dimen/car_quick_controls_entry_points_button_margin_top</item>
<item name="android:layout_marginBottom">@dimen/car_quick_controls_entry_points_button_margin_bottom</item>
<item name="android:layout_marginStart">@dimen/car_quick_controls_entry_points_button_margin_start</item>
@@ -31,19 +33,26 @@
<item name="android:background">@drawable/status_icon_background</item>
</style>
+ <style name="QCFooterButtonViewStyle">
+ <item name="android:minHeight">@dimen/car_quick_controls_footer_button_min_height</item>
+ <item name="android:background">@drawable/car_quick_controls_pill_button_background</item>
+ <item name="android:layout_marginStart">@dimen/car_padding_2</item>
+ <item name="android:layout_marginEnd">@dimen/car_padding_2</item>
+ <item name="android:padding">
+ @dimen/car_quick_controls_panel_footer_button_view_padding
+ </item>
+ </style>
+
<style name="TextAppearance.SystemBar.ClockWithSelection"
parent="@*android:style/TextAppearance.StatusBar.Icon">
<item name="android:textSize">@dimen/car_quick_controls_entry_points_clock_text_size</item>
<item name="android:textColor">@color/system_bar_text_color_with_selection</item>
</style>
- <style name="ControlCenterSystemBarButton">
- <item name="android:layout_height">@dimen/system_bar_button_size</item>
- <item name="android:layout_width">@dimen/system_bar_button_size</item>
- <item name="android:padding">@dimen/system_bar_button_padding</item>
- <item name="android:layout_marginEnd">30dp</item>
- <item name="android:gravity">center</item>
- <item name="unselectedAlpha">0.46</item>
- <item name="android:background">?android:attr/selectableItemBackground</item>
+ <style name="TextAppearance.SystemBar.Username"
+ parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:textSize">@dimen/car_body3_size</item>
+ <item name="android:textColor">@color/system_bar_text_color_with_selection</item>
+ <item name="android:fontFamily">sans-serif-medium</item>
</style>
</resources>
diff --git a/car_product/rro/CarSystemUIPassengerRRO/res/xml/overlays.xml b/car_product/rro/CarSystemUIPassengerRRO/res/xml/overlays.xml
index 520569a..539b8c0 100644
--- a/car_product/rro/CarSystemUIPassengerRRO/res/xml/overlays.xml
+++ b/car_product/rro/CarSystemUIPassengerRRO/res/xml/overlays.xml
@@ -16,8 +16,7 @@
<overlay>
<item target="layout/car_bottom_system_bar" value="@layout/car_passenger_bottom_system_bar"/>
-
- <item target="array/config_quickControlsEntryPointIconControllers" value="@array/config_quickControlsEntryPointIconControllers"/>
+ <item target="layout/car_bottom_system_bar_dock" value="@layout/car_passenger_bottom_system_bar"/>
<item target="bool/config_enableTopSystemBar" value="false"/>
<item target="string/config_notificationPanelViewMediator" value="@string/config_notificationPanelViewMediator" />
@@ -46,28 +45,47 @@
<!-- start the intent as a broad cast instead of an activity if true-->
<item target="attr/broadcast" value="@attr/broadcast"/>
+ <item target="attr/datePattern" value="@attr/datePattern"/>
+ <item target="attr/controller" value="@attr/controller"/>
+ <item target="attr/systemBarDisableFlags" value="@attr/systemBarDisableFlags"/>
+ <item target="attr/systemBarDisable2Flags" value="@attr/systemBarDisable2Flags"/>
+ <item target="attr/disableForLockTaskModeLocked" value="@attr/disableForLockTaskModeLocked"/>
+ <item target="attr/panelLayoutRes" value="@attr/panelLayoutRes"/>
+ <item target="attr/panelWidthRes" value="@attr/panelWidthRes"/>
+ <item target="attr/xOffset" value="@attr/xOffset"/>
+ <item target="attr/yOffset" value="@attr/yOffset"/>
+ <item target="attr/gravity" value="@attr/gravity"/>
+ <item target="attr/disabledWhileDriving" value="@attr/disabledWhileDriving"/>
+ <item target="attr/disabledWhileUnprovisioned" value="@attr/disabledWhileUnprovisioned"/>
+ <item target="attr/showAsDropDown" value="@attr/showAsDropDown"/>
+ <item target="attr/remoteQCProvider" value="@attr/remoteQCProvider"/>
+ <item target="attr/localQCProvider" value="@attr/localQCProvider"/>
<item target="attr/layoutDescription" value="@attr/layoutDescription"/>
+ <item target="attr/layout_constraintHorizontal_chainStyle" value="@attr/layout_constraintHorizontal_chainStyle"/>
+ <item target="attr/layout_constraintHorizontal_bias" value="@attr/layout_constraintHorizontal_bias"/>
+ <item target="attr/layout_constraintHorizontal_weight" value="@attr/layout_constraintHorizontal_weight"/>
+ <item target="attr/layout_constraintDimensionRatio" value="@attr/layout_constraintDimensionRatio"/>
<item target="attr/layout_constraintGuide_end" value="@attr/layout_constraintGuide_end"/>
<item target="attr/layout_constraintBottom_toBottomOf" value="@attr/layout_constraintBottom_toBottomOf"/>
+ <item target="attr/layout_constraintBottom_toTopOf" value="@attr/layout_constraintBottom_toTopOf"/>
+ <item target="attr/layout_constraintEnd_toStartOf" value="@attr/layout_constraintEnd_toStartOf"/>
<item target="attr/layout_constraintEnd_toEndOf" value="@attr/layout_constraintEnd_toEndOf"/>
+ <item target="attr/layout_constraintStart_toStartOf" value="@attr/layout_constraintStart_toStartOf"/>
<item target="attr/layout_constraintStart_toEndOf" value="@attr/layout_constraintStart_toEndOf"/>
+ <item target="attr/layout_constraintTop_toTopOf" value="@attr/layout_constraintTop_toTopOf"/>
<item target="attr/layout_constraintTop_toBottomOf" value="@attr/layout_constraintTop_toBottomOf"/>
-
-
<item target="id/nav_buttons" value="@id/nav_buttons"/>
<item target="id/home" value="@id/home"/>
<item target="id/passenger_hvac" value="@id/passenger_hvac"/>
<item target="id/hvac_decrease_button" value="@id/hvac_decrease_button"/>
<item target="id/hvac_temperature_text" value="@id/hvac_temperature_text"/>
<item target="id/hvac_increase_button" value="@id/hvac_increase_button"/>
- <item target="id/qc_entry_points_container" value="@id/qc_entry_points_container"/>
<item target="id/control_center_nav" value="@id/control_center_nav"/>
<item target="string/system_bar_control_center_label" value="@string/system_bar_control_center_label"/>
<item target="dimen/system_bar_button_size" value="@dimen/system_bar_button_size"/>
<item target="dimen/system_bar_button_padding" value="@dimen/system_bar_button_padding"/>
- <item target="style/ControlCenterSystemBarButton" value="@style/ControlCenterSystemBarButton"/>
<!-- Passenger QuickControls related fields -->
@@ -76,7 +94,6 @@
<item target="color/system_bar_text_color_with_selection" value="@color/system_bar_text_color_with_selection"/>
<item target="color/system_bar_text_selected_color" value="@color/system_bar_text_selected_color"/>
<item target="color/system_bar_text_color" value="@color/system_bar_text_color"/>
- <item target="dimen/system_bar_user_icon_drawing_size" value="@dimen/system_bar_user_icon_drawing_size"/>
<item target="drawable/car_user_icon_circle_background" value="@drawable/car_user_icon_circle_background"/>
<item target="color/car_user_switcher_add_user_background_color" value="@color/car_user_switcher_add_user_background_color"/>
<item target="dimen/user_icon_background_size" value="@dimen/user_icon_background_size"/>
@@ -86,14 +103,13 @@
<item target="drawable/system_bar_pill_rotary_background" value="@drawable/system_bar_pill_rotary_background"/>
<item target="color/system_bar_background_pill_color" value="@color/system_bar_background_pill_color"/>
<item target="drawable/system_bar_background_pill" value="@drawable/system_bar_background_pill"/>
- <item target="attr/showAsDropDown" value="@attr/showAsDropDown"/>
- <item target="attr/panelGravity" value="@attr/panelGravity"/>
- <item target="layout/car_qc_entry_points_button" value="@layout/car_qc_entry_points_button"/>
+ <item target="drawable/car_quick_controls_pill_button_background" value="@drawable/car_quick_controls_pill_button_background"/>
<item target="id/user_icon" value="@id/user_icon"/>
<item target="id/user_name_text" value="@id/user_name_text"/>
<item target="id/clock" value="@id/clock"/>
<item target="attr/amPmStyle" value="@attr/amPmStyle"/>
- <item target="style/QuickControlEntryPointButton" value="@style/QuickControlEntryPointButton"/>
+ <item target="style/TopBarButton" value="@style/TopBarButton"/>
+ <item target="style/QCFooterButtonViewStyle" value="@style/QCFooterButtonViewStyle"/>
<item target="drawable/system_bar_background" value="@drawable/system_bar_background"/>
<item target="attr/showDark" value="@attr/showDark"/>
<item target="dimen/car_quick_controls_entry_points_icon_width" value="@dimen/car_quick_controls_entry_points_icon_width"/>
@@ -102,10 +118,17 @@
<item target="dimen/car_quick_controls_entry_points_button_margin_bottom" value="@dimen/car_quick_controls_entry_points_button_margin_bottom"/>
<item target="dimen/car_quick_controls_entry_points_button_margin_start" value="@dimen/car_quick_controls_entry_points_button_margin_start"/>
<item target="dimen/car_quick_controls_entry_points_button_margin_end" value="@dimen/car_quick_controls_entry_points_button_margin_end"/>
+ <item target="dimen/car_quick_controls_panel_footer_button_view_height" value="@dimen/car_quick_controls_panel_footer_button_view_height"/>
+ <item target="dimen/car_quick_controls_footer_button_min_height" value="@dimen/car_quick_controls_footer_button_min_height"/>
+ <item target="dimen/car_quick_controls_panel_footer_button_view_padding" value="@dimen/car_quick_controls_panel_footer_button_view_padding"/>
+ <item target="dimen/car_quick_controls_icon_drawable_width" value="@dimen/car_quick_controls_icon_drawable_width"/>
+ <item target="dimen/car_quick_controls_icon_drawable_height" value="@dimen/car_quick_controls_icon_drawable_height"/>
<item target="dimen/car_body1_size" value="@dimen/car_body1_size"/>
+ <item target="dimen/car_body3_size" value="@dimen/car_body3_size"/>
<item target="dimen/car_padding_1" value="@dimen/car_padding_1"/>
<item target="dimen/car_padding_2" value="@dimen/car_padding_2"/>
<item target="dimen/car_padding_3" value="@dimen/car_padding_3"/>
- <item target="string/qc_icon_tag" value="@string/qc_icon_tag"/>
+ <item target="dimen/car_padding_4" value="@dimen/car_padding_4"/>
<item target="style/TextAppearance.SystemBar.ClockWithSelection" value="@style/TextAppearance.SystemBar.ClockWithSelection"/>
+ <item target="style/TextAppearance.SystemBar.Username" value="@style/TextAppearance.SystemBar.Username"/>
</overlay>
diff --git a/car_product/rro/CarSystemUIRRO/res/layout-land/auth_credential_password_view.xml b/car_product/rro/CarSystemUIRRO/res/layout-land/auth_credential_password_view.xml
index 7337038..c7af2a4 100644
--- a/car_product/rro/CarSystemUIRRO/res/layout-land/auth_credential_password_view.xml
+++ b/car_product/rro/CarSystemUIRRO/res/layout-land/auth_credential_password_view.xml
@@ -65,6 +65,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
+ <LinearLayout
+ android:id="@+id/customized_view_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
</LinearLayout>
<FrameLayout
diff --git a/car_product/rro/CarSystemUIRRO/res/layout-land/auth_credential_pattern_view.xml b/car_product/rro/CarSystemUIRRO/res/layout-land/auth_credential_pattern_view.xml
index a5457de..500114d 100644
--- a/car_product/rro/CarSystemUIRRO/res/layout-land/auth_credential_pattern_view.xml
+++ b/car_product/rro/CarSystemUIRRO/res/layout-land/auth_credential_pattern_view.xml
@@ -119,6 +119,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
+ <LinearLayout
+ android:id="@+id/customized_view_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
</RelativeLayout>
<com.android.internal.widget.LockPatternView
diff --git a/car_product/rro/CarSystemUIRRO/res/layout-land/auth_credential_pin_view.xml b/car_product/rro/CarSystemUIRRO/res/layout-land/auth_credential_pin_view.xml
index 8cb6965..b835c7f 100644
--- a/car_product/rro/CarSystemUIRRO/res/layout-land/auth_credential_pin_view.xml
+++ b/car_product/rro/CarSystemUIRRO/res/layout-land/auth_credential_pin_view.xml
@@ -129,6 +129,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
+ <LinearLayout
+ android:id="@+id/customized_view_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
</RelativeLayout>
<com.android.systemui.car.biometrics.PinPadView
diff --git a/car_product/rro/CarSystemUIRRO/res/xml/overlays.xml b/car_product/rro/CarSystemUIRRO/res/xml/overlays.xml
index 845dcb0..4aa798d 100644
--- a/car_product/rro/CarSystemUIRRO/res/xml/overlays.xml
+++ b/car_product/rro/CarSystemUIRRO/res/xml/overlays.xml
@@ -27,6 +27,8 @@
<item target="id/description" value="@id/description" />
<item target="id/subtitle" value="@id/subtitle" />
<item target="id/title" value="@id/title" />
+ <item target="id/customized_view_container"
+ value="@id/customized_view_container" />
<item target="layout/auth_credential_password_view"
value="@layout/auth_credential_password_view" />
<item target="layout/auth_credential_pattern_view"
diff --git a/car_product/rro/DriveModeOnRRO/Android.bp b/car_product/rro/DriveModeOnRRO/Android.bp
index 28398b5..d3e4cf9 100644
--- a/car_product/rro/DriveModeOnRRO/Android.bp
+++ b/car_product/rro/DriveModeOnRRO/Android.bp
@@ -21,4 +21,5 @@
manifest: "AndroidManifest.xml",
sdk_version: "current",
product_specific: true,
+ static_libs: ["CarSystemUI-res"],
}
diff --git a/car_product/rro/DriveModeOnRRO/AndroidManifest.xml b/car_product/rro/DriveModeOnRRO/AndroidManifest.xml
index ca34c23..ccd1e17 100644
--- a/car_product/rro/DriveModeOnRRO/AndroidManifest.xml
+++ b/car_product/rro/DriveModeOnRRO/AndroidManifest.xml
@@ -18,7 +18,6 @@
package="com.android.systemui.drivemode.on.rro">
<application android:hasCode="false"/>
<overlay
- android:targetName="CarSystemUI"
android:targetPackage="com.android.systemui"
android:resourcesMap="@xml/overlays"
android:isStatic="false"/>
diff --git a/car_product/rro/DriveModeOnRRO/res/layout/qc_status_icons_horizontal.xml b/car_product/rro/DriveModeOnRRO/res/layout/qc_status_icons_horizontal.xml
new file mode 100644
index 0000000..ddc31e9
--- /dev/null
+++ b/car_product/rro/DriveModeOnRRO/res/layout/qc_status_icons_horizontal.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
+ android:id="@+id/bluetooth_panel_button"
+ android:layout_width="@dimen/car_quick_controls_entry_points_button_width"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center"
+ android:layout_alignParentStart="true"
+ style="@style/TopBarButton"
+ systemui:panelLayoutRes="@layout/qc_bluetooth_panel"
+ systemui:disabledWhileUnprovisioned="true"
+ systemui:systemBarDisable2Flags="quickSettings">
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/bluetooth_status_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.BluetoothStatusIconController"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
+ android:id="@+id/connectivity_panel_button"
+ android:layout_width="@dimen/car_quick_controls_entry_points_button_width"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center"
+ android:layout_alignParentStart="true"
+ style="@style/TopBarButton"
+ systemui:panelLayoutRes="@layout/qc_connectivity_panel"
+ systemui:disabledWhileUnprovisioned="true"
+ systemui:systemBarDisable2Flags="quickSettings">
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/connectivity_status_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.SignalStatusIconController"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
+ android:id="@+id/display_panel_button"
+ android:layout_width="@dimen/car_quick_controls_entry_points_button_width"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center"
+ android:layout_alignParentStart="true"
+ style="@style/TopBarButton"
+ systemui:panelLayoutRes="@layout/qc_display_panel"
+ systemui:disabledWhileUnprovisioned="true"
+ systemui:systemBarDisable2Flags="quickSettings">
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/display_status_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.DisplayStatusIconController"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
+ android:id="@+id/drive_mode_panel_button"
+ android:layout_width="@dimen/car_quick_controls_entry_points_button_width"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:layout_alignParentStart="true"
+ style="@style/TopBarButton"
+ systemui:panelLayoutRes="@layout/qc_drive_mode_panel"
+ systemui:disabledWhileUnprovisioned="true"
+ systemui:systemBarDisable2Flags="quickSettings">
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/drive_mode_status_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.DriveModeStatusIconController"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
+</LinearLayout>
diff --git a/car_product/rro/DriveModeOnRRO/res/layout/qc_status_icons_vertical.xml b/car_product/rro/DriveModeOnRRO/res/layout/qc_status_icons_vertical.xml
new file mode 100644
index 0000000..c991638
--- /dev/null
+++ b/car_product/rro/DriveModeOnRRO/res/layout/qc_status_icons_vertical.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
+ android:id="@+id/bluetooth_panel_button"
+ android:layout_height="@dimen/car_quick_controls_entry_points_button_width"
+ android:layout_width="match_parent"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:layout_alignParentStart="true"
+ style="@style/TopBarButton"
+ systemui:panelLayoutRes="@layout/qc_bluetooth_panel"
+ systemui:disabledWhileUnprovisioned="true"
+ systemui:systemBarDisable2Flags="quickSettings">
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/bluetooth_status_icon"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.BluetoothStatusIconController"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
+ android:id="@+id/connectivity_panel_button"
+ android:layout_height="@dimen/car_quick_controls_entry_points_button_width"
+ android:layout_width="match_parent"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:layout_alignParentStart="true"
+ style="@style/TopBarButton"
+ systemui:panelLayoutRes="@layout/qc_connectivity_panel"
+ systemui:disabledWhileUnprovisioned="true"
+ systemui:systemBarDisable2Flags="quickSettings">
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/connectivity_status_icon"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.SignalStatusIconController"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
+ android:id="@+id/display_panel_button"
+ android:layout_height="@dimen/car_quick_controls_entry_points_button_width"
+ android:layout_width="match_parent"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:layout_alignParentStart="true"
+ style="@style/TopBarButton"
+ systemui:panelLayoutRes="@layout/qc_display_panel"
+ systemui:disabledWhileUnprovisioned="true"
+ systemui:systemBarDisable2Flags="quickSettings">
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/display_status_icon"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.DisplayStatusIconController"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
+ <com.android.systemui.car.systembar.CarSystemBarPanelButtonView
+ android:id="@+id/drive_mode_panel_button"
+ android:layout_height="@dimen/car_quick_controls_entry_points_button_width"
+ android:layout_width="match_parent"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:layout_alignParentStart="true"
+ style="@style/TopBarButton"
+ systemui:panelLayoutRes="@layout/qc_drive_mode_panel"
+ systemui:disabledWhileUnprovisioned="true"
+ systemui:systemBarDisable2Flags="quickSettings">
+ <com.android.systemui.car.statusicon.StatusIconView
+ android:id="@+id/drive_mode_status_icon"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
+ android:tint="@color/car_status_icon_color"
+ android:duplicateParentState="true"
+ systemui:controller="com.android.systemui.car.statusicon.ui.DriveModeStatusIconController"/>
+ </com.android.systemui.car.systembar.CarSystemBarPanelButtonView>
+</LinearLayout>
diff --git a/car_product/rro/DriveModeOnRRO/res/values/config.xml b/car_product/rro/DriveModeOnRRO/res/values/config.xml
deleted file mode 100644
index 0dbe121..0000000
--- a/car_product/rro/DriveModeOnRRO/res/values/config.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ 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.
- -->
-<resources>
- <string-array name="config_quickControlsEntryPointIconControllers">
- <item>com.android.systemui.car.statusicon.ui.BluetoothStatusIconController</item>
- <item>com.android.systemui.car.statusicon.ui.SignalStatusIconController</item>
- <item>com.android.systemui.car.statusicon.ui.DisplayStatusIconController</item>
- <item>com.android.systemui.car.statusicon.ui.DriveModeStatusIconController</item>
- </string-array>
-</resources>
\ No newline at end of file
diff --git a/car_product/rro/DriveModeOnRRO/res/xml/overlays.xml b/car_product/rro/DriveModeOnRRO/res/xml/overlays.xml
index 2114b75..dbe6ffb 100644
--- a/car_product/rro/DriveModeOnRRO/res/xml/overlays.xml
+++ b/car_product/rro/DriveModeOnRRO/res/xml/overlays.xml
@@ -16,6 +16,52 @@
-->
<overlay>
- <item target="array/config_quickControlsEntryPointIconControllers"
- value="@array/config_quickControlsEntryPointIconControllers"/>
+ <item target="layout/qc_status_icons_horizontal"
+ value="@layout/qc_status_icons_horizontal"/>
+ <item target="layout/qc_status_icons_vertical"
+ value="@layout/qc_status_icons_vertical"/>
+
+ <item target="attr/controller" value="@attr/controller"/>
+ <item target="attr/systemBarDisableFlags" value="@attr/systemBarDisableFlags"/>
+ <item target="attr/systemBarDisable2Flags" value="@attr/systemBarDisable2Flags"/>
+ <item target="attr/disableForLockTaskModeLocked" value="@attr/disableForLockTaskModeLocked"/>
+ <item target="attr/panelLayoutRes" value="@attr/panelLayoutRes"/>
+ <item target="attr/panelWidthRes" value="@attr/panelWidthRes"/>
+ <item target="attr/xOffset" value="@attr/xOffset"/>
+ <item target="attr/yOffset" value="@attr/yOffset"/>
+ <item target="attr/gravity" value="@attr/gravity"/>
+ <item target="attr/disabledWhileDriving" value="@attr/disabledWhileDriving"/>
+ <item target="attr/disabledWhileUnprovisioned" value="@attr/disabledWhileUnprovisioned"/>
+ <item target="attr/showAsDropDown" value="@attr/showAsDropDown"/>
+ <item target="attr/remoteQCProvider" value="@attr/remoteQCProvider"/>
+ <item target="attr/localQCProvider" value="@attr/localQCProvider"/>
+
+ <item target="attr/layout_constraintHorizontal_chainStyle" value="@attr/layout_constraintHorizontal_chainStyle"/>
+ <item target="attr/layout_constraintHorizontal_bias" value="@attr/layout_constraintHorizontal_bias"/>
+ <item target="attr/layout_constraintHorizontal_weight" value="@attr/layout_constraintHorizontal_weight"/>
+ <item target="attr/layout_constraintDimensionRatio" value="@attr/layout_constraintDimensionRatio"/>
+ <item target="attr/layout_constraintGuide_end" value="@attr/layout_constraintGuide_end"/>
+ <item target="attr/layout_constraintBottom_toBottomOf" value="@attr/layout_constraintBottom_toBottomOf"/>
+ <item target="attr/layout_constraintBottom_toTopOf" value="@attr/layout_constraintBottom_toTopOf"/>
+ <item target="attr/layout_constraintEnd_toStartOf" value="@attr/layout_constraintEnd_toStartOf"/>
+ <item target="attr/layout_constraintEnd_toEndOf" value="@attr/layout_constraintEnd_toEndOf"/>
+ <item target="attr/layout_constraintStart_toStartOf" value="@attr/layout_constraintStart_toStartOf"/>
+ <item target="attr/layout_constraintStart_toEndOf" value="@attr/layout_constraintStart_toEndOf"/>
+ <item target="attr/layout_constraintTop_toTopOf" value="@attr/layout_constraintTop_toTopOf"/>
+ <item target="attr/layout_constraintTop_toBottomOf" value="@attr/layout_constraintTop_toBottomOf"/>
+ <item target="attr/layout_constraintHeight_default" value="@attr/layout_constraintHeight_default"/>
+
+ <item target="id/bluetooth_panel_button" value="@id/bluetooth_panel_button"/>
+ <item target="id/bluetooth_status_icon" value="@id/bluetooth_status_icon"/>
+ <item target="id/connectivity_panel_button" value="@id/connectivity_panel_button"/>
+ <item target="id/connectivity_status_icon" value="@id/connectivity_status_icon"/>
+ <item target="id/display_panel_button" value="@id/display_panel_button"/>
+ <item target="id/display_status_icon" value="@id/display_status_icon"/>
+ <item target="id/connectivity_panel_button" value="@id/connectivity_panel_button"/>
+ <item target="id/connectivity_status_icon" value="@id/connectivity_status_icon"/>
+ <item target="id/display_panel_button" value="@id/display_panel_button"/>
+ <item target="id/display_status_icon" value="@id/display_status_icon"/>
+ <item target="id/qc_bluetooth_footer_button" value="@id/qc_bluetooth_footer_button"/>
+ <item target="id/qc_connectivity_footer_button" value="@id/qc_connectivity_footer_button"/>
+ <item target="id/qc_display_footer_button" value="@id/qc_display_footer_button"/>
</overlay>
diff --git a/car_product/rro/OWNERS b/car_product/rro/OWNERS
index b17cdad..0ed4a8b 100644
--- a/car_product/rro/OWNERS
+++ b/car_product/rro/OWNERS
@@ -1,8 +1,7 @@
# Car UI Reference OWNERS
babakbo@google.com
stenning@google.com
-igorr@google.com
# Drive Mode RROs OWNERS
per-file DriveModeEcoRRO/*, DriveModeOnRRO/*, DriveModeSportRRO/*=arecchi@google.com
-per-file DriveModeEcoRRO/*, DriveModeOnRRO/*, DriveModeSportRRO/*=amishkovets@google.com
\ No newline at end of file
+per-file DriveModeEcoRRO/*, DriveModeOnRRO/*, DriveModeSportRRO/*=amishkovets@google.com
diff --git a/car_product/rro/overlay-config/androidRRO/Android.bp b/car_product/rro/overlay-config/androidRRO/Android.bp
index 5c431d5..abbbd56 100644
--- a/car_product/rro/overlay-config/androidRRO/Android.bp
+++ b/car_product/rro/overlay-config/androidRRO/Android.bp
@@ -21,5 +21,4 @@
resource_dirs: ["res"],
certificate: "platform",
manifest: "AndroidManifest.xml",
- system_ext_specific: true,
}
diff --git a/car_product/rro/overlay-config/androidRRO/res/values/config.xml b/car_product/rro/overlay-config/androidRRO/res/values/config.xml
index 73d28b0..40429ef 100644
--- a/car_product/rro/overlay-config/androidRRO/res/values/config.xml
+++ b/car_product/rro/overlay-config/androidRRO/res/values/config.xml
@@ -212,6 +212,16 @@
on selected text. -->
<bool name="config_textShareSupported">false</bool>
- <!-- Uses customized system bar controls -->
- <bool name="config_remoteInsetsControllerControlsSystemBars">true</bool>
+ <!-- Make sure that auto-suspend mode is not used. -->
+ <bool name="config_useAutoSuspend">false</bool>
+
+ <!-- Name of the starting activity for launch on private display. -->
+ <string name="config_defaultLaunchOnPrivateDisplayRouterActivity" translatable="false">
+ com.android.systemui/com.android.systemui.car.wm.activity.LaunchOnPrivateDisplayRouterActivity
+ </string>
+
+ <!-- Allowlisted packages for launch on a private display. -->
+ <string-array name="config_defaultAllowlistLaunchOnPrivateDisplayPackages">
+ <item>com.example.android.launchonprivatedisplay</item>
+ </string-array>
</resources>
diff --git a/car_product/rro/overlay-config/androidRRO/res/xml/config_user_types.xml b/car_product/rro/overlay-config/androidRRO/res/xml/config_user_types.xml
index 1ae8e14..cfce9f2 100644
--- a/car_product/rro/overlay-config/androidRRO/res/xml/config_user_types.xml
+++ b/car_product/rro/overlay-config/androidRRO/res/xml/config_user_types.xml
@@ -28,4 +28,7 @@
<profile-type name="android.os.usertype.profile.CLONE"
enabled='0' >
</profile-type>
+ <profile-type name="android.os.usertype.profile.PRIVATE"
+ enabled='0' >
+ </profile-type>
</user-types>
diff --git a/car_product/rro/overlay-visual/androidRRO/Android.bp b/car_product/rro/overlay-visual/androidRRO/Android.bp
index a0e6741..9bde718 100644
--- a/car_product/rro/overlay-visual/androidRRO/Android.bp
+++ b/car_product/rro/overlay-visual/androidRRO/Android.bp
@@ -21,8 +21,6 @@
resource_dirs: ["res"],
certificate: "platform",
manifest: "AndroidManifest.xml",
- system_ext_specific: true,
-
static_libs: [
"car-resource-common",
],
@@ -34,8 +32,6 @@
resource_dirs: ["res"],
certificate: "platform",
manifest: "AndroidManifest-dynamic.xml",
- system_ext_specific: true,
-
static_libs: [
"car-resource-theme-orange-common",
],
@@ -54,8 +50,6 @@
resource_dirs: ["res"],
certificate: "platform",
manifest: "AndroidManifest-dynamic.xml",
- system_ext_specific: true,
-
static_libs: [
"car-resource-theme-pink-common",
],
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml b/car_product/rro/overlay-visual/androidRRO/res/drawable/activated_state_radio_button.xml
similarity index 60%
copy from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
copy to car_product/rro/overlay-visual/androidRRO/res/drawable/activated_state_radio_button.xml
index a6ac85e..a3e469e 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
+++ b/car_product/rro/overlay-visual/androidRRO/res/drawable/activated_state_radio_button.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -13,11 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<ImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/statusbar_sensor_text_width"
- android:layout_height="@*android:dimen/status_bar_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:tag="@string/qc_icon_tag"/>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_activated="false" android:drawable="@drawable/car_radio_button_unchecked" />
+ <item android:state_activated="true" android:drawable="@drawable/car_radio_button_checked" />
+</selector>
\ No newline at end of file
diff --git a/car_product/rro/overlay-visual/androidRRO/res/layout-car/car_alert_dialog.xml b/car_product/rro/overlay-visual/androidRRO/res/layout-car/car_alert_dialog.xml
index ea29567..9825a02 100644
--- a/car_product/rro/overlay-visual/androidRRO/res/layout-car/car_alert_dialog.xml
+++ b/car_product/rro/overlay-visual/androidRRO/res/layout-car/car_alert_dialog.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-<LinearLayout
+<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@*android:id/parentPanel"
android:layout_width="match_parent"
@@ -24,60 +24,74 @@
android:background="@drawable/car_alert_dialog_bg"
android:padding="@dimen/car_alert_dialog_padding"
android:orientation="vertical">
- <include layout="@layout/car_alert_dialog_title"
- android:layout_weight="0"/>
- <FrameLayout
- android:id="@*android:id/contentPanel"
+ <ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:padding="@dimen/car_alert_dialog_padding"
- android:minHeight="@dimen/car_alert_dialog_content_min_height">
- <ScrollView
- android:id="@*android:id/scrollView"
+ android:layout_alignParentTop="true"
+ android:layout_above="@*android:id/buttonPanel"
+ android:fadeScrollbars="false"
+ android:clipToPadding="false">
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:clipToPadding="false">
- <LinearLayout
+ android:orientation="vertical">
+ <include layout="@layout/car_alert_dialog_title"
+ android:layout_weight="0"/>
+ <FrameLayout
+ android:id="@*android:id/contentPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
- <Space
- android:id="@*android:id/textSpacerNoTitle"
- android:visibility="gone"
- android:layout_width="match_parent"
- android:layout_height="0dp" />
- <TextView
- android:id="@android:id/message"
+ android:layout_weight="1"
+ android:padding="@dimen/car_alert_dialog_padding"
+ android:minHeight="@dimen/car_alert_dialog_content_min_height">
+ <ScrollView
+ android:id="@*android:id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/CarDialogMessageText"/>
- <!-- we don't need this spacer, but the id needs to be here for compatibility -->
- <Space
- android:id="@*android:id/textSpacerNoButtons"
- android:visibility="gone"
+ android:clipToPadding="false">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <Space
+ android:id="@*android:id/textSpacerNoTitle"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="0dp" />
+ <TextView
+ android:id="@android:id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/CarDialogMessageText"/>
+ <!-- we don't need this spacer, but the id needs to be here for compatibility -->
+ <Space
+ android:id="@*android:id/textSpacerNoButtons"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="0dp" />
+ </LinearLayout>
+ </ScrollView>
+ </FrameLayout>
+ <FrameLayout
+ android:id="@*android:id/customPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/car_alert_dialog_padding"
+ android:layout_weight="1"
+ android:minHeight="@dimen/car_alert_dialog_content_min_height">
+ <FrameLayout
+ android:id="@android:id/custom"
android:layout_width="match_parent"
- android:layout_height="0dp" />
- </LinearLayout>
- </ScrollView>
- </FrameLayout>
- <FrameLayout
- android:id="@*android:id/customPanel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_margin="@dimen/car_alert_dialog_padding"
- android:layout_weight="1"
- android:minHeight="@dimen/car_alert_dialog_content_min_height">
- <FrameLayout
- android:id="@android:id/custom"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
- </FrameLayout>
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+ </LinearLayout>
+ </ScrollView>
<include
+ android:id="@*android:id/buttonPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
android:layout_marginTop="@dimen/car_alert_dialog_padding"
android:layout_marginHorizontal="@dimen/car_alert_dialog_padding"
- android:layout_weight="0"
layout="@layout/car_alert_dialog_button_bar"/>
-</LinearLayout>
+</RelativeLayout>
diff --git a/car_product/rro/overlay-visual/androidRRO/res/layout/resolve_list_item.xml b/car_product/rro/overlay-visual/androidRRO/res/layout/resolve_list_item.xml
index 2fc9901..8978962 100644
--- a/car_product/rro/overlay-visual/androidRRO/res/layout/resolve_list_item.xml
+++ b/car_product/rro/overlay-visual/androidRRO/res/layout/resolve_list_item.xml
@@ -20,21 +20,26 @@
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:minHeight="?android:attr/listPreferredItemHeightLarge"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:background="@drawable/activated_item_background">
+ android:minHeight="@*android:dimen/car_activity_resolver_list_item_height"
+ android:background="?android:attr/colorBackgroundFloating" >
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
+ android:layout_gravity="center"
+ android:clickable="false"
+ android:focusable="false"
+ android:duplicateParentState="true"
+ android:src="@drawable/activated_state_radio_button"/>
+
<ImageView android:id="@*android:id/icon"
- android:layout_width="75dp"
- android:layout_height="75dp"
+ android:layout_width="@*android:dimen/car_icon_size"
+ android:layout_height="@*android:dimen/car_icon_size"
android:layout_gravity="start|center_vertical"
- android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="12dp"
+ android:layout_marginStart="@*android:dimen/car_padding_4"
android:scaleType="fitXY" />
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:gravity="start|center_vertical"
+ <LinearLayout android:gravity="start|center_vertical"
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
@@ -46,19 +51,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:minHeight="50sp"
+ style="?android:attr/textAppearanceListItem"
android:minLines="1"
android:maxLines="1"
- android:textColor="@android:color/white"
android:ellipsize="marquee" />
<!-- Extended activity info to distinguish between duplicate activity names -->
<TextView android:id="@*android:id/text2"
- android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:textColor="@android:color/white"
+ style="?android:attr/textAppearanceListItemSecondary"
android:minLines="1"
android:maxLines="1"
android:ellipsize="marquee" />
diff --git a/car_product/rro/overlay-visual/androidRRO/res/layout/resolver_different_item_header.xml b/car_product/rro/overlay-visual/androidRRO/res/layout/resolver_different_item_header.xml
index f4bedb2..2019423 100644
--- a/car_product/rro/overlay-visual/androidRRO/res/layout/resolver_different_item_header.xml
+++ b/car_product/rro/overlay-visual/androidRRO/res/layout/resolver_different_item_header.xml
@@ -21,13 +21,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@*android:string/use_a_different_app"
- android:minHeight="56dp"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textSize="@*android:dimen/car_body2_size"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.DialogWindowTitle"
android:gravity="start|center_vertical"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:elevation="8dp"
-/>
+ android:paddingVertical="@*android:dimen/car_padding_4"
+ android:paddingHorizontal="@*android:dimen/car_padding_4" />
diff --git a/car_product/rro/overlay-visual/androidRRO/res/values-h480dp/dimens.xml b/car_product/rro/overlay-visual/androidRRO/res/values-h480dp/dimens.xml
new file mode 100644
index 0000000..7a5151f
--- /dev/null
+++ b/car_product/rro/overlay-visual/androidRRO/res/values-h480dp/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2024, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+ <!-- Padding added to the bottom of button bar of activity resolver -->
+ <dimen name="resolver_button_bar_spacing">0dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/car_product/rro/overlay-visual/androidRRO/res/values/dimens.xml b/car_product/rro/overlay-visual/androidRRO/res/values/dimens.xml
index 7d40f16..9b8e864 100644
--- a/car_product/rro/overlay-visual/androidRRO/res/values/dimens.xml
+++ b/car_product/rro/overlay-visual/androidRRO/res/values/dimens.xml
@@ -33,6 +33,9 @@
<!-- System status icon size in the status bar. -->
<dimen name="status_bar_system_icon_size">32dp</dimen>
+ <dimen name="system_gestures_start_threshold">52dp</dimen>
+ <dimen name="system_gestures_distance_threshold">24dp</dimen>
+
<!-- The size of the right icon. This size is used to down-sample the bitmaps for "large icon". -->
<dimen name="notification_right_icon_size">@*android:dimen/car_touch_target_size</dimen>
@@ -210,4 +213,6 @@
<!-- Sets the Theme android:disabledAlpha value -->
<dimen name="car_disabled_alpha">0.35</dimen>
+
+ <dimen name="resolver_max_collapsed_height">400dp</dimen>
</resources>
diff --git a/car_product/rro/overlay-visual/androidRRO/res/values/styles_device_default.xml b/car_product/rro/overlay-visual/androidRRO/res/values/styles_device_default.xml
index e851c57..beff217 100644
--- a/car_product/rro/overlay-visual/androidRRO/res/values/styles_device_default.xml
+++ b/car_product/rro/overlay-visual/androidRRO/res/values/styles_device_default.xml
@@ -82,10 +82,9 @@
<style name="DialogWindowTitle.DeviceDefault" parent="*android:DialogWindowTitle.Material">
<item name="android:textAppearance">@*android:style/TextAppearance.DeviceDefault.DialogWindowTitle</item>
</style>
- <style name="TextAppearance.DeviceDefault.DialogWindowTitle" parent="android:TextAppearance.Material.DialogWindowTitle">
+ <style name="TextAppearance.DeviceDefault.DialogWindowTitle" parent="android:TextAppearance.DeviceDefault.Large">
<item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:textStyle">normal</item>
- <item name="android:textSize">@*android:dimen/car_body2_size</item>
<item name="android:textColor">@*android:color/car_body2</item>
</style>
<style name="TextAppearance.Material.DialogWindowTitle" parent="android:TextAppearance.Material.Title" />
diff --git a/car_product/rro/overlay-visual/androidRRO/res/values/themes_device_defaults.xml b/car_product/rro/overlay-visual/androidRRO/res/values/themes_device_defaults.xml
index 072a2ec..3d8b913 100644
--- a/car_product/rro/overlay-visual/androidRRO/res/values/themes_device_defaults.xml
+++ b/car_product/rro/overlay-visual/androidRRO/res/values/themes_device_defaults.xml
@@ -19,7 +19,7 @@
It is how the device default is changed to match the desired look for a car theme.
-->
<resources>
- <style name="Theme.DeviceDefault" parent="*android:Theme.DeviceDefaultBase">
+ <style name="Theme.DeviceDefaultBaseCar" parent="*android:Theme.DeviceDefaultBase">
<item name="android:borderlessButtonStyle">@*android:style/Widget.DeviceDefault.Button.Borderless.Colored</item>
<item name="android:buttonBarButtonStyle">@*android:style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
<item name="android:buttonStyle">@*android:style/Widget.DeviceDefault.Button</item>
@@ -48,13 +48,14 @@
<item name="android:textAppearanceListItemSecondary">@*android:style/TextAppearance.DeviceDefault.Small</item>
<item name="android:textAppearanceButton">@*android:style/TextAppearance.DeviceDefault.Widget.Button</item>
<item name="android:actionBarSize">@*android:dimen/car_app_bar_height</item>
- <item name="android:radioButtonStyle">@style/Widget.DeviceDefault.CompoundButton.RadioButton</item>
-
<item name="*android:disabledAlpha">@dimen/car_disabled_alpha</item>
- <item name="android:windowLayoutInDisplayCutoutMode">always</item>
<item name="android:isLightTheme">false</item>
</style>
+ <style name="Theme.DeviceDefault" parent="Theme.DeviceDefaultBaseCar">
+ <item name="android:windowLayoutInDisplayCutoutMode">always</item>
+ </style>
+
<style name="Theme.DeviceDefault.Dialog" parent="android:Theme.Material.Dialog">
<item name="android:textAppearanceLarge">@*android:style/TextAppearance.DeviceDefault.Large</item>
<item name="android:textAppearanceMedium">@*android:style/TextAppearance.DeviceDefault.Medium</item>
@@ -172,8 +173,7 @@
<!-- This theme is applied as the app base. Attributes not defined in app/ activity themes will fallthrough to this
theme. This makes it app facing, regardless if the app is directly using it or a theme that inherits from it. -->
- <style name="Theme.DeviceDefault.Light.DarkActionBar" parent="android:Theme.DeviceDefault">
- <item name="android:windowLayoutInDisplayCutoutMode">default</item>
+ <style name="Theme.DeviceDefault.Light.DarkActionBar" parent="Theme.DeviceDefaultBaseCar">
<item name="android:isLightTheme">false</item>
</style>
diff --git a/car_product/sepolicy/private/carservice_app.te b/car_product/sepolicy/private/carservice_app.te
index e8389d3..f60a8a1 100644
--- a/car_product/sepolicy/private/carservice_app.te
+++ b/car_product/sepolicy/private/carservice_app.te
@@ -48,6 +48,8 @@
overlay_service
power_service
procfsinspector_service
+ radio_service
+ registry_service
sensorservice_service
statsmanager_service
surfaceflinger_service
@@ -138,3 +140,6 @@
# Allow finding AIDL EVS service
allow carservice_app evsmanagerd_service:service_manager find;
+
+# Allow reading car boot information
+get_prop(carservice_app, car_boot_prop);
diff --git a/car_product/sepolicy/private/property.te b/car_product/sepolicy/private/property.te
new file mode 100644
index 0000000..2fa0aee
--- /dev/null
+++ b/car_product/sepolicy/private/property.te
@@ -0,0 +1,2 @@
+# Only kernel, init, and vendor init can write to car_boot_prop properties
+neverallow { domain -kernel -init -vendor_init } car_boot_prop:property_service set;
diff --git a/car_product/sepolicy/private/property_contexts b/car_product/sepolicy/private/property_contexts
index dc6b0c5..1853fc8 100644
--- a/car_product/sepolicy/private/property_contexts
+++ b/car_product/sepolicy/private/property_contexts
@@ -1 +1,3 @@
boot.car_service_created u:object_r:system_prop:s0
+boot.car_kernel_boot_time u:object_r:car_boot_prop:s0 exact string
+boot.car_device_start_time u:object_r:car_boot_prop:s0 exact string
diff --git a/car_product/sepolicy/private/seapp_contexts b/car_product/sepolicy/private/seapp_contexts
index 97217bf..a5bb601 100644
--- a/car_product/sepolicy/private/seapp_contexts
+++ b/car_product/sepolicy/private/seapp_contexts
@@ -1 +1,2 @@
user=system seinfo=platform name=com.android.car domain=carservice_app type=system_app_data_file
+user=system seinfo=platform name=com.android.surroundview domain=surroundview_app type=system_app_data_file
diff --git a/car_product/sepolicy/private/surround_view.te b/car_product/sepolicy/private/surround_view.te
new file mode 100644
index 0000000..99b12ef
--- /dev/null
+++ b/car_product/sepolicy/private/surround_view.te
@@ -0,0 +1,30 @@
+# Domain to run Surround View App (com.android.surroundview)
+# Defined outside `userdebug_or_eng` to ensure domain exists for seapp_contexts reference.
+type surroundview_app, domain;
+app_domain(surroundview_app)
+
+userdebug_or_eng(`
+ # Allow use of EVS service
+ hal_client_domain(surroundview_app, hal_evs)
+ hal_client_domain(surroundview_app, hal_vehicle)
+
+ # Allow "find" permission on certain system services, surfaced as required by SELinux logs.
+ # As far as understood, the reference app does not use some of these (e.g. autofill_service),
+ # but the app will not run without them.
+ allow surroundview_app {
+ activity_service
+ audio_service
+ autofill_service
+ content_capture_service
+ game_service
+ gpu_service
+ hint_service
+ surfaceflinger_service
+ }:service_manager find;
+
+ # Allow R+W /data subdirectory.
+ allow surroundview_app system_app_data_file:dir { getattr search };
+ allow surroundview_app system_app_data_file:file { open };
+ allow surroundview_app system_data_file:dir search;
+ allow surroundview_app user_profile_root_file:dir search;
+')
diff --git a/car_product/sepolicy/public/property.te b/car_product/sepolicy/public/property.te
index 49860f4..2b68684 100644
--- a/car_product/sepolicy/public/property.te
+++ b/car_product/sepolicy/public/property.te
@@ -1 +1,2 @@
+system_public_prop(car_boot_prop)
product_internal_prop(car_bootuser_prop)
diff --git a/cpp/car_binder_lib/largeParcelable/Android.bp b/cpp/car_binder_lib/largeParcelable/Android.bp
index 8197198..957c941 100644
--- a/cpp/car_binder_lib/largeParcelable/Android.bp
+++ b/cpp/car_binder_lib/largeParcelable/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -47,4 +48,5 @@
export_include_dirs: [
"include",
],
+ host_supported: true,
}
diff --git a/cpp/car_binder_lib/largeParcelable/tests/Android.bp b/cpp/car_binder_lib/largeParcelable/tests/Android.bp
index 729df00..223d145 100644
--- a/cpp/car_binder_lib/largeParcelable/tests/Android.bp
+++ b/cpp/car_binder_lib/largeParcelable/tests/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/computepipe/example/Android.bp b/cpp/computepipe/example/Android.bp
index 3eb27ad..decceda 100644
--- a/cpp/computepipe/example/Android.bp
+++ b/cpp/computepipe/example/Android.bp
@@ -60,6 +60,7 @@
strip: {
keep_symbols: true,
},
+ ignore_max_page_size: true,
target: {
android_arm64: {
srcs: ["lib_arm64/libfacegraph.so"],
diff --git a/cpp/computepipe/tests/Android.bp b/cpp/computepipe/tests/Android.bp
index 31451b8..364b068 100644
--- a/cpp/computepipe/tests/Android.bp
+++ b/cpp/computepipe/tests/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_automotive",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/computepipe/tests/fuzz/Android.bp b/cpp/computepipe/tests/fuzz/Android.bp
index ac23408..72188cf 100644
--- a/cpp/computepipe/tests/fuzz/Android.bp
+++ b/cpp/computepipe/tests/fuzz/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_automotive",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/computepipe/tests/runner/client_interface/Android.bp b/cpp/computepipe/tests/runner/client_interface/Android.bp
index 7a5a8db..08786e0 100644
--- a/cpp/computepipe/tests/runner/client_interface/Android.bp
+++ b/cpp/computepipe/tests/runner/client_interface/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_automotive",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/computepipe/tests/runner/graph/Android.bp b/cpp/computepipe/tests/runner/graph/Android.bp
index 4dc94bb..f0fd4fc 100644
--- a/cpp/computepipe/tests/runner/graph/Android.bp
+++ b/cpp/computepipe/tests/runner/graph/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_automotive",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/computepipe/tests/runner/graph/stubgraph/Android.bp b/cpp/computepipe/tests/runner/graph/stubgraph/Android.bp
index 46564a9..d88cddd 100644
--- a/cpp/computepipe/tests/runner/graph/stubgraph/Android.bp
+++ b/cpp/computepipe/tests/runner/graph/stubgraph/Android.bp
@@ -32,6 +32,7 @@
srcs: ["x86_64/libstubgraphimpl.so"],
},
},
+ ignore_max_page_size: true,
shared_libs: [
"libc",
diff --git a/cpp/computepipe/tests/runner/stream_manager/Android.bp b/cpp/computepipe/tests/runner/stream_manager/Android.bp
index 5f0c484..4778153 100644
--- a/cpp/computepipe/tests/runner/stream_manager/Android.bp
+++ b/cpp/computepipe/tests/runner/stream_manager/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_automotive",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/evs/apps/default/inc/RenderBase.h b/cpp/evs/apps/default/inc/RenderBase.h
index 8f594a1..b8b24fd 100644
--- a/cpp/evs/apps/default/inc/RenderBase.h
+++ b/cpp/evs/apps/default/inc/RenderBase.h
@@ -31,7 +31,7 @@
*/
class RenderBase {
public:
- virtual ~RenderBase(){};
+ virtual ~RenderBase() {};
virtual bool activate() = 0;
virtual void deactivate() = 0;
diff --git a/cpp/evs/apps/default/inc/RenderTopView.h b/cpp/evs/apps/default/inc/RenderTopView.h
index d9cccc6..26dbaac 100644
--- a/cpp/evs/apps/default/inc/RenderTopView.h
+++ b/cpp/evs/apps/default/inc/RenderTopView.h
@@ -44,7 +44,7 @@
const ConfigManager::CameraInfo& info;
std::unique_ptr<VideoTex> tex;
- ActiveCamera(const ConfigManager::CameraInfo& c) : info(c){};
+ ActiveCamera(const ConfigManager::CameraInfo& c) : info(c) {};
};
void renderCarTopView();
diff --git a/cpp/evs/apps/default/src/FormatConvert.cpp b/cpp/evs/apps/default/src/FormatConvert.cpp
index 768c489..76275cd 100644
--- a/cpp/evs/apps/default/src/FormatConvert.cpp
+++ b/cpp/evs/apps/default/src/FormatConvert.cpp
@@ -123,7 +123,7 @@
// Note: we're walking two pixels at a time here (even/odd)
uint32_t srcPixel = *srcWords++;
- uint8_t Y1 = (srcPixel)&0xFF;
+ uint8_t Y1 = (srcPixel) & 0xFF;
uint8_t U = (srcPixel >> 8) & 0xFF;
uint8_t Y2 = (srcPixel >> 16) & 0xFF;
uint8_t V = (srcPixel >> 24) & 0xFF;
diff --git a/cpp/evs/apps/default/src/VideoTex.cpp b/cpp/evs/apps/default/src/VideoTex.cpp
index c048210..10eff54 100644
--- a/cpp/evs/apps/default/src/VideoTex.cpp
+++ b/cpp/evs/apps/default/src/VideoTex.cpp
@@ -75,7 +75,7 @@
}
// If we already have an image backing us, then it's time to return it
- if (getNativeHandle(mImageBuffer) != nullptr) {
+ if (auto h = getNativeHandle(mImageBuffer); h != nullptr) {
// Drop our device texture image
if (mKHRimage != EGL_NO_IMAGE_KHR) {
eglDestroyImageKHR(mDisplay, mKHRimage);
@@ -84,6 +84,7 @@
// Return it since we're done with it
mStreamHandler->doneWithFrame(mImageBuffer);
+ free(h);
}
// Get the new image we want to use as our contents
diff --git a/cpp/evs/apps/default/src/evs_app.cpp b/cpp/evs/apps/default/src/evs_app.cpp
index 3243b38..c3758ab 100644
--- a/cpp/evs/apps/default/src/evs_app.cpp
+++ b/cpp/evs/apps/default/src/evs_app.cpp
@@ -122,8 +122,7 @@
useExternalMemory = true;
if (i + 1 >= argc) {
// use RGBA8888 by default
- LOG(INFO) << "External buffer format is not set. "
- << "RGBA8888 will be used.";
+ LOG(INFO) << "External buffer format is not set. " << "RGBA8888 will be used.";
} else {
if (!convertStringToFormat(argv[i + 1], &extMemoryFormat)) {
LOG(WARNING) << "Color format string " << argv[i + 1]
@@ -136,8 +135,7 @@
} else if (strcmp(argv[i], "--gear") == 0) {
// Gear signal to simulate
if (i + 1 >= argc) {
- LOG(INFO) << "Gear signal is not set. "
- << "Reverse signal will be used.";
+ LOG(INFO) << "Gear signal is not set. " << "Reverse signal will be used.";
continue;
}
i += 1; // increase an index to next argument
diff --git a/cpp/evs/apps/demo_app_evs_support_lib/evs_app_support_lib.cpp b/cpp/evs/apps/demo_app_evs_support_lib/evs_app_support_lib.cpp
index 39ffa33..19d1e97 100644
--- a/cpp/evs/apps/demo_app_evs_support_lib/evs_app_support_lib.cpp
+++ b/cpp/evs/apps/demo_app_evs_support_lib/evs_app_support_lib.cpp
@@ -25,8 +25,8 @@
#include <string>
using ::android::automotive::evs::support::AnalyzeUseCase;
-using ::android::automotive::evs::support::BaseRenderCallback;
using ::android::automotive::evs::support::BaseAnalyzeCallback;
+using ::android::automotive::evs::support::BaseRenderCallback;
using ::android::automotive::evs::support::DisplayUseCase;
using ::android::automotive::evs::support::Frame;
using ::android::automotive::evs::support::Utils;
diff --git a/cpp/evs/manager/1.0/HalCamera.h b/cpp/evs/manager/1.0/HalCamera.h
index 529ed5e..7f9812e 100644
--- a/cpp/evs/manager/1.0/HalCamera.h
+++ b/cpp/evs/manager/1.0/HalCamera.h
@@ -42,7 +42,7 @@
// stream from the hardware camera and distribute it to the associated VirtualCamera objects.
class HalCamera : public IEvsCameraStream {
public:
- HalCamera(sp<IEvsCamera> hwCamera) : mHwCamera(hwCamera){};
+ HalCamera(sp<IEvsCamera> hwCamera) : mHwCamera(hwCamera) {};
// Factory methods for client VirtualCameras
sp<VirtualCamera> makeVirtualCamera();
@@ -73,7 +73,7 @@
struct FrameRecord {
uint32_t frameId;
uint32_t refCount;
- FrameRecord(uint32_t id) : frameId(id), refCount(0){};
+ FrameRecord(uint32_t id) : frameId(id), refCount(0) {};
};
std::vector<FrameRecord> mFrames;
};
diff --git a/cpp/evs/manager/1.1/Android.bp b/cpp/evs/manager/1.1/Android.bp
index dc2e1f2..43fda86 100644
--- a/cpp/evs/manager/1.1/Android.bp
+++ b/cpp/evs/manager/1.1/Android.bp
@@ -36,8 +36,6 @@
"libutils",
],
- static_libs: ["libc++fs"],
-
cflags: [
"-DLOG_TAG=\"EvsManagerV1_1\"",
"-DGL_GLEXT_PROTOTYPES",
diff --git a/cpp/evs/manager/1.1/Enumerator.cpp b/cpp/evs/manager/1.1/Enumerator.cpp
index cbcc9cd..b237ff5 100644
--- a/cpp/evs/manager/1.1/Enumerator.cpp
+++ b/cpp/evs/manager/1.1/Enumerator.cpp
@@ -98,9 +98,8 @@
const auto userId = ipc->getCallingUid() / AID_USER_OFFSET;
const auto appId = ipc->getCallingUid() % AID_USER_OFFSET;
if (AID_AUTOMOTIVE_EVS != appId && AID_ROOT != appId && AID_SYSTEM != appId) {
- LOG(ERROR) << "EVS access denied? "
- << "pid = " << ipc->getCallingPid() << ", userId = " << userId
- << ", appId = " << appId;
+ LOG(ERROR) << "EVS access denied? " << "pid = " << ipc->getCallingPid()
+ << ", userId = " << userId << ", appId = " << appId;
return false;
}
@@ -258,7 +257,7 @@
if (it != mCameraDevices.end()) {
it->second.v1 = desc;
} else {
- CameraDesc desc_1_1 = { .v1 = desc };
+ CameraDesc desc_1_1 = {.v1 = desc};
mCameraDevices.emplace(desc.cameraId, desc_1_1);
}
}
diff --git a/cpp/evs/manager/1.1/HalCamera.cpp b/cpp/evs/manager/1.1/HalCamera.cpp
index 2e92d74..a762b0c 100644
--- a/cpp/evs/manager/1.1/HalCamera.cpp
+++ b/cpp/evs/manager/1.1/HalCamera.cpp
@@ -255,7 +255,6 @@
}
return EvsResult::UNDERLYING_SERVICE_ERROR;
}
-
}
void HalCamera::cancelCaptureRequestFromClientLocked(std::deque<struct FrameRequest>* requests,
diff --git a/cpp/evs/manager/1.1/VirtualCamera.cpp b/cpp/evs/manager/1.1/VirtualCamera.cpp
index 87d129a..b5b9afa 100644
--- a/cpp/evs/manager/1.1/VirtualCamera.cpp
+++ b/cpp/evs/manager/1.1/VirtualCamera.cpp
@@ -340,35 +340,45 @@
mStreamState = RUNNING;
// Tell the underlying camera hardware that we want to stream
+ bool cleanUpAndReturn = true;
auto iter = mHalCamera.begin();
while (iter != mHalCamera.end()) {
auto pHwCamera = iter->second.promote();
if (pHwCamera == nullptr) {
- LOG(ERROR) << "Failed to start a video stream on " << iter->first;
+ LOG(WARNING) << "Failed to start a video stream on " << iter->first;
+ ++iter;
continue;
}
LOG(INFO) << __FUNCTION__ << " starts a video stream on " << iter->first;
Return<EvsResult> result = pHwCamera->clientStreamStarting();
if ((!result.isOk()) || (result != EvsResult::OK)) {
- // If we failed to start the underlying stream, then we're not actually running
- mStream = mStream_1_1 = nullptr;
- mStreamState = STOPPED;
-
- // Request to stop streams started by this client.
- auto rb = mHalCamera.begin();
- while (rb != iter) {
- auto ptr = rb->second.promote();
- if (ptr != nullptr) {
- ptr->clientStreamEnding(this);
- }
- ++rb;
- }
- return EvsResult::UNDERLYING_SERVICE_ERROR;
+ LOG(ERROR) << "Failed to start a video stream on " << iter->first;
+ cleanUpAndReturn = true;
+ break;
}
+
+ cleanUpAndReturn = false;
++iter;
}
+ if (cleanUpAndReturn) {
+ // If we failed to start the underlying stream, then we're not actually running
+ mStream = mStream_1_1 = nullptr;
+ mStreamState = STOPPED;
+
+ // Request to stop streams started by this client.
+ auto rb = mHalCamera.begin();
+ while (rb != iter) {
+ auto ptr = rb->second.promote();
+ if (ptr != nullptr) {
+ ptr->clientStreamEnding(this);
+ }
+ ++rb;
+ }
+ return EvsResult::UNDERLYING_SERVICE_ERROR;
+ }
+
// Start a thread that waits on the fence and forwards collected frames
// to the v1.1 client.
auto pHwCamera = mHalCamera.begin()->second.promote();
diff --git a/cpp/evs/manager/1.1/emul/EvsEmulatedCamera.cpp b/cpp/evs/manager/1.1/emul/EvsEmulatedCamera.cpp
index 793adb7..fd23983 100644
--- a/cpp/evs/manager/1.1/emul/EvsEmulatedCamera.cpp
+++ b/cpp/evs/manager/1.1/emul/EvsEmulatedCamera.cpp
@@ -71,7 +71,7 @@
// Note: we're walking two pixels at a time here (even/odd)
uint32_t srcPixel = *src++;
- uint8_t Y1 = (srcPixel)&0xFF;
+ uint8_t Y1 = (srcPixel) & 0xFF;
uint8_t U = (srcPixel >> 8) & 0xFF;
uint8_t Y2 = (srcPixel >> 16) & 0xFF;
uint8_t V = (srcPixel >> 24) & 0xFF;
@@ -410,8 +410,7 @@
// If we've been displaced by another owner of the camera, then we can't do anything else
if (!mVideo->isOpen()) {
- LOG(WARNING) << "Ignoring a request add external buffers "
- << "when camera has been lost.";
+ LOG(WARNING) << "Ignoring a request add external buffers " << "when camera has been lost.";
_hidl_cb(EvsResult::UNDERLYING_SERVICE_ERROR, mFramesAllowed);
return {};
}
diff --git a/cpp/evs/manager/1.1/emul/EvsEmulatedCamera.h b/cpp/evs/manager/1.1/emul/EvsEmulatedCamera.h
index 91b4c52..eb0925d 100644
--- a/cpp/evs/manager/1.1/emul/EvsEmulatedCamera.h
+++ b/cpp/evs/manager/1.1/emul/EvsEmulatedCamera.h
@@ -128,7 +128,7 @@
buffer_handle_t handle;
bool inUse;
- explicit BufferRecord(buffer_handle_t h) : handle(h), inUse(false){};
+ explicit BufferRecord(buffer_handle_t h) : handle(h), inUse(false) {};
};
std::vector<BufferRecord> mBuffers; // Graphics buffers to transfer images
diff --git a/cpp/evs/manager/1.1/emul/VideoCapture.h b/cpp/evs/manager/1.1/emul/VideoCapture.h
index c54db10..36243d4 100644
--- a/cpp/evs/manager/1.1/emul/VideoCapture.h
+++ b/cpp/evs/manager/1.1/emul/VideoCapture.h
@@ -65,7 +65,7 @@
class VideoCapture : public MessageHandler {
public:
- explicit VideoCapture(){};
+ explicit VideoCapture() {};
virtual ~VideoCapture();
bool open(const std::string& path, const std::chrono::nanoseconds interval);
void close();
diff --git a/cpp/evs/manager/1.1/stats/StatsCollector.cpp b/cpp/evs/manager/1.1/stats/StatsCollector.cpp
index 6052ed3..b379a33 100644
--- a/cpp/evs/manager/1.1/stats/StatsCollector.cpp
+++ b/cpp/evs/manager/1.1/stats/StatsCollector.cpp
@@ -264,8 +264,7 @@
AutoMutex lock(mMutex);
if (mCurrentCollectionEvent != CollectionEvent::PERIODIC) {
return Error(INVALID_OPERATION)
- << "Cannot start a custom collection when "
- << "the current collection event "
+ << "Cannot start a custom collection when " << "the current collection event "
<< collectionEventToString(mCurrentCollectionEvent)
<< " != " << collectionEventToString(CollectionEvent::PERIODIC)
<< " collection event";
diff --git a/cpp/evs/manager/1.1/test/fuzzer/Android.bp b/cpp/evs/manager/1.1/test/fuzzer/Android.bp
index d03e27b..66b389c 100644
--- a/cpp/evs/manager/1.1/test/fuzzer/Android.bp
+++ b/cpp/evs/manager/1.1/test/fuzzer/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_automotive",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/evs/manager/Android.bp b/cpp/evs/manager/Android.bp
new file mode 100644
index 0000000..ff8d4fa
--- /dev/null
+++ b/cpp/evs/manager/Android.bp
@@ -0,0 +1,3 @@
+package {
+ default_team: "trendy_team_perception_virtualization",
+}
diff --git a/cpp/evs/manager/aidl/Android.bp b/cpp/evs/manager/aidl/Android.bp
index 0e7b1e6..6def779 100644
--- a/cpp/evs/manager/aidl/Android.bp
+++ b/cpp/evs/manager/aidl/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_automotive",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -38,7 +39,6 @@
"android.hardware.automotive.evs-V2-ndk",
"android.hardware.common-V2-ndk",
"libaidlcommonsupport",
- "libc++fs",
"libcutils",
],
header_libs: [
diff --git a/cpp/evs/manager/aidl/include/HalCamera.h b/cpp/evs/manager/aidl/include/HalCamera.h
index 35bb65b..9c0bf71 100644
--- a/cpp/evs/manager/aidl/include/HalCamera.h
+++ b/cpp/evs/manager/aidl/include/HalCamera.h
@@ -106,9 +106,10 @@
struct FrameRecord {
uint32_t frameId;
uint32_t refCount;
- FrameRecord(uint32_t id) : frameId(id), refCount(0){};
+ FrameRecord(uint32_t id) : frameId(id), refCount(0) {};
+ FrameRecord(uint32_t id, uint32_t count) : frameId(id), refCount(count) {};
};
- std::vector<FrameRecord> mFrames;
+ std::vector<FrameRecord> mFrames GUARDED_BY(mFrameMutex);
std::weak_ptr<VirtualCamera> mPrimaryClient;
std::string mId;
aidlevs::Stream mStreamConfig;
@@ -120,6 +121,8 @@
// synchronization
mutable std::mutex mFrameMutex;
+ std::condition_variable mFrameOpDone;
+ bool mFrameOpInProgress GUARDED_BY(mFrameMutex) = false;
std::deque<FrameRequest> mNextRequests GUARDED_BY(mFrameMutex);
// Time this object was created
diff --git a/cpp/evs/manager/aidl/include/ScopedTrace.h b/cpp/evs/manager/aidl/include/ScopedTrace.h
new file mode 100644
index 0000000..14247ca
--- /dev/null
+++ b/cpp/evs/manager/aidl/include/ScopedTrace.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#define ATRACE_TAG ATRACE_TAG_CAMERA
+
+#include <utils/Trace.h>
+
+#include <ctime>
+#include <string>
+
+class ScopedTrace final {
+public:
+ explicit ScopedTrace(const std::string& name) : ScopedTrace(name, generateRandomInteger()) {}
+ explicit ScopedTrace(const std::string& name, int cookie) : mName(name), mCookie(cookie) {
+ beginTrace(mName, mCookie);
+ }
+
+ explicit ScopedTrace(const std::string& track, const std::string& name) :
+ ScopedTrace(track, name, generateRandomInteger()) {}
+ explicit ScopedTrace(const std::string& track, const std::string& name, int cookie) :
+ mTrack(track), mName(name), mCookie(cookie), mHasTrack(true) {
+ beginTrace(mTrack, mName, mCookie);
+ }
+
+ ~ScopedTrace() {
+ if (mHasTrack) {
+ endTrace(mTrack, mName, mCookie);
+ } else {
+ endTrace(mName, mCookie);
+ }
+ }
+
+ // This should not be a copyable.
+ ScopedTrace(const ScopedTrace&) = delete;
+ ScopedTrace& operator=(const ScopedTrace&) = delete;
+
+private:
+ static void beginTrace(const std::string& name, int cookie) {
+ ATRACE_ASYNC_BEGIN(name.c_str(), cookie);
+ }
+
+ static void beginTrace(const std::string& track, const std::string& name, int cookie) {
+ ATRACE_ASYNC_FOR_TRACK_BEGIN(track.c_str(), name.c_str(), cookie);
+ }
+
+ static void endTrace(const std::string& name, int cookie) {
+ ATRACE_ASYNC_END(name.c_str(), cookie);
+ }
+
+ static void endTrace(const std::string& track, [[maybe_unused]] const std::string& name,
+ int cookie) {
+ ATRACE_ASYNC_FOR_TRACK_END(track.c_str(), cookie);
+ }
+
+ static int generateRandomInteger() {
+ srand(time(nullptr));
+ return rand();
+ }
+
+ std::string mTrack;
+ std::string mName;
+ int mCookie;
+ bool mHasTrack = false;
+};
diff --git a/cpp/evs/manager/aidl/include/VirtualCamera.h b/cpp/evs/manager/aidl/include/VirtualCamera.h
index 2007215..614d4ae 100644
--- a/cpp/evs/manager/aidl/include/VirtualCamera.h
+++ b/cpp/evs/manager/aidl/include/VirtualCamera.h
@@ -110,12 +110,15 @@
STOPPING,
} mStreamState GUARDED_BY(mMutex) = STOPPED;
- std::unordered_map<std::string, std::deque<aidlevs::BufferDesc>> mFramesHeld;
+ std::unordered_map<std::string, std::deque<aidlevs::BufferDesc>> mFramesHeld GUARDED_BY(mMutex);
+ std::unordered_map<std::string, std::deque<aidlevs::BufferDesc>> mFramesUsed GUARDED_BY(mMutex);
std::thread mCaptureThread;
+ std::thread mReturnThread;
aidlevs::CameraDesc* mDesc;
mutable std::mutex mMutex;
std::condition_variable mFramesReadySignal;
+ std::condition_variable mReturnFramesSignal;
std::set<std::string> mSourceCameras;
};
diff --git a/cpp/evs/manager/aidl/src/Enumerator.cpp b/cpp/evs/manager/aidl/src/Enumerator.cpp
index 4dd8510..a1d3805 100644
--- a/cpp/evs/manager/aidl/src/Enumerator.cpp
+++ b/cpp/evs/manager/aidl/src/Enumerator.cpp
@@ -194,8 +194,8 @@
bool Enumerator::checkPermission() const {
const auto uid = AIBinder_getCallingUid();
if (!mDisablePermissionCheck && kAllowedUids.find(uid) == kAllowedUids.end()) {
- LOG(ERROR) << "EVS access denied: "
- << "pid = " << AIBinder_getCallingPid() << ", uid = " << uid;
+ LOG(ERROR) << "EVS access denied: " << "pid = " << AIBinder_getCallingPid()
+ << ", uid = " << uid;
return false;
}
diff --git a/cpp/evs/manager/aidl/src/HalCamera.cpp b/cpp/evs/manager/aidl/src/HalCamera.cpp
index f4d8ab6..a47d241 100644
--- a/cpp/evs/manager/aidl/src/HalCamera.cpp
+++ b/cpp/evs/manager/aidl/src/HalCamera.cpp
@@ -17,6 +17,7 @@
#include "HalCamera.h"
#include "Enumerator.h"
+#include "ScopedTrace.h"
#include "VirtualCamera.h"
#include "utils/include/Utils.h"
@@ -137,16 +138,19 @@
newRecords.reserve(bufferCount);
// Copy and compact the old records that are still active
- for (const auto& rec : mFrames) {
- if (rec.refCount > 0) {
- newRecords.push_back(std::move(rec));
+ {
+ std::lock_guard lock(mFrameMutex);
+ for (const auto& rec : mFrames) {
+ if (rec.refCount > 0) {
+ newRecords.push_back(std::move(rec));
+ }
}
- }
- if (newRecords.size() > static_cast<unsigned>(bufferCount)) {
- LOG(WARNING) << "We found more frames in use than requested.";
- }
+ if (newRecords.size() > static_cast<unsigned>(bufferCount)) {
+ LOG(WARNING) << "We found more frames in use than requested.";
+ }
- mFrames.swap(newRecords);
+ mFrames.swap(newRecords);
+ }
return true;
}
@@ -178,23 +182,27 @@
std::vector<FrameRecord> newRecords;
newRecords.reserve(bufferCount);
- // Copy and compact the old records that are still active
- for (const auto& rec : mFrames) {
- if (rec.refCount > 0) {
- newRecords.push_back(std::move(rec));
+ {
+ std::lock_guard lock(mFrameMutex);
+ // Copy and compact the old records that are still active
+ for (const auto& rec : mFrames) {
+ if (rec.refCount > 0) {
+ newRecords.push_back(std::move(rec));
+ }
}
- }
- if (newRecords.size() > static_cast<unsigned>(bufferCount)) {
- LOG(WARNING) << "We found more frames in use than requested.";
- }
+ if (newRecords.size() > static_cast<unsigned>(bufferCount)) {
+ LOG(WARNING) << "We found more frames in use than requested.";
+ }
- mFrames.swap(newRecords);
+ mFrames.swap(newRecords);
+ }
return true;
}
void HalCamera::requestNewFrame(std::shared_ptr<VirtualCamera> client, int64_t lastTimestamp) {
+ ScopedTrace trace("Camera " + getId(), __PRETTY_FUNCTION__);
FrameRequest req;
req.client = client;
req.timestamp = lastTimestamp;
@@ -266,7 +274,10 @@
}
ScopedAStatus HalCamera::doneWithFrame(BufferDesc buffer) {
- std::lock_guard<std::mutex> lock(mFrameMutex);
+ ScopedTrace trace("Camera " + getId(), __PRETTY_FUNCTION__, buffer.bufferId);
+ std::unique_lock lock(mFrameMutex);
+ ::android::base::ScopedLockAssertion lock_assertion(mFrameMutex);
+ mFrameOpDone.wait(lock, [this]() REQUIRES(mFrameMutex) { return mFrameOpInProgress != true; });
// Find this frame in our list of outstanding frames
auto it = std::find_if(mFrames.begin(), mFrames.end(),
@@ -310,6 +321,9 @@
ScopedAStatus HalCamera::deliverFrame(const std::vector<BufferDesc>& buffers) {
LOG(VERBOSE) << "Received a frame";
+ ScopedTrace trace("Camera " + getId(), __PRETTY_FUNCTION__,
+ buffers.empty() ? std::numeric_limits<int>::min() : buffers[0].bufferId);
+
// Reports the number of received buffers
mUsageStats->framesReceived(buffers);
@@ -327,6 +341,7 @@
std::make_move_iterator(mNextRequests.begin()),
std::make_move_iterator(mNextRequests.end()));
mNextRequests.clear();
+ mFrameOpInProgress = true;
}
while (!currentRequests.empty()) {
@@ -366,7 +381,16 @@
// Reports a returned buffer
mUsageStats->framesReturned(buffers);
+
+ // Adding skipped capture requests back to the queue.
+ std::lock_guard<std::mutex> lock(mFrameMutex);
+ mNextRequests.insert(mNextRequests.end(), std::make_move_iterator(puntedRequests.begin()),
+ std::make_move_iterator(puntedRequests.end()));
+ mFrameOpInProgress = false;
+ mFrameOpDone.notify_all();
} else {
+ std::lock_guard lock(mFrameMutex);
+
// Add an entry for this frame in our tracking list.
unsigned i;
for (i = 0; i < mFrames.size(); ++i) {
@@ -376,19 +400,17 @@
}
if (i == mFrames.size()) {
- mFrames.push_back(buffers[0].bufferId);
+ mFrames.emplace_back(buffers[0].bufferId, frameDeliveries);
} else {
mFrames[i].frameId = buffers[0].bufferId;
+ mFrames[i].refCount = frameDeliveries;
}
- mFrames[i].refCount = frameDeliveries;
- }
- {
// Adding skipped capture requests back to the queue.
- std::lock_guard<std::mutex> lock(mFrameMutex);
- mNextRequests.insert(mNextRequests.end(),
- std::make_move_iterator(puntedRequests.begin()),
+ mNextRequests.insert(mNextRequests.end(), std::make_move_iterator(puntedRequests.begin()),
std::make_move_iterator(puntedRequests.end()));
+ mFrameOpInProgress = false;
+ mFrameOpDone.notify_all();
}
return ScopedAStatus::ok();
@@ -396,6 +418,7 @@
ScopedAStatus HalCamera::notify(const EvsEventDesc& event) {
LOG(DEBUG) << "Received an event id: " << static_cast<int32_t>(event.aType);
+ ScopedTrace trace("Camera " + getId(), __PRETTY_FUNCTION__, static_cast<int>(event.aType));
if (event.aType == EvsEventType::STREAM_STOPPED) {
// This event happens only when there is no more active client.
std::lock_guard lock(mFrameMutex);
@@ -559,8 +582,9 @@
"%srotation: 0x%X\n\n",
indent, double_indent.data(), configuration.id, double_indent.data(),
configuration.width, double_indent.data(), configuration.height,
- double_indent.data(), configuration.format, double_indent.data(),
- configuration.usage, double_indent.data(), configuration.rotation);
+ double_indent.data(), static_cast<unsigned int>(configuration.format),
+ double_indent.data(), static_cast<unsigned long>(configuration.usage),
+ double_indent.data(), static_cast<unsigned int>(configuration.rotation));
return streamInfo;
}
diff --git a/cpp/evs/manager/aidl/src/VirtualCamera.cpp b/cpp/evs/manager/aidl/src/VirtualCamera.cpp
index 9320675..8e6a347 100644
--- a/cpp/evs/manager/aidl/src/VirtualCamera.cpp
+++ b/cpp/evs/manager/aidl/src/VirtualCamera.cpp
@@ -18,6 +18,7 @@
#include "Enumerator.h"
#include "HalCamera.h"
+#include "ScopedTrace.h"
#include "utils/include/Utils.h"
#include <android-base/file.h>
@@ -59,6 +60,8 @@
}
ScopedAStatus VirtualCamera::doneWithFrame(const std::vector<BufferDesc>& buffers) {
+ ScopedTrace trace(__PRETTY_FUNCTION__,
+ buffers.empty() ? std::numeric_limits<int>::min() : buffers[0].bufferId);
std::lock_guard lock(mMutex);
for (auto&& buffer : buffers) {
@@ -75,22 +78,12 @@
continue;
}
- // Take this frame out of our "held" list
- BufferDesc bufferToReturn = std::move(*it);
+ // Move this frame out of our "held" list
+ mFramesUsed[buffer.deviceId].push_back(std::move(*it));
mFramesHeld[buffer.deviceId].erase(it);
-
- // Tell our parent that we're done with this buffer
- std::shared_ptr<HalCamera> pHwCamera = mHalCamera[buffer.deviceId].lock();
- if (pHwCamera) {
- auto status = pHwCamera->doneWithFrame(std::move(bufferToReturn));
- if (!status.isOk()) {
- LOG(WARNING) << "Failed to return a buffer " << buffer.bufferId;
- }
- } else {
- LOG(WARNING) << "Possible memory leak; " << buffer.deviceId << " is not valid.";
- }
}
+ mReturnFramesSignal.notify_all();
return ScopedAStatus::ok();
}
@@ -411,6 +404,7 @@
}
ScopedAStatus VirtualCamera::startVideoStream(const std::shared_ptr<IEvsCameraStream>& receiver) {
+ ScopedTrace trace(__PRETTY_FUNCTION__);
std::lock_guard lock(mMutex);
if (!receiver) {
@@ -432,31 +426,43 @@
mStreamState = RUNNING;
// Tell the underlying camera hardware that we want to stream
- for (auto iter = mHalCamera.begin(); iter != mHalCamera.end(); ++iter) {
+ bool cleanUpAndReturn = true;
+ auto iter = mHalCamera.begin();
+ while (iter != mHalCamera.end()) {
std::shared_ptr<HalCamera> pHwCamera = iter->second.lock();
if (!pHwCamera) {
- LOG(ERROR) << "Failed to start a video stream on " << iter->first;
+ LOG(WARNING) << "Failed to start a video stream on " << iter->first;
+ ++iter;
continue;
}
LOG(INFO) << __FUNCTION__ << " starts a video stream on " << iter->first;
if (!pHwCamera->clientStreamStarting().isOk()) {
- // If we failed to start the underlying stream, then we're not actually running
- mStream = nullptr;
- mStreamState = STOPPED;
-
- // Request to stop streams started by this client.
- auto rb = mHalCamera.begin();
- while (rb != iter) {
- auto ptr = rb->second.lock();
- if (ptr) {
- ptr->clientStreamEnding(this);
- }
- ++rb;
- }
-
- return Utils::buildScopedAStatusFromEvsResult(EvsResult::UNDERLYING_SERVICE_ERROR);
+ LOG(ERROR) << "Failed to start a video stream on " << iter->first;
+ cleanUpAndReturn = true;
+ break;
}
+
+ cleanUpAndReturn = false;
+ ++iter;
+ }
+
+ if (cleanUpAndReturn) {
+ // If we failed to start the underlying stream, then we're not actually running
+ mStream = nullptr;
+ mStreamState = STOPPED;
+
+ // Request to stop streams started by this client.
+ auto rb = mHalCamera.begin();
+ while (rb != iter) {
+ auto ptr = rb->second.lock();
+ if (ptr) {
+ ptr->clientStreamEnding(this);
+ }
+ ++rb;
+ }
+
+ return Utils::buildScopedAStatusFromEvsResult(EvsResult::UNDERLYING_SERVICE_ERROR);
}
mCaptureThread = std::thread([this]() {
@@ -466,6 +472,7 @@
int64_t lastFrameTimestamp = -1;
EvsResult status = EvsResult::OK;
while (true) {
+ ScopedTrace trace("Processing a frame buffer", lastFrameTimestamp);
std::unique_lock lock(mMutex);
::android::base::ScopedLockAssertion assume_lock(mMutex);
@@ -563,6 +570,54 @@
}
});
+ mReturnThread = std::thread([this]() {
+ while (true) {
+ ScopedTrace trace("Returning frame buffers");
+ std::unordered_map<std::string, std::vector<BufferDesc>> framesUsed;
+ {
+ std::unique_lock lock(mMutex);
+ ::android::base::ScopedLockAssertion assume_lock(mMutex);
+
+ mReturnFramesSignal.wait(lock, [this]() REQUIRES(mMutex) {
+ return mStreamState != RUNNING || !mFramesUsed.empty();
+ });
+
+ if (mStreamState != RUNNING) {
+ // A video stream is stopped while a capture thread is waiting
+ // for a new frame or we have lost a client.
+ LOG(DEBUG) << "Requested to stop capturing frames or lost a client";
+ break;
+ }
+
+ for (auto&& [hwCameraId, buffers] : mFramesUsed) {
+ std::vector<BufferDesc> bufferToReturn(std::make_move_iterator(buffers.begin()),
+ std::make_move_iterator(buffers.end()));
+ framesUsed.insert_or_assign(hwCameraId, std::move(bufferToReturn));
+ }
+
+ mFramesUsed.clear();
+ }
+
+ // Tell our parent that we're done with this buffer
+ for (auto&& [hwCameraId, buffers] : framesUsed) {
+ std::shared_ptr<HalCamera> pHwCamera = mHalCamera[hwCameraId].lock();
+ if (!pHwCamera) {
+ LOG(WARNING) << "Possible memory leak; " << hwCameraId << " is not valid.";
+ continue;
+ }
+
+ for (auto&& buffer : buffers) {
+ if (!pHwCamera->doneWithFrame(std::move(buffer)).isOk()) {
+ LOG(WARNING) << "Failed to return a buffer " << buffer.bufferId << " to "
+ << hwCameraId;
+ }
+ }
+ }
+ }
+
+ LOG(DEBUG) << "Exiting a return thread";
+ });
+
// TODO(b/213108625):
// Detect and exit if we encounter a stalled stream or unresponsive driver?
// Consider using a timer and watching for frame arrival?
@@ -571,6 +626,7 @@
}
ScopedAStatus VirtualCamera::stopVideoStream() {
+ ScopedTrace trace(__PRETTY_FUNCTION__);
{
std::lock_guard lock(mMutex);
if (mStreamState != RUNNING) {
@@ -581,8 +637,10 @@
// Tell the frame delivery pipeline we don't want any more frames
mStreamState = STOPPING;
- // Awake the capture thread; this thread will terminate.
+ // Awake the capture and buffer-return threads; they will be terminated.
+ mSourceCameras.clear();
mFramesReadySignal.notify_all();
+ mReturnFramesSignal.notify_all();
// Deliver the stream-ending notification
EvsEventDesc event{
@@ -607,14 +665,15 @@
}
}
- // Signal a condition to unblock a capture thread and then join
- mSourceCameras.clear();
- mFramesReadySignal.notify_all();
-
+ // Join a capture and buffer-return threads.
if (mCaptureThread.joinable()) {
mCaptureThread.join();
}
+ if (mReturnThread.joinable()) {
+ mReturnThread.join();
+ }
+
return ScopedAStatus::ok();
}
@@ -639,6 +698,7 @@
}
void VirtualCamera::shutdown() {
+ ScopedTrace trace(__PRETTY_FUNCTION__);
{
std::lock_guard lock(mMutex);
@@ -683,16 +743,22 @@
pHwCamera->disownVirtualCamera(this);
}
- // Awakes the capture thread; this thread will terminate.
+ mFramesHeld.clear();
+ mFramesUsed.clear();
+
+ // Awake the capture and buffer-return threads; they will be terminated.
mFramesReadySignal.notify_all();
+ mReturnFramesSignal.notify_all();
}
- // Join a capture thread
+ // Join a capture and buffer-return threads.
if (mCaptureThread.joinable()) {
mCaptureThread.join();
}
- mFramesHeld.clear();
+ if (mReturnThread.joinable()) {
+ mReturnThread.join();
+ }
// Drop our reference to our associated hardware camera
mHalCamera.clear();
@@ -711,6 +777,7 @@
}
bool VirtualCamera::deliverFrame(const BufferDesc& bufDesc) {
+ ScopedTrace trace(__PRETTY_FUNCTION__, bufDesc.bufferId);
std::lock_guard lock(mMutex);
if (mStreamState == STOPPED) {
@@ -757,6 +824,7 @@
}
bool VirtualCamera::notify(const EvsEventDesc& event) {
+ ScopedTrace trace(__PRETTY_FUNCTION__, static_cast<int>(event.aType));
switch (event.aType) {
case EvsEventType::STREAM_STOPPED: {
{
diff --git a/cpp/evs/manager/aidl/stats/src/StatsCollector.cpp b/cpp/evs/manager/aidl/stats/src/StatsCollector.cpp
index 2a9d2bc..c5ebd32 100644
--- a/cpp/evs/manager/aidl/stats/src/StatsCollector.cpp
+++ b/cpp/evs/manager/aidl/stats/src/StatsCollector.cpp
@@ -269,8 +269,8 @@
AutoMutex lock(mMutex);
if (mCurrentCollectionEvent != CollectionEvent::PERIODIC) {
return Error(::android::INVALID_OPERATION)
- << "Cannot start a custom collection when "
- << "the current collection event " << toString(mCurrentCollectionEvent)
+ << "Cannot start a custom collection when " << "the current collection event "
+ << toString(mCurrentCollectionEvent)
<< " != " << toString(CollectionEvent::PERIODIC) << " collection event";
}
diff --git a/cpp/evs/manager/aidl/tests/fuzz/HalCameraFuzzer.cpp b/cpp/evs/manager/aidl/tests/fuzz/HalCameraFuzzer.cpp
index 3106268..102cd54 100644
--- a/cpp/evs/manager/aidl/tests/fuzz/HalCameraFuzzer.cpp
+++ b/cpp/evs/manager/aidl/tests/fuzz/HalCameraFuzzer.cpp
@@ -30,12 +30,12 @@
namespace {
using aidl::android::automotive::evs::implementation::HalCamera;
+using aidl::android::automotive::evs::implementation::initializeMockEvsHal;
using aidl::android::automotive::evs::implementation::MockEvsHal;
using aidl::android::automotive::evs::implementation::NiceMockEvsCamera;
+using aidl::android::automotive::evs::implementation::openFirstCamera;
using aidl::android::automotive::evs::implementation::Utils;
using aidl::android::automotive::evs::implementation::VirtualCamera;
-using aidl::android::automotive::evs::implementation::initializeMockEvsHal;
-using aidl::android::automotive::evs::implementation::openFirstCamera;
using aidl::android::hardware::automotive::evs::BufferDesc;
using aidl::android::hardware::automotive::evs::CameraDesc;
using aidl::android::hardware::automotive::evs::CameraParam;
diff --git a/cpp/evs/manager/aidl/tests/fuzz/HalDisplayFuzzer.cpp b/cpp/evs/manager/aidl/tests/fuzz/HalDisplayFuzzer.cpp
index 52d3d6a..75df4ea 100644
--- a/cpp/evs/manager/aidl/tests/fuzz/HalDisplayFuzzer.cpp
+++ b/cpp/evs/manager/aidl/tests/fuzz/HalDisplayFuzzer.cpp
@@ -62,10 +62,9 @@
}
case EVS_FUZZ_SET_DISPLAY_STATE: {
LOG(DEBUG) << "EVS_FUZZ_SET_DISPLAY_STATE";
- uint32_t state =
- fdp.ConsumeIntegralInRange<uint32_t>(0,
- static_cast<uint32_t>(
- DisplayState::DEAD));
+ uint32_t state = fdp.ConsumeIntegralInRange<uint32_t>(0,
+ static_cast<uint32_t>(
+ DisplayState::DEAD));
halDisplay->setDisplayState(static_cast<DisplayState>(state));
break;
}
diff --git a/cpp/evs/manager/aidl/tests/fuzz/VirtualCameraFuzzer.cpp b/cpp/evs/manager/aidl/tests/fuzz/VirtualCameraFuzzer.cpp
index 436605c..3b9b08f 100644
--- a/cpp/evs/manager/aidl/tests/fuzz/VirtualCameraFuzzer.cpp
+++ b/cpp/evs/manager/aidl/tests/fuzz/VirtualCameraFuzzer.cpp
@@ -27,12 +27,12 @@
namespace {
using aidl::android::automotive::evs::implementation::HalCamera;
+using aidl::android::automotive::evs::implementation::initializeMockEvsHal;
using aidl::android::automotive::evs::implementation::MockEvsHal;
using aidl::android::automotive::evs::implementation::NiceMockEvsCamera;
+using aidl::android::automotive::evs::implementation::openFirstCamera;
using aidl::android::automotive::evs::implementation::Utils;
using aidl::android::automotive::evs::implementation::VirtualCamera;
-using aidl::android::automotive::evs::implementation::initializeMockEvsHal;
-using aidl::android::automotive::evs::implementation::openFirstCamera;
using aidl::android::hardware::automotive::evs::BufferDesc;
using aidl::android::hardware::automotive::evs::CameraDesc;
using aidl::android::hardware::automotive::evs::CameraParam;
diff --git a/cpp/evs/manager/aidl/tests/unit/src/EvsEnumeratorHidlUnitTest.cpp b/cpp/evs/manager/aidl/tests/unit/src/EvsEnumeratorHidlUnitTest.cpp
index 3f1b3fc..ff074a8 100644
--- a/cpp/evs/manager/aidl/tests/unit/src/EvsEnumeratorHidlUnitTest.cpp
+++ b/cpp/evs/manager/aidl/tests/unit/src/EvsEnumeratorHidlUnitTest.cpp
@@ -57,6 +57,7 @@
constexpr size_t kNumMockEvsCameras = 4;
constexpr size_t kNumMockEvsDisplays = 2;
+constexpr size_t kNumFramesToRequest = 3;
const std::unordered_set<int32_t> gAllowedUid({AID_ROOT, AID_SYSTEM, AID_AUTOMOTIVE_EVS});
@@ -110,12 +111,14 @@
bool VerifyCameraStream(const hidlevs::V1_1::CameraDesc& desc, size_t framesToReceive,
std::chrono::duration<long double> maxInterval,
std::chrono::duration<long double> eventTimeout,
- const std::string& name, StreamStartedCallbackFunc cb);
+ const std::string& name, StreamStartedCallbackFunc cb,
+ size_t numIterations);
bool VerifyCameraStream_1_0(const hidlevs::V1_0::CameraDesc& desc, size_t framesToReceive,
std::chrono::duration<long double> maxInterval,
std::chrono::duration<long double> stopTimeout,
- const std::string& name, StreamStartedCallbackFunc cb);
+ const std::string& name, StreamStartedCallbackFunc cb,
+ size_t numIterations);
protected:
// Class members declared here can be used by all tests in the test suite
@@ -352,13 +355,14 @@
std::packaged_task<bool()> task(std::bind(&EvsEnumeratorHidlUnitTest::VerifyCameraStream,
this, desc, kFramesToReceive, kMaxFrameInterval,
- kEventTimeout, desc.v1.cameraId, doNothingFunc));
+ kEventTimeout, desc.v1.cameraId, doNothingFunc,
+ /* numIterations= */ 3));
std::future<bool> result = task.get_future();
std::thread t(std::move(task));
t.detach();
EXPECT_EQ(std::future_status::ready, result.wait_for(kResultTimeout));
- EXPECT_TRUE(result.get());
+ ASSERT_TRUE(result.get());
// TODO(b/250699038): This test will likely fail to request a video
// stream on the next camera without this interval.
@@ -382,13 +386,13 @@
std::packaged_task<bool()> task(
std::bind(&EvsEnumeratorHidlUnitTest::VerifyCameraStream_1_0, this, desc,
kFramesToReceive, kMaxFrameInterval, kStopTimeout, desc.cameraId,
- doNothingFunc));
+ doNothingFunc, /* numIterations= */ 3));
std::future<bool> result = task.get_future();
std::thread t(std::move(task));
t.detach();
EXPECT_EQ(std::future_status::ready, result.wait_for(kResultTimeout));
- EXPECT_TRUE(result.get());
+ ASSERT_TRUE(result.get());
// TODO(b/250699038): This test will likely fail to request a video
// stream on the next camera without this interval.
@@ -411,10 +415,12 @@
std::packaged_task<bool()> task0(std::bind(&EvsEnumeratorHidlUnitTest::VerifyCameraStream,
this, desc, kFramesToReceive, kMaxFrameInterval,
- kEventTimeout, "client0", doNothingFunc));
+ kEventTimeout, "client0", doNothingFunc,
+ /* numIterations= */ 1));
std::packaged_task<bool()> task1(std::bind(&EvsEnumeratorHidlUnitTest::VerifyCameraStream,
this, desc, kFramesToReceive, kMaxFrameInterval,
- kEventTimeout, "client1", doNothingFunc));
+ kEventTimeout, "client1", doNothingFunc,
+ /* numIterations= */ 1));
std::future<bool> result0 = task0.get_future();
std::future<bool> result1 = task1.get_future();
@@ -426,8 +432,8 @@
EXPECT_EQ(std::future_status::ready, result0.wait_for(kResultTimeout));
EXPECT_EQ(std::future_status::ready, result1.wait_for(kResultTimeout));
- EXPECT_TRUE(result0.get());
- EXPECT_TRUE(result1.get());
+ ASSERT_TRUE(result0.get());
+ ASSERT_TRUE(result1.get());
// TODO(b/250699038): This test will likely fail to request a video
// stream on the next camera without this interval.
@@ -450,12 +456,12 @@
std::packaged_task<bool()> task0(std::bind(&EvsEnumeratorHidlUnitTest::VerifyCameraStream,
this, desc0, kFramesToReceive, kMaxFrameInterval,
- kEventTimeout, desc0.v1.cameraId,
- doNothingFunc));
+ kEventTimeout, desc0.v1.cameraId, doNothingFunc,
+ /* numIterations= */ 1));
std::packaged_task<bool()> task1(std::bind(&EvsEnumeratorHidlUnitTest::VerifyCameraStream,
this, desc1, kFramesToReceive, kMaxFrameInterval,
- kEventTimeout, desc1.v1.cameraId,
- doNothingFunc));
+ kEventTimeout, desc1.v1.cameraId, doNothingFunc,
+ /* numIterations= */ 1));
// Start sending a frame early.
mMockEvsHal->setNumberOfFramesToSend(/* numFramesToSend = */ 100);
@@ -470,8 +476,8 @@
EXPECT_EQ(std::future_status::ready, result0.wait_for(kResultTimeout));
EXPECT_EQ(std::future_status::ready, result1.wait_for(kResultTimeout));
- EXPECT_TRUE(result0.get());
- EXPECT_TRUE(result1.get());
+ ASSERT_TRUE(result0.get());
+ ASSERT_TRUE(result1.get());
// TODO(b/250699038): This test will likely fail to request a video
// stream on the next camera without this interval.
@@ -901,7 +907,8 @@
std::chrono::duration<long double> maxInterval,
std::chrono::duration<long double> eventTimeout,
const std::string& name,
- StreamStartedCallbackFunc callback) {
+ StreamStartedCallbackFunc callback,
+ size_t numIterations) {
std::mutex m;
std::condition_variable cv;
::android::hardware::hidl_vec<hidlevs::V1_1::BufferDesc> receivedFrames;
@@ -935,48 +942,70 @@
::android::sp<hidlevs::V1_1::IEvsCamera> c =
mEnumerator->openCamera_1_1(desc.v1.cameraId, /* streamCfg= */ {});
- EXPECT_NE(nullptr, c);
- EXPECT_EQ(hidlevs::V1_0::EvsResult::OK, c->setMaxFramesInFlight(/* bufferCount= */ 3));
+ if (!c) {
+ LOG(ERROR) << "Failed to open a camera " << desc.v1.cameraId;
+ return false;
+ }
- // Request to start a video stream and wait for a given number of frames.
- ::android::sp<StreamCallback> cb = new (std::nothrow) StreamCallback(frameCb, eventCb);
- EXPECT_NE(nullptr, cb);
- EXPECT_TRUE(c->startVideoStream(cb).isOk());
+ if (c->setMaxFramesInFlight(kNumFramesToRequest) != hidlevs::V1_0::EvsResult::OK) {
+ LOG(WARNING) << "Failed to adjust the size of the buffer pool.";
+ return false;
+ }
- std::unique_lock lk(m);
- for (auto i = 0; i < framesToReceive; ++i) {
- EXPECT_TRUE(cv.wait_for(lk, maxInterval, [&gotFrameCallback] { return gotFrameCallback; }));
- EXPECT_TRUE(gotFrameCallback);
- if (!gotFrameCallback) {
+ for (auto iter = 0; iter < numIterations; ++iter) {
+ // Request to start a video stream and wait for a given number of frames.
+ ::android::sp<StreamCallback> cb = new (std::nothrow) StreamCallback(frameCb, eventCb);
+ if (!cb) {
+ LOG(ERROR) << "Failed to create a new StreamCallback object.";
continue;
}
- EXPECT_TRUE(c->doneWithFrame_1_1(receivedFrames).isOk());
- gotFrameCallback = false;
+ auto ret = c->startVideoStream(cb);
+ if (ret != hidlevs::V1_0::EvsResult::OK &&
+ ret != hidlevs::V1_0::EvsResult::STREAM_ALREADY_RUNNING) {
+ LOG(ERROR) << "Failed to start a video stream on " << desc.v1.cameraId;
+ continue;
+ }
+
+ std::unique_lock lk(m);
+ for (auto i = 0; i < framesToReceive; ++i) {
+ EXPECT_TRUE(
+ cv.wait_for(lk, maxInterval, [&gotFrameCallback] { return gotFrameCallback; }));
+ if (!gotFrameCallback) {
+ LOG(WARNING) << "Did not receive a frame " << i;
+ continue;
+ }
+
+ EXPECT_TRUE(c->doneWithFrame_1_1(receivedFrames).isOk());
+ gotFrameCallback = false;
+ }
+ lk.unlock();
+
+ // Call two methods that are not implemented yet in a mock EVS HAL
+ // implementation.
+ EXPECT_TRUE(c->pauseVideoStream().isOk());
+ EXPECT_TRUE(c->resumeVideoStream().isOk());
+
+ // Create AidlCamera object and call pauseVideoStream() and
+ // resumeVideoStream().
+ std::shared_ptr<AidlCamera> aidlCamera = ndk::SharedRefBase::make<AidlCamera>(c);
+ if (!aidlCamera) {
+ LOG(ERROR) << "Failed to create a new AidlCamera object.";
+ return false;
+ }
+
+ // Mock HIDL EVS HAL implementation does not support pause/resume; hence
+ // below calls should fail.
+ EXPECT_FALSE(aidlCamera->pauseVideoStream().isOk());
+ EXPECT_FALSE(aidlCamera->resumeVideoStream().isOk());
+
+ // Request to stop a video stream and wait.
+ EXPECT_TRUE(c->stopVideoStream().isOk());
+
+ lk.lock();
+ cv.wait_for(lk, eventTimeout, [&gotEventCallback] { return gotEventCallback; });
+ EXPECT_EQ(hidlevs::V1_1::EvsEventType::STREAM_STOPPED, receivedEvent.aType);
}
- lk.unlock();
-
- // Call two methods that are not implemented yet in a mock EVS HAL
- // implementation.
- EXPECT_TRUE(c->pauseVideoStream().isOk());
- EXPECT_TRUE(c->resumeVideoStream().isOk());
-
- // Create AidlCamera object and call pauseVideoStream() and
- // resumeVideoStream().
- std::shared_ptr<AidlCamera> aidlCamera = ndk::SharedRefBase::make<AidlCamera>(c);
- EXPECT_NE(nullptr, aidlCamera);
-
- // Mock HIDL EVS HAL implementation does not support pause/resume; hence
- // below calls should fail.
- EXPECT_FALSE(aidlCamera->pauseVideoStream().isOk());
- EXPECT_FALSE(aidlCamera->resumeVideoStream().isOk());
-
- // Request to stop a video stream and wait.
- EXPECT_TRUE(c->stopVideoStream().isOk());
-
- lk.lock();
- cv.wait_for(lk, eventTimeout, [&gotEventCallback] { return gotEventCallback; });
- EXPECT_EQ(hidlevs::V1_1::EvsEventType::STREAM_STOPPED, receivedEvent.aType);
EXPECT_TRUE(mEnumerator->closeCamera(c).isOk());
@@ -987,7 +1016,7 @@
const hidlevs::V1_0::CameraDesc& desc, size_t framesToReceive,
std::chrono::duration<long double> maxInterval,
std::chrono::duration<long double> stopTimeout, const std::string& name,
- StreamStartedCallbackFunc callback) {
+ StreamStartedCallbackFunc callback, size_t numIterations) {
std::mutex m;
std::condition_variable cv;
hidlevs::V1_0::BufferDesc receivedFrame;
@@ -1013,43 +1042,65 @@
});
::android::sp<hidlevs::V1_0::IEvsCamera> c = mEnumerator->openCamera(desc.cameraId);
- EXPECT_NE(nullptr, c);
- EXPECT_EQ(hidlevs::V1_0::EvsResult::OK, c->setMaxFramesInFlight(/* bufferCount= */ 3));
+ if (!c) {
+ LOG(ERROR) << "Failed to open a camera " << desc.cameraId;
+ return false;
+ }
- // Request to start a video stream and wait for a given number of frames.
- ::android::sp<StreamCallback_1_0> cb = new (std::nothrow) StreamCallback_1_0(frameCb);
- EXPECT_NE(nullptr, cb);
- EXPECT_TRUE(c->startVideoStream(cb).isOk());
+ if (c->setMaxFramesInFlight(kNumFramesToRequest) != hidlevs::V1_0::EvsResult::OK) {
+ LOG(WARNING) << "Failed to adjust the size of the buffer pool.";
+ return false;
+ }
- std::unique_lock lk(m);
- for (auto i = 0; i < framesToReceive; ++i) {
- EXPECT_TRUE(cv.wait_for(lk, maxInterval, [&gotFrameCallback] { return gotFrameCallback; }));
- EXPECT_TRUE(gotFrameCallback);
- if (!gotFrameCallback) {
+ for (auto iter = 0; iter < numIterations; ++iter) {
+ // Request to start a video stream and wait for a given number of frames.
+ ::android::sp<StreamCallback_1_0> cb = new (std::nothrow) StreamCallback_1_0(frameCb);
+ if (!cb) {
+ LOG(ERROR) << "Failed to create a new StreamCallback object.";
continue;
}
- EXPECT_TRUE(c->doneWithFrame(receivedFrame).isOk());
- gotFrameCallback = false;
+ auto ret = c->startVideoStream(cb);
+ if (ret != hidlevs::V1_0::EvsResult::OK) {
+ LOG(ERROR) << "Failed to start a video stream on " << desc.cameraId;
+ continue;
+ }
+
+ std::unique_lock lk(m);
+ for (auto i = 0; i < framesToReceive; ++i) {
+ EXPECT_TRUE(
+ cv.wait_for(lk, maxInterval, [&gotFrameCallback] { return gotFrameCallback; }));
+ if (!gotFrameCallback) {
+ LOG(WARNING) << "Did not receive a frame " << i;
+ continue;
+ }
+
+ EXPECT_TRUE(c->doneWithFrame(receivedFrame).isOk());
+ gotFrameCallback = false;
+ }
+ lk.unlock();
+
+ // Create AidlCamera object and call pauseVideoStream() and
+ // resumeVideoStream().
+ std::shared_ptr<AidlCamera> aidlCamera =
+ ndk::SharedRefBase::make<AidlCamera>(c, /* forceV1_0= */ true);
+ if (!aidlCamera) {
+ LOG(ERROR) << "Failed to create a new AidlCamera object.";
+ return false;
+ }
+
+ // ::android::hardware::automotive::evs::V1_0::IEvsCamera does not support
+ // pause/resume; hence, below calls should fail.
+ EXPECT_FALSE(aidlCamera->pauseVideoStream().isOk());
+ EXPECT_FALSE(aidlCamera->resumeVideoStream().isOk());
+
+ // Request to stop a video stream and wait.
+ EXPECT_TRUE(c->stopVideoStream().isOk());
+
+ lk.lock();
+ EXPECT_TRUE(cv.wait_for(lk, stopTimeout, [&gotNullFrame] { return gotNullFrame; }));
}
- lk.unlock();
- // Create AidlCamera object and call pauseVideoStream() and
- // resumeVideoStream().
- std::shared_ptr<AidlCamera> aidlCamera =
- ndk::SharedRefBase::make<AidlCamera>(c, /* forceV1_0= */ true);
- EXPECT_NE(nullptr, aidlCamera);
-
- // ::android::hardware::automotive::evs::V1_0::IEvsCamera does not support
- // pause/resume; hence, below calls should fail.
- EXPECT_FALSE(aidlCamera->pauseVideoStream().isOk());
- EXPECT_FALSE(aidlCamera->resumeVideoStream().isOk());
-
- // Request to stop a video stream and wait.
- EXPECT_TRUE(c->stopVideoStream().isOk());
-
- lk.lock();
- EXPECT_TRUE(cv.wait_for(lk, stopTimeout, [&gotNullFrame] { return gotNullFrame; }));
EXPECT_TRUE(mEnumerator->closeCamera(c).isOk());
return true;
diff --git a/cpp/evs/manager/aidl/tests/unit/src/EvsEnumeratorHidlUnitTest_1_0.cpp b/cpp/evs/manager/aidl/tests/unit/src/EvsEnumeratorHidlUnitTest_1_0.cpp
index f773cd5..2b54bbe 100644
--- a/cpp/evs/manager/aidl/tests/unit/src/EvsEnumeratorHidlUnitTest_1_0.cpp
+++ b/cpp/evs/manager/aidl/tests/unit/src/EvsEnumeratorHidlUnitTest_1_0.cpp
@@ -47,6 +47,7 @@
constexpr size_t kNumMockEvsCameras = 4;
constexpr size_t kNumMockEvsDisplays = 2;
+constexpr size_t kNumFramesToRequest = 3;
const std::unordered_set<int32_t> gAllowedUid({AID_ROOT, AID_SYSTEM, AID_AUTOMOTIVE_EVS});
@@ -100,7 +101,7 @@
bool VerifyCameraStream(const hidlevs::V1_0::CameraDesc& desc, size_t framesToReceive,
std::chrono::duration<long double> maxInterval,
std::chrono::duration<long double> stopTimeout, const std::string& name,
- StreamStartedCallbackFunc cb);
+ StreamStartedCallbackFunc cb, size_t numIterations);
protected:
// Class members declared here can be used by all tests in the test suite
@@ -200,13 +201,13 @@
std::packaged_task<bool()> task(
std::bind(&EvsEnumeratorHidlUnitTest_1_0::VerifyCameraStream, this, desc,
kFramesToReceive, kMaxFrameInterval, kStopTimeout, desc.cameraId,
- doNothingFunc));
+ doNothingFunc, /* numIterations= */ 3));
std::future<bool> result = task.get_future();
std::thread t(std::move(task));
t.detach();
EXPECT_EQ(std::future_status::ready, result.wait_for(kResultTimeout));
- EXPECT_TRUE(result.get());
+ ASSERT_TRUE(result.get());
// TODO(b/250699038): This test will likely fail to request a video
// stream on the next camera without this interval.
@@ -230,11 +231,11 @@
std::packaged_task<bool()> task0(
std::bind(&EvsEnumeratorHidlUnitTest_1_0::VerifyCameraStream, this, desc,
kFramesToReceive, kMaxFrameInterval, kStopTimeout, "client0",
- doNothingFunc));
+ doNothingFunc, /* numIterations= */ 1));
std::packaged_task<bool()> task1(
std::bind(&EvsEnumeratorHidlUnitTest_1_0::VerifyCameraStream, this, desc,
kFramesToReceive, kMaxFrameInterval, kStopTimeout, "client1",
- doNothingFunc));
+ doNothingFunc, /* numIterations= */ 1));
std::future<bool> result0 = task0.get_future();
std::future<bool> result1 = task1.get_future();
@@ -246,8 +247,8 @@
EXPECT_EQ(std::future_status::ready, result0.wait_for(kResultTimeout));
EXPECT_EQ(std::future_status::ready, result1.wait_for(kResultTimeout));
- EXPECT_TRUE(result0.get());
- EXPECT_TRUE(result1.get());
+ ASSERT_TRUE(result0.get());
+ ASSERT_TRUE(result1.get());
// TODO(b/250699038): This test will likely fail to request a video
// stream on the next camera without this interval.
@@ -271,11 +272,11 @@
std::packaged_task<bool()> task0(
std::bind(&EvsEnumeratorHidlUnitTest_1_0::VerifyCameraStream, this, desc0,
kFramesToReceive, kMaxFrameInterval, kStopTimeout, desc0.cameraId,
- doNothingFunc));
+ doNothingFunc, /* numIterations= */ 1));
std::packaged_task<bool()> task1(
std::bind(&EvsEnumeratorHidlUnitTest_1_0::VerifyCameraStream, this, desc1,
kFramesToReceive, kMaxFrameInterval, kStopTimeout, desc1.cameraId,
- doNothingFunc));
+ doNothingFunc, /* numIterations= */ 1));
// Start sending a frame early.
mMockEvsHal->setNumberOfFramesToSend(/* numFramesToSend = */ 100);
@@ -290,8 +291,8 @@
EXPECT_EQ(std::future_status::ready, result0.wait_for(kResultTimeout));
EXPECT_EQ(std::future_status::ready, result1.wait_for(kResultTimeout));
- EXPECT_TRUE(result0.get());
- EXPECT_TRUE(result1.get());
+ ASSERT_TRUE(result0.get());
+ ASSERT_TRUE(result1.get());
// TODO(b/250699038): This test will likely fail to request a video
// stream on the next camera without this interval.
@@ -351,7 +352,7 @@
const hidlevs::V1_0::CameraDesc& desc, size_t framesToReceive,
std::chrono::duration<long double> maxInterval,
std::chrono::duration<long double> stopTimeout, const std::string& name,
- StreamStartedCallbackFunc callback) {
+ StreamStartedCallbackFunc callback, size_t numIterations) {
std::mutex m;
std::condition_variable cv;
hidlevs::V1_0::BufferDesc receivedFrame;
@@ -377,31 +378,51 @@
});
::android::sp<hidlevs::V1_0::IEvsCamera> c = mEnumerator->openCamera(desc.cameraId);
- EXPECT_NE(nullptr, c);
+ if (!c) {
+ LOG(ERROR) << "Failed to open a camera " << desc.cameraId;
+ return false;
+ }
- // Request to start a video stream and wait for a given number of frames.
- ::android::sp<StreamCallback> cb = new (std::nothrow) StreamCallback(frameCb);
- EXPECT_NE(nullptr, cb);
- EXPECT_TRUE(c->startVideoStream(cb).isOk());
+ if (c->setMaxFramesInFlight(kNumFramesToRequest) != hidlevs::V1_0::EvsResult::OK) {
+ LOG(WARNING) << "Failed to adjust the size of the buffer pool.";
+ return false;
+ }
- std::unique_lock lk(m);
- for (auto i = 0; i < framesToReceive; ++i) {
- EXPECT_TRUE(cv.wait_for(lk, maxInterval, [&gotFrameCallback] { return gotFrameCallback; }));
- EXPECT_TRUE(gotFrameCallback);
- if (!gotFrameCallback) {
+ for (auto iter = 0; iter < numIterations; ++iter) {
+ // Request to start a video stream and wait for a given number of frames.
+ ::android::sp<StreamCallback> cb = new (std::nothrow) StreamCallback(frameCb);
+ if (!cb) {
+ LOG(ERROR) << "Failed to create a new StreamCallback object.";
continue;
}
- EXPECT_TRUE(c->doneWithFrame(receivedFrame).isOk());
- gotFrameCallback = false;
+ auto ret = c->startVideoStream(cb);
+ if (ret != hidlevs::V1_0::EvsResult::OK) {
+ LOG(ERROR) << "Failed to start a video stream on " << desc.cameraId;
+ continue;
+ }
+
+ std::unique_lock lk(m);
+ for (auto i = 0; i < framesToReceive; ++i) {
+ EXPECT_TRUE(
+ cv.wait_for(lk, maxInterval, [&gotFrameCallback] { return gotFrameCallback; }));
+ if (!gotFrameCallback) {
+ LOG(WARNING) << "Did not receive a frame " << i;
+ continue;
+ }
+
+ EXPECT_TRUE(c->doneWithFrame(receivedFrame).isOk());
+ gotFrameCallback = false;
+ }
+ lk.unlock();
+
+ // Request to stop a video stream and wait.
+ EXPECT_TRUE(c->stopVideoStream().isOk());
+
+ lk.lock();
+ EXPECT_TRUE(cv.wait_for(lk, stopTimeout, [&gotNullFrame] { return gotNullFrame; }));
}
- lk.unlock();
- // Request to stop a video stream and wait.
- EXPECT_TRUE(c->stopVideoStream().isOk());
-
- lk.lock();
- EXPECT_TRUE(cv.wait_for(lk, stopTimeout, [&gotNullFrame] { return gotNullFrame; }));
EXPECT_TRUE(mEnumerator->closeCamera(c).isOk());
return true;
diff --git a/cpp/evs/manager/aidl/tests/unit/src/EvsEnumeratorUnitTest.cpp b/cpp/evs/manager/aidl/tests/unit/src/EvsEnumeratorUnitTest.cpp
index 7cb883c..f442560 100644
--- a/cpp/evs/manager/aidl/tests/unit/src/EvsEnumeratorUnitTest.cpp
+++ b/cpp/evs/manager/aidl/tests/unit/src/EvsEnumeratorUnitTest.cpp
@@ -103,7 +103,8 @@
bool VerifyCameraStream(const CameraDesc& desc, size_t framesToReceive,
std::chrono::duration<long double> maxInterval,
std::chrono::duration<long double> eventTimeout,
- const std::string& name, StreamStartedCallbackFunc cb);
+ const std::string& name, StreamStartedCallbackFunc cb,
+ size_t numIterations);
protected:
// Class members declared here can be used by all tests in the test suite
@@ -306,13 +307,14 @@
std::packaged_task<bool()> task(std::bind(&EvsEnumeratorUnitTest::VerifyCameraStream, this,
desc, kFramesToReceive, kMaxFrameInterval,
- kEventTimeout, desc.id, doNothingFunc));
+ kEventTimeout, desc.id, doNothingFunc,
+ /* numIterations= */ 3));
std::future<bool> result = task.get_future();
std::thread t(std::move(task));
t.detach();
EXPECT_EQ(std::future_status::ready, result.wait_for(kResultTimeout));
- EXPECT_TRUE(result.get());
+ ASSERT_TRUE(result.get());
// TODO(b/250699038): This test will likely fail to request a video
// stream on the next camera without this interval.
@@ -340,10 +342,12 @@
std::packaged_task<bool()> task0(std::bind(&EvsEnumeratorUnitTest::VerifyCameraStream, this,
desc, kFramesToReceive, kMaxFrameInterval,
- kEventTimeout, "client0", doNothingFunc));
+ kEventTimeout, "client0", doNothingFunc,
+ /* numIterations= */ 1));
std::packaged_task<bool()> task1(std::bind(&EvsEnumeratorUnitTest::VerifyCameraStream, this,
desc, kFramesToReceive, kMaxFrameInterval,
- kEventTimeout, "client1", doNothingFunc));
+ kEventTimeout, "client1", doNothingFunc,
+ /* numIterations= */ 1));
std::future<bool> result0 = task0.get_future();
std::future<bool> result1 = task1.get_future();
@@ -355,8 +359,8 @@
EXPECT_EQ(std::future_status::ready, result0.wait_for(kResultTimeout));
EXPECT_EQ(std::future_status::ready, result1.wait_for(kResultTimeout));
- EXPECT_TRUE(result0.get());
- EXPECT_TRUE(result1.get());
+ ASSERT_TRUE(result0.get());
+ ASSERT_TRUE(result1.get());
// TODO(b/250699038): This test will likely fail to request a video
// stream on the next camera without this interval.
@@ -380,10 +384,12 @@
std::packaged_task<bool()> task0(std::bind(&EvsEnumeratorUnitTest::VerifyCameraStream, this,
desc0, kFramesToReceive, kMaxFrameInterval,
- kEventTimeout, desc0.id, doNothingFunc));
+ kEventTimeout, desc0.id, doNothingFunc,
+ /* numIterations= */ 1));
std::packaged_task<bool()> task1(std::bind(&EvsEnumeratorUnitTest::VerifyCameraStream, this,
desc1, kFramesToReceive, kMaxFrameInterval,
- kEventTimeout, desc1.id, doNothingFunc));
+ kEventTimeout, desc1.id, doNothingFunc,
+ /* numIterations= */ 1));
// Start sending a frame early.
mMockEvsHal->setNumberOfFramesToSend(/* numFramesToSend = */ 100);
@@ -398,8 +404,8 @@
EXPECT_EQ(std::future_status::ready, result0.wait_for(kResultTimeout));
EXPECT_EQ(std::future_status::ready, result1.wait_for(kResultTimeout));
- EXPECT_TRUE(result0.get());
- EXPECT_TRUE(result1.get());
+ ASSERT_TRUE(result0.get());
+ ASSERT_TRUE(result1.get());
// TODO(b/250699038): This test will likely fail to request a video
// stream on the next camera without this interval.
@@ -674,7 +680,8 @@
std::packaged_task<bool()> task(std::bind(&EvsEnumeratorUnitTest::VerifyCameraStream, this,
cameras[0], kFramesToReceive, kMaxFrameInterval,
- kEventTimeout, cameras[0].id, streamCb));
+ kEventTimeout, cameras[0].id, streamCb,
+ /* numIterations= */ 1));
std::future<bool> result = task.get_future();
std::thread t(std::move(task));
t.detach();
@@ -706,7 +713,7 @@
EXPECT_EQ(STATUS_OK, mEnumerator->dump(fileno(stdout), (const char**)&args[0], args.size()));
EXPECT_EQ(std::future_status::ready, result.wait_for(kResultTimeout));
- EXPECT_TRUE(result.get());
+ ASSERT_TRUE(result.get());
args.pop_back();
args.pop_back();
@@ -721,7 +728,8 @@
std::chrono::duration<long double> maxInterval,
std::chrono::duration<long double> eventTimeout,
const std::string& name,
- StreamStartedCallbackFunc callback) {
+ StreamStartedCallbackFunc callback,
+ size_t numIterations) {
std::mutex m;
std::condition_variable cv;
std::vector<BufferDesc> receivedFrames;
@@ -759,43 +767,60 @@
// Retrieve available stream configurations.
std::vector<Stream> config;
EXPECT_TRUE(mEnumerator->getStreamList(desc, &config).isOk());
- EXPECT_FALSE(config.empty());
+ if (config.empty()) {
+ LOG(ERROR) << "No stream is available.";
+ return false;
+ }
// Open a camera with the first configuration.
std::shared_ptr<IEvsCamera> c;
EXPECT_TRUE(mEnumerator->openCamera(desc.id, config[0], &c).isOk());
- EXPECT_NE(nullptr, c);
+ if (!c) {
+ LOG(ERROR) << "Failed to open a camera " << desc.id;
+ return false;
+ }
- // Request to start a video stream and wait for a given number of frames.
- std::shared_ptr<StreamCallback> cb =
- ::ndk::SharedRefBase::make<StreamCallback>(frameCb, eventCb);
- EXPECT_TRUE(c->startVideoStream(cb).isOk());
-
- std::unique_lock lk(m);
- for (auto i = 0; i < framesToReceive; ++i) {
- EXPECT_TRUE(cv.wait_for(lk, maxInterval, [&gotFrameCallback] { return gotFrameCallback; }));
- EXPECT_TRUE(gotFrameCallback);
- if (!gotFrameCallback) {
+ for (auto iter = 0; iter < numIterations; ++iter) {
+ // Request to start a video stream and wait for a given number of frames.
+ std::shared_ptr<StreamCallback> cb =
+ ::ndk::SharedRefBase::make<StreamCallback>(frameCb, eventCb);
+ if (!cb) {
+ LOG(ERROR) << "Failed to create a new StreamCallback object.";
continue;
}
- EXPECT_TRUE(c->doneWithFrame(receivedFrames).isOk());
- receivedFrames.clear();
- gotFrameCallback = false;
+ if (!c->startVideoStream(cb).isOk()) {
+ LOG(ERROR) << "Failed to start a video stream on " << desc.id;
+ continue;
+ }
+
+ std::unique_lock lk(m);
+ for (auto i = 0; i < framesToReceive; ++i) {
+ EXPECT_TRUE(
+ cv.wait_for(lk, maxInterval, [&gotFrameCallback] { return gotFrameCallback; }));
+ if (!gotFrameCallback) {
+ LOG(WARNING) << "Did not receive a frame " << i;
+ continue;
+ }
+
+ EXPECT_TRUE(c->doneWithFrame(receivedFrames).isOk());
+ receivedFrames.clear();
+ gotFrameCallback = false;
+ }
+ lk.unlock();
+
+ // Call two methods that are not implemented yet in a mock EVS HAL
+ // implementation.
+ EXPECT_TRUE(c->pauseVideoStream().isOk());
+ EXPECT_TRUE(c->resumeVideoStream().isOk());
+
+ // Request to stop a video stream and wait.
+ EXPECT_TRUE(c->stopVideoStream().isOk());
+
+ lk.lock();
+ cv.wait_for(lk, eventTimeout, [&gotEventCallback] { return gotEventCallback; });
+ EXPECT_EQ(EvsEventType::STREAM_STOPPED, receivedEvent.aType);
}
- lk.unlock();
-
- // Call two methods that are not implemented yet in a mock EVS HAL
- // implementation.
- EXPECT_TRUE(c->pauseVideoStream().isOk());
- EXPECT_TRUE(c->resumeVideoStream().isOk());
-
- // Request to stop a video stream and wait.
- EXPECT_TRUE(c->stopVideoStream().isOk());
-
- lk.lock();
- cv.wait_for(lk, eventTimeout, [&gotEventCallback] { return gotEventCallback; });
- EXPECT_EQ(EvsEventType::STREAM_STOPPED, receivedEvent.aType);
EXPECT_TRUE(mEnumerator->closeCamera(c).isOk());
diff --git a/cpp/evs/manager/aidl/tests/unit/src/MockEvsHal.cpp b/cpp/evs/manager/aidl/tests/unit/src/MockEvsHal.cpp
index f88826b..1c27d1b 100644
--- a/cpp/evs/manager/aidl/tests/unit/src/MockEvsHal.cpp
+++ b/cpp/evs/manager/aidl/tests/unit/src/MockEvsHal.cpp
@@ -253,8 +253,8 @@
.width = 64,
.height = 32,
.layers = 1,
- .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
.format = HAL_PIXEL_FORMAT_RGBA_8888,
+ .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
};
AHardwareBuffer* ahwb;
if (AHardwareBuffer_allocate(&desc, &ahwb) != ::android::NO_ERROR) {
@@ -270,8 +270,8 @@
.width = 64,
.height = 32,
.layers = 1,
- .usage = BufferUsage::CPU_READ_OFTEN,
.format = PixelFormat::RGBA_8888,
+ .usage = BufferUsage::CPU_READ_OFTEN,
.stride = 64,
},
.handle = ::android::dupToAidl(memHandle),
@@ -612,8 +612,8 @@
if (cb) {
EvsEventDesc e = {
- .deviceId = id,
.aType = EvsEventType::STREAM_STOPPED,
+ .deviceId = id,
};
cb->notify(e);
}
@@ -681,9 +681,9 @@
ON_CALL(*mockDisplay, getDisplayInfo).WillByDefault([id](DisplayDesc* out) {
DisplayDesc desc = {
.width = 1920,
+ .id = "MockDisplay" + std::to_string(id),
.height = 1080,
.orientation = Rotation::ROTATION_0,
- .id = "MockDisplay" + std::to_string(id),
.vendorFlags = id, // For the testing purpose, we put a display id in the vendor
// flag field.
};
diff --git a/cpp/evs/manager/aidl/tests/unit/src/MockHidlEvsHal.cpp b/cpp/evs/manager/aidl/tests/unit/src/MockHidlEvsHal.cpp
index ae0f188..4c204ac 100644
--- a/cpp/evs/manager/aidl/tests/unit/src/MockHidlEvsHal.cpp
+++ b/cpp/evs/manager/aidl/tests/unit/src/MockHidlEvsHal.cpp
@@ -180,8 +180,8 @@
.width = 64,
.height = 32,
.layers = 1,
- .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
.format = HAL_PIXEL_FORMAT_RGBA_8888,
+ .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
};
AHardwareBuffer* ahwb;
if (AHardwareBuffer_allocate(&desc, &ahwb) != ::android::NO_ERROR) {
@@ -612,8 +612,8 @@
if (cb) {
EvsEventDesc e = {
- .deviceId = id,
.aType = EvsEventType::STREAM_STOPPED,
+ .deviceId = id,
};
cb->notify(e);
}
diff --git a/cpp/evs/manager/aidl/tests/unit/src/MockHidlEvsHal_1_0.cpp b/cpp/evs/manager/aidl/tests/unit/src/MockHidlEvsHal_1_0.cpp
index 0541b48..c44f7c6 100644
--- a/cpp/evs/manager/aidl/tests/unit/src/MockHidlEvsHal_1_0.cpp
+++ b/cpp/evs/manager/aidl/tests/unit/src/MockHidlEvsHal_1_0.cpp
@@ -141,8 +141,8 @@
.width = 64,
.height = 32,
.layers = 1,
- .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
.format = HAL_PIXEL_FORMAT_RGBA_8888,
+ .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
};
AHardwareBuffer* ahwb;
if (AHardwareBuffer_allocate(&desc, &ahwb) != ::android::NO_ERROR) {
@@ -270,22 +270,31 @@
ON_CALL(*mockCamera, setMaxFramesInFlight)
.WillByDefault([this, id = mockCamera->getId()](uint32_t bufferCount) {
std::lock_guard l(mLock);
- size_t totalSize = mBufferPoolSize + bufferCount;
- if (totalSize < kMinimumNumBuffers) {
+ if (bufferCount < kMinimumNumBuffers) {
LOG(WARNING) << "Requested buffer pool size is too small to run a camera; "
"adjusting the pool size to "
<< kMinimumNumBuffers;
- totalSize = kMinimumNumBuffers;
- } else if (totalSize > kMaximumNumBuffers) {
+ bufferCount = kMinimumNumBuffers;
+ }
+
+ int64_t delta = bufferCount;
+ auto it = mCameraBufferPoolSize.find(id);
+ if (it != mCameraBufferPoolSize.end()) {
+ delta -= it->second;
+ }
+
+ if (delta == 0) {
+ // No further action required.
+ return EvsResult::OK;
+ }
+
+ size_t totalSize = mBufferPoolSize + delta;
+ if (totalSize > kMaximumNumBuffers) {
LOG(ERROR) << "Requested size, " << totalSize << ", exceeds the limitation.";
return EvsResult::INVALID_ARG;
}
mBufferPoolSize = totalSize;
- auto it = mCameraBufferPoolSize.find(id);
- if (it != mCameraBufferPoolSize.end()) {
- bufferCount += it->second;
- }
mCameraBufferPoolSize.insert_or_assign(id, bufferCount);
return EvsResult::OK;
});
@@ -343,7 +352,7 @@
}
if (cb) {
- // TODO(b/263438927): notify the end of a video stream by sending a null buffer.
+ cb->deliverFrame({});
}
// Join a frame-forward thread
diff --git a/cpp/evs/manager/aidl/wrappers/include/AidlDisplay.h b/cpp/evs/manager/aidl/wrappers/include/AidlDisplay.h
index a6587d7..adaa3ae 100644
--- a/cpp/evs/manager/aidl/wrappers/include/AidlDisplay.h
+++ b/cpp/evs/manager/aidl/wrappers/include/AidlDisplay.h
@@ -37,7 +37,7 @@
::ndk::ScopedAStatus setDisplayState(aidlevs::DisplayState state) override;
explicit AidlDisplay(const ::android::sp<hidlevs::V1_0::IEvsDisplay>& display) :
- mHidlDisplay(display){};
+ mHidlDisplay(display) {};
virtual ~AidlDisplay();
const ::android::sp<hidlevs::V1_0::IEvsDisplay> getHidlDisplay() const { return mHidlDisplay; }
diff --git a/cpp/evs/manager/aidl/wrappers/include/HidlDisplay.h b/cpp/evs/manager/aidl/wrappers/include/HidlDisplay.h
index 2f543a3..52aadbb 100644
--- a/cpp/evs/manager/aidl/wrappers/include/HidlDisplay.h
+++ b/cpp/evs/manager/aidl/wrappers/include/HidlDisplay.h
@@ -42,7 +42,7 @@
::android::hardware::Return<void> getDisplayInfo_1_1(getDisplayInfo_1_1_cb _hidl_cb) override;
explicit HidlDisplay(const std::shared_ptr<aidlevs::IEvsDisplay>& display) :
- mAidlDisplay(display){};
+ mAidlDisplay(display) {};
virtual ~HidlDisplay();
const std::shared_ptr<aidlevs::IEvsDisplay> getAidlDisplay() const { return mAidlDisplay; }
diff --git a/cpp/evs/manager/aidl/wrappers/src/HidlCamera.cpp b/cpp/evs/manager/aidl/wrappers/src/HidlCamera.cpp
index 5311bbd..4809720 100644
--- a/cpp/evs/manager/aidl/wrappers/src/HidlCamera.cpp
+++ b/cpp/evs/manager/aidl/wrappers/src/HidlCamera.cpp
@@ -132,6 +132,7 @@
}
mAidlCamera->stopVideoStream();
+ mAidlStream = nullptr;
return {};
}
diff --git a/cpp/evs/manager/aidl/wrappers/src/HidlCameraStream.cpp b/cpp/evs/manager/aidl/wrappers/src/HidlCameraStream.cpp
index fd53cfc..ccb0570 100644
--- a/cpp/evs/manager/aidl/wrappers/src/HidlCameraStream.cpp
+++ b/cpp/evs/manager/aidl/wrappers/src/HidlCameraStream.cpp
@@ -19,6 +19,7 @@
#include "HidlCamera.h"
#include "utils/include/Utils.h"
+#include <aidlcommonsupport/NativeHandle.h>
#include <android-base/logging.h>
#include <utils/SystemClock.h>
@@ -27,6 +28,8 @@
namespace hidlevs = ::android::hardware::automotive::evs;
using ::aidl::android::hardware::automotive::evs::BufferDesc;
+using ::aidl::android::hardware::automotive::evs::EvsEventDesc;
+using ::aidl::android::hardware::automotive::evs::EvsEventType;
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Status;
@@ -41,6 +44,16 @@
std::vector<BufferDesc> aidlBuffers(1);
aidlBuffers[0] = std::move(Utils::makeFromHidl(buffer, /* doDup= */ true));
+ if (::android::isAidlNativeHandleEmpty(aidlBuffers[0].buffer.handle)) {
+ LOG(DEBUG) << "Received a null buffer, which is the mark of the end of the stream.";
+ EvsEventDesc event{
+ .aType = EvsEventType::STREAM_STOPPED,
+ };
+ if (!mAidlStream->notify(event).isOk()) {
+ LOG(ERROR) << "Error delivering the end of stream marker";
+ }
+ }
+
// android::hardware::automotive::evs::V1_0::BufferDesc does not contain a
// timestamp so we need to fill it here.
aidlBuffers[0].timestamp = static_cast<int64_t>(::android::elapsedRealtimeNano() * 1e+3);
diff --git a/cpp/evs/sampleDriver/aidl/include/EvsEnumerator.h b/cpp/evs/sampleDriver/aidl/include/EvsEnumerator.h
index 1f3de8e..313ecb7 100644
--- a/cpp/evs/sampleDriver/aidl/include/EvsEnumerator.h
+++ b/cpp/evs/sampleDriver/aidl/include/EvsEnumerator.h
@@ -135,7 +135,7 @@
// never accessed concurrently despite potentially having multiple instance objects
// using them.
static std::unordered_map<std::string, CameraRecord> sCameraList;
- // Object destructs if client dies.
+ // Object destructs if client dies.
static std::mutex sLock; // Mutex on shared camera device list.
static std::condition_variable sCameraSignal; // Signal on camera device addition.
static std::unique_ptr<ConfigManager> sConfigManager; // ConfigManager
diff --git a/cpp/evs/sampleDriver/aidl/include/EvsV4lCamera.h b/cpp/evs/sampleDriver/aidl/include/EvsV4lCamera.h
index 1e13b30..c4b4683 100644
--- a/cpp/evs/sampleDriver/aidl/include/EvsV4lCamera.h
+++ b/cpp/evs/sampleDriver/aidl/include/EvsV4lCamera.h
@@ -116,7 +116,7 @@
buffer_handle_t handle;
bool inUse;
- explicit BufferRecord(buffer_handle_t h) : handle(h), inUse(false){};
+ explicit BufferRecord(buffer_handle_t h) : handle(h), inUse(false) {};
};
// Graphics buffers to transfer images
diff --git a/cpp/evs/sampleDriver/aidl/src/ConfigManager.cpp b/cpp/evs/sampleDriver/aidl/src/ConfigManager.cpp
index c8c4453..1e31d2f 100644
--- a/cpp/evs/sampleDriver/aidl/src/ConfigManager.cpp
+++ b/cpp/evs/sampleDriver/aidl/src/ConfigManager.cpp
@@ -612,8 +612,7 @@
p += count * sizeof(camera_metadata_rational_t);
break;
default:
- LOG(WARNING) << "Type " << type << " is unknown; "
- << "data may be corrupted.";
+ LOG(WARNING) << "Type " << type << " is unknown; " << "data may be corrupted.";
break;
}
}
@@ -715,8 +714,7 @@
p += count * sizeof(camera_metadata_rational_t);
break;
default:
- LOG(WARNING) << "Type " << type << " is unknown; "
- << "data may be corrupted.";
+ LOG(WARNING) << "Type " << type << " is unknown; " << "data may be corrupted.";
break;
}
}
diff --git a/cpp/evs/sampleDriver/aidl/src/EvsEnumerator.cpp b/cpp/evs/sampleDriver/aidl/src/EvsEnumerator.cpp
index 4759d82..b79ed10 100644
--- a/cpp/evs/sampleDriver/aidl/src/EvsEnumerator.cpp
+++ b/cpp/evs/sampleDriver/aidl/src/EvsEnumerator.cpp
@@ -154,8 +154,8 @@
bool EvsEnumerator::checkPermission() {
const auto uid = AIBinder_getCallingUid();
if (kAllowedUids.find(uid) == kAllowedUids.end()) {
- LOG(ERROR) << "EVS access denied: "
- << "pid = " << AIBinder_getCallingPid() << ", uid = " << uid;
+ LOG(ERROR) << "EVS access denied: " << "pid = " << AIBinder_getCallingPid()
+ << ", uid = " << uid;
return false;
}
@@ -229,8 +229,8 @@
}
}
- LOG(INFO) << "Found " << captureCount << " qualified video capture devices "
- << "of " << videoCount << " checked.";
+ LOG(INFO) << "Found " << captureCount << " qualified video capture devices " << "of "
+ << videoCount << " checked.";
}
uint64_t EvsEnumerator::enumerateDisplays() {
diff --git a/cpp/evs/sampleDriver/aidl/src/EvsGlDisplay.cpp b/cpp/evs/sampleDriver/aidl/src/EvsGlDisplay.cpp
index 84d350a..bef01f1 100644
--- a/cpp/evs/sampleDriver/aidl/src/EvsGlDisplay.cpp
+++ b/cpp/evs/sampleDriver/aidl/src/EvsGlDisplay.cpp
@@ -335,9 +335,7 @@
// If we don't already have a buffer, allocate one now
// mBuffer.memHandle is a type of buffer_handle_t, which is equal to
// native_handle_t*.
- mBufferReadyToUse.wait(lock, [this]() REQUIRES(mLock) {
- return !mBufferBusy;
- });
+ mBufferReadyToUse.wait(lock, [this]() REQUIRES(mLock) { return !mBufferBusy; });
// Do we have a frame available?
if (mBufferBusy) {
@@ -413,9 +411,7 @@
mBufferReady = true;
mBufferReadyToRender.notify_all();
- if (!mBufferDone.wait_for(lock, kTimeout, [this]() REQUIRES(mLock) {
- return !mBufferBusy;
- })) {
+ if (!mBufferDone.wait_for(lock, kTimeout, [this]() REQUIRES(mLock) { return !mBufferBusy; })) {
return ScopedAStatus::fromServiceSpecificError(
static_cast<int>(EvsResult::UNDERLYING_SERVICE_ERROR));
}
diff --git a/cpp/evs/sampleDriver/aidl/src/EvsV4lCamera.cpp b/cpp/evs/sampleDriver/aidl/src/EvsV4lCamera.cpp
index 5f957a8..f9338c6 100644
--- a/cpp/evs/sampleDriver/aidl/src/EvsV4lCamera.cpp
+++ b/cpp/evs/sampleDriver/aidl/src/EvsV4lCamera.cpp
@@ -382,8 +382,7 @@
// If we've been displaced by another owner of the camera, then we can't do anything else
if (!mVideo.isOpen()) {
- LOG(WARNING) << "Ignoring a request add external buffers "
- << "when camera has been lost.";
+ LOG(WARNING) << "Ignoring a request add external buffers " << "when camera has been lost.";
*_aidl_return = 0;
return ScopedAStatus::fromServiceSpecificError(static_cast<int>(EvsResult::OWNERSHIP_LOST));
}
diff --git a/cpp/evs/sampleDriver/aidl/src/VideoCapture.cpp b/cpp/evs/sampleDriver/aidl/src/VideoCapture.cpp
index d1d860c..d0e9009 100644
--- a/cpp/evs/sampleDriver/aidl/src/VideoCapture.cpp
+++ b/cpp/evs/sampleDriver/aidl/src/VideoCapture.cpp
@@ -109,10 +109,9 @@
mHeight = format.fmt.pix.height;
mStride = format.fmt.pix.bytesperline;
- LOG(INFO) << "Current output format: "
- << "fmt=0x" << std::hex << format.fmt.pix.pixelformat << ", " << std::dec
- << format.fmt.pix.width << " x " << format.fmt.pix.height
- << ", pitch=" << format.fmt.pix.bytesperline;
+ LOG(INFO) << "Current output format: " << "fmt=0x" << std::hex
+ << format.fmt.pix.pixelformat << ", " << std::dec << format.fmt.pix.width << " x "
+ << format.fmt.pix.height << ", pitch=" << format.fmt.pix.bytesperline;
} else {
PLOG(ERROR) << "VIDIOC_G_FMT failed";
return false;
@@ -309,8 +308,7 @@
int VideoCapture::setParameter(v4l2_control& control) {
int status = ioctl(mDeviceFd, VIDIOC_S_CTRL, &control);
if (status < 0) {
- PLOG(ERROR) << "Failed to program a parameter value "
- << "id = " << std::hex << control.id;
+ PLOG(ERROR) << "Failed to program a parameter value " << "id = " << std::hex << control.id;
}
return status;
@@ -319,8 +317,8 @@
int VideoCapture::getParameter(v4l2_control& control) {
int status = ioctl(mDeviceFd, VIDIOC_G_CTRL, &control);
if (status < 0) {
- PLOG(ERROR) << "Failed to read a parameter value"
- << " fd = " << std::hex << mDeviceFd << " id = " << control.id;
+ PLOG(ERROR) << "Failed to read a parameter value" << " fd = " << std::hex << mDeviceFd
+ << " id = " << control.id;
}
return status;
diff --git a/cpp/evs/sampleDriver/aidl/src/bufferCopy.cpp b/cpp/evs/sampleDriver/aidl/src/bufferCopy.cpp
index 09a1dcc..8d70d6a 100644
--- a/cpp/evs/sampleDriver/aidl/src/bufferCopy.cpp
+++ b/cpp/evs/sampleDriver/aidl/src/bufferCopy.cpp
@@ -172,7 +172,7 @@
// Note: we're walking two pixels at a time here (even/odd)
uint32_t srcPixel = *src++;
- uint8_t Y1 = (srcPixel)&0xFF;
+ uint8_t Y1 = (srcPixel) & 0xFF;
uint8_t U = (srcPixel >> 8) & 0xFF;
uint8_t Y2 = (srcPixel >> 16) & 0xFF;
uint8_t V = (srcPixel >> 24) & 0xFF;
diff --git a/cpp/evs/sampleDriver/hidl/ConfigManager.cpp b/cpp/evs/sampleDriver/hidl/ConfigManager.cpp
index 74fb59d..b1a8b11 100644
--- a/cpp/evs/sampleDriver/hidl/ConfigManager.cpp
+++ b/cpp/evs/sampleDriver/hidl/ConfigManager.cpp
@@ -612,8 +612,7 @@
p += count * sizeof(camera_metadata_rational_t);
break;
default:
- LOG(WARNING) << "Type " << type << " is unknown; "
- << "data may be corrupted.";
+ LOG(WARNING) << "Type " << type << " is unknown; " << "data may be corrupted.";
break;
}
}
@@ -714,8 +713,7 @@
p += count * sizeof(camera_metadata_rational_t);
break;
default:
- LOG(WARNING) << "Type " << type << " is unknown; "
- << "data may be corrupted.";
+ LOG(WARNING) << "Type " << type << " is unknown; " << "data may be corrupted.";
break;
}
}
diff --git a/cpp/evs/sampleDriver/hidl/ConfigManager.h b/cpp/evs/sampleDriver/hidl/ConfigManager.h
index 91e32a5..9ff6e84 100644
--- a/cpp/evs/sampleDriver/hidl/ConfigManager.h
+++ b/cpp/evs/sampleDriver/hidl/ConfigManager.h
@@ -53,8 +53,7 @@
/* Camera device's capabilities and metadata */
class CameraInfo {
public:
- CameraInfo() : characteristics(nullptr) { /* Nothing to do */
- }
+ CameraInfo() : characteristics(nullptr) { /* Nothing to do */ }
virtual ~CameraInfo();
diff --git a/cpp/evs/sampleDriver/hidl/EvsEnumerator.cpp b/cpp/evs/sampleDriver/hidl/EvsEnumerator.cpp
index 2e95ddd..2279b22 100644
--- a/cpp/evs/sampleDriver/hidl/EvsEnumerator.cpp
+++ b/cpp/evs/sampleDriver/hidl/EvsEnumerator.cpp
@@ -63,8 +63,8 @@
bool EvsEnumerator::checkPermission() {
hardware::IPCThreadState* ipc = hardware::IPCThreadState::self();
if (AID_AUTOMOTIVE_EVS != ipc->getCallingUid() && AID_ROOT != ipc->getCallingUid()) {
- LOG(ERROR) << "EVS access denied: "
- << "pid = " << ipc->getCallingPid() << ", uid = " << ipc->getCallingUid();
+ LOG(ERROR) << "EVS access denied: " << "pid = " << ipc->getCallingPid()
+ << ", uid = " << ipc->getCallingUid();
return false;
}
@@ -189,8 +189,8 @@
}
}
- LOG(INFO) << "Found " << captureCount << " qualified video capture devices "
- << "of " << videoCount << " checked.";
+ LOG(INFO) << "Found " << captureCount << " qualified video capture devices " << "of "
+ << videoCount << " checked.";
}
void EvsEnumerator::enumerateDisplays() {
diff --git a/cpp/evs/sampleDriver/hidl/EvsV4lCamera.cpp b/cpp/evs/sampleDriver/hidl/EvsV4lCamera.cpp
index 780ebde..a8cd6de 100644
--- a/cpp/evs/sampleDriver/hidl/EvsV4lCamera.cpp
+++ b/cpp/evs/sampleDriver/hidl/EvsV4lCamera.cpp
@@ -439,8 +439,7 @@
// If we've been displaced by another owner of the camera, then we can't do anything else
if (!mVideo.isOpen()) {
- LOG(WARNING) << "Ignoring a request add external buffers "
- << "when camera has been lost.";
+ LOG(WARNING) << "Ignoring a request add external buffers " << "when camera has been lost.";
_hidl_cb(EvsResult::UNDERLYING_SERVICE_ERROR, mFramesAllowed);
return {};
}
diff --git a/cpp/evs/sampleDriver/hidl/EvsV4lCamera.h b/cpp/evs/sampleDriver/hidl/EvsV4lCamera.h
index 290f217..358e8af 100644
--- a/cpp/evs/sampleDriver/hidl/EvsV4lCamera.h
+++ b/cpp/evs/sampleDriver/hidl/EvsV4lCamera.h
@@ -128,7 +128,7 @@
buffer_handle_t handle;
bool inUse;
- explicit BufferRecord(buffer_handle_t h) : handle(h), inUse(false){};
+ explicit BufferRecord(buffer_handle_t h) : handle(h), inUse(false) {};
};
std::vector<BufferRecord> mBuffers; // Graphics buffers to transfer images
diff --git a/cpp/evs/sampleDriver/hidl/VideoCapture.cpp b/cpp/evs/sampleDriver/hidl/VideoCapture.cpp
index f0f4ab9..ff2de96 100644
--- a/cpp/evs/sampleDriver/hidl/VideoCapture.cpp
+++ b/cpp/evs/sampleDriver/hidl/VideoCapture.cpp
@@ -109,10 +109,9 @@
mHeight = format.fmt.pix.height;
mStride = format.fmt.pix.bytesperline;
- LOG(INFO) << "Current output format: "
- << "fmt=0x" << std::hex << format.fmt.pix.pixelformat << ", " << std::dec
- << format.fmt.pix.width << " x " << format.fmt.pix.height
- << ", pitch=" << format.fmt.pix.bytesperline;
+ LOG(INFO) << "Current output format: " << "fmt=0x" << std::hex
+ << format.fmt.pix.pixelformat << ", " << std::dec << format.fmt.pix.width << " x "
+ << format.fmt.pix.height << ", pitch=" << format.fmt.pix.bytesperline;
} else {
PLOG(ERROR) << "VIDIOC_G_FMT failed";
return false;
@@ -309,8 +308,7 @@
int VideoCapture::setParameter(v4l2_control& control) {
int status = ioctl(mDeviceFd, VIDIOC_S_CTRL, &control);
if (status < 0) {
- PLOG(ERROR) << "Failed to program a parameter value "
- << "id = " << std::hex << control.id;
+ PLOG(ERROR) << "Failed to program a parameter value " << "id = " << std::hex << control.id;
}
return status;
@@ -319,8 +317,8 @@
int VideoCapture::getParameter(v4l2_control& control) {
int status = ioctl(mDeviceFd, VIDIOC_G_CTRL, &control);
if (status < 0) {
- PLOG(ERROR) << "Failed to read a parameter value"
- << " fd = " << std::hex << mDeviceFd << " id = " << control.id;
+ PLOG(ERROR) << "Failed to read a parameter value" << " fd = " << std::hex << mDeviceFd
+ << " id = " << control.id;
}
return status;
diff --git a/cpp/evs/sampleDriver/hidl/bufferCopy.cpp b/cpp/evs/sampleDriver/hidl/bufferCopy.cpp
index ea62298..9af9e7f 100644
--- a/cpp/evs/sampleDriver/hidl/bufferCopy.cpp
+++ b/cpp/evs/sampleDriver/hidl/bufferCopy.cpp
@@ -212,7 +212,7 @@
// Note: we're walking two pixels at a time here (even/odd)
uint32_t srcPixel = *src++;
- uint8_t Y1 = (srcPixel)&0xFF;
+ uint8_t Y1 = (srcPixel) & 0xFF;
uint8_t U = (srcPixel >> 8) & 0xFF;
uint8_t Y2 = (srcPixel >> 16) & 0xFF;
uint8_t V = (srcPixel >> 24) & 0xFF;
diff --git a/cpp/evs/support_library/BaseAnalyzeCallback.h b/cpp/evs/support_library/BaseAnalyzeCallback.h
index b406b31..7ec628a 100644
--- a/cpp/evs/support_library/BaseAnalyzeCallback.h
+++ b/cpp/evs/support_library/BaseAnalyzeCallback.h
@@ -27,7 +27,7 @@
class BaseAnalyzeCallback {
public:
virtual void analyze(const Frame&) = 0;
- virtual ~BaseAnalyzeCallback(){};
+ virtual ~BaseAnalyzeCallback() {};
};
} // namespace support
diff --git a/cpp/evs/support_library/BaseUseCase.h b/cpp/evs/support_library/BaseUseCase.h
index d0c3ed7..a76174d 100644
--- a/cpp/evs/support_library/BaseUseCase.h
+++ b/cpp/evs/support_library/BaseUseCase.h
@@ -66,7 +66,7 @@
*
* @param The ids for the desired EVS cameras.
*/
- BaseUseCase(vector<string> cameraIds) : mCameraIds(cameraIds){};
+ BaseUseCase(vector<string> cameraIds) : mCameraIds(cameraIds) {};
virtual ~BaseUseCase() {}
diff --git a/cpp/evs/support_library/DisplayUseCase.cpp b/cpp/evs/support_library/DisplayUseCase.cpp
index 5f30e39..b635dd2 100644
--- a/cpp/evs/support_library/DisplayUseCase.cpp
+++ b/cpp/evs/support_library/DisplayUseCase.cpp
@@ -153,8 +153,7 @@
return;
}
- while (mIsReadyToRun && streamFrame())
- ;
+ while (mIsReadyToRun && streamFrame());
ALOGD("Worker thread stops.");
});
diff --git a/cpp/evs/support_library/FormatConvert.cpp b/cpp/evs/support_library/FormatConvert.cpp
index 1999b47..e8dd6aa 100644
--- a/cpp/evs/support_library/FormatConvert.cpp
+++ b/cpp/evs/support_library/FormatConvert.cpp
@@ -126,7 +126,7 @@
// Note: we're walking two pixels at a time here (even/odd)
uint32_t srcPixel = *srcWords++;
- uint8_t Y1 = (srcPixel)&0xFF;
+ uint8_t Y1 = (srcPixel) & 0xFF;
uint8_t U = (srcPixel >> 8) & 0xFF;
uint8_t Y2 = (srcPixel >> 16) & 0xFF;
uint8_t V = (srcPixel >> 24) & 0xFF;
diff --git a/cpp/evs/support_library/RenderBase.h b/cpp/evs/support_library/RenderBase.h
index 0cc5ac0..43dd7c2 100644
--- a/cpp/evs/support_library/RenderBase.h
+++ b/cpp/evs/support_library/RenderBase.h
@@ -40,7 +40,7 @@
*/
class RenderBase {
public:
- virtual ~RenderBase(){};
+ virtual ~RenderBase() {};
virtual bool activate() = 0;
virtual void deactivate() = 0;
diff --git a/cpp/libsysfsmonitor/Android.bp b/cpp/libsysfsmonitor/Android.bp
index 545255b..f945786 100644
--- a/cpp/libsysfsmonitor/Android.bp
+++ b/cpp/libsysfsmonitor/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/powerpolicy/aidl/Android.bp b/cpp/powerpolicy/aidl/Android.bp
index 56a578f..4ed1246 100644
--- a/cpp/powerpolicy/aidl/Android.bp
+++ b/cpp/powerpolicy/aidl/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -40,4 +41,12 @@
imports: [
"android.frameworks.automotive.powerpolicy-V3",
],
+ versions_with_info: [
+ {
+ version: "1",
+ imports: ["android.frameworks.automotive.powerpolicy-V3"],
+ },
+ ],
+ frozen: true,
+
}
diff --git a/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/.hash b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/.hash
new file mode 100644
index 0000000..2332a47
--- /dev/null
+++ b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/.hash
@@ -0,0 +1 @@
+6680964727eb3dc4dd6b0eac15a0945f00be1fa1
diff --git a/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegate.aidl b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegate.aidl
new file mode 100644
index 0000000..f4c458c
--- /dev/null
+++ b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegate.aidl
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.automotive.powerpolicy.internal;
+/* @hide */
+interface ICarPowerPolicyDelegate {
+ android.automotive.powerpolicy.internal.PowerPolicyInitData notifyCarServiceReady(in android.automotive.powerpolicy.internal.ICarPowerPolicyDelegateCallback callback);
+ void applyPowerPolicyAsync(int requestId, in @utf8InCpp String policyId, boolean force);
+ void setPowerPolicyGroup(in @utf8InCpp String policyGroupId);
+ void notifyPowerPolicyDefinition(in @utf8InCpp String policyId, in @utf8InCpp String[] enabledComponents, in @utf8InCpp String[] disabledComponents);
+ void notifyPowerPolicyGroupDefinition(in @utf8InCpp String policyGroupId, in String[] powerPolicyPerState);
+ void applyPowerPolicyPerPowerStateChangeAsync(int requestId, in android.automotive.powerpolicy.internal.ICarPowerPolicyDelegate.PowerState state);
+ void setSilentMode(in @utf8InCpp String silentMode);
+ @Backing(type="int")
+ enum PowerState {
+ INVALID = 0,
+ WAIT_FOR_VHAL = 1,
+ SUSPEND_ENTER = 2,
+ SUSPEND_EXIT = 3,
+ SHUTDOWN_ENTER = 5,
+ ON = 6,
+ SHUTDOWN_PREPARE = 7,
+ SHUTDOWN_CANCELLED = 8,
+ HIBERNATION_ENTER = 9,
+ HIBERNATION_EXIT = 10,
+ PRE_SHUTDOWN_PREPARE = 11,
+ POST_SUSPEND_ENTER = 12,
+ POST_SHUTDOWN_ENTER = 13,
+ POST_HIBERNATION_ENTER = 14,
+ }
+}
diff --git a/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegateCallback.aidl b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegateCallback.aidl
new file mode 100644
index 0000000..ccd6db0
--- /dev/null
+++ b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegateCallback.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.automotive.powerpolicy.internal;
+/* @hide */
+interface ICarPowerPolicyDelegateCallback {
+ void updatePowerComponents(in android.frameworks.automotive.powerpolicy.CarPowerPolicy powerPolicy);
+ oneway void onApplyPowerPolicySucceeded(int requestId, in android.frameworks.automotive.powerpolicy.CarPowerPolicy accumulatedPolicy, boolean deferred);
+ oneway void onApplyPowerPolicyFailed(int requestId, in android.automotive.powerpolicy.internal.PowerPolicyFailureReason reason);
+ oneway void onPowerPolicyChanged(in android.frameworks.automotive.powerpolicy.CarPowerPolicy accumulatedPolicy);
+}
diff --git a/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/android/automotive/powerpolicy/internal/PowerPolicyFailureReason.aidl b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/android/automotive/powerpolicy/internal/PowerPolicyFailureReason.aidl
new file mode 100644
index 0000000..f72b81b
--- /dev/null
+++ b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/android/automotive/powerpolicy/internal/PowerPolicyFailureReason.aidl
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *////////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.automotive.powerpolicy.internal;
+/* @hide */
+@Backing(type="int")
+enum PowerPolicyFailureReason {
+ POWER_POLICY_FAILURE_UNKNOWN = 0,
+ POWER_POLICY_FAILURE_NOT_REGISTERED_ID = 1,
+ POWER_POLICY_FAILURE_CANNOT_OVERRIDE = 2,
+}
diff --git a/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/android/automotive/powerpolicy/internal/PowerPolicyInitData.aidl b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/android/automotive/powerpolicy/internal/PowerPolicyInitData.aidl
new file mode 100644
index 0000000..e4aa331
--- /dev/null
+++ b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/1/android/automotive/powerpolicy/internal/PowerPolicyInitData.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.automotive.powerpolicy.internal;
+/* @hide */
+parcelable PowerPolicyInitData {
+ int[] registeredCustomComponents;
+ android.frameworks.automotive.powerpolicy.CarPowerPolicy currentPowerPolicy;
+ android.frameworks.automotive.powerpolicy.CarPowerPolicy[] registeredPolicies;
+}
diff --git a/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/current/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegate.aidl b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/current/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegate.aidl
index d674e10..f4c458c 100644
--- a/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/current/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegate.aidl
+++ b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/current/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegate.aidl
@@ -40,6 +40,7 @@
void notifyPowerPolicyDefinition(in @utf8InCpp String policyId, in @utf8InCpp String[] enabledComponents, in @utf8InCpp String[] disabledComponents);
void notifyPowerPolicyGroupDefinition(in @utf8InCpp String policyGroupId, in String[] powerPolicyPerState);
void applyPowerPolicyPerPowerStateChangeAsync(int requestId, in android.automotive.powerpolicy.internal.ICarPowerPolicyDelegate.PowerState state);
+ void setSilentMode(in @utf8InCpp String silentMode);
@Backing(type="int")
enum PowerState {
INVALID = 0,
diff --git a/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/current/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegateCallback.aidl b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/current/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegateCallback.aidl
index 96c18bf..ccd6db0 100644
--- a/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/current/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegateCallback.aidl
+++ b/cpp/powerpolicy/aidl/aidl_api/android.automotive.powerpolicy.delegate/current/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegateCallback.aidl
@@ -35,7 +35,7 @@
/* @hide */
interface ICarPowerPolicyDelegateCallback {
void updatePowerComponents(in android.frameworks.automotive.powerpolicy.CarPowerPolicy powerPolicy);
- oneway void onApplyPowerPolicySucceeded(int requestId, in android.frameworks.automotive.powerpolicy.CarPowerPolicy accumulatedPolicy);
+ oneway void onApplyPowerPolicySucceeded(int requestId, in android.frameworks.automotive.powerpolicy.CarPowerPolicy accumulatedPolicy, boolean deferred);
oneway void onApplyPowerPolicyFailed(int requestId, in android.automotive.powerpolicy.internal.PowerPolicyFailureReason reason);
oneway void onPowerPolicyChanged(in android.frameworks.automotive.powerpolicy.CarPowerPolicy accumulatedPolicy);
}
diff --git a/cpp/powerpolicy/aidl/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegate.aidl b/cpp/powerpolicy/aidl/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegate.aidl
index 38ba34d..3db2b09 100644
--- a/cpp/powerpolicy/aidl/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegate.aidl
+++ b/cpp/powerpolicy/aidl/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegate.aidl
@@ -140,4 +140,14 @@
* @throws SecurityException if the caller doesn't have sufficient permissions.
*/
void applyPowerPolicyPerPowerStateChangeAsync(int requestId, in PowerState state);
+
+ /**
+ * CarService uses this method to tell how Silent Mode works.
+ *
+ * @param silentMode Mode telling how Silent Mode works. It should be one of "forced-silent",
+ * "forced-non-silent", "non-forced-silent-mode".
+ * @throws IllegalArgumentException if the given silentMode is not valid.
+ * @throws SecurityException if the caller doesn't have sufficient permissions.
+ */
+ void setSilentMode(in @utf8InCpp String silentMode);
}
diff --git a/cpp/powerpolicy/aidl/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegateCallback.aidl b/cpp/powerpolicy/aidl/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegateCallback.aidl
index 5a06a6a..d1d8055 100644
--- a/cpp/powerpolicy/aidl/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegateCallback.aidl
+++ b/cpp/powerpolicy/aidl/android/automotive/powerpolicy/internal/ICarPowerPolicyDelegateCallback.aidl
@@ -45,8 +45,11 @@
*
* @param requestId ID returned by {@code applyPowerPolicyAsync}.
* @param accumulatedPolicy the current accumulated power policy after the request was applied.
+ * @param deferred if {@code true}, the power policy will be applied later, and
+ * {@code accululatedPolicy} should be ignored.
*/
- oneway void onApplyPowerPolicySucceeded(int requestId, in CarPowerPolicy accumulatedPolicy);
+ oneway void onApplyPowerPolicySucceeded(int requestId, in CarPowerPolicy accumulatedPolicy,
+ boolean deferred);
/**
* This is called when car power policy daemon fails to apply the power policy.
diff --git a/cpp/powerpolicy/client/Android.bp b/cpp/powerpolicy/client/Android.bp
index f6d5ff1..b37671a 100644
--- a/cpp/powerpolicy/client/Android.bp
+++ b/cpp/powerpolicy/client/Android.bp
@@ -13,11 +13,12 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
cc_defaults {
- name: "powerpolicyclient_defaults",
+ name: "powerpolicyclient_lib_build_defaults",
cflags: [
"-Wall",
"-Wno-missing-field-initializers",
@@ -26,13 +27,21 @@
"-Wunused-parameter",
],
shared_libs: [
- "android.frameworks.automotive.powerpolicy-V2-ndk",
+ "android.frameworks.automotive.powerpolicy-V3-ndk",
"libbase",
"libbinder_ndk",
"libutils",
],
}
+cc_defaults {
+ name: "powerpolicyclient_defaults",
+ shared_libs: [
+ "android.frameworks.automotive.powerpolicy-V3-ndk",
+ "libpowerpolicyclient",
+ ],
+}
+
cc_library {
name: "libpowerpolicyclient",
vendor_available: true,
@@ -41,7 +50,7 @@
"src/PowerPolicyClientBase.cpp",
],
defaults: [
- "powerpolicyclient_defaults",
+ "powerpolicyclient_lib_build_defaults",
],
export_include_dirs: ["include"],
}
diff --git a/cpp/powerpolicy/server/Android.bp b/cpp/powerpolicy/server/Android.bp
index 114075d..d9352ed 100644
--- a/cpp/powerpolicy/server/Android.bp
+++ b/cpp/powerpolicy/server/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -38,10 +39,10 @@
"libtinyxml2",
"libutils",
"server_configurable_flags",
+ "libaconfig_storage_read_api_cc",
],
static_libs: [
"android.car.feature-aconfig-cpp",
- "libc++fs",
"libsysfsmonitor",
"libvhalclient",
],
diff --git a/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp b/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
index 59c14e3..7a62c95 100644
--- a/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
+++ b/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
@@ -312,6 +312,12 @@
"applyPowerPolicyPerPowerStateChangeAsync");
}
+ScopedAStatus CarPowerPolicyDelegate::setSilentMode(const std::string& silentMode) {
+ return runWithService([silentMode](CarPowerPolicyServer* service)
+ -> ScopedAStatus { return service->setSilentMode(silentMode); },
+ "setSilentMode");
+}
+
ScopedAStatus CarPowerPolicyDelegate::runWithService(
const std::function<ScopedAStatus(CarPowerPolicyServer*)>& action,
const std::string& actionTitle) {
@@ -599,7 +605,7 @@
fromServiceSpecificErrorWithMessage(EX_ILLEGAL_ARGUMENT,
StringPrintf("Power policy cannot be "
"changed for power state(%d)",
- state)
+ static_cast<int32_t>(state))
.c_str());
}
std::string powerStateName = toString(apPowerState);
@@ -622,7 +628,9 @@
powerStateName.c_str());
}
- if (auto ret = enqueuePowerPolicyRequest(requestId, policyId, /*force=*/false); !ret.isOk()) {
+ const bool useForce = !mSilentModeHandler.isSilentMode();
+
+ if (auto ret = enqueuePowerPolicyRequest(requestId, policyId, useForce); !ret.isOk()) {
ALOGW("Failed to apply power policy(%s) for power state(%s) with request ID(%d)",
policyId.c_str(), powerStateName.c_str(), requestId);
return ret;
@@ -630,6 +638,18 @@
return ScopedAStatus::ok();
}
+ScopedAStatus CarPowerPolicyServer::setSilentMode(const std::string& silentMode) {
+ ScopedAStatus status = checkSystemPermission();
+ if (!status.isOk()) {
+ return status;
+ }
+ if (auto ret = mSilentModeHandler.setSilentMode(silentMode); !ret.isOk()) {
+ ALOGW("Failed to set Silent Mode(%s)", silentMode.c_str());
+ return ret;
+ }
+ return ScopedAStatus::ok();
+}
+
ScopedAStatus CarPowerPolicyServer::applyPowerPolicyAsync(int32_t requestId,
const std::string& policyId, bool force) {
ScopedAStatus status = checkSystemPermission();
@@ -887,9 +907,9 @@
convertErrorToFailureReason(ret.error().code()));
}
return;
- }
- if (callback != nullptr) {
- callback->onApplyPowerPolicySucceeded(requestId, *mComponentHandler.getAccumulatedPolicy());
+ } else if (callback != nullptr) {
+ callback->onApplyPowerPolicySucceeded(requestId, *mComponentHandler.getAccumulatedPolicy(),
+ !*ret);
}
}
@@ -987,7 +1007,7 @@
ALOGI("The current power policy is %s", policyId.c_str());
}
-Result<void> CarPowerPolicyServer::applyPowerPolicyInternal(const std::string& policyId,
+Result<bool> CarPowerPolicyServer::applyPowerPolicyInternal(const std::string& policyId,
const bool force,
const bool notifyCarService) {
auto policyMeta = mPolicyManager.getPowerPolicy(policyId);
@@ -999,21 +1019,22 @@
{
Mutex::Autolock lock(mMutex);
if (!canApplyPowerPolicyLocked(*policyMeta, force, /*out*/ clients)) {
- return {};
+ return false;
}
}
applyAndNotifyPowerPolicy(*policyMeta, clients, notifyCarService);
- return {};
+ return true;
}
Result<void> CarPowerPolicyServer::setPowerPolicyGroupInternal(const std::string& groupId) {
if (!mPolicyManager.isPowerPolicyGroupAvailable(groupId)) {
- return Error() << StringPrintf("Power policy group(%s) is not available", groupId.c_str());
+ return Error(EX_ILLEGAL_ARGUMENT)
+ << StringPrintf("Power policy group(%s) is not available", groupId.c_str());
}
Mutex::Autolock lock(mMutex);
- if (mIsCarServiceInOperation) {
- return Error() << "After CarService starts serving, power policy group cannot be set in "
- "car power policy daemon";
+ if (!car_power_policy_refactoring() && mIsCarServiceInOperation) {
+ return Error(EX_ILLEGAL_STATE) << "After CarService starts serving, power policy group "
+ "cannot be set in car power policy daemon";
}
mCurrentPolicyGroupId = groupId;
ALOGI("The current power policy group is |%s|", groupId.c_str());
@@ -1021,6 +1042,14 @@
}
void CarPowerPolicyServer::notifySilentModeChange(const bool isSilent) {
+ if (car_power_policy_refactoring()) {
+ notifySilentModeChangeInternal(isSilent);
+ } else {
+ notifySilentModeChangeLegacy(isSilent);
+ }
+}
+
+void CarPowerPolicyServer::notifySilentModeChangeLegacy(const bool isSilent) {
std::string pendingPowerPolicyId;
if (Mutex::Autolock lock(mMutex); mIsCarServiceInOperation) {
return;
@@ -1041,6 +1070,26 @@
}
}
+void CarPowerPolicyServer::notifySilentModeChangeInternal(const bool isSilent) {
+ std::string pendingPowerPolicyId;
+ {
+ Mutex::Autolock lock(mMutex);
+ pendingPowerPolicyId = mPendingPowerPolicyId;
+ }
+ ALOGI("Silent Mode is set to %s", isSilent ? "silent" : "non-silent");
+ Result<bool> ret;
+ if (isSilent) {
+ ret = applyPowerPolicyInternal(kSystemPolicyIdNoUserInteraction, /*force=*/false,
+ /*notifyCarService=*/true);
+ } else {
+ ret = applyPowerPolicyInternal(pendingPowerPolicyId, /*force=*/true,
+ /*notifyCarService=*/true);
+ }
+ if (!ret.ok()) {
+ ALOGW("Failed to apply power policy: %s", ret.error().message().c_str());
+ }
+}
+
bool CarPowerPolicyServer::isRegisteredLocked(const AIBinder* binder) {
return lookupPowerPolicyChangeCallback(mPolicyChangeCallbacks, binder) !=
mPolicyChangeCallbacks.end();
diff --git a/cpp/powerpolicy/server/src/CarPowerPolicyServer.h b/cpp/powerpolicy/server/src/CarPowerPolicyServer.h
index bb5a13d..a971787 100644
--- a/cpp/powerpolicy/server/src/CarPowerPolicyServer.h
+++ b/cpp/powerpolicy/server/src/CarPowerPolicyServer.h
@@ -150,7 +150,8 @@
ndk::ScopedAStatus applyPowerPolicyPerPowerStateChangeAsync(
int32_t requestId,
aidl::android::automotive::powerpolicy::internal::ICarPowerPolicyDelegate::PowerState
- state);
+ state) override;
+ ndk::ScopedAStatus setSilentMode(const std::string& silentMode) override;
void terminate() EXCLUDES(mMutex);
ndk::ScopedAStatus runWithService(
@@ -223,6 +224,7 @@
int32_t requestId,
aidl::android::automotive::powerpolicy::internal::ICarPowerPolicyDelegate::PowerState
state);
+ ndk::ScopedAStatus setSilentMode(const std::string& silentMode);
// Internal implementation of ICarPowerPolicyDelegate.aidl.
ndk::ScopedAStatus applyPowerPolicyAsync(int32_t requestId, const std::string& policyId,
@@ -233,7 +235,7 @@
aidl::android::automotive::powerpolicy::internal::PowerPolicyInitData* aidlReturn);
// Implements ISilentModeChangeHandler.
- void notifySilentModeChange(const bool isSilent) EXCLUDES(mMutex);
+ void notifySilentModeChange(const bool isSilent);
/**
* Applies the given power policy.
@@ -309,7 +311,8 @@
void applyAndNotifyPowerPolicy(const CarPowerPolicyMeta& policyMeta,
const std::vector<CallbackInfo>& clients,
const bool notifyCarService);
- android::base::Result<void> applyPowerPolicyInternal(const std::string& policyId,
+ // Returns true if the application is done, false if it is deferred.
+ android::base::Result<bool> applyPowerPolicyInternal(const std::string& policyId,
const bool force,
const bool notifyCarService)
EXCLUDES(mMutex);
@@ -317,6 +320,8 @@
EXCLUDES(mMutex);
ndk::ScopedAStatus enqueuePowerPolicyRequest(int32_t requestId, const std::string& policyId,
bool force) EXCLUDES(mMutex);
+ void notifySilentModeChangeInternal(const bool isSilent) EXCLUDES(mMutex);
+ void notifySilentModeChangeLegacy(const bool isSilent) EXCLUDES(mMutex);
static void onClientBinderDied(void* cookie);
static void onCarServiceBinderDied(void* cookie);
diff --git a/cpp/powerpolicy/server/src/SilentModeHandler.cpp b/cpp/powerpolicy/server/src/SilentModeHandler.cpp
index 8c006ae..f757f28 100644
--- a/cpp/powerpolicy/server/src/SilentModeHandler.cpp
+++ b/cpp/powerpolicy/server/src/SilentModeHandler.cpp
@@ -47,6 +47,7 @@
using ::android::base::Trim;
using ::android::base::unique_fd;
using ::android::base::WriteStringToFd;
+using ::ndk::ScopedAStatus;
namespace {
@@ -58,6 +59,18 @@
// To stop boot animation while it is being played.
constexpr const char kPropertyBootAnimationExit[] = "service.bootanim.exit";
constexpr const char* kSysfsDirForSilentMode[] = {"/sys/kernel/silent_boot", "/sys/power"};
+// Silent Mode types. Must be identical to definitions in SilentModeHandler.java.
+constexpr const char kSilentModeStringForcedSilent[] = "forced-silent";
+constexpr const char kSilentModeStringForcedNonSilent[] = "forced-non-silent";
+constexpr const char kSilentModeStringNonForced[] = "non-forced-silent-mode";
+constexpr int32_t kSilentModeTypeForcedSilent = 1;
+constexpr int32_t kSilentModeTypeForcedNonSilent = 2;
+constexpr int32_t kSilentModeTypeNonForced = 3;
+
+const std::unordered_map<std::string, int32_t> kSilentModeToType =
+ {{kSilentModeStringForcedSilent, kSilentModeTypeForcedSilent},
+ {kSilentModeStringForcedNonSilent, kSilentModeTypeForcedNonSilent},
+ {kSilentModeStringNonForced, kSilentModeTypeNonForced}};
bool fileExists(const char* filename) {
struct stat buffer;
@@ -90,14 +103,20 @@
std::string sysfsDir = searchForSysfsDir();
mSilentModeHwStateFilename = sysfsDir + kHwStateFilename;
mKernelSilentModeFilename = sysfsDir + kKernelStateFilename;
- if (mBootReason == kBootReasonForcedSilent) {
- mForcedMode = true;
- mSilentModeByHwState = true;
- } else if (mBootReason == kBootReasonForcedNonSilent) {
- mForcedMode = true;
- mSilentModeByHwState = false;
+ bool forcedMode;
+ {
+ Mutex::Autolock lock(mMutex);
+ mForcedMode = false;
+ if (mBootReason == kBootReasonForcedSilent) {
+ mForcedMode = true;
+ mSilentModeByHwState = true;
+ } else if (mBootReason == kBootReasonForcedNonSilent) {
+ mForcedMode = true;
+ mSilentModeByHwState = false;
+ }
+ forcedMode = mForcedMode;
}
- if (mForcedMode) {
+ if (forcedMode) {
handleSilentModeChange(mSilentModeByHwState);
mSilentModeChangeHandler->notifySilentModeChange(mSilentModeByHwState);
ALOGI("Now in forced mode: monitoring %s is disabled", mSilentModeHwStateFilename.c_str());
@@ -125,6 +144,29 @@
mSysfsMonitor->release();
}
+ScopedAStatus SilentModeHandler::setSilentMode(const std::string& silentMode) {
+ if (kSilentModeToType.count(silentMode) == 0) {
+ return ScopedAStatus::
+ fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+ StringPrintf("Unsupported Silent Mode(%s)",
+ silentMode.c_str())
+ .c_str());
+ }
+ int32_t silentModeType = kSilentModeToType.at(silentMode);
+ switch (silentModeType) {
+ case kSilentModeTypeForcedSilent:
+ switchToForcedMode(true);
+ break;
+ case kSilentModeTypeForcedNonSilent:
+ switchToForcedMode(false);
+ break;
+ case kSilentModeTypeNonForced:
+ switchToNonForcedMode();
+ break;
+ }
+ return ScopedAStatus::ok();
+}
+
Result<void> SilentModeHandler::dump(int fd, const Vector<String16>& /*args*/) {
const char* indent = " ";
WriteStringToFd(StringPrintf("%sHW state filename: %s\n", indent,
@@ -185,6 +227,7 @@
return;
}
mIsMonitoring = true;
+ ALOGI("Started monitoring Silent Mode HW state");
// Read the current silent mode HW state.
handleSilentModeHwStateChange();
@@ -238,6 +281,43 @@
return {};
}
+void SilentModeHandler::switchToForcedMode(bool silent) {
+ bool updated = false;
+ {
+ Mutex::Autolock lock(mMutex);
+ if (!mForcedMode) {
+ stopMonitoringSilentModeHwState();
+ mForcedMode = true;
+ }
+ if (mSilentModeByHwState != silent) {
+ mSilentModeByHwState = silent;
+ updated = true;
+ }
+ }
+ if (updated) {
+ updateKernelSilentMode(silent);
+ mSilentModeChangeHandler->notifySilentModeChange(silent);
+ }
+ ALOGI("Now in forced %s mode: monitoring %s is disabled", silent ? "silent" : "non-silent",
+ mSilentModeHwStateFilename.c_str());
+}
+
+void SilentModeHandler::switchToNonForcedMode() {
+ bool updated = false;
+ {
+ Mutex::Autolock lock(mMutex);
+ if (mForcedMode) {
+ ALOGI("Now in non forced mode: monitoring %s is started",
+ mSilentModeHwStateFilename.c_str());
+ mForcedMode = false;
+ updated = true;
+ }
+ }
+ if (updated) {
+ startMonitoringSilentModeHwState();
+ }
+}
+
Result<void> SilentModeHandler::updateKernelSilentMode(bool silent) {
int fd = open(mKernelSilentModeFilename.c_str(), O_WRONLY | O_NONBLOCK);
if (fd < 0) {
diff --git a/cpp/powerpolicy/server/src/SilentModeHandler.h b/cpp/powerpolicy/server/src/SilentModeHandler.h
index 0ac19af..74a98eb 100644
--- a/cpp/powerpolicy/server/src/SilentModeHandler.h
+++ b/cpp/powerpolicy/server/src/SilentModeHandler.h
@@ -19,6 +19,7 @@
#include <android-base/result.h>
#include <android-base/unique_fd.h>
+#include <android/binder_auto_utils.h>
#include <utils/Mutex.h>
#include <utils/String16.h>
#include <utils/StrongPointer.h>
@@ -66,6 +67,8 @@
bool isSilentMode();
// Stops monitoring the change on pm_silentmode_hw_state.
void stopMonitoringSilentModeHwState();
+ // Set the Silent Mode
+ ndk::ScopedAStatus setSilentMode(const std::string& silentMode);
// Dumps the internal state.
android::base::Result<void> dump(int fd, const Vector<String16>& args);
@@ -75,10 +78,12 @@
void handleSilentModeHwStateChange();
void handleSilentModeChange(bool silent);
android::base::Result<void> enableBootAnimation(bool enabled);
+ void switchToForcedMode(bool silent);
+ void switchToNonForcedMode();
android::Mutex mMutex;
bool mSilentModeByHwState GUARDED_BY(mMutex);
- bool mForcedMode = false;
+ bool mForcedMode GUARDED_BY(mMutex) = false;
std::string mBootReason;
std::string mSilentModeHwStateFilename;
std::string mKernelSilentModeFilename;
diff --git a/cpp/powerpolicy/server/tests/CarPowerPolicyServerTest.cpp b/cpp/powerpolicy/server/tests/CarPowerPolicyServerTest.cpp
index a43039d..7de7684 100644
--- a/cpp/powerpolicy/server/tests/CarPowerPolicyServerTest.cpp
+++ b/cpp/powerpolicy/server/tests/CarPowerPolicyServerTest.cpp
@@ -93,7 +93,7 @@
class MockPowerPolicyDelegateCallback : public BnCarPowerPolicyDelegateCallback {
public:
MOCK_METHOD(ScopedAStatus, updatePowerComponents, (const CarPowerPolicy&), (override));
- MOCK_METHOD(ScopedAStatus, onApplyPowerPolicySucceeded, (int32_t, const CarPowerPolicy&),
+ MOCK_METHOD(ScopedAStatus, onApplyPowerPolicySucceeded, (int32_t, const CarPowerPolicy&, bool),
(override));
MOCK_METHOD(ScopedAStatus, onApplyPowerPolicyFailed, (int32_t, PowerPolicyFailureReason),
(override));
@@ -357,8 +357,8 @@
EXPECT_CALL(*callback, onApplyPowerPolicySucceeded)
.WillRepeatedly(
Invoke([&calledRequestId, &policyIdForNotification, &cv,
- &mutex](int32_t requestId,
- const CarPowerPolicy& accumulatedPolicy) -> ScopedAStatus {
+ &mutex](int32_t requestId, const CarPowerPolicy& accumulatedPolicy,
+ [[maybe_unused]] bool deferred) -> ScopedAStatus {
calledRequestId = requestId;
policyIdForNotification = accumulatedPolicy.policyId;
std::unique_lock lock(mutex);
@@ -543,8 +543,10 @@
}));
EXPECT_CALL(*callback, onApplyPowerPolicySucceeded)
.WillRepeatedly(Invoke([]([[maybe_unused]] int32_t requestId,
- [[maybe_unused]] const CarPowerPolicy& accumulatedPolicy)
- -> ScopedAStatus { return ScopedAStatus::ok(); }));
+ [[maybe_unused]] const CarPowerPolicy& accumulatedPolicy,
+ [[maybe_unused]] bool deferred) -> ScopedAStatus {
+ return ScopedAStatus::ok();
+ }));
ScopedAStatus status = server->applyPowerPolicyAsync(/*requestId=*/9999, "policy_id_other_off",
/*force=*/false);
@@ -637,8 +639,10 @@
}));
EXPECT_CALL(*callback, onApplyPowerPolicySucceeded)
.WillRepeatedly(Invoke([]([[maybe_unused]] int32_t requestId,
- [[maybe_unused]] const CarPowerPolicy& accumulatedPolicy)
- -> ScopedAStatus { return ScopedAStatus::ok(); }));
+ [[maybe_unused]] const CarPowerPolicy& accumulatedPolicy,
+ [[maybe_unused]] bool deferred) -> ScopedAStatus {
+ return ScopedAStatus::ok();
+ }));
PowerPolicyInitData initData;
server->notifyCarServiceReady(callback, &initData);
diff --git a/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp b/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp
index 0926a92..ef643bb 100644
--- a/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp
+++ b/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp
@@ -87,6 +87,10 @@
void updateKernelSilentMode(bool isSilent) { mHandler->updateKernelSilentMode(isSilent); }
+ ScopedAStatus setSilentMode(const std::string& silentMode) {
+ return mHandler->setSilentMode(silentMode);
+ }
+
private:
SilentModeHandler* mHandler;
TemporaryFile mFileSilentModeHwState;
@@ -168,6 +172,71 @@
<< "Kernel silent mode file should have 0";
}
+TEST_F(SilentModeHandlerTest, TestSetSilentModeForForcedSilent) {
+ SilentModeHandler handler(carPowerPolicyServer.get());
+ internal::SilentModeHandlerPeer handlerPeer(&handler);
+ handlerPeer.injectBootReason(kBootReasonNormal);
+ handlerPeer.init();
+
+ ScopedAStatus status = handlerPeer.setSilentMode("forced-silent");
+
+ ASSERT_TRUE(status.isOk()) << "setSilentMode(\"forced-silent\") should return OK";
+ ASSERT_TRUE(handler.isSilentMode())
+ << "When in forced-silent, silent mode should be set to true";
+
+ handlerPeer.updateSilentModeHwState(/*isSilent=*/false);
+
+ ASSERT_TRUE(handler.isSilentMode())
+ << "When in forced-silent, silent mode should not change by HW state";
+}
+
+TEST_F(SilentModeHandlerTest, TestSetSilentModeForForcedNonSilent) {
+ SilentModeHandler handler(carPowerPolicyServer.get());
+ internal::SilentModeHandlerPeer handlerPeer(&handler);
+ handlerPeer.injectBootReason(kBootReasonNormal);
+ handlerPeer.init();
+
+ ScopedAStatus status = handlerPeer.setSilentMode("forced-non-silent");
+
+ ASSERT_TRUE(status.isOk()) << "setSilentMode(\"forced-non-silent\") should return OK";
+ ASSERT_FALSE(handler.isSilentMode())
+ << "When in forced-non-silent, silent mode should be set to false";
+
+ handlerPeer.updateSilentModeHwState(/*isSilent=*/true);
+
+ ASSERT_FALSE(handler.isSilentMode())
+ << "When in forced-non-silent, silent mode should not change by HW state";
+}
+
+TEST_F(SilentModeHandlerTest, TestSetSilentModeForNonForcedSilent) {
+ SilentModeHandler handler(carPowerPolicyServer.get());
+ internal::SilentModeHandlerPeer handlerPeer(&handler);
+ handlerPeer.injectBootReason(kBootReasonNormal);
+ handlerPeer.init();
+
+ ScopedAStatus status = handlerPeer.setSilentMode("forced-non-silent");
+ status = handlerPeer.setSilentMode("non-forced-silent-mode");
+
+ ASSERT_TRUE(status.isOk()) << "setSilentMode(\"non-forced-silent-mode\") should return OK";
+ ASSERT_FALSE(handler.isSilentMode())
+ << "Changing to non-forced mode should keep the current silent mode";
+
+ handlerPeer.updateSilentModeHwState(/*isSilent=*/true);
+
+ ASSERT_FALSE(handler.isSilentMode())
+ << "When in non-forced-silent, silent mode should change by HW state";
+}
+
+TEST_F(SilentModeHandlerTest, TestSetSilentModeWithErrorMode) {
+ SilentModeHandler handler(carPowerPolicyServer.get());
+ internal::SilentModeHandlerPeer handlerPeer(&handler);
+ handlerPeer.injectBootReason(kBootReasonNormal);
+ handlerPeer.init();
+
+ ScopedAStatus status = handlerPeer.setSilentMode("error-silent-mode");
+ ASSERT_FALSE(status.isOk()) << "setSilentMode with an erroneous mode should not return OK";
+}
+
} // namespace powerpolicy
} // namespace automotive
} // namespace frameworks
diff --git a/cpp/security/vehicle_binding_util/tests/Android.bp b/cpp/security/vehicle_binding_util/tests/Android.bp
index 5e2220a..2417704 100644
--- a/cpp/security/vehicle_binding_util/tests/Android.bp
+++ b/cpp/security/vehicle_binding_util/tests/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_security",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/telemetry/cartelemetryd/Android.bp b/cpp/telemetry/cartelemetryd/Android.bp
index d6929cd..7575af6 100644
--- a/cpp/telemetry/cartelemetryd/Android.bp
+++ b/cpp/telemetry/cartelemetryd/Android.bp
@@ -13,12 +13,12 @@
// limitations under the License.
package {
+ default_team: "trendy_team_automotive",
default_applicable_licenses: ["Android-Apache-2.0"],
}
cc_defaults {
name: "cartelemetryd_defaults",
- cpp_std: "gnu++17",
cflags: [
"-Werror",
"-Wall",
diff --git a/cpp/telemetry/cartelemetryd/src/BufferedCarData.h b/cpp/telemetry/cartelemetryd/src/BufferedCarData.h
index 2987980..fab7c0b 100644
--- a/cpp/telemetry/cartelemetryd/src/BufferedCarData.h
+++ b/cpp/telemetry/cartelemetryd/src/BufferedCarData.h
@@ -28,6 +28,8 @@
// Internally stored `CarData` with some extras.
struct BufferedCarData {
+ BufferedCarData(int32_t id, const std::vector<uint8_t>& content, uid_t publisherUid)
+ : mId(id), mContent(std::move(content)), mPublisherUid(publisherUid) {}
BufferedCarData(BufferedCarData&& other) = default;
BufferedCarData(const BufferedCarData&) = default;
BufferedCarData& operator=(BufferedCarData&& other) = delete;
diff --git a/cpp/telemetry/cartelemetryd/src/TelemetryServer.cpp b/cpp/telemetry/cartelemetryd/src/TelemetryServer.cpp
index 73182d8..47b29a5 100644
--- a/cpp/telemetry/cartelemetryd/src/TelemetryServer.cpp
+++ b/cpp/telemetry/cartelemetryd/src/TelemetryServer.cpp
@@ -237,9 +237,7 @@
LOG(VERBOSE) << "Ignoring CarData with ID=" << data.id;
continue;
}
- mRingBuffer.push({.mId = data.id,
- .mContent = std::move(data.content),
- .mPublisherUid = publisherUid});
+ mRingBuffer.push({data.id, data.content, publisherUid});
}
// If the mRingBuffer was not empty, the message is already scheduled. It prevents scheduling
// too many unnecessary idendical messages in the looper.
diff --git a/cpp/vhal/client/Android.bp b/cpp/vhal/client/Android.bp
index 602f763..c74c793 100644
--- a/cpp/vhal/client/Android.bp
+++ b/cpp/vhal/client/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/vhal/client/src/AidlHalPropConfig.cpp b/cpp/vhal/client/src/AidlHalPropConfig.cpp
index 7f36619..f0d7071 100644
--- a/cpp/vhal/client/src/AidlHalPropConfig.cpp
+++ b/cpp/vhal/client/src/AidlHalPropConfig.cpp
@@ -31,11 +31,19 @@
AidlHalPropConfig::AidlHalPropConfig(VehiclePropConfig&& config) {
mPropConfig = std::move(config);
- for (VehicleAreaConfig& areaConfig : mPropConfig.areaConfigs) {
- int32_t access = (areaConfig.access == VehiclePropertyAccess::NONE)
- ? toInt(mPropConfig.access)
- : toInt(areaConfig.access);
- mAreaConfigs.push_back(std::make_unique<AidlHalAreaConfig>(std::move(areaConfig), access));
+ if (mPropConfig.areaConfigs.size() == 0) {
+ VehicleAreaConfig globalAreaConfig;
+ globalAreaConfig.areaId = 0;
+ mAreaConfigs.push_back(std::make_unique<AidlHalAreaConfig>(std::move(globalAreaConfig),
+ toInt(mPropConfig.access)));
+ } else {
+ for (VehicleAreaConfig& areaConfig : mPropConfig.areaConfigs) {
+ int32_t access = (areaConfig.access == VehiclePropertyAccess::NONE)
+ ? toInt(mPropConfig.access)
+ : toInt(areaConfig.access);
+ mAreaConfigs.push_back(
+ std::make_unique<AidlHalAreaConfig>(std::move(areaConfig), access));
+ }
}
}
diff --git a/cpp/vhal/client/src/HidlHalPropConfig.cpp b/cpp/vhal/client/src/HidlHalPropConfig.cpp
index b0f71fa..8bdb1c1 100644
--- a/cpp/vhal/client/src/HidlHalPropConfig.cpp
+++ b/cpp/vhal/client/src/HidlHalPropConfig.cpp
@@ -30,9 +30,16 @@
HidlHalPropConfig::HidlHalPropConfig(VehiclePropConfig&& config) {
mPropConfig = std::move(config);
- for (VehicleAreaConfig& areaConfig : mPropConfig.areaConfigs) {
- mAreaConfigs.push_back(std::make_unique<HidlHalAreaConfig>(std::move(areaConfig),
+ if (mPropConfig.areaConfigs.size() == 0) {
+ VehicleAreaConfig globalAreaConfig;
+ globalAreaConfig.areaId = 0;
+ mAreaConfigs.push_back(std::make_unique<HidlHalAreaConfig>(std::move(globalAreaConfig),
toInt(mPropConfig.access)));
+ } else {
+ for (VehicleAreaConfig& areaConfig : mPropConfig.areaConfigs) {
+ mAreaConfigs.push_back(std::make_unique<HidlHalAreaConfig>(std::move(areaConfig),
+ toInt(mPropConfig.access)));
+ }
}
}
diff --git a/cpp/vhal/client/test/AidlVhalClientTest.cpp b/cpp/vhal/client/test/AidlVhalClientTest.cpp
index 7ed2be5..fc48949 100644
--- a/cpp/vhal/client/test/AidlVhalClientTest.cpp
+++ b/cpp/vhal/client/test/AidlVhalClientTest.cpp
@@ -55,6 +55,7 @@
using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfigs;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropError;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropErrors;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyAccess;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyStatus;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropValues;
@@ -266,6 +267,8 @@
constexpr static int32_t TEST_AREA_ID = 2;
constexpr static int32_t TEST_PROP_ID_2 = 3;
constexpr static int32_t TEST_AREA_ID_2 = 4;
+ constexpr static VehiclePropertyAccess TEST_GLOBAL_ACCESS = VehiclePropertyAccess::READ_WRITE;
+ constexpr static VehiclePropertyAccess TEST_AREA_ACCESS = VehiclePropertyAccess::READ;
constexpr static int64_t TEST_TIMEOUT_IN_MS = 100;
void SetUp() override {
@@ -803,6 +806,7 @@
getVhal()->setPropConfigs({
VehiclePropConfig{
.prop = TEST_PROP_ID,
+ .access = TEST_GLOBAL_ACCESS,
.areaConfigs = {{
.areaId = TEST_AREA_ID,
.minInt32Value = 0,
@@ -811,6 +815,7 @@
},
{
.areaId = TEST_AREA_ID_2,
+ .access = TEST_AREA_ACCESS,
.minInt32Value = 2,
.maxInt32Value = 3,
}},
@@ -827,22 +832,31 @@
ASSERT_EQ(configs.size(), static_cast<size_t>(2));
ASSERT_EQ(configs[0]->getPropId(), TEST_PROP_ID);
+ ASSERT_EQ(configs[0]->getAccess(), toInt(TEST_GLOBAL_ACCESS));
ASSERT_EQ(configs[0]->getAreaConfigSize(), static_cast<size_t>(2));
const std::unique_ptr<IHalAreaConfig>& areaConfig0 = configs[0]->getAreaConfigs()[0];
ASSERT_EQ(areaConfig0->getAreaId(), TEST_AREA_ID);
+ ASSERT_EQ(areaConfig0->getAccess(), toInt(TEST_GLOBAL_ACCESS));
ASSERT_EQ(areaConfig0->getMinInt32Value(), 0);
ASSERT_EQ(areaConfig0->getMaxInt32Value(), 1);
ASSERT_TRUE(areaConfig0->isVariableUpdateRateSupported());
const std::unique_ptr<IHalAreaConfig>& areaConfig1 = configs[0]->getAreaConfigs()[1];
ASSERT_EQ(areaConfig1->getAreaId(), TEST_AREA_ID_2);
+ ASSERT_EQ(areaConfig1->getAccess(), toInt(TEST_AREA_ACCESS));
ASSERT_EQ(areaConfig1->getMinInt32Value(), 2);
ASSERT_EQ(areaConfig1->getMaxInt32Value(), 3);
ASSERT_FALSE(areaConfig1->isVariableUpdateRateSupported());
ASSERT_EQ(configs[1]->getPropId(), TEST_PROP_ID_2);
- ASSERT_EQ(configs[1]->getAreaConfigSize(), static_cast<size_t>(0));
+ ASSERT_EQ(configs[1]->getAccess(), 0);
+ ASSERT_EQ(configs[1]->getAreaConfigSize(), static_cast<size_t>(1));
+
+ const std::unique_ptr<IHalAreaConfig>& areaConfig2 = configs[1]->getAreaConfigs()[0];
+ ASSERT_EQ(areaConfig2->getAreaId(), 0);
+ ASSERT_EQ(areaConfig2->getAccess(), 0);
+ ASSERT_FALSE(areaConfig2->isVariableUpdateRateSupported());
}
TEST_F(AidlVhalClientTest, testGetAllPropConfigsError) {
@@ -858,13 +872,16 @@
getVhal()->setPropConfigs({
VehiclePropConfig{
.prop = TEST_PROP_ID,
+ .access = TEST_GLOBAL_ACCESS,
.areaConfigs = {{
.areaId = TEST_AREA_ID,
.minInt32Value = 0,
.maxInt32Value = 1,
+ .supportVariableUpdateRate = true,
},
{
.areaId = TEST_AREA_ID_2,
+ .access = TEST_AREA_ACCESS,
.minInt32Value = 2,
.maxInt32Value = 3,
}},
@@ -883,20 +900,31 @@
ASSERT_EQ(configs.size(), static_cast<size_t>(2));
ASSERT_EQ(configs[0]->getPropId(), TEST_PROP_ID);
+ ASSERT_EQ(configs[0]->getAccess(), toInt(TEST_GLOBAL_ACCESS));
ASSERT_EQ(configs[0]->getAreaConfigSize(), static_cast<size_t>(2));
const std::unique_ptr<IHalAreaConfig>& areaConfig0 = configs[0]->getAreaConfigs()[0];
ASSERT_EQ(areaConfig0->getAreaId(), TEST_AREA_ID);
+ ASSERT_EQ(areaConfig0->getAccess(), toInt(TEST_GLOBAL_ACCESS));
ASSERT_EQ(areaConfig0->getMinInt32Value(), 0);
ASSERT_EQ(areaConfig0->getMaxInt32Value(), 1);
+ ASSERT_TRUE(areaConfig0->isVariableUpdateRateSupported());
const std::unique_ptr<IHalAreaConfig>& areaConfig1 = configs[0]->getAreaConfigs()[1];
ASSERT_EQ(areaConfig1->getAreaId(), TEST_AREA_ID_2);
+ ASSERT_EQ(areaConfig1->getAccess(), toInt(TEST_AREA_ACCESS));
ASSERT_EQ(areaConfig1->getMinInt32Value(), 2);
ASSERT_EQ(areaConfig1->getMaxInt32Value(), 3);
+ ASSERT_FALSE(areaConfig1->isVariableUpdateRateSupported());
ASSERT_EQ(configs[1]->getPropId(), TEST_PROP_ID_2);
- ASSERT_EQ(configs[1]->getAreaConfigSize(), static_cast<size_t>(0));
+ ASSERT_EQ(configs[1]->getAccess(), 0);
+ ASSERT_EQ(configs[1]->getAreaConfigSize(), static_cast<size_t>(1));
+
+ const std::unique_ptr<IHalAreaConfig>& areaConfig2 = configs[1]->getAreaConfigs()[0];
+ ASSERT_EQ(areaConfig2->getAreaId(), 0);
+ ASSERT_EQ(areaConfig2->getAccess(), 0);
+ ASSERT_FALSE(areaConfig2->isVariableUpdateRateSupported());
}
TEST_F(AidlVhalClientTest, testGetPropConfigsError) {
diff --git a/cpp/vhal/client/test/Android.bp b/cpp/vhal/client/test/Android.bp
index 6a2ed8b..a29dda9 100644
--- a/cpp/vhal/client/test/Android.bp
+++ b/cpp/vhal/client/test/Android.bp
@@ -15,6 +15,7 @@
*/
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/vhal/client/test/HidlVhalClientTest.cpp b/cpp/vhal/client/test/HidlVhalClientTest.cpp
index 15bbf59..d8f74f7 100644
--- a/cpp/vhal/client/test/HidlVhalClientTest.cpp
+++ b/cpp/vhal/client/test/HidlVhalClientTest.cpp
@@ -345,20 +345,31 @@
ASSERT_EQ(configs.size(), static_cast<size_t>(2));
ASSERT_EQ(configs[0]->getPropId(), TEST_PROP_ID);
+ ASSERT_EQ(configs[0]->getAccess(), 0);
ASSERT_EQ(configs[0]->getAreaConfigSize(), static_cast<size_t>(2));
const std::unique_ptr<IHalAreaConfig>& areaConfig0 = configs[0]->getAreaConfigs()[0];
ASSERT_EQ(areaConfig0->getAreaId(), TEST_AREA_ID);
+ ASSERT_EQ(areaConfig0->getAccess(), 0);
ASSERT_EQ(areaConfig0->getMinInt32Value(), 0);
ASSERT_EQ(areaConfig0->getMaxInt32Value(), 1);
+ ASSERT_FALSE(areaConfig0->isVariableUpdateRateSupported());
const std::unique_ptr<IHalAreaConfig>& areaConfig1 = configs[0]->getAreaConfigs()[1];
ASSERT_EQ(areaConfig1->getAreaId(), TEST_AREA_ID_2);
+ ASSERT_EQ(areaConfig1->getAccess(), 0);
ASSERT_EQ(areaConfig1->getMinInt32Value(), 2);
ASSERT_EQ(areaConfig1->getMaxInt32Value(), 3);
+ ASSERT_FALSE(areaConfig1->isVariableUpdateRateSupported());
ASSERT_EQ(configs[1]->getPropId(), TEST_PROP_ID_2);
- ASSERT_EQ(configs[1]->getAreaConfigSize(), static_cast<size_t>(0));
+ ASSERT_EQ(configs[1]->getAccess(), 0);
+ ASSERT_EQ(configs[1]->getAreaConfigSize(), static_cast<size_t>(1));
+
+ const std::unique_ptr<IHalAreaConfig>& areaConfig2 = configs[1]->getAreaConfigs()[0];
+ ASSERT_EQ(areaConfig2->getAreaId(), 0);
+ ASSERT_EQ(areaConfig2->getAccess(), 0);
+ ASSERT_FALSE(areaConfig2->isVariableUpdateRateSupported());
}
TEST_F(HidlVhalClientTest, testGetPropConfigs) {
@@ -390,22 +401,31 @@
ASSERT_EQ(configs.size(), static_cast<size_t>(2));
ASSERT_EQ(configs[0]->getPropId(), TEST_PROP_ID);
+ ASSERT_EQ(configs[0]->getAccess(), 0);
ASSERT_EQ(configs[0]->getAreaConfigSize(), static_cast<size_t>(2));
const std::unique_ptr<IHalAreaConfig>& areaConfig0 = configs[0]->getAreaConfigs()[0];
ASSERT_EQ(areaConfig0->getAreaId(), TEST_AREA_ID);
+ ASSERT_EQ(areaConfig0->getAccess(), 0);
ASSERT_EQ(areaConfig0->getMinInt32Value(), 0);
ASSERT_EQ(areaConfig0->getMaxInt32Value(), 1);
ASSERT_FALSE(areaConfig0->isVariableUpdateRateSupported());
const std::unique_ptr<IHalAreaConfig>& areaConfig1 = configs[0]->getAreaConfigs()[1];
ASSERT_EQ(areaConfig1->getAreaId(), TEST_AREA_ID_2);
+ ASSERT_EQ(areaConfig1->getAccess(), 0);
ASSERT_EQ(areaConfig1->getMinInt32Value(), 2);
ASSERT_EQ(areaConfig1->getMaxInt32Value(), 3);
ASSERT_FALSE(areaConfig1->isVariableUpdateRateSupported());
ASSERT_EQ(configs[1]->getPropId(), TEST_PROP_ID_2);
- ASSERT_EQ(configs[1]->getAreaConfigSize(), static_cast<size_t>(0));
+ ASSERT_EQ(configs[1]->getAccess(), 0);
+ ASSERT_EQ(configs[1]->getAreaConfigSize(), static_cast<size_t>(1));
+
+ const std::unique_ptr<IHalAreaConfig>& areaConfig2 = configs[1]->getAreaConfigs()[0];
+ ASSERT_EQ(areaConfig2->getAreaId(), 0);
+ ASSERT_EQ(areaConfig2->getAccess(), 0);
+ ASSERT_FALSE(areaConfig2->isVariableUpdateRateSupported());
}
TEST_F(HidlVhalClientTest, testGetPropConfigsError) {
diff --git a/cpp/watchdog/aidl/Android.bp b/cpp/watchdog/aidl/Android.bp
index 29e25e6..4ad596e 100644
--- a/cpp/watchdog/aidl/Android.bp
+++ b/cpp/watchdog/aidl/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/watchdog/car-watchdog-lib/Android.bp b/cpp/watchdog/car-watchdog-lib/Android.bp
index 2827d17..0063520 100644
--- a/cpp/watchdog/car-watchdog-lib/Android.bp
+++ b/cpp/watchdog/car-watchdog-lib/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/watchdog/sepolicy/private/carwatchdog.te b/cpp/watchdog/sepolicy/private/carwatchdog.te
index 98cbddd..0fd81a9 100644
--- a/cpp/watchdog/sepolicy/private/carwatchdog.te
+++ b/cpp/watchdog/sepolicy/private/carwatchdog.te
@@ -32,6 +32,9 @@
# Read /proc/uid_cputime/show_uid_stat file.
allow carwatchdogd proc_uid_cputime_showstat:file r_file_perms;
+# Read/Write /proc/pressure/memory file.
+allow carwatchdogd proc_pressure_mem:file rw_file_perms;
+
# List HALs to get pid of vehicle HAL.
allow carwatchdogd hwservicemanager:hwservice_manager list;
diff --git a/cpp/watchdog/server/Android.bp b/cpp/watchdog/server/Android.bp
index 0a67293..968f12d 100644
--- a/cpp/watchdog/server/Android.bp
+++ b/cpp/watchdog/server/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -39,14 +40,18 @@
"libbinder",
"libbinder_ndk",
"liblog",
+ "libmeminfo",
+ "libpsi",
"libutils",
"packagemanager_aidl-cpp",
"libprotoutil",
"libcutils",
+ "server_configurable_flags",
],
static_libs: [
"android.automotive.watchdog.internal-V3-ndk",
"android.automotive.watchdog-V3-ndk",
+ "android.car.feature-aconfig-cpp",
"libvhalclient",
"libwatchdog_properties",
],
@@ -54,6 +59,7 @@
"libgtest_prod_headers",
],
defaults: [
+ "aconfig_lib_cc_shared_link.defaults",
"vhalclient_defaults",
],
}
@@ -115,17 +121,18 @@
"libwatchdog_perf_service_defaults",
],
srcs: [
- "src/WatchdogPerfService.cpp",
"src/IoOveruseConfigs.cpp",
"src/IoOveruseMonitor.cpp",
- "src/PerformanceProfiler.cpp",
"src/OveruseConfigurationXmlHelper.cpp",
+ "src/PerformanceProfiler.cpp",
+ "src/PressureMonitor.cpp",
"src/ProcDiskStatsCollector.cpp",
"src/ProcStatCollector.cpp",
"src/UidCpuStatsCollector.cpp",
"src/UidIoStatsCollector.cpp",
"src/UidProcStatsCollector.cpp",
"src/UidStatsCollector.cpp",
+ "src/WatchdogPerfService.cpp",
],
static_libs: [
"libwatchdog_binder_utils",
@@ -179,6 +186,7 @@
"tests/IoOveruseConfigsTest.cpp",
"tests/IoOveruseMonitorTest.cpp",
"tests/PerformanceProfilerTest.cpp",
+ "tests/PressureMonitorTest.cpp",
"tests/LooperStub.cpp",
"tests/OveruseConfigurationTestUtils.cpp",
"tests/OveruseConfigurationXmlHelperTest.cpp",
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.cpp b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
index 5ed639f..07c146f 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.cpp
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
@@ -120,8 +120,8 @@
startTm.tm_min = 0;
startTm.tm_hour = 0;
- int64_t startTime = static_cast<int64_t>(mktime(&startTm));
- int64_t currentEpochSeconds = static_cast<int64_t>(mktime(¤tTm));
+ int64_t startTime = static_cast<int64_t>(timegm(&startTm));
+ int64_t currentEpochSeconds = static_cast<int64_t>(timegm(¤tTm));
return std::make_tuple(startTime, currentEpochSeconds - startTime);
}
@@ -219,9 +219,12 @@
}
void IoOveruseMonitor::terminate() {
- std::unique_lock writeLock(mRwMutex);
-
ALOGW("Terminating %s", name().c_str());
+ if (mWriteToDiskThread.joinable()) {
+ mWriteToDiskThread.join();
+ ALOGI("Write to disk has completed. Proceeding with termination");
+ }
+ std::unique_lock writeLock(mRwMutex);
mWatchdogServiceHelper.clear();
mIoOveruseConfigs.clear();
mSystemWideWrittenBytes.clear();
@@ -538,7 +541,23 @@
if (const auto result = mIoOveruseConfigs->update(configs); !result.ok()) {
return result;
}
- std::thread writeToDiskThread([&]() {
+ // When mWriteToDiskThread is already active, don't create a new thread to perform the same
+ // work. This thread writes to disk only after acquiring the mRwMutex write lock and the below
+ // check is performed after acquiring the same write lock. Thus, if the thread is still active
+ // and mIsWriteToDiskPending is true at this point, it indicates the thread hasn't performed
+ // the write and will write the latest updated configs when it executes.
+ if (bool isJoinable = mWriteToDiskThread.joinable(); isJoinable && mIsWriteToDiskPending) {
+ ALOGW("Skipping resource overuse configs write to disk due to ongoing write");
+ return {};
+ } else if (isJoinable) {
+ // At this point we know the thread has completed execution. Join the thread before
+ // creating a new one. Failure to join can lead to a crash since std::thread cannot
+ // destruct a thread object without first calling join.
+ mWriteToDiskThread.join();
+ }
+ mIsWriteToDiskPending = true;
+ mWriteToDiskThread = std::thread([&]() {
+ ALOGI("Writing resource overuse configs to disk");
if (set_sched_policy(0, SP_BACKGROUND) != 0) {
ALOGW("Failed to set background scheduling priority for writing resource overuse "
"configs to disk");
@@ -549,15 +568,15 @@
std::unique_lock writeLock(mRwMutex);
if (mIoOveruseConfigs == nullptr) {
ALOGE("IoOveruseConfigs instance is null");
- return;
- }
- if (const auto result = mIoOveruseConfigs->writeToDisk(); !result.ok()) {
+ } else if (const auto result = mIoOveruseConfigs->writeToDisk(); !result.ok()) {
ALOGE("Failed to write resource overuse configs to disk: %s",
result.error().message().c_str());
+ } else {
+ ALOGI("Successfully wrote resource overuse configs to disk");
}
+ mIsWriteToDiskPending = false;
});
- writeToDiskThread.detach();
return {};
}
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.h b/cpp/watchdog/server/src/IoOveruseMonitor.h
index de9205b..a147640 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.h
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.h
@@ -291,6 +291,15 @@
// Makes sure only one collection is running at any given time.
mutable std::shared_mutex mRwMutex;
+ // This is the thread on which the write to disk is performed. In the event the monitor begins
+ // to terminate before the write has completed, the termination procedure should wait for this
+ // thread to complete. Otherwise, this thread will run independently, which will cause
+ // the thread to access stale lock or member fields leading to crashing the process.
+ std::thread mWriteToDiskThread;
+
+ // Tracks if mWriteToDiskThread is actively writing to disk.
+ bool mIsWriteToDiskPending GUARDED_BY(mRwMutex);
+
// Indicates whether or not today's I/O usage stats, that were collected during previous boot,
// are read from CarService because CarService persists these stats in database across reboot.
bool mDidReadTodayPrevBootStats GUARDED_BY(mRwMutex);
diff --git a/cpp/watchdog/server/src/PerformanceProfiler.cpp b/cpp/watchdog/server/src/PerformanceProfiler.cpp
index 083a1df..c8430d6 100644
--- a/cpp/watchdog/server/src/PerformanceProfiler.cpp
+++ b/cpp/watchdog/server/src/PerformanceProfiler.cpp
@@ -18,11 +18,14 @@
#include "PerformanceProfiler.h"
+#include "ServiceManager.h"
+
#include <WatchdogProperties.sysprop.h>
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android/util/ProtoOutputStream.h>
#include <log/log.h>
+#include <meminfo/androidprocheaps.h>
#include <inttypes.h>
@@ -39,6 +42,9 @@
namespace android {
namespace automotive {
namespace watchdog {
+
+namespace {
+
using ::aidl::android::automotive::watchdog::PerStateBytes;
using ::aidl::android::automotive::watchdog::internal::PackageIdentifier;
using ::aidl::android::automotive::watchdog::internal::ProcessCpuUsageStats;
@@ -55,13 +61,14 @@
using ::android::base::WriteStringToFd;
using ::android::util::ProtoOutputStream;
-namespace {
+using PressureLevelDurationMap =
+ std::unordered_map<PressureMonitorInterface::PressureLevel, std::chrono::milliseconds>;
constexpr int32_t kDefaultTopNStatsPerCategory = 10;
constexpr int32_t kDefaultTopNStatsPerSubcategory = 5;
constexpr int32_t kDefaultMaxUserSwitchEvents = 5;
constexpr std::chrono::seconds kSystemEventDataCacheDurationSec = 1h;
-constexpr const char kBootTimeCollectionTitle[] = "%s\nBoot-time performance report:\n%s\n";
+constexpr const char kBootTimeCollectionTitle[] = "\n%s\nBoot-time performance report:\n%s\n";
constexpr const char kPeriodicCollectionTitle[] = "%s\nLast N minutes performance report:\n%s\n";
constexpr const char kUserSwitchCollectionTitle[] =
"%s\nUser-switch events performance report:\n%s\n";
@@ -70,14 +77,14 @@
constexpr const char kCustomCollectionTitle[] = "%s\nCustom performance data report:\n%s\n";
constexpr const char kUserSwitchEventTitle[] = "\nEvent %zu: From: %d To: %d\n%s\n";
constexpr const char kCollectionTitle[] =
- "Collection duration: %.f seconds\nNumber of collections: %zu\n";
+ "Collection duration: %.f seconds\nMaximum cache size: %zu\nNumber of collections: %zu\n";
constexpr const char kRecordTitle[] = "\nCollection %zu: <%s>\n%s\n%s";
-constexpr const char kCpuTimeTitle[] = "\nTop N CPU Times:\n%s\n";
+constexpr const char kCpuTimeTitle[] = "\nTop N CPU times:\n%s\n";
constexpr const char kCpuTimeHeader[] = "Android User ID, Package Name, CPU Time (ms), Percentage "
"of total CPU time, CPU Cycles\n\tCommand, CPU Time (ms), "
"Percentage of UID's CPU Time, CPU Cycles\n";
-constexpr const char kIoReadsTitle[] = "\nTop N Storage I/O Reads:\n%s\n";
-constexpr const char kIoWritesTitle[] = "\nTop N Storage I/O Writes:\n%s\n";
+constexpr const char kIoReadsTitle[] = "\nTop N storage I/O reads:\n%s\n";
+constexpr const char kIoWritesTitle[] = "\nTop N storage I/O writes:\n%s\n";
constexpr const char kIoStatsHeader[] =
"Android User ID, Package Name, Foreground Bytes, Foreground Bytes %%, Foreground Fsync, "
"Foreground Fsync %%, Background Bytes, Background Bytes %%, Background Fsync, "
@@ -95,6 +102,12 @@
constexpr const char kMajorFaultsSummary[] =
"Number of major page faults since last collection: %" PRIu64 "\n"
"Percentage of change in major page faults since last collection: %.2f%%\n";
+constexpr const char kMemStatsTitle[] = "\nTop N memory stats:\n%s\n";
+constexpr const char kMemStatsHeader[] =
+ "Android User ID, Package Name, RSS (kb), RSS %%, PSS (kb), PSS %%, USS (kb), Swap PSS (kb)"
+ "\n\tCommand, RSS (kb), PSS (kb), USS (kb), Swap PSS (kb)\n";
+constexpr const char kMemStatsSummary[] = "Total RSS (kb): %" PRIu64 "\n"
+ "Total PSS (kb): %" PRIu64 "\n";
double percentage(uint64_t numer, uint64_t denom) {
return denom == 0 ? 0.0 : (static_cast<double>(numer) / static_cast<double>(denom)) * 100.0;
@@ -252,6 +265,27 @@
};
}
+std::string pressureLevelDurationMapToString(
+ const PressureLevelDurationMap& pressureLevelDurations) {
+ std::string buffer;
+ StringAppendF(&buffer, "Duration spent in various memory pressure levels:\n");
+ // The keys stored in PressureLevelDurationMap are unordered, so the pressure level ordering is
+ // inconsistent across different runs. In order to print values in a consistent order, iterate
+ // through the PressureLevel enum instead.
+ for (int i = PressureMonitorInterface::PRESSURE_LEVEL_NONE;
+ i < PressureMonitorInterface::PRESSURE_LEVEL_COUNT; ++i) {
+ PressureMonitorInterface::PressureLevel pressureLevel =
+ static_cast<PressureMonitorInterface::PressureLevel>(i);
+ if (const auto& it = pressureLevelDurations.find(pressureLevel);
+ it != pressureLevelDurations.end()) {
+ StringAppendF(&buffer, "\tPressure level: %s, Duration: %" PRIi64 " ms\n",
+ PressureMonitorInterface::PressureLevelToString(pressureLevel).c_str(),
+ it->second.count());
+ }
+ }
+ return buffer;
+}
+
} // namespace
UserPackageStats::UserPackageStats(MetricType metricType, const UidStats& uidStats) {
@@ -266,26 +300,61 @@
}
UserPackageStats::UserPackageStats(ProcStatType procStatType, const UidStats& uidStats,
- int topNProcessCount) {
- uint64_t value = procStatType == CPU_TIME ? uidStats.cpuTimeMillis
- : procStatType == IO_BLOCKED_TASKS_COUNT ? uidStats.procStats.ioBlockedTasksCount
- : uidStats.procStats.totalMajorFaults;
+ int topNProcessCount, bool isSmapsRollupSupported = false) {
uid = uidStats.uid();
genericPackageName = uidStats.genericPackageName();
- if (procStatType == CPU_TIME) {
- statsView = UserPackageStats::ProcCpuStatsView{.cpuTimeMillis = static_cast<int64_t>(value),
- .cpuCycles = static_cast<int64_t>(
- uidStats.procStats.cpuCycles)};
- auto& procCpuStatsView = std::get<UserPackageStats::ProcCpuStatsView>(statsView);
- procCpuStatsView.topNProcesses.resize(topNProcessCount);
- cacheTopNProcessCpuStats(uidStats, topNProcessCount, &procCpuStatsView.topNProcesses);
- return;
+ switch (procStatType) {
+ case CPU_TIME: {
+ statsView = UserPackageStats::ProcCpuStatsView{.cpuTimeMillis = static_cast<int64_t>(
+ uidStats.cpuTimeMillis),
+ .cpuCycles = static_cast<int64_t>(
+ uidStats.procStats.cpuCycles)};
+ auto& procCpuStatsView = std::get<UserPackageStats::ProcCpuStatsView>(statsView);
+ procCpuStatsView.topNProcesses.resize(topNProcessCount);
+ cacheTopNProcessCpuStats(uidStats, topNProcessCount, &procCpuStatsView.topNProcesses);
+ break;
+ }
+ case MEMORY_STATS: {
+ uint64_t totalUssKb = 0;
+ uint64_t totalSwapPssKb = 0;
+ // TODO(b/333212872): Move totalUssKb, totalSwapPssKb calculation logic to
+ // UidProcStatsCollector
+ for (auto [_, processStats] : uidStats.procStats.processStatsByPid) {
+ totalUssKb += processStats.ussKb;
+ totalSwapPssKb += processStats.swapPssKb;
+ }
+ statsView = UserPackageStats::UidMemoryStats{.memoryStats.rssKb =
+ uidStats.procStats.totalRssKb,
+ .memoryStats.pssKb =
+ uidStats.procStats.totalPssKb,
+ .memoryStats.ussKb = totalUssKb,
+ .memoryStats.swapPssKb = totalSwapPssKb,
+ .isSmapsRollupSupported =
+ isSmapsRollupSupported};
+ auto& uidMemoryStats = std::get<UserPackageStats::UidMemoryStats>(statsView);
+ uidMemoryStats.topNProcesses.resize(topNProcessCount);
+ cacheTopNProcessMemStats(uidStats, topNProcessCount,
+ uidMemoryStats.isSmapsRollupSupported,
+ &uidMemoryStats.topNProcesses);
+ break;
+ }
+ case IO_BLOCKED_TASKS_COUNT:
+ case MAJOR_FAULTS: {
+ uint64_t value = procStatType == IO_BLOCKED_TASKS_COUNT
+ ? uidStats.procStats.ioBlockedTasksCount
+ : uidStats.procStats.totalMajorFaults;
+ statsView = UserPackageStats::ProcSingleStatsView{.value = value};
+ auto& procStatsView = std::get<UserPackageStats::ProcSingleStatsView>(statsView);
+ procStatsView.topNProcesses.resize(topNProcessCount);
+ cacheTopNProcessSingleStats(procStatType, uidStats, topNProcessCount,
+ &procStatsView.topNProcesses);
+ break;
+ }
+ default: {
+ ALOGE("Invalid process stat type: %u", procStatType);
+ break;
+ }
}
- statsView = UserPackageStats::ProcSingleStatsView{.value = value};
- auto& procStatsView = std::get<UserPackageStats::ProcSingleStatsView>(statsView);
- procStatsView.topNProcesses.resize(topNProcessCount);
- cacheTopNProcessSingleStats(procStatType, uidStats, topNProcessCount,
- &procStatsView.topNProcesses);
}
uint64_t UserPackageStats::getValue() const {
@@ -301,6 +370,10 @@
if constexpr (std::is_same_v<T, UserPackageStats::ProcCpuStatsView>) {
return arg.cpuTimeMillis;
}
+ if constexpr (std::is_same_v<T, UserPackageStats::UidMemoryStats>) {
+ return arg.isSmapsRollupSupported ? arg.memoryStats.pssKb
+ : arg.memoryStats.rssKb;
+ }
// Unknown stats view
return 0;
},
@@ -352,6 +425,30 @@
return buffer;
}
+std::string UserPackageStats::toString(int64_t totalRssKb, int64_t totalPssKb) const {
+ std::string buffer;
+ const auto* uidMemoryStats = std::get_if<UserPackageStats::UidMemoryStats>(&statsView);
+ if (uidMemoryStats == nullptr) {
+ return buffer;
+ }
+ StringAppendF(&buffer,
+ "%" PRIu32 ", %s, %" PRIu64 ", %.2f%%, %" PRIu64 ", %.2f%%, %" PRIu64 ", %" PRIu64
+ "\n",
+ multiuser_get_user_id(uid), genericPackageName.c_str(),
+ uidMemoryStats->memoryStats.rssKb,
+ percentage(uidMemoryStats->memoryStats.rssKb, totalRssKb),
+ uidMemoryStats->memoryStats.pssKb,
+ percentage(uidMemoryStats->memoryStats.pssKb, totalPssKb),
+ uidMemoryStats->memoryStats.ussKb, uidMemoryStats->memoryStats.swapPssKb);
+ for (const auto& processMemoryStats : uidMemoryStats->topNProcesses) {
+ StringAppendF(&buffer, "\t%s, %" PRIu64 ", %" PRIu64 ", %" PRIu64 ", %" PRIu64 "\n",
+ processMemoryStats.comm.c_str(), processMemoryStats.memoryStats.rssKb,
+ processMemoryStats.memoryStats.pssKb, processMemoryStats.memoryStats.ussKb,
+ processMemoryStats.memoryStats.swapPssKb);
+ }
+ return buffer;
+}
+
void UserPackageStats::cacheTopNProcessSingleStats(
ProcStatType procStatType, const UidStats& uidStats, int topNProcessCount,
std::vector<UserPackageStats::ProcSingleStatsView::ProcessValue>* topNProcesses) {
@@ -410,6 +507,38 @@
}
}
+void UserPackageStats::cacheTopNProcessMemStats(
+ const UidStats& uidStats, int topNProcessCount, bool isSmapsRollupSupported,
+ std::vector<UserPackageStats::UidMemoryStats::ProcessMemoryStats>* topNProcesses) {
+ int cachedProcessCount = 0;
+ for (const auto& [pid, processStats] : uidStats.procStats.processStatsByPid) {
+ uint64_t pssKb = processStats.pssKb;
+ uint64_t rssKb = processStats.rssKb;
+ if ((isSmapsRollupSupported ? pssKb : rssKb) == 0) {
+ continue;
+ }
+ for (auto it = topNProcesses->begin(); it != topNProcesses->end(); ++it) {
+ if (isSmapsRollupSupported ? pssKb > it->memoryStats.pssKb
+ : rssKb > it->memoryStats.rssKb) {
+ topNProcesses->insert(it,
+ UserPackageStats::UidMemoryStats::ProcessMemoryStats{
+ .comm = processStats.comm,
+ .memoryStats.rssKb = rssKb,
+ .memoryStats.pssKb = pssKb,
+ .memoryStats.ussKb = processStats.ussKb,
+ .memoryStats.swapPssKb = processStats.swapPssKb,
+ });
+ topNProcesses->pop_back();
+ ++cachedProcessCount;
+ break;
+ }
+ }
+ }
+ if (cachedProcessCount < topNProcessCount) {
+ topNProcesses->erase(topNProcesses->begin() + cachedProcessCount, topNProcesses->end());
+ }
+}
+
std::string UserPackageSummaryStats::toString() const {
std::string buffer;
if (!topNCpuTimes.empty()) {
@@ -454,26 +583,39 @@
}
StringAppendF(&buffer, kMajorFaultsSummary, totalMajorFaults, majorFaultsPercentChange);
}
+ if (!topNMemStats.empty()) {
+ StringAppendF(&buffer, kMemStatsTitle, std::string(19, '-').c_str());
+ StringAppendF(&buffer, kMemStatsHeader);
+ for (const auto& stats : topNMemStats) {
+ StringAppendF(&buffer, "%s", stats.toString(totalRssKb, totalPssKb).c_str());
+ }
+ StringAppendF(&buffer, kMemStatsSummary, totalRssKb, totalPssKb);
+ }
return buffer;
}
std::string SystemSummaryStats::toString() const {
std::string buffer;
- StringAppendF(&buffer, "Total CPU time (ms): %" PRIu64 "\n", totalCpuTimeMillis);
- StringAppendF(&buffer, "Total CPU cycles: %" PRIu64 "\n", totalCpuCycles);
- StringAppendF(&buffer, "Total idle CPU time (ms)/percent: %" PRIu64 " / %.2f%%\n",
+ StringAppendF(&buffer, "System summary stats:\n");
+ StringAppendF(&buffer, "\tTotal CPU time (ms): %" PRIu64 "\n", totalCpuTimeMillis);
+ StringAppendF(&buffer, "\tTotal CPU cycles: %" PRIu64 "\n", totalCpuCycles);
+ StringAppendF(&buffer, "\tTotal idle CPU time (ms)/percent: %" PRIu64 " / %.2f%%\n",
cpuIdleTimeMillis, percentage(cpuIdleTimeMillis, totalCpuTimeMillis));
- StringAppendF(&buffer, "CPU I/O wait time (ms)/percent: %" PRIu64 " / %.2f%%\n",
+ StringAppendF(&buffer, "\tCPU I/O wait time (ms)/percent: %" PRIu64 " / %.2f%%\n",
cpuIoWaitTimeMillis, percentage(cpuIoWaitTimeMillis, totalCpuTimeMillis));
- StringAppendF(&buffer, "Number of context switches: %" PRIu64 "\n", contextSwitchesCount);
- StringAppendF(&buffer, "Number of I/O blocked processes/percent: %" PRIu32 " / %.2f%%\n",
+ StringAppendF(&buffer, "\tNumber of context switches: %" PRIu64 "\n", contextSwitchesCount);
+ StringAppendF(&buffer, "\tNumber of I/O blocked processes/percent: %" PRIu32 " / %.2f%%\n",
ioBlockedProcessCount, percentage(ioBlockedProcessCount, totalProcessCount));
+ // TODO(b/337115923): Report `totalMajorFaults`, `totalRssKb`, `totalPssKb`, and
+ // `majorFaultsPercentChange` here.
return buffer;
}
std::string PerfStatsRecord::toString() const {
std::string buffer;
- StringAppendF(&buffer, "%s%s", systemSummaryStats.toString().c_str(),
+ StringAppendF(&buffer, "%s\n%s\n%s",
+ pressureLevelDurationMapToString(memoryPressureLevelDurations).c_str(),
+ systemSummaryStats.toString().c_str(),
userPackageSummaryStats.toString().c_str());
return buffer;
}
@@ -486,7 +628,7 @@
double duration =
difftime(std::chrono::system_clock::to_time_t(records.back().collectionTimeMillis),
std::chrono::system_clock::to_time_t(records.front().collectionTimeMillis));
- StringAppendF(&buffer, kCollectionTitle, duration, records.size());
+ StringAppendF(&buffer, kCollectionTitle, duration, maxCacheSize, records.size());
for (size_t i = 0; i < records.size(); ++i) {
const auto& record = records[i];
std::stringstream timestamp;
@@ -530,14 +672,27 @@
.maxCacheSize = std::numeric_limits<std::size_t>::max(),
.records = {},
};
+ if (!mIsMemoryProfilingEnabled || !kPressureMonitor->isEnabled()) {
+ return {};
+ }
+ if (auto result = kPressureMonitor->registerPressureChangeCallback(
+ sp<PerformanceProfiler>::fromExisting(this));
+ !result.ok()) {
+ ALOGE("Failed to register pressure change callback for '%s'. Error: %s", name().c_str(),
+ result.error().message().c_str());
+ }
return {};
}
void PerformanceProfiler::terminate() {
Mutex::Autolock lock(mMutex);
-
ALOGW("Terminating %s", name().c_str());
+ if (mIsMemoryProfilingEnabled && kPressureMonitor->isEnabled()) {
+ kPressureMonitor->unregisterPressureChangeCallback(
+ sp<PerformanceProfiler>::fromExisting(this));
+ }
+
mBoottimeCollection.records.clear();
mBoottimeCollection = {};
@@ -552,7 +707,14 @@
Result<void> PerformanceProfiler::onDump(int fd) const {
Mutex::Autolock lock(mMutex);
- if (!WriteStringToFd(StringPrintf(kBootTimeCollectionTitle, std::string(75, '-').c_str(),
+ if (!WriteStringToFd(StringPrintf("/proc/<pid>/smaps_rollup is %s\n",
+ mIsSmapsRollupSupported
+ ? "supported. So, using PSS to rank top memory "
+ "consuming processes."
+ : "not supported. So, using RSS to rank top memory "
+ "consuming processes."),
+ fd) ||
+ !WriteStringToFd(StringPrintf(kBootTimeCollectionTitle, std::string(75, '-').c_str(),
std::string(33, '=').c_str()),
fd) ||
!WriteStringToFd(mBoottimeCollection.toString(), fd)) {
@@ -576,8 +738,8 @@
return {};
}
-Result<void> PerformanceProfiler::onDumpProto(
- const CollectionIntervals& collectionIntervals, ProtoOutputStream& outProto) const {
+Result<void> PerformanceProfiler::onDumpProto(const CollectionIntervals& collectionIntervals,
+ ProtoOutputStream& outProto) const {
Mutex::Autolock lock(mMutex);
uint64_t performanceStatsToken = outProto.start(PerformanceProfilerDump::PERFORMANCE_STATS);
@@ -965,6 +1127,9 @@
PerfStatsRecord record{
.collectionTimeMillis = time,
};
+ if (mIsMemoryProfilingEnabled) {
+ record.memoryPressureLevelDurations = mMemoryPressureLevelDeltaInfo.onCollectionLocked();
+ }
bool isGarageModeActive = systemState == SystemState::GARAGE_MODE;
bool shouldSendResourceUsageStats = mDoSendResourceUsageStats && (resourceStats != nullptr);
std::vector<UidResourceUsageStats>* uidResourceUsageStats =
@@ -979,7 +1144,7 @@
// The system-wide CPU cycles are the aggregate of all the UID's CPU cycles collected during
// each poll.
record.systemSummaryStats.totalCpuCycles = record.userPackageSummaryStats.totalCpuCycles;
- if (collectionInfo->records.size() > collectionInfo->maxCacheSize) {
+ if (collectionInfo->records.size() >= collectionInfo->maxCacheSize) {
collectionInfo->records.erase(collectionInfo->records.begin()); // Erase the oldest record.
}
collectionInfo->records.push_back(record);
@@ -1013,12 +1178,14 @@
if (uidStats.empty()) {
return;
}
+
if (filterPackages.empty()) {
userPackageSummaryStats->topNCpuTimes.resize(mTopNStatsPerCategory);
userPackageSummaryStats->topNIoReads.resize(mTopNStatsPerCategory);
userPackageSummaryStats->topNIoWrites.resize(mTopNStatsPerCategory);
userPackageSummaryStats->topNIoBlocked.resize(mTopNStatsPerCategory);
userPackageSummaryStats->topNMajorFaults.resize(mTopNStatsPerCategory);
+ userPackageSummaryStats->topNMemStats.resize(mTopNStatsPerCategory);
}
int64_t elapsedTimeSinceBootMs = kGetElapsedTimeSinceBootMillisFunc();
for (const auto& curUidStats : uidStats) {
@@ -1026,6 +1193,10 @@
userPackageSummaryStats->totalCpuCycles += curUidStats.procStats.cpuCycles;
addUidIoStats(curUidStats.ioStats.metrics, userPackageSummaryStats->totalIoStats);
userPackageSummaryStats->totalMajorFaults += curUidStats.procStats.totalMajorFaults;
+ if (mIsMemoryProfilingEnabled) {
+ userPackageSummaryStats->totalRssKb += curUidStats.procStats.totalRssKb;
+ userPackageSummaryStats->totalPssKb += curUidStats.procStats.totalPssKb;
+ }
// Transform |UidStats| to |UserPackageStats| for each stats view.
auto ioReadsPackageStats = UserPackageStats(MetricType::READ_BYTES, curUidStats);
@@ -1036,6 +1207,12 @@
UserPackageStats(IO_BLOCKED_TASKS_COUNT, curUidStats, mTopNStatsPerSubcategory);
auto majorFaultsPackageStats =
UserPackageStats(MAJOR_FAULTS, curUidStats, mTopNStatsPerSubcategory);
+ UserPackageStats memoryPackageStats;
+ if (mIsMemoryProfilingEnabled) {
+ memoryPackageStats =
+ UserPackageStats(MEMORY_STATS, curUidStats, mTopNStatsPerSubcategory,
+ mIsSmapsRollupSupported);
+ }
if (filterPackages.empty()) {
cacheTopNStats(ioReadsPackageStats, &userPackageSummaryStats->topNIoReads);
@@ -1046,6 +1223,9 @@
curUidStats.procStats.totalTasksCount;
}
cacheTopNStats(majorFaultsPackageStats, &userPackageSummaryStats->topNMajorFaults);
+ if (mIsMemoryProfilingEnabled) {
+ cacheTopNStats(memoryPackageStats, &userPackageSummaryStats->topNMemStats);
+ }
} else if (filterPackages.count(curUidStats.genericPackageName()) != 0) {
userPackageSummaryStats->topNIoReads.push_back(ioReadsPackageStats);
userPackageSummaryStats->topNIoWrites.push_back(ioWritesPackageStats);
@@ -1054,6 +1234,9 @@
userPackageSummaryStats->topNMajorFaults.push_back(majorFaultsPackageStats);
userPackageSummaryStats->taskCountByUid[ioBlockedPackageStats.uid] =
curUidStats.procStats.totalTasksCount;
+ if (mIsMemoryProfilingEnabled) {
+ userPackageSummaryStats->topNMemStats.push_back(memoryPackageStats);
+ }
}
// A null value in uidResourceUsageStats indicates that uid resource usage stats
@@ -1106,6 +1289,7 @@
removeEmptyStats(userPackageSummaryStats->topNIoWrites);
removeEmptyStats(userPackageSummaryStats->topNIoBlocked);
removeEmptyStats(userPackageSummaryStats->topNMajorFaults);
+ removeEmptyStats(userPackageSummaryStats->topNMemStats);
}
void PerformanceProfiler::processProcStatLocked(
@@ -1146,6 +1330,35 @@
return {};
}
+void PerformanceProfiler::PressureLevelDeltaInfo::setLatestPressureLevelLocked(
+ PressureMonitorInterface::PressureLevel pressureLevel) {
+ auto now = kGetElapsedTimeSinceBootMillisFunc();
+ auto duration = now - mLatestPressureLevelElapsedRealtimeMillis;
+ if (mPressureLevelDurations.find(pressureLevel) == mPressureLevelDurations.end()) {
+ mPressureLevelDurations[pressureLevel] = 0ms;
+ }
+ mPressureLevelDurations[pressureLevel] += std::chrono::milliseconds(duration);
+ mLatestPressureLevelElapsedRealtimeMillis = now;
+ mLatestPressureLevel = pressureLevel;
+}
+
+PressureLevelDurationMap PerformanceProfiler::PressureLevelDeltaInfo::onCollectionLocked() {
+ // Reset pressure level to trigger accounting and flushing the latest timing info to
+ // mPressureLevelDurations.
+ setLatestPressureLevelLocked(mLatestPressureLevel);
+ auto durationsMap = mPressureLevelDurations;
+ mPressureLevelDurations.clear();
+ return durationsMap;
+}
+
+void PerformanceProfiler::onPressureChanged(PressureMonitorInterface::PressureLevel pressureLevel) {
+ if (!mIsMemoryProfilingEnabled) {
+ return;
+ }
+ Mutex::Autolock lock(mMutex);
+ mMemoryPressureLevelDeltaInfo.setLatestPressureLevelLocked(pressureLevel);
+}
+
void PerformanceProfiler::clearExpiredSystemEventCollections(time_point_millis now) {
Mutex::Autolock lock(mMutex);
auto clearExpiredSystemEvent = [&](CollectionInfo* info) -> bool {
diff --git a/cpp/watchdog/server/src/PerformanceProfiler.h b/cpp/watchdog/server/src/PerformanceProfiler.h
index d373c7f..836c3af 100644
--- a/cpp/watchdog/server/src/PerformanceProfiler.h
+++ b/cpp/watchdog/server/src/PerformanceProfiler.h
@@ -17,6 +17,7 @@
#ifndef CPP_WATCHDOG_SERVER_SRC_PERFORMANCEPROFILER_H_
#define CPP_WATCHDOG_SERVER_SRC_PERFORMANCEPROFILER_H_
+#include "PressureMonitor.h"
#include "ProcDiskStatsCollector.h"
#include "ProcStatCollector.h"
#include "UidStatsCollector.h"
@@ -27,11 +28,14 @@
#include <android/util/ProtoOutputStream.h>
#include <cutils/multiuser.h>
#include <gtest/gtest_prod.h>
+#include <meminfo/procmeminfo.h>
#include <utils/Errors.h>
#include <utils/Mutex.h>
#include <utils/RefBase.h>
#include <utils/SystemClock.h>
+#include <android_car_feature.h>
+
#include <ctime>
#include <string>
#include <unordered_set>
@@ -58,12 +62,16 @@
IO_BLOCKED_TASKS_COUNT = 0,
MAJOR_FAULTS,
CPU_TIME,
+ MEMORY_STATS,
PROC_STAT_TYPES,
};
// UserPackageStats represents the user package performance stats.
class UserPackageStats {
public:
+ // TODO(b/332773702): Rename nested structs
+ // first-level IoStatsView, ProcSingleStatsView, and ProcCpuStatsView renames to Uid*Stats
+ // second-level ProcessValue and ProcessCpuValue renames to Process*Stats
struct IoStatsView {
int64_t bytes[UID_STATES] = {0};
int64_t fsync[UID_STATES] = {0};
@@ -94,17 +102,33 @@
};
std::vector<ProcessCpuValue> topNProcesses = {};
};
+ struct MemoryStats {
+ uint64_t rssKb = 0;
+ uint64_t pssKb = 0;
+ uint64_t ussKb = 0;
+ uint64_t swapPssKb = 0;
+ };
+ struct UidMemoryStats {
+ MemoryStats memoryStats;
+ bool isSmapsRollupSupported;
+ struct ProcessMemoryStats {
+ std::string comm = "";
+ MemoryStats memoryStats;
+ };
+ std::vector<ProcessMemoryStats> topNProcesses = {};
+ };
UserPackageStats(MetricType metricType, const UidStats& uidStats);
- UserPackageStats(ProcStatType procStatType, const UidStats& uidStats, int topNProcessCount);
+ UserPackageStats(ProcStatType procStatType, const UidStats& uidStats, int topNProcessCount,
+ bool isSmapsRollupSupported);
// Class must be DefaultInsertable for std::vector<T>::resize to work
UserPackageStats() : uid(0), genericPackageName("") {}
// For unit test case only
- UserPackageStats(
- uid_t uid, std::string genericPackageName,
- std::variant<std::monostate, IoStatsView, ProcSingleStatsView, ProcCpuStatsView>
- statsView) :
+ UserPackageStats(uid_t uid, std::string genericPackageName,
+ std::variant<std::monostate, IoStatsView, ProcSingleStatsView,
+ ProcCpuStatsView, UidMemoryStats>
+ statsView) :
uid(uid),
genericPackageName(std::move(genericPackageName)),
statsView(std::move(statsView)) {}
@@ -116,10 +140,12 @@
uint64_t getValue() const;
std::string toString(MetricType metricsType, const int64_t totalIoStats[][UID_STATES]) const;
std::string toString(int64_t totalValue) const;
+ std::string toString(int64_t totalRssKb, int64_t totalPssKb) const;
uid_t uid;
std::string genericPackageName;
- std::variant<std::monostate, IoStatsView, ProcSingleStatsView, ProcCpuStatsView> statsView;
+ std::variant<std::monostate, IoStatsView, ProcSingleStatsView, ProcCpuStatsView, UidMemoryStats>
+ statsView;
private:
void cacheTopNProcessSingleStats(
@@ -128,6 +154,9 @@
void cacheTopNProcessCpuStats(
const UidStats& uidStats, int topNProcessCount,
std::vector<UserPackageStats::ProcCpuStatsView::ProcessCpuValue>* topNProcesses);
+ void cacheTopNProcessMemStats(
+ const UidStats& uidStats, int topNProcessCount, bool isSmapsRollupSupported,
+ std::vector<UserPackageStats::UidMemoryStats::ProcessMemoryStats>* topNProcesses);
};
/**
@@ -140,11 +169,16 @@
std::vector<UserPackageStats> topNIoWrites = {};
std::vector<UserPackageStats> topNIoBlocked = {};
std::vector<UserPackageStats> topNMajorFaults = {};
+ std::vector<UserPackageStats> topNMemStats = {};
int64_t totalIoStats[METRIC_TYPES][UID_STATES] = {{0}};
std::unordered_map<uid_t, uint64_t> taskCountByUid = {};
+ // TODO(b/337115923): Clean up below duplicate fields and report `totalMajorFaults`,
+ // `totalRssKb`, `totalPssKb`, and `majorFaultsPercentChange` as part of `SystemSummaryStats`.
int64_t totalCpuTimeMillis = 0;
uint64_t totalCpuCycles = 0;
uint64_t totalMajorFaults = 0;
+ uint64_t totalRssKb = 0;
+ uint64_t totalPssKb = 0;
// Percentage of increase/decrease in the major page faults since last collection.
double majorFaultsPercentChange = 0.0;
std::string toString() const;
@@ -168,6 +202,8 @@
time_point_millis collectionTimeMillis;
SystemSummaryStats systemSummaryStats;
UserPackageSummaryStats userPackageSummaryStats;
+ std::unordered_map<PressureMonitorInterface::PressureLevel, std::chrono::milliseconds>
+ memoryPressureLevelDurations;
std::string toString() const;
};
@@ -185,21 +221,34 @@
};
// PerformanceProfiler implements the I/O performance data collection module.
-class PerformanceProfiler final : public DataProcessorInterface {
+class PerformanceProfiler final :
+ public DataProcessorInterface,
+ public PressureMonitorInterface::PressureChangeCallbackInterface {
public:
- PerformanceProfiler() :
- kGetElapsedTimeSinceBootMillisFunc(elapsedRealtime),
+ PerformanceProfiler(
+ const android::sp<PressureMonitorInterface>& pressureMonitor,
+ const std::function<int64_t()>& getElapsedTimeSinceBootMillisFunc = &elapsedRealtime) :
+ kPressureMonitor(pressureMonitor),
+ kGetElapsedTimeSinceBootMillisFunc(getElapsedTimeSinceBootMillisFunc),
mTopNStatsPerCategory(0),
mTopNStatsPerSubcategory(0),
mMaxUserSwitchEvents(0),
mSystemEventDataCacheDurationSec(0),
+ // TODO(b/333722043): Once carwatchdogd has sys_ptrace capability, set
+ // mIsSmapsRollupSupported field from `android::meminfo::IsSmapsRollupSupported()`.
+ // Disabling smaps_rollup support because this file cannot be read without sys_ptrace
+ // capability.
+ mIsSmapsRollupSupported(false),
+ mIsMemoryProfilingEnabled(android::car::feature::car_watchdog_memory_profiling()),
mBoottimeCollection({}),
mPeriodicCollection({}),
mUserSwitchCollections({}),
mWakeUpCollection({}),
mCustomCollection({}),
mLastMajorFaults(0),
- mDoSendResourceUsageStats(false) {}
+ mDoSendResourceUsageStats(false),
+ mMemoryPressureLevelDeltaInfo(PressureLevelDeltaInfo(getElapsedTimeSinceBootMillisFunc)) {
+ }
~PerformanceProfiler() { terminate(); }
@@ -256,6 +305,8 @@
android::base::Result<void> onCustomCollectionDump(int fd) override;
+ void onPressureChanged(PressureMonitorInterface::PressureLevel) override;
+
protected:
android::base::Result<void> init();
@@ -263,6 +314,38 @@
void terminate();
private:
+ class PressureLevelDeltaInfo {
+ public:
+ explicit PressureLevelDeltaInfo(
+ const std::function<int64_t()>& getElapsedTimeSinceBootMillisFunc) :
+ kGetElapsedTimeSinceBootMillisFunc(getElapsedTimeSinceBootMillisFunc),
+ mLatestPressureLevel(PressureMonitorInterface::PRESSURE_LEVEL_NONE),
+ mLatestPressureLevelElapsedRealtimeMillis(getElapsedTimeSinceBootMillisFunc()) {}
+
+ // Calculates the duration for the previously reported pressure level, updates it in
+ // mPressureLevelDurations, and sets the latest pressure level and its elapsed realtime.
+ void setLatestPressureLevelLocked(PressureMonitorInterface::PressureLevel pressureLevel);
+
+ // Returns the latest pressure stats and flushes stats to mPressureLevelDurations.
+ std::unordered_map<PressureMonitorInterface::PressureLevel, std::chrono::milliseconds>
+ onCollectionLocked();
+
+ private:
+ // Updated by test for mocking elapsed time.
+ const std::function<int64_t()> kGetElapsedTimeSinceBootMillisFunc;
+
+ // Latest pressure level reported by the PressureMonitor.
+ PressureMonitorInterface::PressureLevel mLatestPressureLevel;
+
+ // Time when the latest pressure level was recorded. Used to calculate
+ // pressureLevelDurations.
+ int64_t mLatestPressureLevelElapsedRealtimeMillis = 0;
+
+ // Duration spent in different pressure levels since the last poll.
+ std::unordered_map<PressureMonitorInterface::PressureLevel, std::chrono::milliseconds>
+ mPressureLevelDurations = {};
+ };
+
// Processes the collected data.
android::base::Result<void> processLocked(
time_point_millis time, SystemState systemState,
@@ -307,7 +390,11 @@
void dumpPackageMajorPageFaultsProto(const std::vector<UserPackageStats>& userPackageStats,
android::util::ProtoOutputStream& outProto) const;
- std::function<int64_t()> kGetElapsedTimeSinceBootMillisFunc;
+ // Pressure monitor instance.
+ const android::sp<PressureMonitorInterface> kPressureMonitor;
+
+ // Updated by test for mocking elapsed time.
+ const std::function<int64_t()> kGetElapsedTimeSinceBootMillisFunc;
// Top N per-UID stats per category.
int mTopNStatsPerCategory;
@@ -321,6 +408,12 @@
// Amount of seconds before a system event's cache is cleared.
std::chrono::seconds mSystemEventDataCacheDurationSec;
+ // Smaps rollup is supported by kernel or not.
+ bool mIsSmapsRollupSupported;
+
+ // Memory Profiling feature flag is enabled or not.
+ bool mIsMemoryProfilingEnabled;
+
// Makes sure only one collection is running at any given time.
mutable Mutex mMutex;
@@ -349,6 +442,9 @@
// Enables the sending of resource usage stats to CarService.
bool mDoSendResourceUsageStats GUARDED_BY(mMutex);
+ // Aggregated pressure level changes occurred since the last collection.
+ PressureLevelDeltaInfo mMemoryPressureLevelDeltaInfo GUARDED_BY(mMutex);
+
friend class WatchdogPerfService;
// For unit tests.
diff --git a/cpp/watchdog/server/src/PressureMonitor.cpp b/cpp/watchdog/server/src/PressureMonitor.cpp
new file mode 100644
index 0000000..356a257
--- /dev/null
+++ b/cpp/watchdog/server/src/PressureMonitor.cpp
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "carwatchdogd"
+#define DEBUG false // STOPSHIP if true.
+
+#include "PressureMonitor.h"
+
+#include <android-base/stringprintf.h>
+#include <log/log.h>
+#include <processgroup/sched_policy.h>
+
+#include <errno.h>
+#include <string.h>
+#include <sys/epoll.h>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+using ::android::sp;
+using ::android::base::Error;
+using ::android::base::Result;
+using ::android::base::StringPrintf;
+
+constexpr const char kThreadName[] = "PressureMonitor";
+
+std::string PressureMonitorInterface::PressureLevelToString(PressureLevel pressureLevel) {
+ switch (pressureLevel) {
+ case PRESSURE_LEVEL_NONE:
+ return "PRESSURE_LEVEL_NONE";
+ case PRESSURE_LEVEL_LOW:
+ return "PRESSURE_LEVEL_LOW";
+ case PRESSURE_LEVEL_MEDIUM:
+ return "PRESSURE_LEVEL_MEDIUM";
+ case PRESSURE_LEVEL_HIGH:
+ return "PRESSURE_LEVEL_HIGH";
+ default:
+ return "UNKNOWN_PRESSURE_LEVEL";
+ }
+}
+
+Result<void> PressureMonitor::init() {
+ std::string memoryPath = StringPrintf("%s/%s", kProcPressureDirPath.c_str(), kMemoryFile);
+ if (access(memoryPath.c_str(), R_OK) != 0) {
+ return Error() << "'" << memoryPath << "' path is not accessible";
+ }
+
+ Mutex::Autolock lock(mMutex);
+ // TODO(b/335508921): Read the below stall type and thresholds from system properties (one per
+ // pressure level).
+ mPressureLevels.push_back(PressureLevelInfo{
+ .kPressureLevel = PRESSURE_LEVEL_LOW,
+ .kStallType = kLowPsiStallLevel,
+ .kThresholdUs = kLowThresholdUs,
+ });
+ mPressureLevels.push_back(PressureLevelInfo{
+ .kPressureLevel = PRESSURE_LEVEL_MEDIUM,
+ .kStallType = kMediumPsiStallLevel,
+ .kThresholdUs = kMediumThresholdUs,
+ });
+ mPressureLevels.push_back(PressureLevelInfo{
+ .kPressureLevel = PRESSURE_LEVEL_HIGH,
+ .kStallType = kHighPsiStallLevel,
+ .kThresholdUs = kHighThresholdUs,
+ });
+
+ if (const auto& result = initializePsiMonitorsLocked(); !result.ok()) {
+ destroyActivePsiMonitorsLocked();
+ return Error() << "Failed to initialize memory PSI monitors: " << result.error();
+ }
+
+ mIsEnabled = true;
+ return {};
+}
+
+void PressureMonitor::terminate() {
+ {
+ Mutex::Autolock lock(mMutex);
+ mIsMonitorActive = false;
+ mHandlerLooper->removeMessages(sp<PressureMonitor>::fromExisting(this));
+ mHandlerLooper->wake();
+ }
+ if (mMonitorThread.joinable()) {
+ mMonitorThread.join();
+ }
+ {
+ Mutex::Autolock lock(mMutex);
+ destroyActivePsiMonitorsLocked();
+ }
+}
+
+Result<void> PressureMonitor::initializePsiMonitorsLocked() {
+ if (mPsiEpollFd = epoll_create(mPressureLevels.size()); mPsiEpollFd < 0) {
+ return Error() << "epoll_create failed: " << strerror(errno);
+ }
+
+ int totalActivePsiMonitors = 0;
+ for (auto& info : mPressureLevels) {
+ if (info.kThresholdUs.count() == 0) {
+ ALOGI("Disabled PSI monitor for %s",
+ PressureLevelToString(info.kPressureLevel).c_str());
+ continue;
+ }
+ // TODO(b/335508921): Read the below window size from system properties. This need to be
+ // read from system properties (one per pressure level) and store in the PressureLevelInfo.
+ if (info.kThresholdUs >= kPsiWindowSizeUs) {
+ return Error() << "Threshold duration (" << info.kThresholdUs.count()
+ << ") must be less than the window size duration ("
+ << kPsiWindowSizeUs.count() << ") for "
+ << PressureLevelToString(info.kPressureLevel);
+ }
+ // The algorithm that determines the current pressure level and notifies the clients
+ // require all PSI monitors to be initialized successfully. So, early fail when one of
+ // PSI monitor fails to initialize.
+ int fd = mInitPsiMonitorFunc(info.kStallType, info.kThresholdUs.count(),
+ kPsiWindowSizeUs.count(), PSI_MEMORY);
+ if (fd < 0) {
+ return Error() << "Failed to initialize memory PSI monitor for "
+ << PressureLevelToString(info.kPressureLevel) << ": " << strerror(errno);
+ }
+ if (mRegisterPsiMonitorFunc(mPsiEpollFd, fd, reinterpret_cast<void*>(info.kPressureLevel)) <
+ 0) {
+ mDestroyPsiMonitorFunc(fd);
+ return Error() << "Failed to register memory PSI monitor for "
+ << PressureLevelToString(info.kPressureLevel) << ": " << strerror(errno);
+ }
+ info.psiMonitorFd = fd;
+ ++totalActivePsiMonitors;
+ }
+ if (totalActivePsiMonitors == 0) {
+ return Error() << "No PSI monitors are initialized because all PSI levels are disabled";
+ }
+ ALOGI("Successfully initialized %d memory PSI monitors", totalActivePsiMonitors);
+ return {};
+}
+
+void PressureMonitor::destroyActivePsiMonitorsLocked() {
+ int totalDestroyedPsiMonitors = 0;
+ for (auto& info : mPressureLevels) {
+ if (info.psiMonitorFd < 0) {
+ continue;
+ }
+ if (mUnregisterPsiMonitorFunc(mPsiEpollFd, info.psiMonitorFd) < 0) {
+ ALOGE("Failed to unregister memory PSI monitor for %s: %s",
+ PressureLevelToString(info.kPressureLevel).c_str(), strerror(errno));
+ }
+ mDestroyPsiMonitorFunc(info.psiMonitorFd);
+ info.psiMonitorFd = -1;
+ ++totalDestroyedPsiMonitors;
+ }
+ if (mPsiEpollFd > 0) {
+ close(mPsiEpollFd);
+ mPsiEpollFd = -1;
+ }
+ ALOGI("Destroyed %d memory PSI monitors", totalDestroyedPsiMonitors);
+}
+
+Result<void> PressureMonitor::start() {
+ {
+ Mutex::Autolock lock(mMutex);
+ if (!mIsEnabled) {
+ return Error() << "Monitor is either disabled or not initialized";
+ }
+ if (mMonitorThread.joinable()) {
+ return Error()
+ << "Pressure monitoring is already in progress. So skipping this request";
+ }
+ mIsMonitorActive = true;
+ }
+ mMonitorThread = std::thread([&]() {
+ if (set_sched_policy(0, SP_BACKGROUND) != 0) {
+ ALOGW("Failed to set background scheduling priority to %s thread", kThreadName);
+ }
+ if (int result = pthread_setname_np(pthread_self(), kThreadName); result != 0) {
+ ALOGW("Failed to set %s thread name: %d", kThreadName, result);
+ }
+ bool isMonitorActive;
+ {
+ Mutex::Autolock lock(mMutex);
+ mHandlerLooper->setLooper(Looper::prepare(/*opts=*/0));
+ mLastPollUptimeNs = mHandlerLooper->now();
+ mHandlerLooper->sendMessage(sp<PressureMonitor>::fromExisting(this),
+ LooperMessage::MONITOR_PRESSURE);
+ isMonitorActive = mIsMonitorActive;
+ }
+ ALOGI("Starting pressure monitor");
+ while (isMonitorActive) {
+ mHandlerLooper->pollAll(/*timeoutMillis=*/-1);
+ Mutex::Autolock lock(mMutex);
+ isMonitorActive = mIsMonitorActive;
+ }
+ });
+ return {};
+}
+
+Result<void> PressureMonitor::registerPressureChangeCallback(
+ sp<PressureChangeCallbackInterface> callback) {
+ Mutex::Autolock lock(mMutex);
+ if (mPressureChangeCallbacks.find(callback) != mPressureChangeCallbacks.end()) {
+ return Error() << "Callback is already registered";
+ }
+ mPressureChangeCallbacks.insert(callback);
+ return {};
+}
+
+void PressureMonitor::unregisterPressureChangeCallback(
+ sp<PressureChangeCallbackInterface> callback) {
+ Mutex::Autolock lock(mMutex);
+ const auto& it = mPressureChangeCallbacks.find(callback);
+ if (it == mPressureChangeCallbacks.end()) {
+ ALOGE("Pressure change callback is not registered. Skipping unregister request");
+ return;
+ }
+ mPressureChangeCallbacks.erase(it);
+}
+
+void PressureMonitor::handleMessage(const Message& message) {
+ Result<void> result;
+ switch (message.what) {
+ case LooperMessage::MONITOR_PRESSURE:
+ if (const auto& monitorResult = monitorPressure(); !monitorResult.ok()) {
+ result = Error() << "Failed to monitor pressure: " << monitorResult.error();
+ }
+ break;
+ case LooperMessage::NOTIFY_PRESSURE_CHANGE:
+ notifyPressureChange();
+ break;
+ default:
+ ALOGE("Skipping unknown pressure monitor message: %d", message.what);
+ }
+ if (!result.ok()) {
+ ALOGE("Terminating pressure monitor: %s", result.error().message().c_str());
+ Mutex::Autolock lock(mMutex);
+ mIsMonitorActive = false;
+ }
+}
+
+Result<void> PressureMonitor::monitorPressure() {
+ size_t maxEvents;
+ int psiEpollFd;
+ {
+ Mutex::Autolock lock(mMutex);
+ psiEpollFd = mPsiEpollFd;
+ maxEvents = mPressureLevels.size();
+ }
+ if (psiEpollFd < 0) {
+ return Error() << "Memory pressure monitor is not initialized";
+ }
+ struct epoll_event* events = new epoll_event[maxEvents];
+ auto result = waitForLatestPressureLevel(psiEpollFd, events, maxEvents);
+ if (!result.ok()) {
+ delete[] events;
+ return Error() << "Failed to get the latest pressure level: " << result.error();
+ }
+ delete[] events;
+
+ Mutex::Autolock lock(mMutex);
+ if (mLatestPressureLevel != *result) {
+ mLatestPressureLevel = *result;
+ mHandlerLooper->sendMessage(sp<PressureMonitor>::fromExisting(this),
+ LooperMessage::NOTIFY_PRESSURE_CHANGE);
+ }
+
+ mLastPollUptimeNs +=
+ std::chrono::duration_cast<std::chrono::nanoseconds>(mPollingIntervalMillis).count();
+ // The NOTIFY_PRESSURE_CHANGE message must be handled before MONITOR_PRESSURE message.
+ // Otherwise, the callbacks won't be notified of the recent pressure level change. To avoid
+ // inserting MONITOR_PRESSURE message before NOTIFY_PRESSURE_CHANGE message, check the uptime.
+ nsecs_t now = mHandlerLooper->now();
+ mHandlerLooper->sendMessageAtTime(mLastPollUptimeNs > now ? mLastPollUptimeNs : now,
+ sp<PressureMonitor>::fromExisting(this),
+ LooperMessage::MONITOR_PRESSURE);
+ return {};
+}
+
+Result<PressureMonitor::PressureLevel> PressureMonitor::waitForLatestPressureLevel(
+ int psiEpollFd, epoll_event* events, size_t maxEvents) {
+ PressureLevel highestActivePressure;
+ {
+ Mutex::Autolock lock(mMutex);
+ highestActivePressure = mLatestPressureLevel;
+ }
+ int totalActiveEvents;
+ do {
+ if (highestActivePressure == PRESSURE_LEVEL_NONE) {
+ // When the recent pressure level was none, wait with no timeout until the pressure
+ // increases.
+ totalActiveEvents = mEpollWaitFunc(psiEpollFd, events, maxEvents, /*timeout=*/-1);
+ } else {
+ // When the recent pressure level was high, assume that the pressure will stay high
+ // for at least 1 second. Within 1 second window, the memory pressure state can go up
+ // causing an event to trigger or it can go down when the window expires.
+
+ // TODO(b/333411972): Review whether 1 second wait is sufficient and whether an event
+ // will trigger if the memory pressure continues to stay higher for more than this
+ // period.
+ totalActiveEvents =
+ mEpollWaitFunc(psiEpollFd, events, maxEvents, mPollingIntervalMillis.count());
+ if (totalActiveEvents == 0) {
+ return PRESSURE_LEVEL_NONE;
+ }
+ }
+ // Keep waiting if interrupted.
+ } while (totalActiveEvents == -1 && errno == EINTR);
+
+ if (totalActiveEvents == -1) {
+ return Error() << "epoll_wait failed while waiting for PSI events: " << strerror(errno);
+ }
+ // Reset and identify the recent highest active pressure from the PSI events.
+ highestActivePressure = PRESSURE_LEVEL_NONE;
+
+ for (int i = 0; i < totalActiveEvents; i++) {
+ if (events[i].events & (EPOLLERR | EPOLLHUP)) {
+ // Should never happen unless psi got disabled in the Kernel.
+ return Error() << "Memory pressure events are not available anymore";
+ }
+ if (events[i].data.u32 > highestActivePressure) {
+ highestActivePressure = static_cast<PressureLevel>(events[i].data.u32);
+ }
+ }
+ return highestActivePressure;
+}
+
+void PressureMonitor::notifyPressureChange() {
+ PressureLevel pressureLevel;
+ std::unordered_set<sp<PressureChangeCallbackInterface>, SpHash<PressureChangeCallbackInterface>>
+ callbacks;
+ {
+ Mutex::Autolock lock(mMutex);
+ pressureLevel = mLatestPressureLevel;
+ callbacks = mPressureChangeCallbacks;
+ }
+ if (DEBUG) {
+ ALOGD("Sending pressure change notification to %zu callbacks", callbacks.size());
+ }
+ for (const sp<PressureChangeCallbackInterface>& callback : callbacks) {
+ callback->onPressureChanged(pressureLevel);
+ }
+}
+
+} // namespace watchdog
+} // namespace automotive
+} // namespace android
diff --git a/cpp/watchdog/server/src/PressureMonitor.h b/cpp/watchdog/server/src/PressureMonitor.h
new file mode 100644
index 0000000..7894773
--- /dev/null
+++ b/cpp/watchdog/server/src/PressureMonitor.h
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CPP_WATCHDOG_SERVER_SRC_PRESSUREMONITOR_H_
+#define CPP_WATCHDOG_SERVER_SRC_PRESSUREMONITOR_H_
+
+#include "LooperWrapper.h"
+
+#include <android-base/chrono_utils.h>
+#include <android-base/result.h>
+#include <psi/psi.h>
+#include <utils/Mutex.h>
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+
+#include <unistd.h>
+
+#include <thread> // NOLINT(build/c++11)
+#include <unordered_set>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+constexpr const char kDefaultProcPressureDirPath[] = "/proc/pressure";
+constexpr const char kMemoryFile[] = "memory";
+
+// PSI monitor window over which the PSI thresholds are defined.
+constexpr std::chrono::microseconds kPsiWindowSizeUs = 1s;
+
+// PSI stall levels for different PSI levels.
+constexpr psi_stall_type kLowPsiStallLevel = PSI_SOME;
+constexpr psi_stall_type kMediumPsiStallLevel = PSI_FULL;
+constexpr psi_stall_type kHighPsiStallLevel = PSI_FULL;
+
+// Threshold durations for different PSI levels for the above window size.
+constexpr std::chrono::microseconds kLowThresholdUs = 15ms;
+constexpr std::chrono::microseconds kMediumThresholdUs = 30ms;
+constexpr std::chrono::microseconds kHighThresholdUs = 50ms;
+
+// Time between consecutive polling of pressure events.
+constexpr std::chrono::milliseconds kPollingIntervalMillis = 1s;
+
+class PressureMonitorInterface : virtual public android::RefBase {
+public:
+ enum PressureLevel {
+ PRESSURE_LEVEL_NONE = 0,
+ PRESSURE_LEVEL_LOW,
+ PRESSURE_LEVEL_MEDIUM,
+ PRESSURE_LEVEL_HIGH,
+ PRESSURE_LEVEL_COUNT,
+ };
+
+ // Clients implement and register this callback to get notified on pressure changes.
+ class PressureChangeCallbackInterface : virtual public android::RefBase {
+ public:
+ virtual ~PressureChangeCallbackInterface() {}
+
+ // Called when the memory pressure level is changed.
+ virtual void onPressureChanged(PressureLevel pressureLevel) = 0;
+ };
+
+ // Initializes the PSI monitors for pressure levels defined in PressureLevel enum.
+ virtual android::base::Result<void> init() = 0;
+
+ // Terminates the active PSI monitors and joins the pressure monitor thread.
+ virtual void terminate() = 0;
+
+ // Returns true when the pressure monitor is enabled.
+ virtual bool isEnabled() = 0;
+
+ // Starts the pressure monitor thread, which listens for PSI events and notifies clients on
+ // pressure changes.
+ virtual android::base::Result<void> start() = 0;
+
+ // Registers a callback for pressure change notifications.
+ virtual android::base::Result<void> registerPressureChangeCallback(
+ android::sp<PressureChangeCallbackInterface> callback) = 0;
+
+ // Unregisters a previously registered pressure change callback.
+ virtual void unregisterPressureChangeCallback(
+ android::sp<PressureChangeCallbackInterface> callback) = 0;
+
+ // Returns the string value for the given pressure level.
+ static std::string PressureLevelToString(PressureLevel pressureLevel);
+};
+
+// Monitors memory pressure and notifies registered callbacks when the pressure level changes.
+class PressureMonitor final :
+ public PressureMonitorInterface,
+ virtual public android::MessageHandler {
+public:
+ PressureMonitor() :
+ PressureMonitor(kDefaultProcPressureDirPath, kPollingIntervalMillis, &init_psi_monitor,
+ ®ister_psi_monitor, &unregister_psi_monitor, &destroy_psi_monitor,
+ &epoll_wait) {}
+
+ // Used by unittest to configure the internal state and mock the outgoing API calls.
+ PressureMonitor(const std::string& procPressureDirPath,
+
+ std::chrono::milliseconds pollingIntervalMillis,
+ const std::function<int(enum psi_stall_type, int, int, enum psi_resource)>&
+ initPsiMonitorFunc,
+ const std::function<int(int, int, void*)>& registerPsiMonitorFunc,
+ const std::function<int(int, int)>& unregisterPsiMonitorFunc,
+ const std::function<void(int)>& destroyPsiMonitorFunc,
+ const std::function<int(int, epoll_event*, int, int)>& epollWaitFunc) :
+ kProcPressureDirPath(procPressureDirPath),
+ mPollingIntervalMillis(pollingIntervalMillis),
+ mInitPsiMonitorFunc(initPsiMonitorFunc),
+ mRegisterPsiMonitorFunc(registerPsiMonitorFunc),
+ mUnregisterPsiMonitorFunc(unregisterPsiMonitorFunc),
+ mDestroyPsiMonitorFunc(destroyPsiMonitorFunc),
+ mEpollWaitFunc(epollWaitFunc),
+ mHandlerLooper(android::sp<LooperWrapper>::make()),
+ mIsEnabled(false),
+ mIsMonitorActive(false),
+ mPsiEpollFd(-1),
+ mLastPollUptimeNs(0),
+ mLatestPressureLevel(PRESSURE_LEVEL_NONE) {}
+
+ // Overrides PressureMonitorInterface methods.
+ android::base::Result<void> init() override;
+
+ void terminate() override;
+
+ bool isEnabled() override {
+ Mutex::Autolock lock(mMutex);
+ return mIsEnabled;
+ }
+
+ android::base::Result<void> start() override;
+
+ android::base::Result<void> registerPressureChangeCallback(
+ android::sp<PressureChangeCallbackInterface> callback) override;
+
+ void unregisterPressureChangeCallback(
+ android::sp<PressureChangeCallbackInterface> callback) override;
+
+ // Returns true when the pressure monitor thread is active.
+ bool isMonitorActive() { return mIsMonitorActive; }
+
+private:
+ template <typename T>
+ struct SpHash {
+ size_t operator()(const sp<T>& k) const { return std::hash<T*>()(k.get()); }
+ };
+ // Looper messages to post / handle pressure monitor events.
+ enum LooperMessage {
+ MONITOR_PRESSURE = 0,
+ NOTIFY_PRESSURE_CHANGE,
+ LOOPER_MESSAGE_COUNT,
+ };
+ // Contains information about a pressure level.
+ struct PressureLevelInfo {
+ const PressureLevel kPressureLevel = PRESSURE_LEVEL_NONE;
+ const psi_stall_type kStallType = PSI_TYPE_COUNT;
+ const std::chrono::microseconds kThresholdUs = 0us;
+ int psiMonitorFd = -1;
+ };
+
+ // Initializes the PSI monitors for different pressure levels.
+ android::base::Result<void> initializePsiMonitorsLocked();
+
+ // Destroys active PSI monitors.
+ void destroyActivePsiMonitorsLocked();
+
+ // Monitors current pressure levels.
+ android::base::Result<void> monitorPressure();
+
+ // Waits for the latest PSI events and returns the latest pressure level.
+ android::base::Result<PressureLevel> waitForLatestPressureLevel(int psiEpollFd,
+ epoll_event* events,
+ size_t maxEvents);
+
+ // Handles the looper messages.
+ void handleMessage(const Message& message);
+
+ // Notifies the clients of the latest pressure level changes.
+ void notifyPressureChange();
+
+ // Proc pressure directory path.
+ const std::string kProcPressureDirPath;
+
+ // Thread that waits for PSI triggers and notifies the pressure changes.
+ std::thread mMonitorThread;
+
+ // Time between consecutive polling of pressure events. Also used for the epoll_wait timeout.
+ std::chrono::milliseconds mPollingIntervalMillis;
+
+ // Updated by test to mock the PSI interfaces.
+ std::function<int(enum psi_stall_type, int, int, enum psi_resource)> mInitPsiMonitorFunc;
+ std::function<int(int, int, void*)> mRegisterPsiMonitorFunc;
+ std::function<int(int, int)> mUnregisterPsiMonitorFunc;
+ std::function<void(int)> mDestroyPsiMonitorFunc;
+ std::function<int(int, epoll_event*, int, int)> mEpollWaitFunc;
+
+ // Lock to guard internal state against multi-threaded access.
+ mutable Mutex mMutex;
+
+ // Handler looper to monitor pressure and notify callbacks.
+ android::sp<LooperWrapper> mHandlerLooper GUARDED_BY(mMutex);
+
+ // Set to true only when the required Kernel interfaces are accessible.
+ bool mIsEnabled GUARDED_BY(mMutex);
+
+ // Indicates whether or not the pressure monitor should continue monitoring.
+ bool mIsMonitorActive GUARDED_BY(mMutex);
+
+ // Epoll fd used to monitor the psi triggers.
+ int mPsiEpollFd GUARDED_BY(mMutex);
+
+ // Uptime NS when the last poll was performed. Used to calculate the next poll uptime.
+ nsecs_t mLastPollUptimeNs GUARDED_BY(mMutex);
+
+ // Latest highest active pressure level since the previous polling.
+ PressureLevel mLatestPressureLevel GUARDED_BY(mMutex);
+
+ // Cache of supported pressure level info.
+ std::vector<PressureLevelInfo> mPressureLevels GUARDED_BY(mMutex);
+
+ // Callbacks to notify when the pressure level changes.
+ std::unordered_set<android::sp<PressureChangeCallbackInterface>,
+ SpHash<PressureChangeCallbackInterface>>
+ mPressureChangeCallbacks GUARDED_BY(mMutex);
+};
+
+} // namespace watchdog
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_WATCHDOG_SERVER_SRC_PRESSUREMONITOR_H_
diff --git a/cpp/watchdog/server/src/ServiceManager.cpp b/cpp/watchdog/server/src/ServiceManager.cpp
index 3e3a0c7..65ad079 100644
--- a/cpp/watchdog/server/src/ServiceManager.cpp
+++ b/cpp/watchdog/server/src/ServiceManager.cpp
@@ -22,6 +22,7 @@
#include "PerformanceProfiler.h"
#include <android/binder_interface_utils.h>
+#include <log/log.h>
#include <utils/SystemClock.h>
namespace android {
@@ -31,6 +32,7 @@
using ::android::sp;
using ::android::base::Error;
using ::android::base::Result;
+using ::android::car::feature::car_watchdog_memory_profiling;
using ::ndk::SharedRefBase;
Result<void> ServiceManager::startServices(const sp<Looper>& mainLooper) {
@@ -39,10 +41,10 @@
return Error(INVALID_OPERATION) << "Cannot start services more than once";
}
/*
- * PackageInfoResolver must be initialized first time on the main thread before starting any
- * other thread as the getInstance method isn't thread safe. Thus initialize PackageInfoResolver
- * by calling the getInstance method before starting other services as they may access
- * PackageInfoResolver's instance during initialization.
+ * PackageInfoResolver must be initialized first on the main thread before starting any other
+ * thread because the PackageInfoResolver::getInstance method isn't thread safe. Thus initialize
+ * PackageInfoResolver by calling the PackageInfoResolver::getInstance method before starting
+ * other services as they may access PackageInfoResolver's instance during initialization.
*/
sp<PackageInfoResolverInterface> packageInfoResolver = PackageInfoResolver::getInstance();
if (auto result = startWatchdogProcessService(mainLooper); !result.ok()) {
@@ -52,6 +54,11 @@
if (auto result = mWatchdogServiceHelper->init(mWatchdogProcessService); !result.ok()) {
return Error() << "Failed to initialize watchdog service helper: " << result.error();
}
+ if (car_watchdog_memory_profiling()) {
+ if (auto result = startPressureMonitor(); !result.ok()) {
+ ALOGE("%s", result.error().message().c_str());
+ }
+ }
if (auto result = startWatchdogPerfService(mWatchdogServiceHelper); !result.ok()) {
return result;
}
@@ -88,6 +95,10 @@
mWatchdogServiceHelper->terminate();
mWatchdogServiceHelper.clear();
}
+ if (mPressureMonitor != nullptr) {
+ mPressureMonitor->terminate();
+ mPressureMonitor.clear();
+ }
mIoOveruseMonitor.clear();
PackageInfoResolver::terminate();
}
@@ -101,10 +112,22 @@
return {};
}
+Result<void> ServiceManager::startPressureMonitor() {
+ mPressureMonitor = sp<PressureMonitor>::make();
+ if (auto result = mPressureMonitor->init(); !result.ok()) {
+ return Error() << "Failed to initialize pressure monitor: " << result.error();
+ }
+ if (auto result = mPressureMonitor->start(); !result.ok()) {
+ return Error() << "Failed to start pressure monitor: " << result.error();
+ }
+ return {};
+}
+
Result<void> ServiceManager::startWatchdogPerfService(
const sp<WatchdogServiceHelperInterface>& watchdogServiceHelper) {
mWatchdogPerfService = sp<WatchdogPerfService>::make(watchdogServiceHelper, elapsedRealtime);
- if (auto result = mWatchdogPerfService->registerDataProcessor(sp<PerformanceProfiler>::make());
+ if (auto result = mWatchdogPerfService->registerDataProcessor(
+ sp<PerformanceProfiler>::make(mPressureMonitor));
!result.ok()) {
return Error() << "Failed to register performance profiler: " << result.error();
}
diff --git a/cpp/watchdog/server/src/ServiceManager.h b/cpp/watchdog/server/src/ServiceManager.h
index 7f594dd..2bca4e6 100644
--- a/cpp/watchdog/server/src/ServiceManager.h
+++ b/cpp/watchdog/server/src/ServiceManager.h
@@ -18,6 +18,7 @@
#define CPP_WATCHDOG_SERVER_SRC_SERVICEMANAGER_H_
#include "IoOveruseMonitor.h"
+#include "PressureMonitor.h"
#include "WatchdogBinderMediator.h"
#include "WatchdogPerfService.h"
#include "WatchdogProcessService.h"
@@ -40,8 +41,10 @@
mWatchdogPerfService(nullptr),
mWatchdogBinderMediator(nullptr),
mWatchdogServiceHelper(nullptr),
- mIoOveruseMonitor(nullptr) {}
+ mIoOveruseMonitor(nullptr),
+ mPressureMonitor(nullptr) {}
+ // Returns the singleton ServiceManager instance.
static android::sp<ServiceManager> getInstance() {
if (sServiceManager == nullptr) {
sServiceManager = android::sp<ServiceManager>::make();
@@ -49,6 +52,7 @@
return sServiceManager;
}
+ // Terminates all services and resets the singleton instance.
static void terminate() {
if (sServiceManager == nullptr) {
return;
@@ -80,6 +84,7 @@
void terminateServices();
android::base::Result<void> startWatchdogProcessService(const android::sp<Looper>& mainLooper);
+ android::base::Result<void> startPressureMonitor();
android::base::Result<void> startWatchdogPerfService(
const sp<WatchdogServiceHelperInterface>& watchdogServiceHelper);
@@ -88,6 +93,7 @@
std::shared_ptr<WatchdogBinderMediatorInterface> mWatchdogBinderMediator;
android::sp<WatchdogServiceHelperInterface> mWatchdogServiceHelper;
android::sp<IoOveruseMonitorInterface> mIoOveruseMonitor;
+ android::sp<PressureMonitorInterface> mPressureMonitor;
};
} // namespace watchdog
diff --git a/cpp/watchdog/server/src/UidProcStatsCollector.cpp b/cpp/watchdog/server/src/UidProcStatsCollector.cpp
index 9b5edbc..1deaed6 100644
--- a/cpp/watchdog/server/src/UidProcStatsCollector.cpp
+++ b/cpp/watchdog/server/src/UidProcStatsCollector.cpp
@@ -47,6 +47,8 @@
using ::android::base::StartsWith;
using ::android::base::StringAppendF;
using ::android::base::Trim;
+using ::android::meminfo::MemUsage;
+using ::android::meminfo::ProcMemInfo;
namespace {
@@ -282,6 +284,34 @@
return mul(oneTenthCpuCycles, cyclesPerKHzClockTicks);
}
+/**
+ * Returns the RSS and Shared pages from the given /proc/PID/statm file.
+ *
+ * /proc/PID/statm format:
+ * <Total program size> <Resident pages> <Shared pages> <Text pages> 0 <Data pages> 0
+ * Example: 2969783 1481 938 530 0 5067 0
+ */
+Result<std::tuple<uint64_t, uint64_t>> readPidStatmFile(const std::string& path) {
+ std::string buffer;
+ if (!ReadFileToString(path, &buffer)) {
+ return Error(READ_WARNING) << "ReadFileToString failed for " << path;
+ }
+ std::vector<std::string> lines = Split(std::move(buffer), "\n");
+ if (lines.size() != 1 && (lines.size() != 2 || !lines[1].empty())) {
+ return Error(READ_ERROR) << path << " contains " << lines.size() << " lines != 1";
+ }
+ std::vector<std::string> fields = Split(std::move(lines[0]), " ");
+ if (fields.size() < 6) {
+ return Error(READ_ERROR) << path << " contains insufficient entries";
+ }
+ uint64_t rssPages = 0;
+ uint64_t sharedPages = 0;
+ if (!ParseUint(fields[1], &rssPages) || !ParseUint(fields[2], &sharedPages)) {
+ return Error(READ_ERROR) << "Failed to parse fields from " << path;
+ }
+ return std::make_tuple(rssPages, sharedPages);
+}
+
} // namespace
std::string ProcessStats::toString() const {
@@ -295,7 +325,11 @@
for (const auto& [tid, cpuCycles] : cpuCyclesByTid) {
StringAppendF(&buffer, "{tid: %d, cpuCycles: %" PRIu64 "},", tid, cpuCycles);
}
- StringAppendF(&buffer, "}");
+ buffer.erase(buffer.length() - 1);
+ StringAppendF(&buffer,
+ "}, rssKb: %" PRIu64 ", pssKb: %" PRIu64 ", ussKb: %" PRIu64
+ ", swapPsskb: %" PRIu64 "} ",
+ rssKb, pssKb, ussKb, swapPssKb);
return buffer;
}
@@ -304,45 +338,92 @@
StringAppendF(&buffer,
"UidProcStats{cpuTimeMillis: %" PRIu64 ", cpuCycles: %" PRIu64
", totalMajorFaults: %" PRIu64 ", totalTasksCount: %d, ioBlockedTasksCount: %d"
- ", processStatsByPid: {",
- cpuTimeMillis, cpuCycles, totalMajorFaults, totalTasksCount, ioBlockedTasksCount);
+ ", totalRssKb: %" PRIu64 ", totalPssKb: %" PRIu64 ", processStatsByPid: {",
+ cpuTimeMillis, cpuCycles, totalMajorFaults, totalTasksCount, ioBlockedTasksCount,
+ totalRssKb, totalPssKb);
for (const auto& [pid, processStats] : processStatsByPid) {
StringAppendF(&buffer, "{pid: %" PRIi32 ", processStats: %s},", pid,
processStats.toString().c_str());
}
- StringAppendF(&buffer, "}");
+ buffer.erase(buffer.length() - 1);
+ StringAppendF(&buffer, "}}");
return buffer;
}
+UidProcStatsCollector::UidProcStatsCollector(const std::string& path, bool isSmapsRollupSupported) :
+ mIsMemoryProfilingEnabled(::android::car::feature::car_watchdog_memory_profiling()),
+ mMillisPerClockTick(1000 / sysconf(_SC_CLK_TCK)),
+ mPath(path),
+ mLatestStats({}),
+ mDeltaStats({}) {
+ mIsSmapsRollupSupported = isSmapsRollupSupported;
+ mPageSizeKb =
+ sysconf(_SC_PAGESIZE) > 1024 ? static_cast<size_t>(sysconf(_SC_PAGESIZE) / 1024) : 1;
+}
+
void UidProcStatsCollector::init() {
// Note: Verify proc file access outside the constructor. Otherwise, the unittests of
// dependent classes would call the constructor before mocking and get killed due to
// sepolicy violation.
std::string pidStatPath = StringPrintf((mPath + kStatFileFormat).c_str(), PID_FOR_INIT);
+ bool isPidStatPathAccessible = access(pidStatPath.c_str(), R_OK) == 0;
+
std::string tidStatPath = StringPrintf((mPath + kTaskDirFormat + kStatFileFormat).c_str(),
PID_FOR_INIT, PID_FOR_INIT);
+ bool isTidStatPathAccessible = access(tidStatPath.c_str(), R_OK) == 0;
+
std::string pidStatusPath = StringPrintf((mPath + kStatusFileFormat).c_str(), PID_FOR_INIT);
+ bool isPidStatusPathAccessible = access(pidStatusPath.c_str(), R_OK) == 0;
+
std::string tidTimeInStatePath =
- StringPrintf((mPath + kTaskDirFormat + kTimeInStateFormat).c_str(), PID_FOR_INIT,
+ StringPrintf((mPath + kTaskDirFormat + kTimeInStateFileFormat).c_str(), PID_FOR_INIT,
PID_FOR_INIT);
+ bool isTidTimeInStatePathAccessible = access(tidTimeInStatePath.c_str(), R_OK) == 0;
+
+ bool isStatmPathAccessible;
+ std::string statmPath = StringPrintf((mPath + kStatmFileFormat).c_str(), PID_FOR_INIT);
+ if (mIsMemoryProfilingEnabled) {
+ isStatmPathAccessible = access(statmPath.c_str(), R_OK) == 0;
+ }
Mutex::Autolock lock(mMutex);
- mEnabled = access(pidStatPath.c_str(), R_OK) == 0 && access(tidStatPath.c_str(), R_OK) == 0 &&
- access(pidStatusPath.c_str(), R_OK) == 0;
-
- mTimeInStateEnabled = access(tidTimeInStatePath.c_str(), R_OK) == 0;
- if (mTimeInStateEnabled) {
- auto tidCpuCycles = readTimeInStateFile(tidTimeInStatePath);
- mTimeInStateEnabled = tidCpuCycles.ok() && *tidCpuCycles > 0;
+ mIsEnabled = isPidStatPathAccessible && isTidStatPathAccessible && isPidStatusPathAccessible;
+ if (mIsMemoryProfilingEnabled) {
+ mIsEnabled &= isStatmPathAccessible || mIsSmapsRollupSupported;
}
- if (!mTimeInStateEnabled) {
- ALOGW("Time in state files are not enabled. Missing time in state file at path: %s",
+
+ if (isTidTimeInStatePathAccessible) {
+ auto tidCpuCycles = readTimeInStateFile(tidTimeInStatePath);
+ mIsTimeInStateEnabled = tidCpuCycles.ok() && *tidCpuCycles > 0;
+ }
+
+ if (!mIsTimeInStateEnabled) {
+ ALOGW("Time in state collection is not enabled. Missing time in state file at path: %s",
tidTimeInStatePath.c_str());
}
+
+ if (!mIsEnabled) {
+ std::string inaccessiblePaths;
+ if (!isPidStatPathAccessible) {
+ StringAppendF(&inaccessiblePaths, "%s, ", pidStatPath.c_str());
+ }
+ if (!isTidStatPathAccessible) {
+ StringAppendF(&inaccessiblePaths, "%s, ", pidStatPath.c_str());
+ }
+ if (!isPidStatusPathAccessible) {
+ StringAppendF(&inaccessiblePaths, "%s, ", pidStatusPath.c_str());
+ }
+ if (mIsMemoryProfilingEnabled && !isStatmPathAccessible) {
+ StringAppendF(&inaccessiblePaths, "%s, ", statmPath.c_str());
+ }
+ ALOGE("Disabling UidProcStatsCollector because access to the following files are not "
+ "available: '%s'",
+ inaccessiblePaths.substr(0, inaccessiblePaths.length() - 2).c_str());
+ }
}
Result<void> UidProcStatsCollector::collect() {
- if (!mEnabled) {
+ if (!mIsEnabled) {
return Error() << "Can not access PID stat files under " << kProcDirPath;
}
@@ -362,6 +443,8 @@
UidProcStats deltaUidStats = {
.totalTasksCount = currUidStats.totalTasksCount,
.ioBlockedTasksCount = currUidStats.ioBlockedTasksCount,
+ .totalRssKb = currUidStats.totalRssKb,
+ .totalPssKb = currUidStats.totalPssKb,
};
// Generate the delta stats since the previous collection. Delta stats are generated by
// calculating the difference between the |prevUidStats| and the |currUidStats|.
@@ -426,7 +509,7 @@
}
continue;
}
- uid_t uid = std::get<0>(*result);
+ uid_t uid = std::get<uid_t>(*result);
ProcessStats processStats = std::get<ProcessStats>(*result);
if (uidProcStatsByUid.find(uid) == uidProcStatsByUid.end()) {
uidProcStatsByUid[uid] = {};
@@ -437,6 +520,8 @@
uidProcStats->totalMajorFaults += processStats.totalMajorFaults;
uidProcStats->totalTasksCount += processStats.totalTasksCount;
uidProcStats->ioBlockedTasksCount += processStats.ioBlockedTasksCount;
+ uidProcStats->totalRssKb += processStats.rssKb;
+ uidProcStats->totalPssKb += processStats.pssKb;
uidProcStats->processStatsByPid[pid] = std::move(processStats);
}
return uidProcStatsByUid;
@@ -486,16 +571,35 @@
.startTimeMillis = pidStat->startTimeMillis,
.cpuTimeMillis = pidStat->cpuTimeMillis,
.totalCpuCycles = 0,
- .totalTasksCount = 1,
/* Top-level process stats has the aggregated major page faults count and this should be
* persistent across thread creation/termination. Thus use the value from this field.
*/
.totalMajorFaults = pidStat->majorFaults,
+ .totalTasksCount = 1,
.ioBlockedTasksCount = pidStat->state == "D" ? 1 : 0,
.cpuCyclesByTid = {},
};
- // 3. Read per-thread stats.
+ // 3. Read memory usage summary.
+ if (mIsMemoryProfilingEnabled && !readSmapsRollup(pid, &processStats)) {
+ path = StringPrintf((mPath + kStatmFileFormat).c_str(), pid);
+ if (auto result = readPidStatmFile(path); !result.ok()) {
+ if (result.error().code() != READ_WARNING) {
+ return Error() << result.error();
+ }
+ if (DEBUG) {
+ ALOGD("%s", result.error().message().c_str());
+ }
+ } else {
+ processStats.rssKb = std::get<0>(*result) * mPageSizeKb;
+ // RSS pages - Shared pages = USS pages.
+ uint64_t ussKb = processStats.rssKb - (std::get<1>(*result) * mPageSizeKb);
+ // Check for overflow and correct the result.
+ processStats.ussKb = ussKb < processStats.rssKb ? ussKb : 0;
+ }
+ }
+
+ // 4. Read per-thread stats.
std::string taskDir = StringPrintf((mPath + kTaskDirFormat).c_str(), pid);
bool didReadMainThread = false;
auto taskDirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(taskDir.c_str()), closedir);
@@ -524,11 +628,12 @@
processStats.totalTasksCount += 1;
}
- if (!mTimeInStateEnabled) {
+ if (!mIsTimeInStateEnabled) {
continue;
}
- path = StringPrintf((taskDir + kTimeInStateFormat).c_str(), tid);
+ // 5. Read time-in-state stats only when the corresponding file is accessible.
+ path = StringPrintf((taskDir + kTimeInStateFileFormat).c_str(), tid);
auto tidCpuCycles = readTimeInStateFile(path);
if (!tidCpuCycles.ok() || *tidCpuCycles <= 0) {
if (!tidCpuCycles.ok() && tidCpuCycles.error().code() != READ_WARNING) {
@@ -559,6 +664,22 @@
return readPidStatusFile(path);
}
+bool UidProcStatsCollector::readSmapsRollup(pid_t pid, ProcessStats* processStatsOut) const {
+ if (!mIsSmapsRollupSupported) {
+ return false;
+ }
+ MemUsage memUsage;
+ std::string path = StringPrintf((mPath + kSmapsRollupFileFormat).c_str(), pid);
+ if (!SmapsOrRollupFromFile(path, &memUsage)) {
+ return false;
+ }
+ processStatsOut->pssKb = memUsage.pss;
+ processStatsOut->rssKb = memUsage.rss;
+ processStatsOut->ussKb = memUsage.uss;
+ processStatsOut->swapPssKb = memUsage.swap_pss;
+ return memUsage.pss > 0 && memUsage.rss > 0 && memUsage.uss > 0;
+}
+
} // namespace watchdog
} // namespace automotive
} // namespace android
diff --git a/cpp/watchdog/server/src/UidProcStatsCollector.h b/cpp/watchdog/server/src/UidProcStatsCollector.h
index 99472ac..0c69491 100644
--- a/cpp/watchdog/server/src/UidProcStatsCollector.h
+++ b/cpp/watchdog/server/src/UidProcStatsCollector.h
@@ -20,9 +20,11 @@
#include <android-base/result.h>
#include <android-base/stringprintf.h>
#include <gtest/gtest_prod.h>
+#include <meminfo/procmeminfo.h>
#include <utils/Mutex.h>
#include <utils/RefBase.h>
+#include <android_car_feature.h>
#include <inttypes.h>
#include <stdint.h>
#include <unistd.h>
@@ -43,9 +45,15 @@
constexpr const char kStatFileFormat[] = "/%" PRIu32 "/stat";
constexpr const char kTaskDirFormat[] = "/%" PRIu32 "/task";
constexpr const char kStatusFileFormat[] = "/%" PRIu32 "/status";
-constexpr const char kTimeInStateFormat[] = "/%" PRIu32 "/time_in_state";
-// Per-pid/tid stats.
-// The int64_t type is used due to AIDL limitations representing long field values.
+constexpr const char kSmapsRollupFileFormat[] = "/%" PRIu32 "/smaps_rollup";
+constexpr const char kStatmFileFormat[] = "/%" PRIu32 "/statm";
+constexpr const char kTimeInStateFileFormat[] = "/%" PRIu32 "/time_in_state";
+
+/**
+ * Per-pid/tid stats.
+ *
+ * The int64_t type is used due to AIDL limitations representing long field values.
+ */
struct PidStat {
std::string comm = "";
std::string state = "";
@@ -65,6 +73,21 @@
int totalTasksCount = 0;
int ioBlockedTasksCount = 0;
std::unordered_map<pid_t, uint64_t> cpuCyclesByTid = {};
+ uint64_t rssKb = 0;
+ /**
+ * PSS/SwapPss will be missing when the smaps_rollup file is not supported or missing for
+ * a process. In such cases, use RSS to rank the processes by memory usage.
+ */
+ uint64_t pssKb = 0;
+ /**
+ * Unique set size is the portion of memory unique (private) to the process. Unshared memory
+ * is reported as USS.
+ *
+ * PSS - USS = Proportional portion of memory shared with one or more process.
+ * RSS - USS = Total portion of memory shared with one or more process.
+ */
+ uint64_t ussKb = 0;
+ uint64_t swapPssKb = 0;
std::string toString() const;
};
@@ -75,12 +98,29 @@
uint64_t totalMajorFaults = 0;
int totalTasksCount = 0;
int ioBlockedTasksCount = 0;
+ /**
+ * When smaps_rollup is supported by the Kernel, totalPssKb will be populated. When this
+ * feature is not supported, use totalRssKb to rank the UIDs.
+ *
+ * totalRssKb counts total shared memory from each of the processes. Thus leading to counting
+ * the same portion of memory more than once:
+ *
+ * For example, if N processes share X amount of memory and a subset of the processes (say M)
+ * belong to same UID, then
+ * 1. totalRssKb across all UIDs += Unique memory for N processes + (N * X).
+ * 2. totalRssKb for the UID += Unique memory for M processes + (M * X).
+ */
+ uint64_t totalRssKb = 0;
+ uint64_t totalPssKb = 0;
+ // TODO(b/333212872): Handles totalUssKb, totalSwapPssKb calculation logic here.
std::unordered_map<pid_t, ProcessStats> processStatsByPid = {};
std::string toString() const;
};
-// Collector/parser for `/proc/[pid]/stat`, `/proc/[pid]/task/[tid]/stat` and /proc/[pid]/status`
-// files.
+/**
+ * Collector/parser for `/proc/[pid]/stat`, `/proc/[pid]/task/[tid]/stat` and /proc/[pid]/status`
+ * files.
+ */
class UidProcStatsCollectorInterface : public RefBase {
public:
// Initializes the collector.
@@ -99,11 +139,14 @@
class UidProcStatsCollector final : public UidProcStatsCollectorInterface {
public:
- explicit UidProcStatsCollector(const std::string& path = kProcDirPath) :
- mMillisPerClockTick(1000 / sysconf(_SC_CLK_TCK)),
- mPath(path),
- mLatestStats({}),
- mDeltaStats({}) {}
+ // TODO(b/333722043): Once carwatchdogd has sys_ptrace capability, set mIsSmapsRollupSupported
+ // field from `android::meminfo::IsSmapsRollupSupported()`.
+ // Disabling smaps_rollup support because this file cannot be read without sys_ptrace
+ // capability.
+ UidProcStatsCollector() :
+ UidProcStatsCollector(kProcDirPath, /*isSmapsRollupSupported=*/false) {}
+ // Used by tests.
+ UidProcStatsCollector(const std::string& path, bool isSmapsRollupSupported);
~UidProcStatsCollector() {}
@@ -123,7 +166,7 @@
bool enabled() const {
Mutex::Autolock lock(mMutex);
- return mEnabled;
+ return mIsEnabled;
}
const std::string dirPath() const { return mPath; }
@@ -135,32 +178,60 @@
private:
android::base::Result<std::unordered_map<uid_t, UidProcStats>> readUidProcStatsLocked() const;
- // Reads the contents of the below files:
- // 1. Pid stat file at |mPath| + |kStatFileFormat|
- // 2. Aggregated per-process status at |mPath| + |kStatusFileFormat|
- // 3. Tid stat file at |mPath| + |kTaskDirFormat| + |kStatFileFormat|
+ /**
+ * Reads the contents of the below files:
+ * 1. Pid stat file at |mPath| + |kStatFileFormat|
+ * 2. Aggregated per-process status at |mPath| + |kStatusFileFormat|
+ * 3. Tid stat file at |mPath| + |kTaskDirFormat| + |kStatFileFormat|
+ */
android::base::Result<std::tuple<uid_t, ProcessStats>> readProcessStatsLocked(pid_t pid) const;
+ /**
+ * Reads the smaps rollup file and populates the ProcessStats pointer with info for the given
+ * pid.
+ * Returns true and updates the out pointer only when the read is successful.
+ * Returns false when either the smaps_rollup file is not supported or not available for
+ * the process. When the process terminates while reading, the file won't be available.
+ */
+ bool readSmapsRollup(pid_t pid, ProcessStats* processStatsOut) const;
+
+ size_t mPageSizeKb;
+
+ // Tracks memory profiling feature flag.
+ bool mIsMemoryProfilingEnabled;
+
+ // Tracks smaps rollup support in the Kernel.
+ bool mIsSmapsRollupSupported;
+
// Number of milliseconds per clock cycle.
int32_t mMillisPerClockTick;
- // Proc directory path. Default value is |kProcDirPath|.
- // Updated by tests to point to a different location when needed.
+ /**
+ * Proc directory path. Default value is |kProcDirPath|.
+ *
+ * Updated by tests to point to a different location when needed.
+ */
std::string mPath;
// Makes sure only one collection is running at any given time.
mutable Mutex mMutex;
- // True if the below files are accessible:
- // 1. Pid stat file at |mPath| + |kTaskStatFileFormat|
- // 2. Tid stat file at |mPath| + |kTaskDirFormat| + |kStatFileFormat|
- // 3. Pid status file at |mPath| + |kStatusFileFormat|
- // Otherwise, set to false.
- bool mEnabled GUARDED_BY(mMutex);
+ /**
+ * True if the below files are accessible:
+ * 1. Pid stat file at |mPath| + |kStatFileFormat|
+ * 2. Tid stat file at |mPath| + |kTaskDirFormat| + |kStatFileFormat|
+ * 3. Pid status file at |mPath| + |kStatusFileFormat|
+ * 4. Pid statm file at |mPath| + |kStatmFileFormat|
+ *
+ * Otherwise, set to false.
+ */
+ bool mIsEnabled GUARDED_BY(mMutex);
- // True if the tid time_in_state file at
- // |mPath| + |kTaskDirFormat| + |kTimeInStateFormat| is available.
- bool mTimeInStateEnabled GUARDED_BY(mMutex);
+ /**
+ * True if the tid time_in_state file at |mPath| + |kTaskDirFormat| + |kTimeInStateFileFormat|
+ * is available.
+ */
+ bool mIsTimeInStateEnabled GUARDED_BY(mMutex);
// Latest dump of per-UID stats.
std::unordered_map<uid_t, UidProcStats> mLatestStats GUARDED_BY(mMutex);
diff --git a/cpp/watchdog/server/src/WatchdogPerfService.cpp b/cpp/watchdog/server/src/WatchdogPerfService.cpp
index 0075c56..ef0eec7 100644
--- a/cpp/watchdog/server/src/WatchdogPerfService.cpp
+++ b/cpp/watchdog/server/src/WatchdogPerfService.cpp
@@ -368,6 +368,10 @@
Mutex::Autolock lock(mMutex);
if (mCurrCollectionEvent == EventType::BOOT_TIME_COLLECTION ||
mCurrCollectionEvent == EventType::CUSTOM_COLLECTION) {
+ mUserSwitchCollection.from = mUserSwitchCollection.to;
+ mUserSwitchCollection.to = userId;
+ ALOGI("Current collection: %s. Ignoring user switch from userId = %d to userId = %d)",
+ toString(mCurrCollectionEvent), mUserSwitchCollection.from, mUserSwitchCollection.to);
// Ignoring the user switch events because the boot-time and custom collections take
// precedence over other collections.
if (mCurrCollectionEvent == EventType::CUSTOM_COLLECTION) {
diff --git a/cpp/watchdog/server/src/WatchdogProcessService.cpp b/cpp/watchdog/server/src/WatchdogProcessService.cpp
index ce60c81..33cfa2e 100644
--- a/cpp/watchdog/server/src/WatchdogProcessService.cpp
+++ b/cpp/watchdog/server/src/WatchdogProcessService.cpp
@@ -16,6 +16,7 @@
#define LOG_TAG "carwatchdogd"
#define DEBUG false // STOPSHIP if true.
+#define ATRACE_TAG ATRACE_TAG_SYSTEM_SERVER
#include "WatchdogProcessService.h"
@@ -35,6 +36,7 @@
#include <binder/IPCThreadState.h>
#include <hidl/HidlTransportSupport.h>
#include <utils/SystemClock.h>
+#include <utils/Trace.h>
#include <IVhalClient.h>
#include <VehicleHalTypes.h>
@@ -212,6 +214,19 @@
} // namespace
+std::string timeoutToString(TimeoutLength timeout) {
+ switch (timeout) {
+ case TimeoutLength::TIMEOUT_CRITICAL:
+ return "TIMEOUT_CRITICAL";
+ case TimeoutLength::TIMEOUT_MODERATE:
+ return "TIMEOUT_MODERATE";
+ case TimeoutLength::TIMEOUT_NORMAL:
+ return "TIMEOUT_NORMAL";
+ default:
+ return "UNKNOWN TIMEOUT";
+ }
+}
+
WatchdogProcessService::WatchdogProcessService(const sp<Looper>& handlerLooper) :
WatchdogProcessService(IVhalClient::tryCreate, kDefaultTryGetHidlServiceManager,
getStartTimeForPid, kDefaultVhalPidCachingRetryDelayNs, handlerLooper,
@@ -267,6 +282,7 @@
ScopedAStatus WatchdogProcessService::registerClient(
const std::shared_ptr<ICarWatchdogClient>& client, TimeoutLength timeout) {
+ ATRACE_CALL();
if (client == nullptr) {
return ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
"Must provide non-null client");
@@ -292,6 +308,7 @@
ScopedAStatus WatchdogProcessService::registerCarWatchdogService(
const SpAIBinder& binder, const sp<WatchdogServiceHelperInterface>& helper) {
+ ATRACE_CALL();
pid_t callingPid = IPCThreadState::self()->getCallingPid();
uid_t callingUid = IPCThreadState::self()->getCallingUid();
userid_t callingUserId = multiuser_get_user_id(callingUid);
@@ -328,6 +345,7 @@
ScopedAStatus WatchdogProcessService::registerMonitor(
const std::shared_ptr<ICarWatchdogMonitor>& monitor) {
+ ATRACE_CALL();
if (monitor == nullptr) {
return ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
"Must provide non-null monitor");
@@ -732,6 +750,7 @@
Result<void> WatchdogProcessService::registerClient(const ClientInfo& clientInfo,
TimeoutLength timeout) {
+ ATRACE_CALL();
uintptr_t cookieId = reinterpret_cast<uintptr_t>(clientInfo.getAIBinder());
{
Mutex::Autolock lock(mMutex);
@@ -974,6 +993,7 @@
}
void WatchdogProcessService::reportWatchdogAliveToVhal() {
+ ATRACE_CALL();
if (mNotSupportedVhalProperties.count(VehicleProperty::WATCHDOG_ALIVE) > 0) {
ALOGW("VHAL doesn't support WATCHDOG_ALIVE. Car watchdog will not update WATCHDOG_ALIVE.");
return;
@@ -997,6 +1017,7 @@
void WatchdogProcessService::reportTerminatedProcessToVhal(
const std::vector<ProcessIdentifier>& processesNotResponding) {
+ ATRACE_CALL();
if (mNotSupportedVhalProperties.count(VehicleProperty::WATCHDOG_TERMINATED_PROCESS) > 0) {
ALOGW("VHAL doesn't support WATCHDOG_TERMINATED_PROCESS. Terminated process is not "
"reported to VHAL.");
@@ -1025,6 +1046,7 @@
}
Result<void> WatchdogProcessService::updateVhal(const VehiclePropValue& value) {
+ ATRACE_CALL();
const auto& connectRet = connectToVhal();
if (!connectRet.ok()) {
std::string errorMsg = "VHAL is not connected: " + connectRet.error().message();
@@ -1364,6 +1386,7 @@
}
ScopedAStatus WatchdogProcessService::ClientInfo::checkIfAlive(TimeoutLength timeout) const {
+ ATRACE_NAME(StringPrintf("checkIfAlive - %s", timeoutToString(timeout).c_str()).c_str());
if (kType == ClientType::Regular) {
return kClient->checkIfAlive(sessionId, timeout);
}
@@ -1371,6 +1394,7 @@
}
ScopedAStatus WatchdogProcessService::ClientInfo::prepareProcessTermination() const {
+ ATRACE_CALL();
if (kType == ClientType::Regular) {
return kClient->prepareProcessTermination();
}
diff --git a/cpp/watchdog/server/sysprop/Android.bp b/cpp/watchdog/server/sysprop/Android.bp
index aab3ef9..a58f749 100644
--- a/cpp/watchdog/server/sysprop/Android.bp
+++ b/cpp/watchdog/server/sysprop/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
index 16999ae..0e6484c 100644
--- a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
+++ b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
@@ -227,12 +227,12 @@
virtual void TearDown() {
mMockWatchdogServiceHelper.clear();
- mMockDeathRegistrationWrapper.clear();
mMockIoOveruseConfigs.clear();
mMockPackageInfoResolver.clear();
mMockUidStatsCollector.clear();
mIoOveruseMonitor.clear();
mIoOveruseMonitorPeer.clear();
+ mMockDeathRegistrationWrapper.clear();
}
void setUpPackagesAndConfigurations() {
diff --git a/cpp/watchdog/server/tests/MockPressureChangeCallback.h b/cpp/watchdog/server/tests/MockPressureChangeCallback.h
new file mode 100644
index 0000000..fe9fd70
--- /dev/null
+++ b/cpp/watchdog/server/tests/MockPressureChangeCallback.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CPP_WATCHDOG_SERVER_TESTS_MOCKPRESSURECHANGECALLBACK_H_
+#define CPP_WATCHDOG_SERVER_TESTS_MOCKPRESSURECHANGECALLBACK_H_
+
+#include "PressureMonitor.h"
+
+#include <gmock/gmock.h>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+class MockPressureChangeCallback :
+ virtual public PressureMonitorInterface::PressureChangeCallbackInterface {
+public:
+ MOCK_METHOD(void, onPressureChanged, (PressureMonitorInterface::PressureLevel), (override));
+};
+
+} // namespace watchdog
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_WATCHDOG_SERVER_TESTS_MOCKPRESSURECHANGECALLBACK_H_
diff --git a/cpp/watchdog/server/tests/MockPressureMonitor.h b/cpp/watchdog/server/tests/MockPressureMonitor.h
new file mode 100644
index 0000000..18e24a6
--- /dev/null
+++ b/cpp/watchdog/server/tests/MockPressureMonitor.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CPP_WATCHDOG_SERVER_TESTS_MOCKPRESSUREMONITOR_H_
+#define CPP_WATCHDOG_SERVER_TESTS_MOCKPRESSUREMONITOR_H_
+
+#include "PressureMonitor.h"
+
+#include <gmock/gmock.h>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+class MockPressureMonitor : virtual public PressureMonitorInterface {
+public:
+ MockPressureMonitor() {
+ ON_CALL(*this, registerPressureChangeCallback(testing::_))
+ .WillByDefault(testing::Return(android::base::Result<void>()));
+ }
+
+ MOCK_METHOD(android::base::Result<void>, init, (), (override));
+
+ MOCK_METHOD(void, terminate, (), (override));
+
+ MOCK_METHOD(bool, isEnabled, (), (override));
+
+ MOCK_METHOD(android::base::Result<void>, start, (), (override));
+
+ MOCK_METHOD(android::base::Result<void>, registerPressureChangeCallback,
+ (android::sp<PressureChangeCallbackInterface>), (override));
+
+ MOCK_METHOD(void, unregisterPressureChangeCallback,
+ (android::sp<PressureChangeCallbackInterface>), (override));
+};
+
+} // namespace watchdog
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_WATCHDOG_SERVER_TESTS_MOCKPRESSUREMONITOR_H_
diff --git a/cpp/watchdog/server/tests/PerformanceProfilerTest.cpp b/cpp/watchdog/server/tests/PerformanceProfilerTest.cpp
index b4e5010..371cf90 100644
--- a/cpp/watchdog/server/tests/PerformanceProfilerTest.cpp
+++ b/cpp/watchdog/server/tests/PerformanceProfilerTest.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include "MockPressureMonitor.h"
#include "MockProcStatCollector.h"
#include "MockUidStatsCollector.h"
#include "MockWatchdogServiceHelper.h"
@@ -25,9 +26,11 @@
#include <gmock/gmock.h>
#include <utils/RefBase.h>
+#include <android_car_feature.h>
#include <sys/types.h>
#include <unistd.h>
+#include <algorithm>
#include <string>
#include <type_traits>
#include <vector>
@@ -48,6 +51,8 @@
using ::android::sp;
using ::android::base::ReadFdToString;
using ::android::base::Result;
+using ::android::base::StringAppendF;
+using ::android::car::feature::car_watchdog_memory_profiling;
using ::android::util::ProtoReader;
using ::google::protobuf::RepeatedPtrField;
using ::testing::_;
@@ -58,23 +63,36 @@
using ::testing::Field;
using ::testing::IsSubsetOf;
using ::testing::Matcher;
+using ::testing::Pointer;
using ::testing::Property;
using ::testing::Return;
using ::testing::Test;
using ::testing::UnorderedElementsAreArray;
using ::testing::VariantWith;
-using time_point_ms = std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>;
+using PressureLevelDurationPair = std::pair<PressureMonitorInterface::PressureLevel, int64_t>;
+using PressureLevelTransitions = std::vector<PressureLevelDurationPair>;
+using PressureLevelDurations =
+ std::unordered_map<PressureMonitorInterface::PressureLevel, std::chrono::milliseconds>;
+constexpr int kTestBaseUserId = 100;
+constexpr bool kTestIsSmapsRollupSupported = true;
constexpr int kTestTopNStatsPerCategory = 5;
constexpr int kTestTopNStatsPerSubcategory = 5;
constexpr int kTestMaxUserSwitchEvents = 3;
+constexpr size_t kTestPeriodicCollectionBufferSize = 3;
constexpr std::chrono::seconds kTestSystemEventDataCacheDurationSec = 60s;
-const auto kTestNow = std::chrono::time_point_cast<std::chrono::milliseconds>(
+const auto kTestNowMillis = std::chrono::time_point_cast<std::chrono::milliseconds>(
std::chrono::system_clock::from_time_t(1'683'270'000));
+constexpr int64_t kTestElapsedRealtimeSinceBootMillis = 19'000;
-int64_t getTestElapsedRealtimeSinceBootMs() {
- return 20'000;
+void applyFeatureFilter(UserPackageSummaryStats* userPackageSummaryStatsOut) {
+ if (car_watchdog_memory_profiling()) {
+ return;
+ }
+ userPackageSummaryStatsOut->totalRssKb = 0;
+ userPackageSummaryStatsOut->totalPssKb = 0;
+ userPackageSummaryStatsOut->topNMemStats = {};
}
MATCHER_P(IoStatsViewEq, expected, "") {
@@ -150,6 +168,49 @@
arg, result_listener);
}
+MATCHER_P(MemoryStatsEq, expected, "") {
+ return ExplainMatchResult(AllOf(Field("rssKb", &UserPackageStats::MemoryStats::rssKb,
+ Eq(expected.rssKb)),
+ Field("pssKb", &UserPackageStats::MemoryStats::pssKb,
+ Eq(expected.pssKb)),
+ Field("ussKb", &UserPackageStats::MemoryStats::ussKb,
+ Eq(expected.ussKb)),
+ Field("swapPssKb", &UserPackageStats::MemoryStats::swapPssKb,
+ Eq(expected.swapPssKb))),
+ arg, result_listener);
+}
+
+MATCHER_P(ProcessMemoryStatsEq, expected, "") {
+ return ExplainMatchResult(AllOf(Field("comm",
+ &UserPackageStats::UidMemoryStats::ProcessMemoryStats::
+ comm,
+ Eq(expected.comm)),
+ Field("memoryStats",
+ &UserPackageStats::UidMemoryStats::ProcessMemoryStats::
+ memoryStats,
+ MemoryStatsEq(expected.memoryStats))),
+ arg, result_listener);
+}
+
+MATCHER_P(UidMemoryStatsEq, expected, "") {
+ std::vector<Matcher<const UserPackageStats::UidMemoryStats::ProcessMemoryStats&>>
+ processValueMatchers;
+ processValueMatchers.reserve(expected.topNProcesses.size());
+ for (const auto& processValue : expected.topNProcesses) {
+ processValueMatchers.push_back(ProcessMemoryStatsEq(processValue));
+ }
+ return ExplainMatchResult(AllOf(Field("memoryStats",
+ &UserPackageStats::UidMemoryStats::memoryStats,
+ MemoryStatsEq(expected.memoryStats)),
+ Field("isSmapsRollupSupported",
+ &UserPackageStats::UidMemoryStats::isSmapsRollupSupported,
+ Eq(expected.isSmapsRollupSupported)),
+ Field("topNProcesses",
+ &UserPackageStats::UidMemoryStats::topNProcesses,
+ ElementsAreArray(processValueMatchers))),
+ arg, result_listener);
+}
+
MATCHER_P(UserPackageStatsEq, expected, "") {
const auto uidMatcher = Field("uid", &UserPackageStats::uid, Eq(expected.uid));
const auto packageNameMatcher =
@@ -183,6 +244,14 @@
ProcCpuStatsView>(
ProcCpuStatsViewEq(statsView)))),
arg, result_listener);
+ } else if constexpr (std::is_same_v<T, UserPackageStats::UidMemoryStats>) {
+ return ExplainMatchResult(AllOf(uidMatcher, packageNameMatcher,
+ Field("statsView:UidMemoryStats",
+ &UserPackageStats::statsView,
+ VariantWith<
+ UserPackageStats::UidMemoryStats>(
+ UidMemoryStatsEq(statsView)))),
+ arg, result_listener);
}
*result_listener << "Unexpected variant in UserPackageStats::stats";
return false;
@@ -216,6 +285,8 @@
Field("topNMajorFaults",
&UserPackageSummaryStats::topNMajorFaults,
userPackageStatsMatchers(expected.topNMajorFaults)),
+ Field("topNMemStats", &UserPackageSummaryStats::topNMemStats,
+ userPackageStatsMatchers(expected.topNMemStats)),
Field("totalIoStats", &UserPackageSummaryStats::totalIoStats,
totalIoStatsArrayMatcher(expected.totalIoStats)),
Field("taskCountByUid",
@@ -230,6 +301,10 @@
Field("totalMajorFaults",
&UserPackageSummaryStats::totalMajorFaults,
Eq(expected.totalMajorFaults)),
+ Field("totalRssKb", &UserPackageSummaryStats::totalRssKb,
+ Eq(expected.totalRssKb)),
+ Field("totalPssKb", &UserPackageSummaryStats::totalPssKb,
+ Eq(expected.totalPssKb)),
Field("majorFaultsPercentChange",
&UserPackageSummaryStats::majorFaultsPercentChange,
Eq(expected.majorFaultsPercentChange))),
@@ -261,11 +336,16 @@
}
MATCHER_P(PerfStatsRecordEq, expected, "") {
- return ExplainMatchResult(AllOf(Field(&PerfStatsRecord::systemSummaryStats,
+ return ExplainMatchResult(AllOf(Field(&PerfStatsRecord::collectionTimeMillis,
+ Eq(expected.collectionTimeMillis)),
+ Field(&PerfStatsRecord::systemSummaryStats,
SystemSummaryStatsEq(expected.systemSummaryStats)),
Field(&PerfStatsRecord::userPackageSummaryStats,
UserPackageSummaryStatsEq(
- expected.userPackageSummaryStats))),
+ expected.userPackageSummaryStats)),
+ Field(&PerfStatsRecord::memoryPressureLevelDurations,
+ UnorderedElementsAreArray(
+ expected.memoryPressureLevelDurations))),
arg, result_listener);
}
@@ -317,8 +397,8 @@
return occurrences;
}
-std::tuple<std::vector<UidStats>, UserPackageSummaryStats> sampleUidStats(auto int64Multiplier,
- auto uint64Multiplier) {
+std::tuple<std::vector<UidStats>, UserPackageSummaryStats> sampleUidStats(
+ auto int64Multiplier, auto uint64Multiplier, bool isSmapsRollupSupported = true) {
/* The number of returned sample stats are less that the top N stats per category/sub-category.
* The top N stats per category/sub-category is set to % during test setup. Thus, the default
* testing behavior is # reported stats < top N stats.
@@ -336,6 +416,8 @@
.totalMajorFaults = uint64Multiplier(11'000),
.totalTasksCount = 1,
.ioBlockedTasksCount = 1,
+ .totalRssKb = 2010,
+ .totalPssKb = 1635,
.processStatsByPid =
{{/*pid=*/100,
{/*comm=*/"disk I/O", /*startTime=*/234,
@@ -344,7 +426,9 @@
/*totalMajorFaults=*/uint64Multiplier(11'000),
/*totalTasksCount=*/1,
/*ioBlockedTasksCount=*/1,
- /*cpuCyclesByTid=*/{{100, 4000}}}}}}},
+ /*cpuCyclesByTid=*/{{100, 4000}},
+ /*rssKb=*/2010, /*pssKb=*/1635,
+ /*ussKb=*/1286, /*swapPssKb=*/600}}}}},
{.packageInfo =
constructPackageInfo("com.google.android.car.kitchensink", 1002001),
.cpuTimeMillis = int64Multiplier(60),
@@ -359,6 +443,8 @@
.totalMajorFaults = uint64Multiplier(22'445),
.totalTasksCount = 5,
.ioBlockedTasksCount = 3,
+ .totalRssKb = 2000,
+ .totalPssKb = 1645,
.processStatsByPid =
{{/*pid=*/1001,
{/*comm=*/"CTS", /*startTime=*/789,
@@ -367,7 +453,9 @@
/*totalMajorFaults=*/uint64Multiplier(10'100),
/*totalTasksCount=*/3,
/*ioBlockedTasksCount=*/2,
- /*cpuCyclesByTid=*/{{1001, 3000}, {1002, 2000}}}},
+ /*cpuCyclesByTid=*/{{1001, 3000}, {1002, 2000}},
+ /*rssKb=*/1000, /*pssKb=*/770,
+ /*ussKb=*/656, /*swapPssKb=*/200}},
{/*pid=*/1000,
{/*comm=*/"KitchenSinkApp", /*startTime=*/467,
/*cpuTimeMillis=*/int64Multiplier(25),
@@ -375,7 +463,9 @@
/*totalMajorFaults=*/uint64Multiplier(12'345),
/*totalTasksCount=*/2,
/*ioBlockedTasksCount=*/1,
- /*cpuCyclesByTid=*/{{1000, 4000}}}}}}},
+ /*cpuCyclesByTid=*/{{1000, 4000}},
+ /*rssKb=*/1000, /*pssKb=*/875,
+ /*ussKb=*/630, /*swapPssKb=*/400}}}}},
{.packageInfo = constructPackageInfo("", 1012345),
.cpuTimeMillis = int64Multiplier(100),
.ioStats = {/*fgRdBytes=*/int64Multiplier(1'000),
@@ -389,6 +479,8 @@
.totalMajorFaults = uint64Multiplier(50'900),
.totalTasksCount = 4,
.ioBlockedTasksCount = 2,
+ .totalRssKb = 1000,
+ .totalPssKb = 865,
.processStatsByPid =
{{/*pid=*/2345,
{/*comm=*/"MapsApp", /*startTime=*/6789,
@@ -397,7 +489,9 @@
/*totalMajorFaults=*/uint64Multiplier(50'900),
/*totalTasksCount=*/4,
/*ioBlockedTasksCount=*/2,
- /*cpuCyclesByTid=*/{{2345, 50'000}}}}}}},
+ /*cpuCyclesByTid=*/{{2345, 50'000}},
+ /*rssKb=*/1000, /*pssKb=*/865,
+ /*ussKb=*/656, /*swapPssKb=*/200}}}}},
{.packageInfo = constructPackageInfo("com.google.radio", 1015678),
.cpuTimeMillis = 0,
.ioStats = {/*fgRdBytes=*/0,
@@ -412,7 +506,7 @@
.ioBlockedTasksCount = 0,
.processStatsByPid = {
{/*pid=*/2345,
- {/*comm=*/"RadioApp", /*startTime=*/19789,
+ {/*comm=*/"RadioApp", /*startTime=*/10789,
/*cpuTimeMillis=*/0,
/*totalCpuCycles=*/0,
/*totalMajorFaults=*/0,
@@ -420,6 +514,56 @@
/*ioBlockedTasksCount=*/0,
/*cpuCyclesByTid=*/{}}}}}}};
+ std::vector<UserPackageStats> topNMemStatsRankedByPss =
+ {{1002001, "com.google.android.car.kitchensink",
+ UserPackageStats::UidMemoryStats{{/*rssKb=*/2000, /*pssKb=*/1645,
+ /*ussKb=*/1286, /*swapPssKb=*/600},
+ isSmapsRollupSupported,
+ {{"KitchenSinkApp",
+ {/*rssKb=*/1000, /*pssKb=*/875,
+ /*ussKb=*/630, /*swapPssKb=*/400}},
+ {"CTS",
+ {/*rssKb=*/1000, /*pssKb=*/770,
+ /*ussKb=*/656, /*swapPssKb=*/200}}}}},
+ {1009, "mount",
+ UserPackageStats::UidMemoryStats{{/*rssKb=*/2010, /*pssKb=*/1635,
+ /*ussKb=*/1286, /*swapPssKb=*/600},
+ isSmapsRollupSupported,
+ {{"disk I/O",
+ {/*rssKb=*/2010, /*pssKb=*/1635,
+ /*ussKb=*/1286, /*swapPssKb=*/600}}}}},
+ {1012345, "1012345",
+ UserPackageStats::UidMemoryStats{{/*rssKb=*/1000, /*pssKb=*/865,
+ /*ussKb=*/656, /*swapPssKb=*/200},
+ isSmapsRollupSupported,
+ {{"MapsApp",
+ {/*rssKb=*/1000, /*pssKb=*/865,
+ /*ussKb=*/656, /*swapPssKb=*/200}}}}}};
+ std::vector<UserPackageStats> topNMemStatsRankedByRss =
+ {{1009, "mount",
+ UserPackageStats::UidMemoryStats{{/*rssKb=*/2010, /*pssKb=*/1635,
+ /*ussKb=*/1286, /*swapPssKb=*/600},
+ isSmapsRollupSupported,
+ {{"disk I/O",
+ {/*rssKb=*/2010, /*pssKb=*/1635,
+ /*ussKb=*/1286, /*swapPssKb=*/600}}}}},
+ {1002001, "com.google.android.car.kitchensink",
+ UserPackageStats::UidMemoryStats{{/*rssKb=*/2000, /*pssKb=*/1645,
+ /*ussKb=*/1286, /*swapPssKb=*/600},
+ isSmapsRollupSupported,
+ {{"KitchenSinkApp",
+ {/*rssKb=*/1000, /*pssKb=*/875,
+ /*ussKb=*/630, /*swapPssKb=*/400}},
+ {"CTS",
+ {/*rssKb=*/1000, /*pssKb=*/770,
+ /*ussKb=*/656, /*swapPssKb=*/200}}}}},
+ {1012345, "1012345",
+ UserPackageStats::UidMemoryStats{{/*rssKb=*/1000, /*pssKb=*/865,
+ /*ussKb=*/656, /*swapPssKb=*/200},
+ isSmapsRollupSupported,
+ {{"MapsApp",
+ {/*rssKb=*/1000, /*pssKb=*/865,
+ /*ussKb=*/656, /*swapPssKb=*/200}}}}}};
UserPackageSummaryStats userPackageSummaryStats{
.topNCpuTimes = {{1012345, "1012345",
UserPackageStats::ProcCpuStatsView{int64Multiplier(100),
@@ -480,6 +624,8 @@
UserPackageStats::ProcSingleStatsView{uint64Multiplier(11'000),
{{"disk I/O",
uint64Multiplier(11'000)}}}}},
+ .topNMemStats =
+ isSmapsRollupSupported ? topNMemStatsRankedByPss : topNMemStatsRankedByRss,
.totalIoStats = {{int64Multiplier(1'000), int64Multiplier(21'600)},
{int64Multiplier(300), int64Multiplier(28'300)},
{int64Multiplier(600), int64Multiplier(600)}},
@@ -487,8 +633,11 @@
.totalCpuTimeMillis = int64Multiplier(48'376),
.totalCpuCycles = 64'000,
.totalMajorFaults = uint64Multiplier(84'345),
+ .totalRssKb = 5010,
+ .totalPssKb = 4145,
.majorFaultsPercentChange = 0.0,
};
+ applyFeatureFilter(&userPackageSummaryStats);
return std::make_tuple(uidStats, userPackageSummaryStats);
}
@@ -513,11 +662,35 @@
return std::make_tuple(procStatInfo, systemSummaryStats);
}
-ResourceStats getResourceStatsForSampledStats(auto int32Multiplier, auto int64Multiplier) {
+std::tuple<PressureLevelTransitions, PressureLevelDurations> samplePressureLevels(
+ int advanceUptimeSec = 1) {
+ PressureLevelTransitions pressureLevelTransitions{
+ PressureLevelDurationPair{PressureMonitor::PRESSURE_LEVEL_NONE, 100 * advanceUptimeSec},
+ PressureLevelDurationPair{PressureMonitor::PRESSURE_LEVEL_HIGH, 200 * advanceUptimeSec},
+ PressureLevelDurationPair{PressureMonitor::PRESSURE_LEVEL_HIGH, 100 * advanceUptimeSec},
+ PressureLevelDurationPair{PressureMonitor::PRESSURE_LEVEL_LOW, 200 * advanceUptimeSec},
+ PressureLevelDurationPair{PressureMonitor::PRESSURE_LEVEL_MEDIUM,
+ 100 * advanceUptimeSec},
+ PressureLevelDurationPair{PressureMonitor::PRESSURE_LEVEL_LOW, 200 * advanceUptimeSec},
+ PressureLevelDurationPair{PressureMonitor::PRESSURE_LEVEL_MEDIUM,
+ 100 * advanceUptimeSec},
+ };
+ PressureLevelDurations pressureLevelDurations{
+ {PressureMonitor::PRESSURE_LEVEL_NONE, 100ms * advanceUptimeSec},
+ {PressureMonitor::PRESSURE_LEVEL_LOW, 400ms * advanceUptimeSec},
+ {PressureMonitor::PRESSURE_LEVEL_MEDIUM, 200ms * advanceUptimeSec},
+ {PressureMonitor::PRESSURE_LEVEL_HIGH, 300ms * advanceUptimeSec},
+ };
+ return std::make_tuple(pressureLevelTransitions, pressureLevelDurations);
+}
+
+ResourceStats getResourceStatsForSampledStats(auto int32Multiplier, auto int64Multiplier,
+ time_point_millis nowMillis,
+ int64_t elapsedRealtimeSinceBootMillis) {
// clang-format off
return {
.resourceUsageStats = std::make_optional<ResourceUsageStats>({
- .startTimeEpochMillis = 1'683'270'000'000,
+ .startTimeEpochMillis = nowMillis.time_since_epoch().count(),
// Set durationInMillis to zero since this field is set by WatchdogPerfService.
.durationInMillis = 0,
.systemSummaryUsageStats = {
@@ -545,7 +718,7 @@
.name = "mount",
.uid = 1009,
},
- .uidUptimeMillis = 19'766,
+ .uidUptimeMillis = elapsedRealtimeSinceBootMillis - 234,
.cpuUsageStats = {
.cpuTimeMillis = int64Multiplier(50),
.cpuCycles = 4'000,
@@ -577,7 +750,7 @@
.name = "com.google.android.car.kitchensink",
.uid = 1002001,
},
- .uidUptimeMillis = 19'533,
+ .uidUptimeMillis = elapsedRealtimeSinceBootMillis - 467,
.cpuUsageStats = {
.cpuTimeMillis = int64Multiplier(60),
.cpuCycles = 10'000,
@@ -615,7 +788,7 @@
.name = "1012345",
.uid = 1012345,
},
- .uidUptimeMillis = 13'211,
+ .uidUptimeMillis = elapsedRealtimeSinceBootMillis - 6789,
.cpuUsageStats = {
.cpuTimeMillis = int64Multiplier(100),
.cpuCycles = 50'000,
@@ -647,7 +820,7 @@
.name = "com.google.radio",
.uid = 1015678,
},
- .uidUptimeMillis = 211,
+ .uidUptimeMillis = elapsedRealtimeSinceBootMillis - 10789,
.cpuUsageStats = {
.cpuTimeMillis = 0,
.cpuCycles = 0,
@@ -673,40 +846,13 @@
}
struct StatsInfo {
- std::vector<UidStats> uidStats;
- UserPackageSummaryStats userPackageSummaryStats;
- ProcStatInfo procStatInfo;
- SystemSummaryStats systemSummaryStats;
- ResourceStats resourceStats;
+ std::vector<UidStats> uidStats = {};
+ UserPackageSummaryStats userPackageSummaryStats = {};
+ ProcStatInfo procStatInfo = {};
+ SystemSummaryStats systemSummaryStats = {};
+ ResourceStats resourceStats = {};
};
-StatsInfo getSampleStats(int multiplier = 1) {
- /* Return results in a single value from three methods: sampleUidStats,
- * sampleProcStat and getResourceStatsForSampledStats.
- */
- const auto int64Multiplier = [&](int64_t bytes) -> int64_t {
- return static_cast<int64_t>(bytes * multiplier);
- };
- const auto uint64Multiplier = [&](uint64_t count) -> uint64_t {
- return static_cast<uint64_t>(count * multiplier);
- };
- const auto int32Multiplier = [&](int32_t bytes) -> int32_t {
- return static_cast<int32_t>(bytes * multiplier);
- };
- const auto uint32Multiplier = [&](uint32_t bytes) -> uint32_t {
- return static_cast<uint32_t>(bytes * multiplier);
- };
-
- auto [uidStats, userPackageSummaryStats] = sampleUidStats(int64Multiplier, uint64Multiplier);
- auto [procStatInfo, systemSummaryStats] =
- sampleProcStat(int64Multiplier, uint64Multiplier, uint32Multiplier);
- ResourceStats resourceStats = getResourceStatsForSampledStats(int32Multiplier, int64Multiplier);
-
- StatsInfo statsInfo(uidStats, userPackageSummaryStats, procStatInfo, systemSummaryStats,
- resourceStats);
- return statsInfo;
-}
-
MATCHER_P(UserPackageInfoProtoEq, expected, "") {
return ExplainMatchResult(AllOf(Property("user_id", &UserPackageInfo::user_id,
static_cast<int>(multiuser_get_user_id(expected.uid))),
@@ -932,6 +1078,27 @@
ElementsAreArray(packageMajorPageFaultsMatchers))),
arg, result_listener);
}
+
+std::string toString(util::ProtoOutputStream* proto) {
+ std::string content;
+ content.reserve(proto->size());
+ sp<ProtoReader> reader = proto->data();
+ while (reader->hasNext()) {
+ content.push_back(reader->next());
+ }
+ return content;
+}
+
+std::string toString(const std::vector<UserSwitchCollectionInfo>& infos) {
+ std::string buffer;
+ StringAppendF(&buffer, "{");
+ for (const auto& info : infos) {
+ StringAppendF(&buffer, "%s\n", info.toString().c_str());
+ }
+ StringAppendF(&buffer, "}");
+ return buffer;
+}
+
} // namespace
namespace internal {
@@ -960,12 +1127,16 @@
mCollector->mSystemEventDataCacheDurationSec = value;
}
+ void setPeriodicCollectionBufferSize(size_t bufferSize) {
+ mCollector->mPeriodicCollection.maxCacheSize = bufferSize;
+ }
+
void setSendResourceUsageStatsEnabled(bool enable) {
mCollector->mDoSendResourceUsageStats = enable;
}
- void setGetElapsedTimeSinceBootMillisFunc(const std::function<int64_t()>& func) {
- mCollector->kGetElapsedTimeSinceBootMillisFunc = func;
+ void setSmapsRollupSupportedEnabled(bool enable) {
+ mCollector->mIsSmapsRollupSupported = enable;
}
const CollectionInfo& getBoottimeCollectionInfo() {
@@ -1002,26 +1173,47 @@
class PerformanceProfilerTest : public Test {
protected:
void SetUp() override {
+ mPeriodicCollectionBufferSize =
+ static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
+ kDefaultPeriodicCollectionBufferSize));
+ mElapsedRealtimeSinceBootMillis = kTestElapsedRealtimeSinceBootMillis;
+ mNowMillis = kTestNowMillis;
mMockUidStatsCollector = sp<MockUidStatsCollector>::make();
+ mMockPressureMonitor = sp<MockPressureMonitor>::make();
mMockProcStatCollector = sp<MockProcStatCollector>::make();
- mCollector = sp<PerformanceProfiler>::make();
+ mCollector = sp<PerformanceProfiler>::
+ make(mMockPressureMonitor,
+ std::bind(&PerformanceProfilerTest::getTestElapsedRealtimeSinceBootMs, this));
mCollectorPeer = sp<internal::PerformanceProfilerPeer>::make(mCollector);
+
+ EXPECT_CALL(*mMockPressureMonitor, registerPressureChangeCallback(Eq(mCollector)))
+ .Times(car_watchdog_memory_profiling() ? 1 : 0);
+
ASSERT_RESULT_OK(mCollectorPeer->init());
+
mCollectorPeer->setTopNStatsPerCategory(kTestTopNStatsPerCategory);
mCollectorPeer->setTopNStatsPerSubcategory(kTestTopNStatsPerSubcategory);
mCollectorPeer->setMaxUserSwitchEvents(kTestMaxUserSwitchEvents);
mCollectorPeer->setSystemEventDataCacheDuration(kTestSystemEventDataCacheDurationSec);
mCollectorPeer->setSendResourceUsageStatsEnabled(true);
- mCollectorPeer->setGetElapsedTimeSinceBootMillisFunc(getTestElapsedRealtimeSinceBootMs);
+ mCollectorPeer->setSmapsRollupSupportedEnabled(true);
+ mCollectorPeer->setPeriodicCollectionBufferSize(kTestPeriodicCollectionBufferSize);
}
void TearDown() override {
mMockUidStatsCollector.clear();
mMockProcStatCollector.clear();
+
+ EXPECT_CALL(*mMockPressureMonitor, unregisterPressureChangeCallback(Eq(mCollector)))
+ .Times(car_watchdog_memory_profiling() ? 1 : 0);
+
mCollector.clear();
mCollectorPeer.clear();
}
+ int64_t getTestElapsedRealtimeSinceBootMs() { return mElapsedRealtimeSinceBootMillis; }
+
+protected:
void checkDumpContents(int wantedEmptyCollectionInstances) {
TemporaryFile dump;
ASSERT_RESULT_OK(mCollector->onDump(dump.fd));
@@ -1036,16 +1228,134 @@
checkDumpFd(/*wantedEmptyCollectionInstances=*/0, dump.fd);
}
- std::string toString(util::ProtoOutputStream* proto) {
- std::string content;
- content.reserve(proto->size());
- sp<ProtoReader> reader = proto->data();
- while (reader->hasNext()) {
- content.push_back(reader->next());
+ PressureLevelDurations injectPressureLevelTransitions(int advanceUptimeSec) {
+ if (!car_watchdog_memory_profiling()) {
+ mElapsedRealtimeSinceBootMillis += advanceUptimeSec * 1000;
+ return PressureLevelDurations{};
}
- return content;
+ auto [pressureLevelTransitions, pressureLevelDurations] =
+ samplePressureLevels(advanceUptimeSec);
+ for (const auto transition : pressureLevelTransitions) {
+ mElapsedRealtimeSinceBootMillis += transition.second;
+ mCollector->onPressureChanged(transition.first);
+ }
+ return pressureLevelDurations;
}
+ // Direct use of this method in tests is not recommended because further setup (such as calling
+ // injectPressureLevelTransitions, constructing CollectionInfo struct, advancing time, and
+ // setting up EXPECT_CALL) is required before testing a collection. Please consider using one of
+ // the PerformanceProfilerTest::setup* methods instead. If none of them work for a new use case,
+ // either update the existing PerformanceProfilerTest::setup* methods or add a new
+ // PerformanceProfilerTest::setup* method.
+ StatsInfo getSampleStatsInfo(int multiplier = 1,
+ bool isSmapsRollupSupported = kTestIsSmapsRollupSupported) {
+ const auto int64Multiplier = [&](int64_t bytes) -> int64_t {
+ return static_cast<int64_t>(bytes * multiplier);
+ };
+ const auto uint64Multiplier = [&](uint64_t count) -> uint64_t {
+ return static_cast<uint64_t>(count * multiplier);
+ };
+ const auto int32Multiplier = [&](int32_t bytes) -> int32_t {
+ return static_cast<int32_t>(bytes * multiplier);
+ };
+ const auto uint32Multiplier = [&](uint32_t bytes) -> uint32_t {
+ return static_cast<uint32_t>(bytes * multiplier);
+ };
+
+ auto [uidStats, userPackageSummaryStats] =
+ sampleUidStats(int64Multiplier, uint64Multiplier, isSmapsRollupSupported);
+
+ applyFeatureFilter(&userPackageSummaryStats);
+
+ auto [procStatInfo, systemSummaryStats] =
+ sampleProcStat(int64Multiplier, uint64Multiplier, uint32Multiplier);
+
+ ResourceStats resourceStats =
+ getResourceStatsForSampledStats(int32Multiplier, int64Multiplier, mNowMillis,
+ mElapsedRealtimeSinceBootMillis);
+
+ StatsInfo statsInfo(uidStats, userPackageSummaryStats, procStatInfo, systemSummaryStats,
+ resourceStats);
+ return statsInfo;
+ }
+
+ void advanceTime(int durationMillis) {
+ mNowMillis += std::chrono::milliseconds(durationMillis);
+ }
+
+ std::tuple<CollectionInfo, ResourceStats> setupFirstCollection(
+ size_t maxCollectionCacheSize = std::numeric_limits<std::size_t>::max(),
+ bool isSmapsRollupSupported = kTestIsSmapsRollupSupported) {
+ // Trigger pressure level transitions to test the pressure level accounting done by the
+ // implementation.
+ auto pressureLevelDurations = injectPressureLevelTransitions(/*advanceUptimeSec=*/1);
+ auto statsInfo = getSampleStatsInfo(/*multiplier=*/1, isSmapsRollupSupported);
+
+ EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(statsInfo.uidStats));
+ EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(statsInfo.procStatInfo));
+
+ auto expectedCollectionInfo =
+ CollectionInfo{.maxCacheSize = maxCollectionCacheSize,
+ .records = {{
+ .collectionTimeMillis = mNowMillis,
+ .systemSummaryStats = statsInfo.systemSummaryStats,
+ .userPackageSummaryStats = statsInfo.userPackageSummaryStats,
+ .memoryPressureLevelDurations = pressureLevelDurations,
+ }}};
+ auto expectedResourceStats = statsInfo.resourceStats;
+ return std::make_tuple(expectedCollectionInfo, expectedResourceStats);
+ }
+
+ void setupNextCollection(CollectionInfo* prevCollectionInfo, ResourceStats* outResourceStats,
+ int multiplier = 1) {
+ advanceTime(/*durationMillis=*/1000);
+ // Trigger pressure level transitions to test the pressure level accounting done by the
+ // implementation.
+ auto pressureLevelDurations = injectPressureLevelTransitions(/*advanceUptimeSec=*/1);
+ auto statsInfo = getSampleStatsInfo(multiplier, kTestIsSmapsRollupSupported);
+
+ EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(statsInfo.uidStats));
+ EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(statsInfo.procStatInfo));
+
+ auto& prevRecord = prevCollectionInfo->records.back();
+ statsInfo.userPackageSummaryStats.majorFaultsPercentChange =
+ (static_cast<double>(statsInfo.userPackageSummaryStats.totalMajorFaults -
+ prevRecord.userPackageSummaryStats.totalMajorFaults) /
+ static_cast<double>(prevRecord.userPackageSummaryStats.totalMajorFaults)) *
+ 100.0;
+
+ prevCollectionInfo->records.push_back(PerfStatsRecord{
+ .collectionTimeMillis = mNowMillis,
+ .systemSummaryStats = statsInfo.systemSummaryStats,
+ .userPackageSummaryStats = statsInfo.userPackageSummaryStats,
+ .memoryPressureLevelDurations = pressureLevelDurations,
+ });
+ *outResourceStats = statsInfo.resourceStats;
+ }
+
+ UserSwitchCollectionInfo setupUserSwitchCollection(userid_t fromUserId, userid_t toUserId) {
+ auto [collectionInfo, _] = setupFirstCollection();
+ return UserSwitchCollectionInfo{
+ collectionInfo,
+ .from = fromUserId,
+ .to = toUserId,
+ };
+ }
+
+ // Use this method only in tests where the returned CollectionInfo / UserSwitchCollectionInfo
+ // is not verified.
+ void setupMultipleCollections() {
+ auto statsInfo = getSampleStatsInfo();
+
+ EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+ .WillRepeatedly(Return(statsInfo.uidStats));
+ EXPECT_CALL(*mMockProcStatCollector, deltaStats())
+ .WillRepeatedly(Return(statsInfo.procStatInfo));
+ }
+
+ time_point_millis getNowMillis() { return mNowMillis; }
+
private:
void checkDumpFd(int wantedEmptyCollectionInstances, int fd) {
lseek(fd, 0, SEEK_SET);
@@ -1059,39 +1369,32 @@
}
protected:
+ size_t mPeriodicCollectionBufferSize;
sp<MockUidStatsCollector> mMockUidStatsCollector;
+ sp<MockPressureMonitor> mMockPressureMonitor;
sp<MockProcStatCollector> mMockProcStatCollector;
sp<PerformanceProfiler> mCollector;
sp<internal::PerformanceProfilerPeer> mCollectorPeer;
+
+private:
+ int64_t mElapsedRealtimeSinceBootMillis;
+ time_point_millis mNowMillis;
};
TEST_F(PerformanceProfilerTest, TestOnBoottimeCollection) {
- const auto statsInfo = getSampleStats();
+ auto [expectedCollectionInfo, expectedResourceStats] = setupFirstCollection();
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(statsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(statsInfo.procStatInfo));
-
- ResourceStats expectedResourceStats = statsInfo.resourceStats;
ResourceStats actualResourceStats = {};
-
- ASSERT_RESULT_OK(mCollector->onBoottimeCollection(kTestNow, mMockUidStatsCollector,
+ ASSERT_RESULT_OK(mCollector->onBoottimeCollection(getNowMillis(), mMockUidStatsCollector,
mMockProcStatCollector,
&actualResourceStats));
- const auto actual = mCollectorPeer->getBoottimeCollectionInfo();
+ const auto actualCollectionInfo = mCollectorPeer->getBoottimeCollectionInfo();
- const CollectionInfo expected{
- .maxCacheSize = std::numeric_limits<std::size_t>::max(),
- .records = {{
- .systemSummaryStats = statsInfo.systemSummaryStats,
- .userPackageSummaryStats = statsInfo.userPackageSummaryStats,
- }},
- };
-
- EXPECT_THAT(actual, CollectionInfoEq(expected))
+ EXPECT_THAT(actualCollectionInfo, CollectionInfoEq(expectedCollectionInfo))
<< "Boottime collection info doesn't match.\nExpected:\n"
- << expected.toString() << "\nActual:\n"
- << actual.toString();
+ << expectedCollectionInfo.toString() << "\nActual:\n"
+ << actualCollectionInfo.toString();
ASSERT_EQ(actualResourceStats, expectedResourceStats)
<< "Expected: " << expectedResourceStats.toString()
@@ -1102,44 +1405,29 @@
}
TEST_F(PerformanceProfilerTest, TestOnWakeUpCollection) {
- const auto statsInfo = getSampleStats();
+ auto [expectedCollectionInfo, expectedResourceStats] = setupFirstCollection();
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(statsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(statsInfo.procStatInfo));
-
- ASSERT_RESULT_OK(mCollector->onWakeUpCollection(kTestNow, mMockUidStatsCollector,
+ ASSERT_RESULT_OK(mCollector->onWakeUpCollection(getNowMillis(), mMockUidStatsCollector,
mMockProcStatCollector));
- const auto actual = mCollectorPeer->getWakeUpCollectionInfo();
+ const auto actualCollectionInfo = mCollectorPeer->getWakeUpCollectionInfo();
- const CollectionInfo expected{
- .maxCacheSize = std::numeric_limits<std::size_t>::max(),
- .records = {{
- .systemSummaryStats = statsInfo.systemSummaryStats,
- .userPackageSummaryStats = statsInfo.userPackageSummaryStats,
- }},
- };
-
- EXPECT_THAT(actual, CollectionInfoEq(expected))
+ EXPECT_THAT(actualCollectionInfo, CollectionInfoEq(expectedCollectionInfo))
<< "Wake-up collection info doesn't match.\nExpected:\n"
- << expected.toString() << "\nActual:\n"
- << actual.toString();
+ << expectedCollectionInfo.toString() << "\nActual:\n"
+ << actualCollectionInfo.toString();
ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/3))
<< "Boot-time, periodic, and user-switch collections shouldn't be reported";
}
TEST_F(PerformanceProfilerTest, TestOnSystemStartup) {
- const auto statsInfo = getSampleStats();
-
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillRepeatedly(Return(statsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats())
- .WillRepeatedly(Return(statsInfo.procStatInfo));
+ setupMultipleCollections();
ResourceStats resourceStats = {};
- ASSERT_RESULT_OK(mCollector->onBoottimeCollection(kTestNow, mMockUidStatsCollector,
+ ASSERT_RESULT_OK(mCollector->onBoottimeCollection(getNowMillis(), mMockUidStatsCollector,
mMockProcStatCollector, &resourceStats));
- ASSERT_RESULT_OK(mCollector->onWakeUpCollection(kTestNow, mMockUidStatsCollector,
+ ASSERT_RESULT_OK(mCollector->onWakeUpCollection(getNowMillis(), mMockUidStatsCollector,
mMockProcStatCollector));
auto actualBoottimeCollection = mCollectorPeer->getBoottimeCollectionInfo();
@@ -1161,35 +1449,19 @@
}
TEST_F(PerformanceProfilerTest, TestOnUserSwitchCollection) {
- auto statsInfo = getSampleStats();
+ std::vector<UserSwitchCollectionInfo> expected;
+ expected.push_back(setupUserSwitchCollection(kTestBaseUserId, kTestBaseUserId + 1));
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(statsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(statsInfo.procStatInfo));
-
- ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(kTestNow, 100, 101, mMockUidStatsCollector,
+ ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(getNowMillis(), kTestBaseUserId,
+ kTestBaseUserId + 1, mMockUidStatsCollector,
mMockProcStatCollector));
- const auto& actualInfos = mCollectorPeer->getUserSwitchCollectionInfos();
- const auto& actual = actualInfos[0];
+ auto actual = mCollectorPeer->getUserSwitchCollectionInfos();
- UserSwitchCollectionInfo expected{
- {
- .maxCacheSize = std::numeric_limits<std::size_t>::max(),
- .records = {{
- .systemSummaryStats = statsInfo.systemSummaryStats,
- .userPackageSummaryStats = statsInfo.userPackageSummaryStats,
- }},
- },
- .from = 100,
- .to = 101,
- };
-
- EXPECT_THAT(actualInfos.size(), 1);
-
- EXPECT_THAT(actual, UserSwitchCollectionInfoEq(expected))
- << "User switch collection info doesn't match.\nExpected:\n"
- << expected.toString() << "\nActual:\n"
- << actual.toString();
+ EXPECT_THAT(actual, UserSwitchCollectionsEq(expected))
+ << "User switch collection infos doesn't match.\nExpected:\n"
+ << toString(expected) << "\nActual:\n"
+ << toString(actual);
// Continuation of the previous user switch collection
std::vector<UidStats> nextUidStats = {
@@ -1230,6 +1502,12 @@
.majorFaultsPercentChange = (6'000.0 - 84'345.0) / 84'345.0 * 100.0,
};
+ // TODO(b/336835345): Revisit this test and update the below logic to use setupNextCollection
+ // instead.
+ auto nextPressureLevelDurations = injectPressureLevelTransitions(/*advanceUptimeSec=*/2);
+ advanceTime(/*durationMillis=*/2000);
+
+ const auto statsInfo = getSampleStatsInfo();
ProcStatInfo nextProcStatInfo = statsInfo.procStatInfo;
SystemSummaryStats nextSystemSummaryStats = statsInfo.systemSummaryStats;
@@ -1240,131 +1518,87 @@
EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(nextUidStats));
EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(nextProcStatInfo));
- ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(kTestNow + std::chrono::seconds(2), 100,
- 101, mMockUidStatsCollector,
+ ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(getNowMillis(), kTestBaseUserId,
+ kTestBaseUserId + 1, mMockUidStatsCollector,
mMockProcStatCollector));
- auto& continuationActualInfos = mCollectorPeer->getUserSwitchCollectionInfos();
- auto& continuationActual = continuationActualInfos[0];
+ actual = mCollectorPeer->getUserSwitchCollectionInfos();
- expected = {
- {
- .maxCacheSize = std::numeric_limits<std::size_t>::max(),
- .records = {{.systemSummaryStats = statsInfo.systemSummaryStats,
- .userPackageSummaryStats = statsInfo.userPackageSummaryStats},
- {.systemSummaryStats = nextSystemSummaryStats,
- .userPackageSummaryStats = nextUserPackageSummaryStats}},
- },
- .from = 100,
- .to = 101,
- };
+ expected[0].records.push_back(
+ PerfStatsRecord{.collectionTimeMillis = getNowMillis(),
+ .systemSummaryStats = nextSystemSummaryStats,
+ .userPackageSummaryStats = nextUserPackageSummaryStats,
+ .memoryPressureLevelDurations = nextPressureLevelDurations});
- EXPECT_THAT(continuationActualInfos.size(), 1);
-
- EXPECT_THAT(continuationActual, UserSwitchCollectionInfoEq(expected))
+ EXPECT_THAT(actual, UserSwitchCollectionsEq(expected))
<< "User switch collection info after continuation doesn't match.\nExpected:\n"
- << expected.toString() << "\nActual:\n"
- << actual.toString();
+ << toString(expected) << "\nActual:\n"
+ << toString(actual);
ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/3))
<< "Boot-time, wake-up and periodic collections shouldn't be reported";
}
TEST_F(PerformanceProfilerTest, TestUserSwitchCollectionsMaxCacheSize) {
- auto statsInfo = getSampleStats();
+ std::vector<UserSwitchCollectionInfo> expected;
+ userid_t userIdToTriggerEviction = kTestBaseUserId + kTestMaxUserSwitchEvents;
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillRepeatedly(Return(statsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats())
- .WillRepeatedly(Return(statsInfo.procStatInfo));
-
- std::vector<UserSwitchCollectionInfo> expectedEvents;
- for (userid_t userId = 100; userId < 100 + kTestMaxUserSwitchEvents; ++userId) {
- expectedEvents.push_back({
- {
- .maxCacheSize = std::numeric_limits<std::size_t>::max(),
- .records = {{
- .systemSummaryStats = statsInfo.systemSummaryStats,
- .userPackageSummaryStats = statsInfo.userPackageSummaryStats,
- }},
- },
- .from = userId,
- .to = userId + 1,
- });
- }
-
- for (userid_t userId = 100; userId < 100 + kTestMaxUserSwitchEvents; ++userId) {
- ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(kTestNow, userId, userId + 1,
+ for (userid_t userId = kTestBaseUserId; userId < userIdToTriggerEviction; ++userId) {
+ expected.push_back(setupUserSwitchCollection(userId, userId + 1));
+ ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(getNowMillis(), userId, userId + 1,
mMockUidStatsCollector,
mMockProcStatCollector));
}
- const auto& actual = mCollectorPeer->getUserSwitchCollectionInfos();
+ auto actual = mCollectorPeer->getUserSwitchCollectionInfos();
EXPECT_THAT(actual.size(), kTestMaxUserSwitchEvents);
- EXPECT_THAT(actual, UserSwitchCollectionsEq(expectedEvents))
- << "User switch collection infos don't match.";
+ EXPECT_THAT(actual, UserSwitchCollectionsEq(expected))
+ << "User switch collection infos don't match before crossing limit.\nExpected:\n"
+ << toString(expected) << "\nActual:\n"
+ << toString(actual);
// Add new user switch event with max cache size. The oldest user switch event should be dropped
// and the new one added to the cache.
- userid_t userId = 100 + kTestMaxUserSwitchEvents;
+ expected.push_back(
+ setupUserSwitchCollection(userIdToTriggerEviction, userIdToTriggerEviction + 1));
+ expected.erase(expected.begin());
- expectedEvents.push_back({
- {
- .maxCacheSize = std::numeric_limits<std::size_t>::max(),
- .records = {{
- .systemSummaryStats = statsInfo.systemSummaryStats,
- .userPackageSummaryStats = statsInfo.userPackageSummaryStats,
- }},
- },
- .from = userId,
- .to = userId + 1,
- });
- expectedEvents.erase(expectedEvents.begin());
-
- ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(kTestNow, userId, userId + 1,
+ ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(getNowMillis(), userIdToTriggerEviction,
+ userIdToTriggerEviction + 1,
mMockUidStatsCollector,
mMockProcStatCollector));
- const auto& actualInfos = mCollectorPeer->getUserSwitchCollectionInfos();
+ actual = mCollectorPeer->getUserSwitchCollectionInfos();
- EXPECT_THAT(actualInfos.size(), kTestMaxUserSwitchEvents);
+ EXPECT_THAT(actual.size(), kTestMaxUserSwitchEvents);
- EXPECT_THAT(actualInfos, UserSwitchCollectionsEq(expectedEvents))
- << "User switch collection infos don't match.";
+ EXPECT_THAT(actual, UserSwitchCollectionsEq(expected))
+ << "User switch collection infos don't match after crossing limit.\nExpected:\n"
+ << toString(expected) << "\nActual:\n"
+ << toString(actual);
}
TEST_F(PerformanceProfilerTest, TestOnPeriodicCollection) {
- const auto statsInfo = getSampleStats();
-
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(statsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(statsInfo.procStatInfo));
+ const auto [expectedCollectionInfo, expectedResourceStats] =
+ setupFirstCollection(kTestPeriodicCollectionBufferSize);
ResourceStats actualResourceStats = {};
-
- ASSERT_RESULT_OK(mCollector->onPeriodicCollection(kTestNow, SystemState::NORMAL_MODE,
+ ASSERT_RESULT_OK(mCollector->onPeriodicCollection(getNowMillis(), SystemState::NORMAL_MODE,
mMockUidStatsCollector,
mMockProcStatCollector,
&actualResourceStats));
- const auto actual = mCollectorPeer->getPeriodicCollectionInfo();
+ const auto actualCollectionInfo = mCollectorPeer->getPeriodicCollectionInfo();
- const CollectionInfo expected{
- .maxCacheSize = static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
- kDefaultPeriodicCollectionBufferSize)),
- .records = {{
- .systemSummaryStats = statsInfo.systemSummaryStats,
- .userPackageSummaryStats = statsInfo.userPackageSummaryStats,
- }},
- };
-
- EXPECT_THAT(actual, CollectionInfoEq(expected))
+ EXPECT_THAT(actualCollectionInfo, CollectionInfoEq(expectedCollectionInfo))
<< "Periodic collection info doesn't match.\nExpected:\n"
- << expected.toString() << "\nActual:\n"
- << actual.toString();
+ << expectedCollectionInfo.toString() << "\nActual:\n"
+ << actualCollectionInfo.toString();
- ASSERT_EQ(actualResourceStats, statsInfo.resourceStats)
- << "Expected: " << statsInfo.resourceStats.toString()
+ ASSERT_EQ(actualResourceStats, expectedResourceStats)
+ << "Expected: " << expectedResourceStats.toString()
<< "\nActual: " << actualResourceStats.toString();
ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/3))
@@ -1373,33 +1607,22 @@
TEST_F(PerformanceProfilerTest, TestOnPeriodicCollectionWithSendingUsageStatsDisabled) {
mCollectorPeer->setSendResourceUsageStatsEnabled(false);
- const auto statsInfo = getSampleStats();
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(statsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(statsInfo.procStatInfo));
+ auto [expectedCollectionInfo, _] = setupFirstCollection(kTestPeriodicCollectionBufferSize);
ResourceStats actualResourceStats = {};
- ASSERT_RESULT_OK(mCollector->onPeriodicCollection(kTestNow, SystemState::NORMAL_MODE,
+ ASSERT_RESULT_OK(mCollector->onPeriodicCollection(getNowMillis(), SystemState::NORMAL_MODE,
mMockUidStatsCollector,
mMockProcStatCollector,
&actualResourceStats));
- const auto actual = mCollectorPeer->getPeriodicCollectionInfo();
-
- const CollectionInfo expected{
- .maxCacheSize = static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
- kDefaultPeriodicCollectionBufferSize)),
- .records = {{
- .systemSummaryStats = statsInfo.systemSummaryStats,
- .userPackageSummaryStats = statsInfo.userPackageSummaryStats,
- }},
- };
+ const auto actualCollectionInfo = mCollectorPeer->getPeriodicCollectionInfo();
const ResourceStats expectedResourceStats = {};
- EXPECT_THAT(actual, CollectionInfoEq(expected))
+ EXPECT_THAT(actualCollectionInfo, CollectionInfoEq(expectedCollectionInfo))
<< "Periodic collection info doesn't match.\nExpected:\n"
- << expected.toString() << "\nActual:\n"
- << actual.toString();
+ << expectedCollectionInfo.toString() << "\nActual:\n"
+ << actualCollectionInfo.toString();
ASSERT_EQ(actualResourceStats, expectedResourceStats)
<< "Expected: " << expectedResourceStats.toString()
@@ -1410,32 +1633,19 @@
}
TEST_F(PerformanceProfilerTest, TestOnCustomCollectionWithoutPackageFilter) {
- const auto statsInfo = getSampleStats();
+ auto [expectedCollectionInfo, expectedResourceStats] = setupFirstCollection();
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(statsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(statsInfo.procStatInfo));
-
- ResourceStats expectedResourceStats = statsInfo.resourceStats;
ResourceStats actualResourceStats = {};
-
- ASSERT_RESULT_OK(mCollector->onCustomCollection(kTestNow, SystemState::NORMAL_MODE, {},
+ ASSERT_RESULT_OK(mCollector->onCustomCollection(getNowMillis(), SystemState::NORMAL_MODE, {},
mMockUidStatsCollector, mMockProcStatCollector,
&actualResourceStats));
- const auto actual = mCollectorPeer->getCustomCollectionInfo();
+ const auto actualCollectionInfo = mCollectorPeer->getCustomCollectionInfo();
- CollectionInfo expected{
- .maxCacheSize = std::numeric_limits<std::size_t>::max(),
- .records = {{
- .systemSummaryStats = statsInfo.systemSummaryStats,
- .userPackageSummaryStats = statsInfo.userPackageSummaryStats,
- }},
- };
-
- EXPECT_THAT(actual, CollectionInfoEq(expected))
+ EXPECT_THAT(actualCollectionInfo, CollectionInfoEq(expectedCollectionInfo))
<< "Custom collection info doesn't match.\nExpected:\n"
- << expected.toString() << "\nActual:\n"
- << actual.toString();
+ << expectedCollectionInfo.toString() << "\nActual:\n"
+ << actualCollectionInfo.toString();
ASSERT_EQ(actualResourceStats, expectedResourceStats)
<< "Expected: " << expectedResourceStats.toString()
@@ -1449,9 +1659,9 @@
// Should clear the cache.
ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(-1));
- expected.records.clear();
+ expectedCollectionInfo.records.clear();
const CollectionInfo& emptyCollectionInfo = mCollectorPeer->getCustomCollectionInfo();
- EXPECT_THAT(emptyCollectionInfo, CollectionInfoEq(expected))
+ EXPECT_THAT(emptyCollectionInfo, CollectionInfoEq(expectedCollectionInfo))
<< "Custom collection should be cleared.";
}
@@ -1459,20 +1669,14 @@
// Filter by package name should ignore this limit with package filter.
mCollectorPeer->setTopNStatsPerCategory(1);
- const auto statsInfo = getSampleStats();
+ auto [expectedCollectionInfo, expectedResourceStats] = setupFirstCollection();
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(statsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(statsInfo.procStatInfo));
-
- ResourceStats expectedResourceStats = statsInfo.resourceStats;
ResourceStats actualResourceStats = {};
-
- ASSERT_RESULT_OK(mCollector->onCustomCollection(kTestNow, SystemState::NORMAL_MODE,
+ ASSERT_RESULT_OK(mCollector->onCustomCollection(getNowMillis(), SystemState::NORMAL_MODE,
{"mount", "com.google.android.car.kitchensink"},
mMockUidStatsCollector, mMockProcStatCollector,
&actualResourceStats));
-
- const auto actual = mCollectorPeer->getCustomCollectionInfo();
+ const auto actualCollectionInfo = mCollectorPeer->getCustomCollectionInfo();
UserPackageSummaryStats userPackageSummaryStats{
.topNCpuTimes = {{1009, "mount",
@@ -1503,26 +1707,40 @@
UserPackageStats::ProcSingleStatsView{22'445,
{{"KitchenSinkApp", 12'345},
{"CTS", 10'100}}}}},
+ .topNMemStats =
+ {{1009, "mount",
+ UserPackageStats::UidMemoryStats{{/*rssKb=*/2010, /*pssKb=*/1635,
+ /*ussKb=*/1286, /*swapPssKb=*/600},
+ kTestIsSmapsRollupSupported,
+ {{"disk I/O",
+ {/*rssKb=*/2010, /*pssKb=*/1635,
+ /*ussKb=*/1286, /*swapPssKb=*/600}}}}},
+ {1002001, "com.google.android.car.kitchensink",
+ UserPackageStats::UidMemoryStats{{/*rssKb=*/2000, /*pssKb=*/1645,
+ /*ussKb=*/1286, /*swapPssKb=*/600},
+ kTestIsSmapsRollupSupported,
+ {{"KitchenSinkApp",
+ {/*rssKb=*/1000, /*pssKb=*/875,
+ /*ussKb=*/630, /*swapPssKb=*/400}},
+ {"CTS",
+ {/*rssKb=*/1000, /*pssKb=*/770,
+ /*ussKb=*/656, /*swapPssKb=*/200}}}}}},
.totalIoStats = {{1000, 21'600}, {300, 28'300}, {600, 600}},
.taskCountByUid = {{1009, 1}, {1002001, 5}},
.totalCpuTimeMillis = 48'376,
.totalCpuCycles = 64'000,
.totalMajorFaults = 84'345,
+ .totalRssKb = 5010,
+ .totalPssKb = 4145,
.majorFaultsPercentChange = 0.0,
};
+ applyFeatureFilter(&userPackageSummaryStats);
+ expectedCollectionInfo.records[0].userPackageSummaryStats = userPackageSummaryStats;
- CollectionInfo expected{
- .maxCacheSize = std::numeric_limits<std::size_t>::max(),
- .records = {{
- .systemSummaryStats = statsInfo.systemSummaryStats,
- .userPackageSummaryStats = userPackageSummaryStats,
- }},
- };
-
- EXPECT_THAT(actual, CollectionInfoEq(expected))
+ EXPECT_THAT(actualCollectionInfo, CollectionInfoEq(expectedCollectionInfo))
<< "Custom collection info doesn't match.\nExpected:\n"
- << expected.toString() << "\nActual:\n"
- << actual.toString();
+ << expectedCollectionInfo.toString() << "\nActual:\n"
+ << actualCollectionInfo.toString();
ASSERT_EQ(actualResourceStats, expectedResourceStats)
<< "Expected: " << expectedResourceStats.toString()
@@ -1536,9 +1754,9 @@
// Should clear the cache.
ASSERT_RESULT_OK(mCollector->onCustomCollectionDump(-1));
- expected.records.clear();
+ expectedCollectionInfo.records.clear();
const CollectionInfo& emptyCollectionInfo = mCollectorPeer->getCustomCollectionInfo();
- EXPECT_THAT(emptyCollectionInfo, CollectionInfoEq(expected))
+ EXPECT_THAT(emptyCollectionInfo, CollectionInfoEq(expectedCollectionInfo))
<< "Custom collection should be cleared.";
}
@@ -1546,26 +1764,24 @@
mCollectorPeer->setTopNStatsPerCategory(1);
mCollectorPeer->setTopNStatsPerSubcategory(1);
- auto statsInfo = getSampleStats();
+ auto [expectedCollectionInfo, expectedResourceStats] =
+ setupFirstCollection(kTestPeriodicCollectionBufferSize);
// Top N stats per category/sub-category is set to 1, so remove entries in the
// expected value to match this.
- ASSERT_FALSE(statsInfo.resourceStats.resourceUsageStats->uidResourceUsageStats.empty());
+ ASSERT_FALSE(expectedResourceStats.resourceUsageStats->uidResourceUsageStats.empty());
UidResourceUsageStats& kitchenSinkStats =
- statsInfo.resourceStats.resourceUsageStats->uidResourceUsageStats.at(1);
+ expectedResourceStats.resourceUsageStats->uidResourceUsageStats.at(1);
ASSERT_FALSE(kitchenSinkStats.processCpuUsageStats.empty());
kitchenSinkStats.processCpuUsageStats.pop_back();
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(statsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats()).WillOnce(Return(statsInfo.procStatInfo));
-
ResourceStats actualResourceStats = {};
- ASSERT_RESULT_OK(mCollector->onPeriodicCollection(kTestNow, SystemState::NORMAL_MODE,
+ ASSERT_RESULT_OK(mCollector->onPeriodicCollection(getNowMillis(), SystemState::NORMAL_MODE,
mMockUidStatsCollector,
mMockProcStatCollector,
&actualResourceStats));
- const auto actual = mCollectorPeer->getPeriodicCollectionInfo();
+ const auto actualCollectionInfo = mCollectorPeer->getPeriodicCollectionInfo();
UserPackageSummaryStats userPackageSummaryStats{
.topNCpuTimes = {{1012345, "1012345",
@@ -1580,30 +1796,33 @@
.topNMajorFaults = {{1012345, "1012345",
UserPackageStats::ProcSingleStatsView{50'900,
{{"MapsApp", 50'900}}}}},
+ .topNMemStats = {{1002001, "com.google.android.car.kitchensink",
+ UserPackageStats::UidMemoryStats{{/*rssKb=*/2000, /*pssKb=*/1645,
+ /*ussKb=*/1286, /*swapPssKb=*/600},
+ kTestIsSmapsRollupSupported,
+ {{"KitchenSinkApp",
+ {/*rssKb=*/1000, /*pssKb=*/875,
+ /*ussKb=*/630,
+ /*swapPssKb=*/400}}}}}},
.totalIoStats = {{1000, 21'600}, {300, 28'300}, {600, 600}},
.taskCountByUid = {{1009, 1}, {1002001, 5}, {1012345, 4}},
.totalCpuTimeMillis = 48'376,
.totalCpuCycles = 64'000,
.totalMajorFaults = 84'345,
+ .totalRssKb = 5010,
+ .totalPssKb = 4145,
.majorFaultsPercentChange = 0.0,
};
+ applyFeatureFilter(&userPackageSummaryStats);
+ expectedCollectionInfo.records[0].userPackageSummaryStats = userPackageSummaryStats;
- const CollectionInfo expected{
- .maxCacheSize = static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
- kDefaultPeriodicCollectionBufferSize)),
- .records = {{
- .systemSummaryStats = statsInfo.systemSummaryStats,
- .userPackageSummaryStats = userPackageSummaryStats,
- }},
- };
-
- EXPECT_THAT(actual, CollectionInfoEq(expected))
+ EXPECT_THAT(actualCollectionInfo, CollectionInfoEq(expectedCollectionInfo))
<< "Periodic collection info doesn't match.\nExpected:\n"
- << expected.toString() << "\nActual:\n"
- << actual.toString();
+ << expectedCollectionInfo.toString() << "\nActual:\n"
+ << actualCollectionInfo.toString();
- ASSERT_EQ(actualResourceStats, statsInfo.resourceStats)
- << "Expected: " << statsInfo.resourceStats.toString()
+ ASSERT_EQ(actualResourceStats, expectedResourceStats)
+ << "Expected: " << expectedResourceStats.toString()
<< "\nActual: " << actualResourceStats.toString();
ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/3))
@@ -1611,151 +1830,139 @@
}
TEST_F(PerformanceProfilerTest, TestConsecutiveOnPeriodicCollection) {
- const auto firstStatsInfo = getSampleStats();
-
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(firstStatsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats())
- .WillOnce(Return(firstStatsInfo.procStatInfo));
+ auto [expectedCollectionInfo, expectedResourceStats] =
+ setupFirstCollection(kTestPeriodicCollectionBufferSize);
ResourceStats actualResourceStats = {};
- ASSERT_RESULT_OK(mCollector->onPeriodicCollection(kTestNow, SystemState::NORMAL_MODE,
+ ASSERT_RESULT_OK(mCollector->onPeriodicCollection(getNowMillis(), SystemState::NORMAL_MODE,
mMockUidStatsCollector,
mMockProcStatCollector,
&actualResourceStats));
- auto secondStatsInfo = getSampleStats(/*multiplier=*/2);
- ResourceStats expectedResourceStats = secondStatsInfo.resourceStats;
+ for (size_t i = 1; i < kTestPeriodicCollectionBufferSize; i++) {
+ setupNextCollection(&expectedCollectionInfo, &expectedResourceStats, /*multiplier=*/2);
- secondStatsInfo.userPackageSummaryStats.majorFaultsPercentChange =
- (static_cast<double>(secondStatsInfo.userPackageSummaryStats.totalMajorFaults -
- firstStatsInfo.userPackageSummaryStats.totalMajorFaults) /
- static_cast<double>(firstStatsInfo.userPackageSummaryStats.totalMajorFaults)) *
- 100.0;
+ ASSERT_RESULT_OK(mCollector->onPeriodicCollection(getNowMillis(), SystemState::NORMAL_MODE,
+ mMockUidStatsCollector,
+ mMockProcStatCollector,
+ &actualResourceStats));
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillOnce(Return(secondStatsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats())
- .WillOnce(Return(secondStatsInfo.procStatInfo));
+ ASSERT_EQ(actualResourceStats, expectedResourceStats)
+ << "Resource stats don't match for collection " << i
+ << "\nExpected: " << expectedResourceStats.toString()
+ << "\nActual: " << actualResourceStats.toString();
+ }
- ASSERT_RESULT_OK(mCollector->onPeriodicCollection(kTestNow, SystemState::NORMAL_MODE,
- mMockUidStatsCollector,
- mMockProcStatCollector,
- &actualResourceStats));
+ auto actualCollectionInfo = mCollectorPeer->getPeriodicCollectionInfo();
- const auto actual = mCollectorPeer->getPeriodicCollectionInfo();
-
- const CollectionInfo expected{
- .maxCacheSize = static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
- kDefaultPeriodicCollectionBufferSize)),
- .records = {{.systemSummaryStats = firstStatsInfo.systemSummaryStats,
- .userPackageSummaryStats = firstStatsInfo.userPackageSummaryStats},
- {.systemSummaryStats = secondStatsInfo.systemSummaryStats,
- .userPackageSummaryStats = secondStatsInfo.userPackageSummaryStats}},
- };
-
- EXPECT_THAT(actual, CollectionInfoEq(expected))
+ EXPECT_THAT(actualCollectionInfo, CollectionInfoEq(expectedCollectionInfo))
<< "Periodic collection info doesn't match.\nExpected:\n"
- << expected.toString() << "\nActual:\n"
- << actual.toString();
+ << expectedCollectionInfo.toString() << "\nActual:\n"
+ << actualCollectionInfo.toString();
- ASSERT_EQ(actualResourceStats, expectedResourceStats)
- << "Expected: " << expectedResourceStats.toString()
- << "\nActual: " << actualResourceStats.toString();
+ // Collection beyond kTestPeriodicCollectionBufferSize should evict the first collection data.
+ setupNextCollection(&expectedCollectionInfo, &expectedResourceStats, /*multiplier=*/2);
+ ASSERT_RESULT_OK(mCollector->onPeriodicCollection(getNowMillis(), SystemState::NORMAL_MODE,
+ mMockUidStatsCollector,
+ mMockProcStatCollector,
+ &actualResourceStats));
+
+ expectedCollectionInfo.records.erase(expectedCollectionInfo.records.begin());
+ actualCollectionInfo = mCollectorPeer->getPeriodicCollectionInfo();
+
+ EXPECT_THAT(actualCollectionInfo, CollectionInfoEq(expectedCollectionInfo))
+ << "Periodic collection info doesn't match after exceeding cache limit.\nExpected:\n"
+ << expectedCollectionInfo.toString() << "\nActual:\n"
+ << actualCollectionInfo.toString();
ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/3))
<< "Boot-time, wake-up and user-switch collection shouldn't be reported";
}
-TEST_F(PerformanceProfilerTest, TestBoottimeCollectionCacheEviction) {
- const auto statsInfo = getSampleStats();
+TEST_F(PerformanceProfilerTest, TestBoottimeCollectionCacheEvictionAfterTimeout) {
+ setupMultipleCollections();
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillRepeatedly(Return(statsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats())
- .WillRepeatedly(Return(statsInfo.procStatInfo));
+ ResourceStats actualResourceStats = {};
+ ASSERT_RESULT_OK(mCollector->onBoottimeCollection(getNowMillis(), mMockUidStatsCollector,
+ mMockProcStatCollector,
+ &actualResourceStats));
- ResourceStats resourceStats = {};
- ASSERT_RESULT_OK(mCollector->onBoottimeCollection(kTestNow, mMockUidStatsCollector,
- mMockProcStatCollector, &resourceStats));
+ auto actualCollectionInfo = mCollectorPeer->getBoottimeCollectionInfo();
- auto actual = mCollectorPeer->getBoottimeCollectionInfo();
+ EXPECT_THAT(actualCollectionInfo.records.size(), 1)
+ << "Boot-time collection info missing after collection";
- EXPECT_THAT(actual.records.size(), 1) << "Boot-time collection info doesn't have size 1";
+ advanceTime(/*durationMillis=*/kTestSystemEventDataCacheDurationSec.count() * 1000);
// Call |onPeriodicCollection| 1 hour past the last boot-time collection event.
- ASSERT_RESULT_OK(
- mCollector->onPeriodicCollection(kTestNow + kTestSystemEventDataCacheDurationSec,
- SystemState::NORMAL_MODE, mMockUidStatsCollector,
- mMockProcStatCollector, &resourceStats));
+ ASSERT_RESULT_OK(mCollector->onPeriodicCollection(getNowMillis(), SystemState::NORMAL_MODE,
+ mMockUidStatsCollector,
+ mMockProcStatCollector,
+ &actualResourceStats));
- actual = mCollectorPeer->getBoottimeCollectionInfo();
+ actualCollectionInfo = mCollectorPeer->getBoottimeCollectionInfo();
- EXPECT_THAT(actual.records.empty(), true) << "Boot-time collection info records are not empty";
+ EXPECT_THAT(actualCollectionInfo.records.empty(), true)
+ << "Boot-time collection info records are not empty after cache eviction period";
}
-TEST_F(PerformanceProfilerTest, TestWakeUpCollectionCacheEviction) {
- const auto statsInfo = getSampleStats();
+TEST_F(PerformanceProfilerTest, TestWakeUpCollectionCacheEvictionAfterTimeout) {
+ setupMultipleCollections();
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillRepeatedly(Return(statsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats())
- .WillRepeatedly(Return(statsInfo.procStatInfo));
-
- ASSERT_RESULT_OK(mCollector->onWakeUpCollection(kTestNow, mMockUidStatsCollector,
+ ASSERT_RESULT_OK(mCollector->onWakeUpCollection(getNowMillis(), mMockUidStatsCollector,
mMockProcStatCollector));
- auto actual = mCollectorPeer->getWakeUpCollectionInfo();
+ auto actualCollectionInfo = mCollectorPeer->getWakeUpCollectionInfo();
- EXPECT_THAT(actual.records.size(), 1) << "Wake-up collection info doesn't have size 1";
+ EXPECT_THAT(actualCollectionInfo.records.size(), 1)
+ << "Wake-up collection info missing after collection";
- ResourceStats resourceStats = {};
+ advanceTime(/*durationMillis=*/kTestSystemEventDataCacheDurationSec.count() * 1000);
+ ResourceStats actualResourceStats = {};
// Call |onPeriodicCollection| 1 hour past the last wake-up collection event.
- ASSERT_RESULT_OK(
- mCollector->onPeriodicCollection(kTestNow + kTestSystemEventDataCacheDurationSec,
- SystemState::NORMAL_MODE, mMockUidStatsCollector,
- mMockProcStatCollector, &resourceStats));
+ ASSERT_RESULT_OK(mCollector->onPeriodicCollection(getNowMillis(), SystemState::NORMAL_MODE,
+ mMockUidStatsCollector,
+ mMockProcStatCollector,
+ &actualResourceStats));
- actual = mCollectorPeer->getWakeUpCollectionInfo();
+ actualCollectionInfo = mCollectorPeer->getWakeUpCollectionInfo();
- EXPECT_THAT(actual.records.empty(), true) << "Wake-up collection info records are not empty";
+ EXPECT_THAT(actualCollectionInfo.records.empty(), true)
+ << "Wake-up collection info records are not empty after cache eviction period";
}
-TEST_F(PerformanceProfilerTest, TestUserSwitchCollectionCacheEviction) {
- auto statsInfo = getSampleStats();
-
- EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillRepeatedly(Return(statsInfo.uidStats));
- EXPECT_CALL(*mMockProcStatCollector, deltaStats())
- .WillRepeatedly(Return(statsInfo.procStatInfo));
-
- auto updatedNow = kTestNow;
-
- for (userid_t userId = 100; userId < 100 + kTestMaxUserSwitchEvents; ++userId) {
- ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(updatedNow, userId, userId + 1,
+TEST_F(PerformanceProfilerTest, TestUserSwitchCollectionCacheEvictionAfterTimeout) {
+ userid_t userIdToTriggerEviction = kTestBaseUserId + kTestMaxUserSwitchEvents;
+ for (userid_t userId = kTestBaseUserId; userId < userIdToTriggerEviction; ++userId) {
+ ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(getNowMillis(), userId, userId + 1,
mMockUidStatsCollector,
mMockProcStatCollector));
- updatedNow += kTestSystemEventDataCacheDurationSec;
+ advanceTime(/*advanceUptimeMillis=*/kTestSystemEventDataCacheDurationSec.count() * 1000);
}
const auto& actual = mCollectorPeer->getUserSwitchCollectionInfos();
EXPECT_THAT(actual.size(), kTestMaxUserSwitchEvents);
- updatedNow = kTestNow + kTestSystemEventDataCacheDurationSec;
ResourceStats resourceStats = {};
for (int i = 1; i <= kTestMaxUserSwitchEvents; ++i) {
- ASSERT_RESULT_OK(mCollector->onPeriodicCollection(updatedNow, SystemState::NORMAL_MODE,
+ ASSERT_RESULT_OK(mCollector->onPeriodicCollection(getNowMillis(), SystemState::NORMAL_MODE,
mMockUidStatsCollector,
mMockProcStatCollector, &resourceStats));
const auto& actual = mCollectorPeer->getUserSwitchCollectionInfos();
EXPECT_THAT(actual.size(), kTestMaxUserSwitchEvents - i)
- << "User-switch collection size is incorrect";
+ << "Expired user switch collection infos are still retained after " << i
+ << "iterations";
- updatedNow += kTestSystemEventDataCacheDurationSec;
+ advanceTime(/*durationMillis=*/kTestSystemEventDataCacheDurationSec.count() * 1000);
}
}
TEST_F(PerformanceProfilerTest, TestOnDumpProto) {
- auto statsInfo = getSampleStats();
+ auto statsInfo = getSampleStatsInfo();
EXPECT_CALL(*mMockUidStatsCollector, deltaStats()).WillRepeatedly(Return(statsInfo.uidStats));
EXPECT_CALL(*mMockProcStatCollector, deltaStats())
@@ -1769,26 +1976,25 @@
.mCustomIntervalMillis = std::chrono::milliseconds(10000)};
ResourceStats actualResourceStats = {};
- int userId = 100;
- ASSERT_RESULT_OK(mCollector->onPeriodicCollection(kTestNow, SystemState::NORMAL_MODE,
+ ASSERT_RESULT_OK(mCollector->onPeriodicCollection(getNowMillis(), SystemState::NORMAL_MODE,
mMockUidStatsCollector,
mMockProcStatCollector,
&actualResourceStats));
- ASSERT_RESULT_OK(mCollector->onBoottimeCollection(kTestNow, mMockUidStatsCollector,
+ ASSERT_RESULT_OK(mCollector->onBoottimeCollection(getNowMillis(), mMockUidStatsCollector,
mMockProcStatCollector,
&actualResourceStats));
- ASSERT_RESULT_OK(mCollector->onWakeUpCollection(kTestNow, mMockUidStatsCollector,
+ ASSERT_RESULT_OK(mCollector->onWakeUpCollection(getNowMillis(), mMockUidStatsCollector,
mMockProcStatCollector));
- ASSERT_RESULT_OK(mCollector->onCustomCollection(kTestNow, SystemState::NORMAL_MODE, {},
+ ASSERT_RESULT_OK(mCollector->onCustomCollection(getNowMillis(), SystemState::NORMAL_MODE, {},
mMockUidStatsCollector, mMockProcStatCollector,
&actualResourceStats));
- ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(kTestNow, userId, userId + 1,
- mMockUidStatsCollector,
+ ASSERT_RESULT_OK(mCollector->onUserSwitchCollection(getNowMillis(), kTestBaseUserId,
+ kTestBaseUserId + 1, mMockUidStatsCollector,
mMockProcStatCollector));
util::ProtoOutputStream proto;
@@ -1803,18 +2009,18 @@
for (auto& record : bootTimeStats.records()) {
EXPECT_THAT(record,
StatsRecordProtoEq(statsInfo.userPackageSummaryStats,
- statsInfo.systemSummaryStats, kTestNow));
+ statsInfo.systemSummaryStats, getNowMillis()));
}
for (const auto& userSwitchStat : performanceStats.user_switch_stats()) {
- EXPECT_EQ(userSwitchStat.to_user_id(), userId + 1);
- EXPECT_EQ(userSwitchStat.from_user_id(), userId);
+ EXPECT_EQ(userSwitchStat.to_user_id(), kTestBaseUserId + 1);
+ EXPECT_EQ(userSwitchStat.from_user_id(), kTestBaseUserId);
auto userSwitchCollection = userSwitchStat.user_switch_collection();
EXPECT_EQ(userSwitchCollection.collection_interval_millis(), 100);
for (const auto& record : userSwitchCollection.records()) {
EXPECT_THAT(record,
StatsRecordProtoEq(statsInfo.userPackageSummaryStats,
- statsInfo.systemSummaryStats, kTestNow));
+ statsInfo.systemSummaryStats, getNowMillis()));
}
}
@@ -1823,7 +2029,7 @@
for (auto& record : wakeUpStats.records()) {
EXPECT_THAT(record,
StatsRecordProtoEq(statsInfo.userPackageSummaryStats,
- statsInfo.systemSummaryStats, kTestNow));
+ statsInfo.systemSummaryStats, getNowMillis()));
}
auto lastNMinutesStats = performanceStats.last_n_minutes_stats();
@@ -1831,7 +2037,7 @@
for (auto& record : lastNMinutesStats.records()) {
EXPECT_THAT(record,
StatsRecordProtoEq(statsInfo.userPackageSummaryStats,
- statsInfo.systemSummaryStats, kTestNow));
+ statsInfo.systemSummaryStats, getNowMillis()));
}
auto customCollectionStats = performanceStats.custom_collection_stats();
@@ -1839,10 +2045,37 @@
for (auto& record : customCollectionStats.records()) {
EXPECT_THAT(record,
StatsRecordProtoEq(statsInfo.userPackageSummaryStats,
- statsInfo.systemSummaryStats, kTestNow));
+ statsInfo.systemSummaryStats, getNowMillis()));
}
}
+TEST_F(PerformanceProfilerTest, TestOnPeriodicCollectionWithSmapsRollupSupportInverted) {
+ mCollectorPeer->setSmapsRollupSupportedEnabled(!kTestIsSmapsRollupSupported);
+ auto [expectedCollectionInfo, expectedResourceStats] =
+ setupFirstCollection(kTestPeriodicCollectionBufferSize, !kTestIsSmapsRollupSupported);
+
+ ResourceStats actualResourceStats = {};
+ ASSERT_RESULT_OK(mCollector->onPeriodicCollection(getNowMillis(), SystemState::NORMAL_MODE,
+ mMockUidStatsCollector,
+ mMockProcStatCollector,
+ &actualResourceStats));
+
+ const auto actualCollectionInfo = mCollectorPeer->getPeriodicCollectionInfo();
+
+ EXPECT_THAT(actualCollectionInfo, CollectionInfoEq(expectedCollectionInfo))
+ << "When smaps rollup is not supported, periodic collection info doesn't match."
+ << "\nExpected:\n"
+ << expectedCollectionInfo.toString() << "\nActual:\n"
+ << actualCollectionInfo.toString();
+
+ ASSERT_EQ(actualResourceStats, expectedResourceStats)
+ << "Expected: " << expectedResourceStats.toString()
+ << "\nActual: " << actualResourceStats.toString();
+
+ ASSERT_NO_FATAL_FAILURE(checkDumpContents(/*wantedEmptyCollectionInstances=*/3))
+ << "Boot-time, wake-up and user-switch collections shouldn't be reported";
+}
+
} // namespace watchdog
} // namespace automotive
} // namespace android
diff --git a/cpp/watchdog/server/tests/PressureMonitorTest.cpp b/cpp/watchdog/server/tests/PressureMonitorTest.cpp
new file mode 100644
index 0000000..190c4e7
--- /dev/null
+++ b/cpp/watchdog/server/tests/PressureMonitorTest.cpp
@@ -0,0 +1,564 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MockPressureChangeCallback.h"
+#include "PressureMonitor.h"
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <gmock/gmock.h>
+#include <log/log.h>
+
+#include <sys/epoll.h>
+
+#include <condition_variable> // NOLINT(build/c++11)
+#include <mutex> // NOLINT(build/c++11)
+#include <queue>
+
+namespace android {
+namespace automotive {
+namespace watchdog {
+
+namespace {
+
+using ::android::sp;
+using ::android::base::StringAppendF;
+using ::android::base::StringPrintf;
+using ::android::base::WriteStringToFile;
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::InSequence;
+using ::testing::Matcher;
+using ::testing::MockFunction;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::Test;
+using ::testing::UnorderedElementsAreArray;
+
+constexpr const char kSamplePsiData[] = "some avg10=0.00 avg60=0.00 avg300=0.00 total=51013728\n"
+ "full avg10=0.00 avg60=0.00 avg300=0.00 total=25154435";
+constexpr std::chrono::milliseconds kTestPollingIntervalMillis = 100ms;
+constexpr std::chrono::milliseconds kMaxWaitForResponsesConsumed = 5s;
+
+enum PsiMonitorState {
+ INITIALIZED = 0,
+ REGISTERED,
+ UNREGISTERED,
+ DESTROYED,
+ STATE_COUNT,
+};
+
+struct PsiMonitorInfo {
+ const psi_stall_type kStallType;
+ const int kThresholdUs;
+ const int kWindowUs;
+ epoll_data_t epollData;
+ PsiMonitorState state;
+
+ std::string toString() const {
+ std::string buffer;
+ StringAppendF(&buffer,
+ "PsiMonitorInfo{kStallType = %d, kThresholdUs = %d, kWindowUs = %d, "
+ "epollData = %d, state = %d}",
+ kStallType, kThresholdUs, kWindowUs, epollData.u32, state);
+ return buffer;
+ }
+};
+
+std::string toString(const std::vector<PsiMonitorInfo>& psiMonitorInfos) {
+ std::string buffer;
+ for (const PsiMonitorInfo& info : psiMonitorInfos) {
+ StringAppendF(&buffer, "%s\n", info.toString().c_str());
+ }
+ return buffer;
+}
+
+MATCHER_P(EpollDataEq, expected, "") {
+ return ExplainMatchResult(Field("u32", &epoll_data::u32, Eq(expected.u32)), arg,
+ result_listener);
+}
+
+MATCHER_P(PsiMonitorInfoEq, expected, "") {
+ return ExplainMatchResult(AllOf(Field("kStallType", &PsiMonitorInfo::kStallType,
+ Eq(expected.kStallType)),
+ Field("kThresholdUs", &PsiMonitorInfo::kThresholdUs,
+ Eq(expected.kThresholdUs)),
+ Field("kWindowUs", &PsiMonitorInfo::kWindowUs,
+ Eq(expected.kWindowUs)),
+ Field("epollData.u32", &PsiMonitorInfo::epollData,
+ EpollDataEq(expected.epollData)),
+ Field("state", &PsiMonitorInfo::state, Eq(expected.state))),
+ arg, result_listener);
+}
+
+// TODO(b/335508921): Once stats are read from system properties, mock the system property APIs to
+// pass this value.
+const std::vector<PsiMonitorInfo> kDefaultPsiMonitorInfos =
+ {PsiMonitorInfo{
+ .kStallType = kLowPsiStallLevel,
+ .kThresholdUs = kLowThresholdUs.count(),
+ .kWindowUs = kPsiWindowSizeUs.count(),
+ .epollData.u32 = PressureMonitor::PRESSURE_LEVEL_LOW,
+ .state = REGISTERED,
+ },
+ PsiMonitorInfo{
+ .kStallType = kMediumPsiStallLevel,
+ .kThresholdUs = kMediumThresholdUs.count(),
+ .kWindowUs = kPsiWindowSizeUs.count(),
+ .epollData.u32 = PressureMonitor::PRESSURE_LEVEL_MEDIUM,
+ .state = REGISTERED,
+ },
+ PsiMonitorInfo{
+ .kStallType = kHighPsiStallLevel,
+ .kThresholdUs = kHighThresholdUs.count(),
+ .kWindowUs = kPsiWindowSizeUs.count(),
+ .epollData.u32 = PressureMonitor::PRESSURE_LEVEL_HIGH,
+ .state = REGISTERED,
+ }};
+
+std::vector<Matcher<const PsiMonitorInfo&>> getPsiMonitorInfoMatchers(
+ const std::vector<PsiMonitorInfo>& psiMonitorInfos) {
+ std::vector<Matcher<const PsiMonitorInfo&>> psiMonitorInfoMatchers;
+ for (const auto& psiMonitorInfo : psiMonitorInfos) {
+ psiMonitorInfoMatchers.push_back(PsiMonitorInfoEq(psiMonitorInfo));
+ }
+ return psiMonitorInfoMatchers;
+}
+
+} // namespace
+
+class PressureMonitorTest : public Test {
+protected:
+ enum EpollResponse {
+ EVENT_TRIGGERED = 0,
+ TIMEOUT,
+ EPOLL_ERROR,
+ EPOLL_HUP,
+ };
+
+ struct EpollResponseInfo {
+ EpollResponse response = EVENT_TRIGGERED;
+ PressureMonitorInterface::PressureLevel highestPressureLevel =
+ PressureMonitor::PRESSURE_LEVEL_NONE;
+ };
+
+ void SetUp() override {
+ mTempProcPressureDir = std::make_unique<TemporaryDir>();
+ createPressureFiles();
+ mInitPsiMonitorMockFunc = std::make_unique<
+ MockFunction<int(enum psi_stall_type, int, int, enum psi_resource)>>();
+ mRegisterPsiMonitorMockFunc = std::make_unique<MockFunction<int(int, int, void*)>>();
+ mUnregisterPsiMonitorMockFunc = std::make_unique<MockFunction<int(int, int)>>();
+ mDestroyPsiMonitorMockFunc = std::make_unique<MockFunction<void(int)>>();
+ mEpollWaitMockFunc = std::make_unique<MockFunction<int(int, epoll_event*, int, int)>>();
+ mMockPressureChangeCallback = sp<MockPressureChangeCallback>::make();
+ mPressureMonitor =
+ sp<PressureMonitor>::make(mTempProcPressureDir->path, kTestPollingIntervalMillis,
+ mInitPsiMonitorMockFunc->AsStdFunction(),
+ mRegisterPsiMonitorMockFunc->AsStdFunction(),
+ mUnregisterPsiMonitorMockFunc->AsStdFunction(),
+ mDestroyPsiMonitorMockFunc->AsStdFunction(),
+ mEpollWaitMockFunc->AsStdFunction());
+ ASSERT_TRUE(
+ mPressureMonitor->registerPressureChangeCallback(mMockPressureChangeCallback).ok())
+ << "Failed to register pressure change callback";
+ MockPsiApis();
+ }
+
+ void TearDown() override {
+ mPressureMonitor->unregisterPressureChangeCallback(mMockPressureChangeCallback);
+ mPressureMonitor->terminate();
+ mTempProcPressureDir.reset();
+ mInitPsiMonitorMockFunc.reset();
+ mRegisterPsiMonitorMockFunc.reset();
+ mUnregisterPsiMonitorMockFunc.reset();
+ mDestroyPsiMonitorMockFunc.reset();
+ mMockPressureChangeCallback.clear();
+ mPressureMonitor.clear();
+ mCachedPsiMonitorInfos.clear();
+ }
+
+ void createPressureFiles() {
+ std::string path = StringPrintf("%s/%s", mTempProcPressureDir->path, kMemoryFile);
+ ASSERT_TRUE(WriteStringToFile(kSamplePsiData, path))
+ << "Failed to write memory psi data to file '" << path << "'";
+ }
+
+ void MockPsiApis() {
+ // Note: For failure case, mock individual calls and return error.
+ ON_CALL(*mInitPsiMonitorMockFunc, Call(_, _, _, PSI_MEMORY))
+ .WillByDefault([this](enum psi_stall_type stallType, int thresholdUs, int windowUs,
+ [[maybe_unused]] enum psi_resource _) -> int {
+ mCachedPsiMonitorInfos.push_back(PsiMonitorInfo{
+ .kStallType = stallType,
+ .kThresholdUs = thresholdUs,
+ .kWindowUs = windowUs,
+ .state = INITIALIZED,
+ });
+ // Return the index in mCachedPsiMonitorInfos as the FD.
+ return mCachedPsiMonitorInfos.size() - 1;
+ });
+
+ ON_CALL(*mRegisterPsiMonitorMockFunc, Call(_, _, NotNull()))
+ .WillByDefault([this](int epollFd, int fd, void* pressureLevel) -> int {
+ // mInitPsiMonitorMockFunc returns an index in mCachedPsiMonitorInfos as the
+ // FD.
+ if (fd < 0 || fd >= static_cast<int>(mCachedPsiMonitorInfos.size())) {
+ ALOGE("Failing register_psi_monitor call: FD is out of bounds");
+ return -1;
+ }
+ mCachedPsiMonitorInfos[fd].epollData.ptr = pressureLevel;
+ mCachedPsiMonitorInfos[fd].state = REGISTERED;
+ mEpollFds.insert(epollFd);
+ return 0;
+ });
+
+ ON_CALL(*mUnregisterPsiMonitorMockFunc, Call(_, _))
+ .WillByDefault([this](int epollFd, int fd) -> int {
+ if (mEpollFds.empty() || mCachedPsiMonitorInfos.empty()) {
+ ALOGE("Failing unregister_psi_monitor call: No monitors are registered");
+ return -1;
+ }
+ // mInitPsiMonitorMockFunc returns an index in mCachedPsiMonitorInfos as the
+ // FD.
+ if (fd < 0 || fd >= static_cast<int>(mCachedPsiMonitorInfos.size())) {
+ ALOGE("Failing unregister_psi_monitor call: FD is out of bounds");
+ return -1;
+ }
+ // mEpollFds should contain only one unique FD.
+ if (mEpollFds.find(epollFd) == mEpollFds.end()) {
+ ALOGE("Failing unregister_psi_monitor call: Received epoll FD %d doesn't "
+ "match %d",
+ epollFd, *mEpollFds.begin());
+ return -1;
+ }
+ if (mCachedPsiMonitorInfos[fd].state != REGISTERED) {
+ ALOGE("Failing unregister_psi_monitor call: FD is not in registered state");
+ return -1;
+ }
+ mCachedPsiMonitorInfos[fd].epollData.ptr = nullptr;
+ mCachedPsiMonitorInfos[fd].state = UNREGISTERED;
+ return 0;
+ });
+
+ ON_CALL(*mDestroyPsiMonitorMockFunc, Call(_)).WillByDefault([this](int fd) -> void {
+ // mInitPsiMonitorMockFunc returns an index in mCachedPsiMonitorInfos as the FD.
+ if (fd < 0 || fd >= static_cast<int>(mCachedPsiMonitorInfos.size())) {
+ ALOGE("Failing destroy_psi_monitor call: FD is out of bounds");
+ return;
+ }
+ if (mCachedPsiMonitorInfos[fd].epollData.ptr != nullptr) {
+ ALOGE("Failing destroy_psi_monitor call: epoll data is not null");
+ return;
+ }
+ mCachedPsiMonitorInfos[fd].state = DESTROYED;
+ // Do not erase the entry from mCachedPsiMonitorInfos. Otherwise, indexing based on fd
+ // won't work for following entries.
+ });
+
+ ON_CALL(*mEpollWaitMockFunc, Call(_, _, _, _))
+ .WillByDefault([this](int epollFd, epoll_event* events, int maxEvents,
+ int timeout) -> int {
+ if (mEpollFds.find(epollFd) == mEpollFds.end()) {
+ ALOGE("Failing epoll_wait: Invalid epoll fd received");
+ return -1;
+ }
+ if (events == nullptr ||
+ maxEvents != static_cast<int>(mCachedPsiMonitorInfos.size())) {
+ ALOGE("Failing epoll_wait: Null events or incorrect maxEvents received");
+ return -1;
+ }
+ if (mEpollResponses.empty()) {
+ return 0;
+ }
+ EpollResponseInfo responseInfo = mEpollResponses.front();
+ mEpollResponses.pop();
+
+ if (responseInfo.response == EPOLL_ERROR ||
+ responseInfo.response == EPOLL_HUP) {
+ events[0].events =
+ responseInfo.response == EPOLL_ERROR ? EPOLLERR : EPOLLHUP;
+ std::unique_lock lock(mMutex);
+ mPollCondition.notify_all();
+ return 1;
+ }
+
+ if (responseInfo.response == TIMEOUT) {
+ if (timeout == -1) {
+ ALOGE("Failing epoll_wait: Cannot timeout on indefinite wait");
+ std::unique_lock lock(mMutex);
+ mPollCondition.notify_all();
+ return -1;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(timeout));
+ }
+ int totalEvents = 0;
+ for (const auto& info : mCachedPsiMonitorInfos) {
+ if (info.epollData.u32 <= responseInfo.highestPressureLevel) {
+ events[totalEvents].events = 0;
+ events[totalEvents++].data.u32 = info.epollData.u32;
+ }
+ }
+ std::unique_lock lock(mMutex);
+ mPollCondition.notify_all();
+ return totalEvents;
+ });
+ }
+
+ void queueResponses(const std::vector<EpollResponseInfo>& responses) {
+ std::unique_lock lock(mMutex);
+ for (const auto& response : responses) {
+ mEpollResponses.push(response);
+ }
+ }
+
+ void waitUntilResponsesConsumed() {
+ std::unique_lock lock(mMutex);
+ mPollCondition.wait_for(lock, kMaxWaitForResponsesConsumed,
+ [this]() { return mEpollResponses.empty(); });
+ // Wait for additional polling interval duration before returning to ensure that any
+ // notification message posted at the end of the lopper queue is processed before the test
+ // ends.
+ std::this_thread::sleep_for(std::chrono::milliseconds(kTestPollingIntervalMillis));
+ }
+
+protected:
+ std::unique_ptr<TemporaryDir> mTempProcPressureDir;
+ std::unique_ptr<MockFunction<int(enum psi_stall_type, int, int, enum psi_resource)>>
+ mInitPsiMonitorMockFunc;
+ std::unique_ptr<MockFunction<int(int, int, void*)>> mRegisterPsiMonitorMockFunc;
+ std::unique_ptr<MockFunction<int(int, int)>> mUnregisterPsiMonitorMockFunc;
+ std::unique_ptr<MockFunction<void(int)>> mDestroyPsiMonitorMockFunc;
+ std::unique_ptr<MockFunction<int(int, epoll_event*, int, int)>> mEpollWaitMockFunc;
+ sp<MockPressureChangeCallback> mMockPressureChangeCallback;
+
+ sp<PressureMonitor> mPressureMonitor;
+ std::unordered_set<int> mEpollFds;
+
+ std::vector<PsiMonitorInfo> mCachedPsiMonitorInfos;
+
+private:
+ mutable std::mutex mMutex;
+ std::condition_variable mPollCondition GUARDED_BY(mMutex);
+ std::queue<EpollResponseInfo> mEpollResponses GUARDED_BY(mMutex);
+};
+
+TEST_F(PressureMonitorTest, TestInitializeAndTerminate) {
+ auto result = mPressureMonitor->init();
+ ASSERT_TRUE(result.ok()) << "Initialize pressure monitor. Result: " << result.error();
+
+ std::vector<PsiMonitorInfo> expectedPsiMonitorInfos = kDefaultPsiMonitorInfos;
+ EXPECT_THAT(mCachedPsiMonitorInfos,
+ UnorderedElementsAreArray(getPsiMonitorInfoMatchers(expectedPsiMonitorInfos)))
+ << "PSI monitors after initialization.\nExpected:\n"
+ << toString(expectedPsiMonitorInfos) << "Actual:\n"
+ << toString(mCachedPsiMonitorInfos);
+
+ mPressureMonitor->terminate();
+
+ for (auto& info : expectedPsiMonitorInfos) {
+ info.epollData.ptr = nullptr;
+ info.state = DESTROYED;
+ }
+
+ EXPECT_THAT(mCachedPsiMonitorInfos,
+ UnorderedElementsAreArray(getPsiMonitorInfoMatchers(expectedPsiMonitorInfos)))
+ << "PSI monitors after termination.\nExpected:\n"
+ << toString(expectedPsiMonitorInfos) << "Actual:\n"
+ << toString(mCachedPsiMonitorInfos);
+}
+
+TEST_F(PressureMonitorTest, TestFailInitPsiMonitor) {
+ ON_CALL(*mInitPsiMonitorMockFunc,
+ Call(kHighPsiStallLevel, kHighThresholdUs.count(), kPsiWindowSizeUs.count(),
+ PSI_MEMORY))
+ .WillByDefault(Return(-1));
+
+ auto result = mPressureMonitor->init();
+ ASSERT_FALSE(result.ok()) << "Initialization should fail on error";
+
+ std::vector<PsiMonitorInfo> expectedPsiMonitorInfos{kDefaultPsiMonitorInfos[0],
+ kDefaultPsiMonitorInfos[1]};
+
+ for (auto& info : expectedPsiMonitorInfos) {
+ info.epollData.ptr = nullptr;
+ info.state = DESTROYED;
+ }
+
+ EXPECT_THAT(mCachedPsiMonitorInfos,
+ UnorderedElementsAreArray(getPsiMonitorInfoMatchers(expectedPsiMonitorInfos)))
+ << "PSI monitors after initialization failure.\nExpected:\n"
+ << toString(expectedPsiMonitorInfos) << "Actual:\n"
+ << toString(mCachedPsiMonitorInfos);
+
+ ASSERT_FALSE(mPressureMonitor->start().ok())
+ << "Should fail to start pressure monitor when the initialization has failed";
+
+ ASSERT_FALSE(mPressureMonitor->isMonitorActive())
+ << "Pressure monitor should be inactive when the initialization has failed";
+}
+
+TEST_F(PressureMonitorTest, TestFailRegisterPsiMonitor) {
+ ON_CALL(*mRegisterPsiMonitorMockFunc,
+ Call(_, _, reinterpret_cast<void*>(PressureMonitor::PRESSURE_LEVEL_HIGH)))
+ .WillByDefault(Return(-1));
+
+ auto result = mPressureMonitor->init();
+ ASSERT_FALSE(result.ok()) << "Initialization should fail on error";
+
+ std::vector<PsiMonitorInfo> expectedPsiMonitorInfos = kDefaultPsiMonitorInfos;
+ for (auto& info : expectedPsiMonitorInfos) {
+ info.epollData.ptr = nullptr;
+ info.state = DESTROYED;
+ }
+
+ EXPECT_THAT(mCachedPsiMonitorInfos,
+ UnorderedElementsAreArray(getPsiMonitorInfoMatchers(expectedPsiMonitorInfos)))
+ << "PSI monitors after registration failure.\nExpected:\n"
+ << toString(expectedPsiMonitorInfos) << "Actual:\n"
+ << toString(mCachedPsiMonitorInfos);
+
+ ASSERT_FALSE(mPressureMonitor->start().ok())
+ << "Should fail to start pressure monitor when the initialization has failed";
+
+ ASSERT_FALSE(mPressureMonitor->isMonitorActive())
+ << "Pressure monitor should be inactive when the initialization has failed";
+}
+
+TEST_F(PressureMonitorTest, TestFailToStartMonitorTwice) {
+ auto result = mPressureMonitor->init();
+ ASSERT_TRUE(result.ok()) << "Initialize pressure monitor. Result: " << result.error();
+
+ result = mPressureMonitor->start();
+ ASSERT_TRUE(result.ok()) << "Failed to start pressure monitor thread. Result: "
+ << result.error();
+
+ ASSERT_TRUE(mPressureMonitor->isMonitorActive());
+
+ result = mPressureMonitor->start();
+ ASSERT_FALSE(result.ok()) << "Shouldn't start pressure monitor more than once. Result: "
+ << result.error();
+}
+
+TEST_F(PressureMonitorTest, TestPressureEvents) {
+ auto result = mPressureMonitor->init();
+ ASSERT_TRUE(result.ok()) << "Initialize pressure monitor. Result: " << result.error();
+
+ {
+ InSequence sequence;
+ EXPECT_CALL(*mMockPressureChangeCallback,
+ onPressureChanged(PressureMonitor::PRESSURE_LEVEL_MEDIUM))
+ .Times(1);
+ EXPECT_CALL(*mMockPressureChangeCallback,
+ onPressureChanged(PressureMonitor::PRESSURE_LEVEL_LOW))
+ .Times(1);
+ EXPECT_CALL(*mMockPressureChangeCallback,
+ onPressureChanged(PressureMonitor::PRESSURE_LEVEL_HIGH))
+ .Times(1);
+ }
+ EXPECT_CALL(*mMockPressureChangeCallback,
+ onPressureChanged(PressureMonitor::PRESSURE_LEVEL_NONE))
+ .Times(AnyNumber());
+
+ queueResponses(
+ {EpollResponseInfo{.response = EVENT_TRIGGERED,
+ .highestPressureLevel = PressureMonitor::PRESSURE_LEVEL_MEDIUM},
+ EpollResponseInfo{.response = TIMEOUT,
+ .highestPressureLevel = PressureMonitor::PRESSURE_LEVEL_LOW},
+ EpollResponseInfo{.response = EVENT_TRIGGERED,
+ .highestPressureLevel = PressureMonitor::PRESSURE_LEVEL_HIGH}});
+
+ result = mPressureMonitor->start();
+ ASSERT_TRUE(result.ok()) << "Failed to start pressure monitor thread. Result: "
+ << result.error();
+
+ waitUntilResponsesConsumed();
+
+ ASSERT_TRUE(mPressureMonitor->isMonitorActive());
+}
+
+TEST_F(PressureMonitorTest, TestHighPressureEvents) {
+ auto result = mPressureMonitor->init();
+ ASSERT_TRUE(result.ok()) << "Initialize pressure monitor. Result: " << result.error();
+
+ EXPECT_CALL(*mMockPressureChangeCallback,
+ onPressureChanged(PressureMonitor::PRESSURE_LEVEL_HIGH))
+ .Times(2);
+
+ EXPECT_CALL(*mMockPressureChangeCallback,
+ onPressureChanged(PressureMonitor::PRESSURE_LEVEL_NONE))
+ .Times(AtLeast(1));
+
+ queueResponses(
+ {EpollResponseInfo{.response = EVENT_TRIGGERED,
+ .highestPressureLevel = PressureMonitor::PRESSURE_LEVEL_HIGH},
+
+ EpollResponseInfo{.response = TIMEOUT,
+ .highestPressureLevel = PressureMonitor::PRESSURE_LEVEL_NONE},
+
+ EpollResponseInfo{.response = EVENT_TRIGGERED,
+ .highestPressureLevel = PressureMonitor::PRESSURE_LEVEL_HIGH}});
+
+ result = mPressureMonitor->start();
+ ASSERT_TRUE(result.ok()) << "Failed to start pressure monitor thread. Result: "
+ << result.error();
+
+ waitUntilResponsesConsumed();
+
+ ASSERT_TRUE(mPressureMonitor->isMonitorActive());
+}
+
+TEST_F(PressureMonitorTest, TestFailEpollError) {
+ auto result = mPressureMonitor->init();
+ ASSERT_TRUE(result.ok()) << "Initialize pressure monitor. Result: " << result.error();
+
+ EXPECT_CALL(*mMockPressureChangeCallback, onPressureChanged(_)).Times(0);
+
+ queueResponses({EpollResponseInfo{.response = EPOLL_ERROR}});
+
+ result = mPressureMonitor->start();
+ ASSERT_TRUE(result.ok()) << "Failed to start pressure monitor thread. Result: "
+ << result.error();
+
+ waitUntilResponsesConsumed();
+
+ ASSERT_FALSE(mPressureMonitor->isMonitorActive()) << "Monitor should stop on epoll error";
+}
+
+TEST_F(PressureMonitorTest, TestFailEpollHup) {
+ auto result = mPressureMonitor->init();
+ ASSERT_TRUE(result.ok()) << "Initialize pressure monitor. Result: " << result.error();
+
+ EXPECT_CALL(*mMockPressureChangeCallback, onPressureChanged(_)).Times(0);
+
+ queueResponses({EpollResponseInfo{.response = EPOLL_HUP}});
+
+ result = mPressureMonitor->start();
+ ASSERT_TRUE(result.ok()) << "Failed to start pressure monitor thread. Result: "
+ << result.error();
+
+ waitUntilResponsesConsumed();
+
+ ASSERT_FALSE(mPressureMonitor->isMonitorActive()) << "Monitor should stop on epoll hang up";
+}
+
+} // namespace watchdog
+} // namespace automotive
+} // namespace android
diff --git a/cpp/watchdog/server/tests/ProcPidDir.cpp b/cpp/watchdog/server/tests/ProcPidDir.cpp
index faba2c5..7882fc3 100644
--- a/cpp/watchdog/server/tests/ProcPidDir.cpp
+++ b/cpp/watchdog/server/tests/ProcPidDir.cpp
@@ -47,6 +47,8 @@
const std::unordered_map<pid_t, std::vector<pid_t>>& pidToTids,
const std::unordered_map<pid_t, std::string>& processStat,
const std::unordered_map<pid_t, std::string>& processStatus,
+ const std::unordered_map<pid_t, std::string>& processSmapsRollup,
+ const std::unordered_map<pid_t, std::string>& processStatm,
const std::unordered_map<pid_t, std::string>& threadStat,
const std::unordered_map<pid_t, std::string>& threadTimeInState) {
for (const auto& it : pidToTids) {
@@ -74,13 +76,29 @@
}
}
- // 4. Create /proc/PID/task dir.
+ // 4. Create /proc/PID/smaps_rollup file.
+ if (processSmapsRollup.find(pid) != processSmapsRollup.end()) {
+ std::string path = StringPrintf((procDirPath + kSmapsRollupFileFormat).c_str(), pid);
+ if (!WriteStringToFile(processSmapsRollup.at(pid), path)) {
+ return Error() << "Failed to write pid smaps_rollup file " << path;
+ }
+ }
+
+ // 5. Create /proc/PID/statm file.
+ if (processStatm.find(pid) != processStatm.end()) {
+ std::string path = StringPrintf((procDirPath + kStatmFileFormat).c_str(), pid);
+ if (!WriteStringToFile(processStatm.at(pid), path)) {
+ return Error() << "Failed to write pid statm file " << path;
+ }
+ }
+
+ // 6. Create /proc/PID/task dir.
const auto& taskDirRes = makeDir(StringPrintf((procDirPath + kTaskDirFormat).c_str(), pid));
if (!taskDirRes.ok()) {
return Error() << "Failed to create task directory: " << taskDirRes.error();
}
- // 5. Create /proc/PID/task/TID dirs, /proc/PID/task/TID/stat and
+ // 7. Create /proc/PID/task/TID dirs, /proc/PID/task/TID/stat and
// /proc/PID/task/TID/time_in_state files.
for (const auto& tid : it.second) {
const auto& tidDirRes = makeDir(
@@ -98,7 +116,8 @@
}
if (threadTimeInState.find(tid) != threadTimeInState.end()) {
std::string path =
- StringPrintf((procDirPath + kTaskDirFormat + kTimeInStateFormat).c_str(),
+ StringPrintf((procDirPath + kTaskDirFormat + kTimeInStateFileFormat)
+ .c_str(),
pid, tid);
if (!WriteStringToFile(threadTimeInState.at(tid), path)) {
return Error() << "Failed to write thread time_in_state file " << path;
diff --git a/cpp/watchdog/server/tests/ProcPidDir.h b/cpp/watchdog/server/tests/ProcPidDir.h
index f314a6c..5d0d5b6 100644
--- a/cpp/watchdog/server/tests/ProcPidDir.h
+++ b/cpp/watchdog/server/tests/ProcPidDir.h
@@ -34,6 +34,8 @@
const std::unordered_map<pid_t, std::vector<pid_t>>& pidToTids,
const std::unordered_map<pid_t, std::string>& processStat,
const std::unordered_map<pid_t, std::string>& processStatus,
+ const std::unordered_map<pid_t, std::string>& processSmapsRollup,
+ const std::unordered_map<pid_t, std::string>& processStatm,
const std::unordered_map<pid_t, std::string>& threadStat,
const std::unordered_map<pid_t, std::string>& threadTimeInState);
diff --git a/cpp/watchdog/server/tests/UidProcStatsCollectorTest.cpp b/cpp/watchdog/server/tests/UidProcStatsCollectorTest.cpp
index d9713e4..f32e6593 100644
--- a/cpp/watchdog/server/tests/UidProcStatsCollectorTest.cpp
+++ b/cpp/watchdog/server/tests/UidProcStatsCollectorTest.cpp
@@ -22,6 +22,7 @@
#include <android-base/stringprintf.h>
#include <gmock/gmock.h>
+#include <android_car_feature.h>
#include <inttypes.h>
#include <algorithm>
@@ -34,10 +35,13 @@
using ::android::automotive::watchdog::testing::populateProcPidDir;
using ::android::base::StringAppendF;
using ::android::base::StringPrintf;
+using ::android::car::feature::car_watchdog_memory_profiling;
using ::testing::UnorderedPointwise;
namespace {
+constexpr uint64_t kTestPrivateDirtyKb = 100;
+
MATCHER(UidProcStatsByUidEq, "") {
const auto& actual = std::get<0>(arg);
const auto& expected = std::get<1>(arg);
@@ -50,6 +54,41 @@
uid);
}
+std::string smapsRollupStr(uint64_t rssKb, uint64_t pssKb, uint64_t ussKb, uint64_t swapPssKb) {
+ std::string buffer;
+ StringAppendF(&buffer,
+ "5592470000-7ffc9a9000 ---p 00000000 00:00 0 "
+ "[rollup]\n");
+ // clang-format off
+ StringAppendF(&buffer, "Rss: %" PRIu64 " kB\n", rssKb);
+ StringAppendF(&buffer, "Pss: %" PRIu64 " kB\n", pssKb);
+ StringAppendF(&buffer, "Pss_Anon: 1628 kB\n"
+ "Pss_File: 360 kB\n"
+ "Pss_Shmem: 303 kB\n"
+ "Shared_Clean: 2344 kB\n"
+ "Shared_Dirty: 688 kB\n");
+ /**
+ * Private_Dirty is 100 KB and ussKB = Private_Dirty + Private_Clean. So, ensure that ussKb
+ * is at least 100 KB before proceeding.
+ */
+ EXPECT_GT(ussKb, kTestPrivateDirtyKb);
+ StringAppendF(&buffer, "Private_Clean: %" PRIu64 " kB\n", ussKb - kTestPrivateDirtyKb);
+ StringAppendF(&buffer, "Private_Dirty: %" PRIu64 " kB\n", kTestPrivateDirtyKb);
+ StringAppendF(&buffer, "Referenced: 4908 kB\n"
+ "Anonymous: 1628 kB\n"
+ "LazyFree: 0 kB\n"
+ "AnonHugePages: 0 kB\n"
+ "ShmemPmdMapped: 0 kB\n"
+ "FilePmdMapped: 0 kB\n"
+ "Shared_Hugetlb: 0 kB\n"
+ "Private_Hugetlb: 0 kB\n"
+ "Swap: 5860 kB\n");
+ StringAppendF(&buffer, "SwapPss: %" PRIu64 " kB\n", swapPssKb);
+ StringAppendF(&buffer, "Locked: 0 kB");
+ // clang-format on
+ return buffer;
+}
+
std::string toString(const std::unordered_map<uid_t, UidProcStats>& uidProcStatsByUid) {
std::string buffer;
StringAppendF(&buffer, "Number of UIDs: %" PRIi32 "\n",
@@ -64,6 +103,27 @@
return (clockTicks * 1000) / sysconf(_SC_CLK_TCK);
}
+void applyFeatureFilter(std::unordered_map<uid_t, UidProcStats>* uidProcStatsByUid) {
+ if (car_watchdog_memory_profiling()) {
+ return;
+ }
+ for (auto& [uid, uidProcStats] : *uidProcStatsByUid) {
+ uidProcStats.totalRssKb = 0;
+ uidProcStats.totalPssKb = 0;
+ for (auto& [pid, processStats] : uidProcStats.processStatsByPid) {
+ processStats.rssKb = 0;
+ processStats.pssKb = 0;
+ processStats.ussKb = 0;
+ processStats.swapPssKb = 0;
+ }
+ }
+}
+
+bool isSmapsRollupSupported(std::string rootPath) {
+ std::string path = StringPrintf((rootPath + kSmapsRollupFileFormat).c_str(), 1);
+ return access(path.c_str(), R_OK) == 0;
+}
+
} // namespace
TEST(UidProcStatsCollectorTest, TestValidStatFiles) {
@@ -82,6 +142,12 @@
{1000, pidStatusStr(1000, 10001234)},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ {1000,
+ smapsRollupStr(/*rssKb=*/2000, /*pssKb=*/1635, /*ussKb=*/1286, /*swapPssKb=*/600)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 3 2 0 0 0 0 2 0 19\n"},
{453, "453 (init) D 0 0 0 0 0 0 0 0 20 0 3 2 0 0 0 0 2 0 275\n"},
@@ -100,41 +166,50 @@
std::unordered_map<uid_t, UidProcStats> expected =
{{0,
UidProcStats{.cpuTimeMillis = ticksToMillis(10),
- .cpuCycles = 105000000,
+ .cpuCycles = 105'000'000,
.totalMajorFaults = 220,
.totalTasksCount = 2,
.ioBlockedTasksCount = 1,
- .processStatsByPid = {{1,
- {"init",
- ticksToMillis(19),
- ticksToMillis(10),
- 105000000,
- 220,
- 2,
- 1,
- {{1, 15000000}, {453, 90000000}}}}}}},
+ .totalRssKb = 1000,
+ .totalPssKb = 865,
+ .processStatsByPid =
+ {{1,
+ {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
+ /*cpuTimeMillis=*/ticksToMillis(10),
+ /*totalCpuCycles=*/105'000'000, /*totalMajorFaults=*/220,
+ /*totalTasksCount=*/2, /*ioBlockedTasksCount=*/1,
+ /*cpuCyclesByTid=*/{{1, 15'000'000}, {453, 90'000'000}},
+ /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
+ /*swapPssKb=*/200}}}}},
{10001234,
UidProcStats{.cpuTimeMillis = ticksToMillis(12'000),
.cpuCycles = 216100000000,
.totalMajorFaults = 600,
.totalTasksCount = 2,
.ioBlockedTasksCount = 2,
- .processStatsByPid = {
- {1000,
- {"system_server",
- ticksToMillis(13'400),
- ticksToMillis(12'000),
- 216100000000,
- 600,
- 2,
- 2,
- {{1000, 198100000000}, {1100, 18000000000}}}}}}}};
+ .totalRssKb = 2000,
+ .totalPssKb = 1635,
+ .processStatsByPid = {{1000,
+ {/*comm=*/"system_server",
+ /*startTimeMillis=*/ticksToMillis(13'400),
+ /*cpuTimeMillis=*/ticksToMillis(12'000),
+ /*totalCpuCycles=*/216100000000,
+ /*totalMajorFaults=*/600,
+ /*totalTasksCount=*/2,
+ /*ioBlockedTasksCount=*/2, /*cpuCyclesByTid=*/
+ {{1000, 198100000000}, {1100, 18000000000}},
+ /*rssKb=*/2000,
+ /*pssKb=*/1635,
+ /*ussKb=*/1286,
+ /*swapPssKb=*/600}}}}}};
+ applyFeatureFilter(&expected);
TemporaryDir firstSnapshot;
ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat,
- perProcessStatus, perThreadStat, perThreadTimeInState));
+ perProcessStatus, perProcessSmapsRollup,
+ /*processStatm=*/{}, perThreadStat, perThreadTimeInState));
- UidProcStatsCollector collector(firstSnapshot.path);
+ UidProcStatsCollector collector(firstSnapshot.path, isSmapsRollupSupported(firstSnapshot.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -157,6 +232,12 @@
{1000, "1000 (system_server) R 1 0 0 0 0 0 0 0 1550 0 10000 8000 0 0 0 0 2 0 13400\n"},
};
+ perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/3000, /*pssKb=*/1865, /*ussKb=*/1656, /*swapPssKb=*/900)},
+ {1000,
+ smapsRollupStr(/*rssKb=*/2010, /*pssKb=*/1645, /*ussKb=*/1296, /*swapPssKb=*/610)},
+ };
+
perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 600 0 5 5 0 0 0 0 2 0 19\n"},
{453, "453 (init) S 0 0 0 0 0 0 0 0 320 0 5 5 0 0 0 0 2 0 275\n"},
@@ -180,34 +261,43 @@
.totalMajorFaults = 700,
.totalTasksCount = 2,
.ioBlockedTasksCount = 0,
+ .totalRssKb = 3000,
+ .totalPssKb = 1865,
.processStatsByPid = {{1,
- {"init",
- ticksToMillis(19),
- ticksToMillis(10),
- 200'000'000,
- 700,
- 2,
- 0,
- {{1, 200'000'000}, {453, 0}}}}}}},
+ {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
+ /*cpuTimeMillis=*/ticksToMillis(10),
+ /*totalCpuCycles=*/200'000'000, /*totalMajorFaults=*/700,
+ /*totalTasksCount=*/2, /*ioBlockedTasksCount=*/0,
+ /*cpuCyclesByTid=*/{{1, 200'000'000}, {453, 0}},
+ /*rssKb=*/3000,
+ /*pssKb=*/1865,
+ /*ussKb=*/1656,
+ /*swapPssKb=*/900}}}}},
{10001234,
{.cpuTimeMillis = ticksToMillis(6'000),
.cpuCycles = 18'000'000'000,
.totalMajorFaults = 950,
.totalTasksCount = 2,
.ioBlockedTasksCount = 0,
- .processStatsByPid = {{1000,
- {"system_server",
- ticksToMillis(13'400),
- ticksToMillis(6'000),
- 18'000'000'000,
- 950,
- 2,
- 0,
- {{1000, 0}, {1400, 18'000'000'000}}}}}}}};
+ .totalRssKb = 2010,
+ .totalPssKb = 1645,
+ .processStatsByPid = {
+ {1000,
+ {/*comm=*/"system_server", /*startTimeMillis=*/ticksToMillis(13'400),
+ /*cpuTimeMillis=*/ticksToMillis(6'000),
+ /*totalCpuCycles=*/18'000'000'000, /*totalMajorFaults=*/950,
+ /*totalTasksCount=*/2, /*ioBlockedTasksCount=*/0,
+ /*cpuCyclesByTid=*/{{1000, 0}, {1400, 18'000'000'000}},
+ /*rssKb=*/2010,
+ /*pssKb=*/1645,
+ /*ussKb=*/1296,
+ /*swapPssKb=*/610}}}}}};
+ applyFeatureFilter(&expected);
TemporaryDir secondSnapshot;
ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat,
- perProcessStatus, perThreadStat, perThreadTimeInState));
+ perProcessStatus, perProcessSmapsRollup,
+ /*processStatm=*/{}, perThreadStat, perThreadTimeInState));
collector.mPath = secondSnapshot.path;
@@ -246,6 +336,14 @@
{3000, pidStatusStr(3000, 10001234)},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ {2000,
+ smapsRollupStr(/*rssKb=*/2000, /*pssKb=*/1635, /*ussKb=*/1286, /*swapPssKb=*/600)},
+ {3000,
+ smapsRollupStr(/*rssKb=*/5642, /*pssKb=*/2312, /*ussKb=*/944, /*swapPssKb=*/500)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
// Process 2000 terminated.
@@ -265,45 +363,49 @@
.totalMajorFaults = 220,
.totalTasksCount = 1,
.ioBlockedTasksCount = 0,
- .processStatsByPid = {{1,
- {"init",
- ticksToMillis(19),
- ticksToMillis(20),
- 200'000'000,
- 220,
- 1,
- 0,
- {{1, 200'000'000}}}}}}},
+ .totalRssKb = 1000,
+ .totalPssKb = 865,
+ .processStatsByPid =
+ {{1,
+ {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
+ /*cpuTimeMillis=*/ticksToMillis(20),
+ /*totalCpuCycles=*/200'000'000, /*totalMajorFaults=*/220,
+ /*totalTasksCount=*/1, /*ioBlockedTasksCount=*/0,
+ /*cpuCyclesByTid=*/{{1, 200'000'000}},
+ /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
+ /*swapPssKb=*/200}}}}},
{10001234,
UidProcStats{.cpuTimeMillis = ticksToMillis(140),
.cpuCycles = 0,
.totalMajorFaults = 11500,
.totalTasksCount = 2,
.ioBlockedTasksCount = 0,
- .processStatsByPid = {{2000,
- {"logd",
- ticksToMillis(4567),
- ticksToMillis(60),
- 0,
- 1200,
- 1,
- 0,
- {}}},
- {3000,
- {"disk I/O",
- ticksToMillis(67890),
- ticksToMillis(80),
- 0,
- 10'300,
- 1,
- 0,
- {}}}}}}};
+ .totalRssKb = 7642,
+ .totalPssKb = 3947,
+ .processStatsByPid =
+ {{2000,
+ {/*comm=*/"logd", /*startTimeMillis=*/ticksToMillis(4567),
+ /*cpuTimeMillis=*/ticksToMillis(60), /*totalCpuCycles=*/0,
+ /*totalMajorFaults=*/1200, /*totalTasksCount=*/1,
+ /*ioBlockedTasksCount=*/0, /*cpuCyclesByTid=*/{},
+ /*rssKb=*/2000, /*pssKb=*/1635, /*ussKb=*/1286,
+ /*swapPssKb=*/600}},
+ {3000,
+ {/*comm=*/"disk I/O",
+ /*startTimeMillis=*/ticksToMillis(67890),
+ /*cpuTimeMillis=*/ticksToMillis(80), /*totalCpuCycles=*/0,
+ /*totalMajorFaults=*/10'300, /*totalTasksCount=*/1,
+ /*ioBlockedTasksCount=*/0, /*cpuCyclesByTid=*/{},
+ /*rssKb=*/5642, /*pssKb=*/2312, /*ussKb=*/944,
+ /*swapPssKb=*/500}}}}}};
+ applyFeatureFilter(&expected);
TemporaryDir procDir;
ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
- perThreadStat, perThreadTimeInState));
+ perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
+ perThreadTimeInState));
- UidProcStatsCollector collector(procDir.path);
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -336,6 +438,14 @@
{2345, pidStatusStr(2345, 10001234)},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ {1000,
+ smapsRollupStr(/*rssKb=*/2000, /*pssKb=*/1635, /*ussKb=*/1286, /*swapPssKb=*/600)},
+ {2345,
+ smapsRollupStr(/*rssKb=*/5642, /*pssKb=*/2312, /*ussKb=*/944, /*swapPssKb=*/500)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 4 0 19\n"},
{367, "367 (init) S 0 0 0 0 0 0 0 0 400 0 10 10 0 0 0 0 4 0 100\n"},
@@ -361,48 +471,59 @@
.totalMajorFaults = 1200,
.totalTasksCount = 4,
.ioBlockedTasksCount = 1,
+ .totalRssKb = 1000,
+ .totalPssKb = 865,
.processStatsByPid = {{1,
- {"init",
- ticksToMillis(19),
- ticksToMillis(80),
- 1'160'000'000,
- 1200,
- 4,
- 1,
+ {/*comm=*/"init",
+ /*startTimeMillis=*/ticksToMillis(19),
+ /*cpuTimeMillis=*/ticksToMillis(80),
+ /*totalCpuCycles=*/1'160'000'000,
+ /*totalMajorFaults=*/1200,
+ /*totalTasksCount=*/4,
+ /*ioBlockedTasksCount=*/1, /*cpuCyclesByTid=*/
{{1, 60'000'000},
{367, 340'000'000},
{453, 360'000'000},
- {589, 400'000'000}}}}}}},
+ {589, 400'000'000}},
+ /*rssKb=*/1000,
+ /*pssKb=*/865,
+ /*ussKb=*/656,
+ /*swapPssKb=*/200}}}}},
{10001234,
UidProcStats{.cpuTimeMillis = ticksToMillis(40),
.cpuCycles = 420'000'000,
.totalMajorFaults = 54'604,
.totalTasksCount = 2,
.ioBlockedTasksCount = 0,
- .processStatsByPid = {{1000,
- {"system_server",
- ticksToMillis(1000),
- ticksToMillis(20),
- 60'000'000,
- 250,
- 1,
- 0,
- {{1000, 60'000'000}}}},
- {2345,
- {"logd",
- ticksToMillis(456),
- ticksToMillis(20),
- 360'000'000,
- 54'354,
- 1,
- 0,
- {{2345, 360'000'000}}}}}}}};
+ .totalRssKb = 7642,
+ .totalPssKb = 3947,
+ .processStatsByPid =
+ {{1000,
+ {/*comm=*/"system_server",
+ /*startTimeMillis=*/ticksToMillis(1000),
+ /*cpuTimeMillis=*/ticksToMillis(20),
+ /*totalCpuCycles=*/60'000'000, /*totalMajorFaults=*/250,
+ /*totalTasksCount=*/1, /*ioBlockedTasksCount=*/0,
+ /*cpuCyclesByTid=*/{{1000, 60'000'000}},
+ /*rssKb=*/2000, /*pssKb=*/1635, /*ussKb=*/1286,
+ /*swapPssKb=*/600}},
+ {2345,
+ {/*comm=*/"logd", /*startTimeMillis=*/ticksToMillis(456),
+ /*cpuTimeMillis=*/ticksToMillis(20),
+ /*totalCpuCycles=*/360'000'000,
+ /*totalMajorFaults=*/54'354,
+ /*totalTasksCount=*/1, /*ioBlockedTasksCount=*/0,
+ /*cpuCyclesByTid=*/{{2345, 360'000'000}},
+ /*rssKb=*/5642, /*pssKb=*/2312, /*ussKb=*/944,
+ /*swapPssKb=*/500}}}}}};
+ applyFeatureFilter(&expected);
TemporaryDir firstSnapshot;
ASSERT_RESULT_OK(populateProcPidDir(firstSnapshot.path, pidToTids, perProcessStat,
- perProcessStatus, perThreadStat, perThreadTimeInState));
+ perProcessStatus, perProcessSmapsRollup,
+ /*processStatm=*/{}, perThreadStat, perThreadTimeInState));
- UidProcStatsCollector collector(firstSnapshot.path);
+ UidProcStatsCollector collector(firstSnapshot.path, isSmapsRollupSupported(firstSnapshot.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -435,6 +556,14 @@
{1000, pidStatusStr(1000, 10001234)},
};
+ perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1500, /*pssKb=*/965, /*ussKb=*/756, /*swapPssKb=*/300)},
+ {367,
+ smapsRollupStr(/*rssKb=*/2000, /*pssKb=*/1635, /*ussKb=*/1286, /*swapPssKb=*/600)},
+ {1000,
+ smapsRollupStr(/*rssKb=*/5642, /*pssKb=*/2312, /*ussKb=*/944, /*swapPssKb=*/500)},
+ };
+
perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 500 0 20 20 0 0 0 0 2 0 19\n"},
{589, "589 (init) S 0 0 0 0 0 0 0 0 300 0 10 10 0 0 0 0 2 0 2345\n"},
@@ -459,44 +588,57 @@
.totalMajorFaults = 600,
.totalTasksCount = 2,
.ioBlockedTasksCount = 0,
- .processStatsByPid = {{1,
- {"init",
- ticksToMillis(19),
- ticksToMillis(40),
- 400'000'000,
- 600,
- 2,
- 0,
- {{1, 340'000'000}, {589, 60'000'000}}}}}}},
+ .totalRssKb = 1500,
+ .totalPssKb = 965,
+ .processStatsByPid =
+ {{1,
+ {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
+ /*cpuTimeMillis=*/ticksToMillis(40),
+ /*totalCpuCycles=*/400'000'000, /*totalMajorFaults=*/600,
+ /*totalTasksCount=*/2, /*ioBlockedTasksCount=*/0,
+ /*cpuCyclesByTid=*/{{1, 340'000'000}, {589, 60'000'000}},
+ /*rssKb=*/1500, /*pssKb=*/965, /*ussKb=*/756,
+ /*swapPssKb=*/300}}}}},
{10001234,
UidProcStats{.cpuTimeMillis = ticksToMillis(100),
.cpuCycles = 1'233'000'000,
.totalMajorFaults = 2100,
.totalTasksCount = 4,
.ioBlockedTasksCount = 1,
+ .totalRssKb = 7642,
+ .totalPssKb = 3947,
.processStatsByPid = {{367,
- {"system_server",
- ticksToMillis(3450),
- ticksToMillis(60),
- 813'000'000,
- 100,
- 2,
- 0,
- {{367, 213'000'000}, {2000, 600'000'000}}}},
+ {/*comm=*/"system_server",
+ /*startTimeMillis=*/ticksToMillis(3450),
+ /*cpuTimeMillis=*/ticksToMillis(60),
+ /*totalCpuCycles=*/813'000'000,
+ /*totalMajorFaults=*/100,
+ /*totalTasksCount=*/2,
+ /*ioBlockedTasksCount=*/0, /*cpuCyclesByTid=*/
+ {{367, 213'000'000}, {2000, 600'000'000}},
+ /*rssKb=*/2000,
+ /*pssKb=*/1635,
+ /*ussKb=*/1286,
+ /*swapPssKb=*/600}},
{1000,
- {"logd",
- ticksToMillis(4650),
- ticksToMillis(40),
- 420'000'000,
- 2000,
- 2,
- 1,
- {{1000, 360'000'000},
- {453, 60'000'000}}}}}}}};
+ {/*comm=*/"logd",
+ /*startTimeMillis=*/ticksToMillis(4650),
+ /*cpuTimeMillis=*/ticksToMillis(40),
+ /*totalCpuCycles=*/420'000'000,
+ /*totalMajorFaults=*/2000,
+ /*totalTasksCount=*/2,
+ /*ioBlockedTasksCount=*/1, /*cpuCyclesByTid=*/
+ {{1000, 360'000'000}, {453, 60'000'000}},
+ /*rssKb=*/5642,
+ /*pssKb=*/2312,
+ /*ussKb=*/944,
+ /*swapPssKb=*/500}}}}}};
+ applyFeatureFilter(&expected);
TemporaryDir secondSnapshot;
ASSERT_RESULT_OK(populateProcPidDir(secondSnapshot.path, pidToTids, perProcessStat,
- perProcessStatus, perThreadStat, perThreadTimeInState));
+ perProcessStatus, perProcessSmapsRollup,
+ /*processStatm=*/{}, perThreadStat, perThreadTimeInState));
collector.mPath = secondSnapshot.path;
@@ -512,6 +654,72 @@
<< toString(actual);
}
+TEST(UidProcStatsCollectorTest, TestHandlesNoSmapsRollupKernelSupport) {
+ std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
+ {1, {1}},
+ };
+
+ std::unordered_map<pid_t, std::string> perProcessStat = {
+ {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
+ };
+
+ std::unordered_map<pid_t, std::string> perProcessStatus = {
+ {1, pidStatusStr(1, 0)},
+ };
+
+ std::unordered_map<pid_t, std::string> perProcessStatm = {
+ {1, "2969783 1481 938 530 0 5067 0"},
+ };
+
+ std::unordered_map<pid_t, std::string> perThreadStat = {
+ {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
+ };
+
+ std::unordered_map<pid_t, std::string> perThreadTimeInState = {
+ {1, "cpu0\n300000 0\n1700000 20\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
+ };
+
+ std::unordered_map<uid_t, UidProcStats> expected = {
+ {0,
+ UidProcStats{.cpuTimeMillis = ticksToMillis(20),
+ .cpuCycles = 340'000'000,
+ .totalMajorFaults = 200,
+ .totalTasksCount = 1,
+ .ioBlockedTasksCount = 0,
+ .totalRssKb = 5924,
+ .totalPssKb = 0,
+ .processStatsByPid = {
+ {1,
+ {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
+ /*cpuTimeMillis=*/ticksToMillis(20),
+ /*totalCpuCycles=*/340'000'000,
+ /*totalMajorFaults=*/200, /*totalTasksCount=*/1,
+ /*ioBlockedTasksCount=*/0,
+ /*cpuCyclesByTid=*/{{1, 340'000'000}},
+ /*rssKb=*/5924, /*pssKb=*/0, /*ussKb=*/2172,
+ /*swapPssKb=*/0}}}}}};
+ applyFeatureFilter(&expected);
+
+ TemporaryDir procDir;
+ ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
+ /*processSmapsRollup=*/{}, perProcessStatm, perThreadStat,
+ perThreadTimeInState));
+
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
+ collector.init();
+
+ ASSERT_TRUE(collector.enabled())
+ << "Files under the path `" << procDir.path << "` are inaccessible";
+ ASSERT_RESULT_OK(collector.collect());
+
+ auto actual = collector.deltaStats();
+
+ EXPECT_THAT(actual, UnorderedPointwise(UidProcStatsByUidEq(), expected))
+ << "Proc pid contents doesn't match.\nExpected:\n"
+ << toString(expected) << "\nActual:\n"
+ << toString(actual);
+}
+
TEST(UidProcStatsCollectorTest, TestErrorOnCorruptedProcessStatFile) {
std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
{1, {1}},
@@ -525,6 +733,10 @@
{1, pidStatusStr(1, 0)},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
};
@@ -535,9 +747,10 @@
TemporaryDir procDir;
ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
- perThreadStat, perThreadTimeInState));
+ perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
+ perThreadTimeInState));
- UidProcStatsCollector collector(procDir.path);
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -558,6 +771,10 @@
{1, "Pid:\t1\nTgid:\t1\nCORRUPTED DATA\n"},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
};
@@ -568,9 +785,10 @@
TemporaryDir procDir;
ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
- perThreadStat, perThreadTimeInState));
+ perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
+ perThreadTimeInState));
- UidProcStatsCollector collector(procDir.path);
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -591,6 +809,10 @@
{1, "Pid:\t1\nTgid:\t1\n"},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
};
@@ -601,9 +823,10 @@
TemporaryDir procDir;
ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
- perThreadStat, perThreadTimeInState));
+ perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
+ perThreadTimeInState));
- UidProcStatsCollector collector(procDir.path);
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -625,6 +848,10 @@
{1, "Pid:\t1\nUid:\t1\n"},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 1 0 19\n"},
};
@@ -635,9 +862,10 @@
TemporaryDir procDir;
ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
- perThreadStat, perThreadTimeInState));
+ perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
+ perThreadTimeInState));
- UidProcStatsCollector collector(procDir.path);
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -659,6 +887,10 @@
{1, pidStatusStr(1, 0)},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
{234, "234 (init) D 0 0 0 0 0 0 0 0 200 0 0 0 CORRUPTED DATA\n"},
@@ -670,9 +902,10 @@
TemporaryDir procDir;
ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
- perThreadStat, perThreadTimeInState));
+ perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
+ perThreadTimeInState));
- UidProcStatsCollector collector(procDir.path);
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -693,6 +926,10 @@
{1, pidStatusStr(1, 0)},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 678\n"},
{234, "234 (init) D 0 0 0 0 0 0 0 0 200 0 0 0 0 0 0 0 2 0 500\n"},
@@ -705,9 +942,10 @@
TemporaryDir procDir;
ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
- perThreadStat, perThreadTimeInState));
+ perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
+ perThreadTimeInState));
- UidProcStatsCollector collector(procDir.path);
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -729,6 +967,10 @@
{1, pidStatusStr(1, 0)},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1,
"1 (random process name with space) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
@@ -745,21 +987,26 @@
.totalMajorFaults = 200,
.totalTasksCount = 1,
.ioBlockedTasksCount = 0,
- .processStatsByPid = {{1,
- {"random process name with space",
- ticksToMillis(19),
- ticksToMillis(20),
- 340'000'000,
- 200,
- 1,
- 0,
- {{1, 340'000'000}}}}}}}};
+ .totalRssKb = 1000,
+ .totalPssKb = 865,
+ .processStatsByPid = {
+ {1,
+ {/*comm=*/"random process name with space",
+ /*startTimeMillis=*/ticksToMillis(19),
+ /*cpuTimeMillis=*/ticksToMillis(20),
+ /*totalCpuCycles=*/340'000'000, /*totalMajorFaults=*/200,
+ /*totalTasksCount=*/1, /*ioBlockedTasksCount=*/0,
+ /*cpuCyclesByTid=*/{{1, 340'000'000}},
+ /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
+ /*swapPssKb=*/200}}}}}};
+ applyFeatureFilter(&expected);
TemporaryDir procDir;
ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
- perThreadStat, perThreadTimeInState));
+ perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
+ perThreadTimeInState));
- UidProcStatsCollector collector(procDir.path);
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -787,6 +1034,10 @@
{1, pidStatusStr(1, 0)},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
};
@@ -800,21 +1051,25 @@
.totalMajorFaults = 200,
.totalTasksCount = 1,
.ioBlockedTasksCount = 0,
- .processStatsByPid = {{1,
- {"init",
- ticksToMillis(19),
- ticksToMillis(20),
- 0,
- 200,
- 1,
- 0,
- {}}}}}}};
+ .totalRssKb = 1000,
+ .totalPssKb = 865,
+ .processStatsByPid = {
+ {1,
+ {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
+ /*cpuTimeMillis=*/ticksToMillis(20), /*totalCpuCycles=*/0,
+ /*totalMajorFaults=*/200, /*totalTasksCount=*/1,
+ /*ioBlockedTasksCount=*/0, /*cpuCyclesByTid=*/{},
+ /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
+ /*swapPssKb=*/200}}}}}};
+
+ applyFeatureFilter(&expected);
TemporaryDir procDir;
ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
- perThreadStat, {}));
+ perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
+ {}));
- UidProcStatsCollector collector(procDir.path);
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -842,6 +1097,10 @@
{1, pidStatusStr(1, 0)},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
};
@@ -857,21 +1116,24 @@
.totalMajorFaults = 200,
.totalTasksCount = 1,
.ioBlockedTasksCount = 0,
- .processStatsByPid = {{1,
- {"init",
- ticksToMillis(19),
- ticksToMillis(20),
- 0,
- 200,
- 1,
- 0,
- {}}}}}}};
+ .totalRssKb = 1000,
+ .totalPssKb = 865,
+ .processStatsByPid = {
+ {1,
+ {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
+ /*cpuTimeMillis=*/ticksToMillis(20), /*totalCpuCycles=*/0,
+ /*totalMajorFaults=*/200, /*totalTasksCount=*/1,
+ /*ioBlockedTasksCount=*/0, /*cpuCyclesByTid=*/{},
+ /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
+ /*swapPssKb=*/200}}}}}};
+ applyFeatureFilter(&expected);
TemporaryDir procDir;
ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
- perThreadStat, {}));
+ perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
+ {}));
- UidProcStatsCollector collector(procDir.path);
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -899,6 +1161,10 @@
{1, pidStatusStr(1, 0)},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
};
@@ -914,21 +1180,24 @@
.totalMajorFaults = 200,
.totalTasksCount = 1,
.ioBlockedTasksCount = 0,
- .processStatsByPid = {{1,
- {"init",
- ticksToMillis(19),
- ticksToMillis(20),
- 0,
- 200,
- 1,
- 0,
- {}}}}}}};
+ .totalRssKb = 1000,
+ .totalPssKb = 865,
+ .processStatsByPid = {
+ {1,
+ {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
+ /*cpuTimeMillis=*/ticksToMillis(20), /*totalCpuCycles=*/0,
+ /*totalMajorFaults=*/200, /*totalTasksCount=*/1,
+ /*ioBlockedTasksCount=*/0, /*cpuCyclesByTid=*/{},
+ /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
+ /*swapPssKb=*/200}}}}}};
+ applyFeatureFilter(&expected);
TemporaryDir procDir;
ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
- perThreadStat, {}));
+ perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
+ {}));
- UidProcStatsCollector collector(procDir.path);
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -956,6 +1225,10 @@
{1, pidStatusStr(1, 0)},
};
+ std::unordered_map<pid_t, std::string> perProcessSmapsRollup = {
+ {1, smapsRollupStr(/*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656, /*swapPssKb=*/200)},
+ };
+
std::unordered_map<pid_t, std::string> perThreadStat = {
{1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 2 0 19\n"},
{234, "1 (init) S 0 0 0 0 0 0 0 0 10 0 5 5 0 0 0 0 2 0 19\n"},
@@ -973,21 +1246,25 @@
.totalMajorFaults = 210,
.totalTasksCount = 2,
.ioBlockedTasksCount = 0,
- .processStatsByPid = {{1,
- {"init",
- ticksToMillis(19),
- ticksToMillis(30),
- 340'000'000,
- 210,
- 2,
- 0,
- {{1, 340'000'000}}}}}}}};
+ .totalRssKb = 1000,
+ .totalPssKb = 865,
+ .processStatsByPid = {
+ {1,
+ {/*comm=*/"init", /*startTimeMillis=*/ticksToMillis(19),
+ /*cpuTimeMillis=*/ticksToMillis(30),
+ /*totalCpuCycles=*/340'000'000, /*totalMajorFaults=*/210,
+ /*totalTasksCount=*/2, /*ioBlockedTasksCount=*/0,
+ /*cpuCyclesByTid=*/{{1, 340'000'000}},
+ /*rssKb=*/1000, /*pssKb=*/865, /*ussKb=*/656,
+ /*swapPssKb=*/200}}}}}};
+ applyFeatureFilter(&expected);
TemporaryDir procDir;
ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
- perThreadStat, perThreadTimeInState));
+ perProcessSmapsRollup, /*processStatm=*/{}, perThreadStat,
+ perThreadTimeInState));
- UidProcStatsCollector collector(procDir.path);
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
collector.init();
ASSERT_TRUE(collector.enabled())
@@ -1002,6 +1279,41 @@
<< toString(actual);
}
+TEST(UidProcStatsCollectorTest, TestCollectorStatusOnMissingSmapsRollupAndStatmFiles) {
+ std::unordered_map<pid_t, std::vector<pid_t>> pidToTids = {
+ {1, {1}},
+ };
+
+ std::unordered_map<pid_t, std::string> perProcessStat = {
+ {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
+ };
+
+ std::unordered_map<pid_t, std::string> perProcessStatus = {
+ {1, pidStatusStr(1, 0)},
+ };
+
+ std::unordered_map<pid_t, std::string> perThreadStat = {
+ {1, "1 (init) S 0 0 0 0 0 0 0 0 200 0 10 10 0 0 0 0 1 0 19\n"},
+ };
+
+ std::unordered_map<pid_t, std::string> perThreadTimeInState = {
+ {1, "cpu0\n300000 0\n1700000 20\ncpu4\n710000 0\n1800000 0\ncpu7\n2000000 0"},
+ };
+
+ TemporaryDir procDir;
+ ASSERT_RESULT_OK(populateProcPidDir(procDir.path, pidToTids, perProcessStat, perProcessStatus,
+ /*processSmapsRollup=*/{}, /*processStatm=*/{},
+ perThreadStat, perThreadTimeInState));
+
+ UidProcStatsCollector collector(procDir.path, isSmapsRollupSupported(procDir.path));
+ collector.init();
+
+ ASSERT_EQ(!car_watchdog_memory_profiling(), collector.enabled())
+ << "Collector status when memory profiling feature is "
+ << (car_watchdog_memory_profiling() ? "enabled" : "disabled")
+ << " and per-process smaps rollup / statm are missing";
+}
+
TEST(UidProcStatsCollectorTest, TestUidProcStatsCollectorContentsFromDevice) {
UidProcStatsCollector collector;
collector.init();
diff --git a/cpp/watchdog/server/tests/UidProcStatsCollectorTestUtils.h b/cpp/watchdog/server/tests/UidProcStatsCollectorTestUtils.h
index 6abde86..ae65a81 100644
--- a/cpp/watchdog/server/tests/UidProcStatsCollectorTestUtils.h
+++ b/cpp/watchdog/server/tests/UidProcStatsCollectorTestUtils.h
@@ -43,7 +43,11 @@
::testing::Eq(expected.ioBlockedTasksCount)) &&
::testing::Value(actual.cpuCyclesByTid,
::testing::UnorderedPointwise(CpuCyclesByTidEq(),
- expected.cpuCyclesByTid));
+ expected.cpuCyclesByTid)) &&
+ ::testing::Value(actual.rssKb, ::testing::Eq(expected.rssKb)) &&
+ ::testing::Value(actual.pssKb, ::testing::Eq(expected.pssKb)) &&
+ ::testing::Value(actual.ussKb, ::testing::Eq(expected.ussKb)) &&
+ ::testing::Value(actual.swapPssKb, ::testing::Eq(expected.swapPssKb));
}
MATCHER(ProcessStatsByPidEq, "") {
@@ -61,6 +65,8 @@
::testing::Value(actual.totalTasksCount, ::testing::Eq(expected.totalTasksCount)) &&
::testing::Value(actual.ioBlockedTasksCount,
::testing::Eq(expected.ioBlockedTasksCount)) &&
+ ::testing::Value(actual.totalRssKb, ::testing::Eq(expected.totalRssKb)) &&
+ ::testing::Value(actual.totalPssKb, ::testing::Eq(expected.totalPssKb)) &&
::testing::Value(actual.processStatsByPid,
::testing::UnorderedPointwise(ProcessStatsByPidEq(),
expected.processStatsByPid));
diff --git a/cpp/watchdog/testclient/Android.bp b/cpp/watchdog/testclient/Android.bp
index ded23d2..ba19369 100644
--- a/cpp/watchdog/testclient/Android.bp
+++ b/cpp/watchdog/testclient/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/cpp/watchdog/vts/Android.bp b/cpp/watchdog/vts/Android.bp
index 9ca633b..436eee6 100644
--- a/cpp/watchdog/vts/Android.bp
+++ b/cpp/watchdog/vts/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 6248104..77d7635 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -15,6 +15,7 @@
// Privapp permission allowlist files
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -90,6 +91,13 @@
}
prebuilt_etc {
+ name: "allowed_privapp_com.android.car.documentsui",
+ sub_dir: "permissions",
+ src: "com.android.car.documentsui.xml",
+ filename_from_src: true,
+}
+
+prebuilt_etc {
name: "allowed_privapp_com.android.car.hvac",
sub_dir: "permissions",
src: "com.android.car.hvac.xml",
diff --git a/data/etc/com.android.car.bugreport.xml b/data/etc/com.android.car.bugreport.xml
index 5a671d6..a10f6d8 100644
--- a/data/etc/com.android.car.bugreport.xml
+++ b/data/etc/com.android.car.bugreport.xml
@@ -21,9 +21,10 @@
<permission name="android.permission.READ_LOGS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"/>
+ <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
<permission name="android.car.permission.CAR_DRIVING_STATE"/>
+ <permission name="android.car.permission.CAR_MONITOR_INPUT"/>
<permission name="android.car.permission.CONTROL_CAR_CLIMATE"/>
<permission name="android.car.permission.ACCESS_PRIVATE_DISPLAY_ID"/>
-
</privapp-permissions>
</permissions>
diff --git a/data/etc/com.android.car.developeroptions.xml b/data/etc/com.android.car.developeroptions.xml
index a7a7271..c9fdc4f 100644
--- a/data/etc/com.android.car.developeroptions.xml
+++ b/data/etc/com.android.car.developeroptions.xml
@@ -48,6 +48,7 @@
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.READ_SEARCH_INDEXABLES"/>
<permission name="android.permission.REBOOT"/>
+ <permission name="android.permission.SCHEDULE_EXACT_ALARM"/>
<permission name="android.permission.STATUS_BAR"/>
<permission name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"/>
<permission name="android.permission.TETHER_PRIVILEGED"/>
@@ -63,6 +64,7 @@
<permission name="android.permission.READ_DREAM_SUPPRESSION"/>
<permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" />
<permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
<permission name="android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS" />
<permission name="android.permission.SATELLITE_COMMUNICATION" />
diff --git a/data/etc/com.android.car.documentsui.xml b/data/etc/com.android.car.documentsui.xml
new file mode 100644
index 0000000..bcc05bb
--- /dev/null
+++ b/data/etc/com.android.car.documentsui.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<!-- Keep the permission list the same as com.android.document.xml -->
+<permissions>
+ <privapp-permissions package="com.android.car.documentsui">
+ <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <!-- Permissions required for reading and logging compat changes -->
+ <permission name="android.permission.LOG_COMPAT_CHANGE"/>
+ <permission name="android.permission.MODIFY_QUIET_MODE"/>
+ <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
+ <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/>
+ <!-- Permissions required for reading device configs -->
+ <permission name="android.permission.READ_DEVICE_CONFIG" />
+ </privapp-permissions>
+</permissions>
+
diff --git a/data/etc/com.android.car.media.xml b/data/etc/com.android.car.media.xml
index d17453d..7937d3a 100644
--- a/data/etc/com.android.car.media.xml
+++ b/data/etc/com.android.car.media.xml
@@ -17,5 +17,6 @@
<permissions>
<privapp-permissions package="com.android.car.media">
<permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+ <permission name="android.car.permission.CAR_DRIVING_STATE"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/com.android.car.multidisplay.controlcenter.xml b/data/etc/com.android.car.multidisplay.controlcenter.xml
index 8d8f7b9..67b72a5 100644
--- a/data/etc/com.android.car.multidisplay.controlcenter.xml
+++ b/data/etc/com.android.car.multidisplay.controlcenter.xml
@@ -30,5 +30,6 @@
<permission name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT" />
<permission name="android.permission.ADD_TRUSTED_DISPLAY"/>
<permission name="android.car.permission.CONTROL_CAR_APP_LAUNCH" />
+ <permission name="android.permission.QUERY_ALL_PACKAGES"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/com.android.car.settings.xml b/data/etc/com.android.car.settings.xml
index 8a3f310..5c308cb 100644
--- a/data/etc/com.android.car.settings.xml
+++ b/data/etc/com.android.car.settings.xml
@@ -18,6 +18,7 @@
<privapp-permissions package="com.android.car.settings">
<permission name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS"/>
<permission name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/>
+ <permission name="android.car.permission.READ_PERSIST_TETHERING_SETTINGS"/>
<permission name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
<permission name="android.permission.ACCESS_NOTIFICATIONS"/>
<permission name="android.permission.BACKUP"/>
@@ -51,6 +52,7 @@
<permission name="android.permission.TETHER_PRIVILEGED"/>
<permission name="android.permission.USE_RESERVED_DISK"/>
<permission name="android.permission.USER_ACTIVITY"/>
+ <permission name="android.permission.UWB_PRIVILEGED"/>
<permission name="android.permission.WRITE_APN_SETTINGS"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
<permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
diff --git a/data/etc/com.android.car.shell.xml b/data/etc/com.android.car.shell.xml
index 328a15f..3eb041b 100644
--- a/data/etc/com.android.car.shell.xml
+++ b/data/etc/com.android.car.shell.xml
@@ -142,7 +142,8 @@
<permission name="android.car.permission.MANAGE_OCCUPANT_CONNECTION" />
<permission name="android.car.permission.MANAGE_REMOTE_DEVICE" />
<permission name="android.car.permission.MANAGE_CAR_SYSTEM_UI" />
- <permission name="android.car.permission.QUERY_DISPLAY_COMPATIBILITY" />
+ <permission name="android.car.permission.MANAGE_DISPLAY_COMPATIBILITY" />
<permission name="android.car.permission.CONTROL_REMOTE_ACCESS" />
+ <permission name="android.car.permission.READ_PERSIST_TETHERING_SETTINGS" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/com.android.carsystemui.xml b/data/etc/com.android.carsystemui.xml
index 487c175..858c191 100644
--- a/data/etc/com.android.carsystemui.xml
+++ b/data/etc/com.android.carsystemui.xml
@@ -29,13 +29,18 @@
<permission name="android.car.permission.CONTROL_CAR_APP_LAUNCH"/>
<!-- used by ActivityBlockingActivity -->
<permission name="android.car.permission.ACCESS_PRIVATE_DISPLAY_ID"/>
+ <!-- used by InCallController#getInCallServiceType to bind InCallService as non-ui -->
+ <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+ <permission name="android.permission.MANAGE_ONGOING_CALLS"/>
+ <!-- used to query media session related apis -->
+ <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<!-- used to register the system ui proxy with the car service to provide the necessary
system ui interaction needed by other apps. -->
<permission name="android.car.permission.REGISTER_CAR_SYSTEM_UI_PROXY"/>
<!-- used for enabling RemoteCarDefaultRootTaskView -->
<permission name="android.car.permission.MANAGE_CAR_SYSTEM_UI"/>
- <!-- used for querying AutoEnhance packages -->
- <permission name="android.car.permission.QUERY_DISPLAY_COMPATIBILITY"/>
+ <!-- used for managing display compat packages -->
+ <permission name="android.car.permission.MANAGE_DISPLAY_COMPATIBILITY"/>
<!-- used for receiving broadcasted dock events -->
<permission name="com.android.car.docklib.permission.BROADCAST_RECEIVER"/>
</privapp-permissions>
diff --git a/data/etc/com.google.android.car.kitchensink.xml b/data/etc/com.google.android.car.kitchensink.xml
index ef8d013..98d43c5 100644
--- a/data/etc/com.google.android.car.kitchensink.xml
+++ b/data/etc/com.google.android.car.kitchensink.xml
@@ -83,12 +83,15 @@
<permission name="android.car.permission.CAR_VENDOR_EXTENSION"/>
<permission name="android.car.permission.CAR_EPOCH_TIME"/>
<!-- use for AndroidCarApiTest -->
+ <permission name="android.car.permission.PRIVILEGED_CAR_INFO"/>
<permission name="android.car.permission.READ_DRIVER_MONITORING_SETTINGS"/>
<permission name="android.car.permission.CONTROL_DRIVER_MONITORING_SETTINGS"/>
<permission name="android.car.permission.READ_DRIVER_MONITORING_STATES"/>
<permission name="android.car.permission.CONTROL_CAR_APP_LAUNCH"/>
<permission name="android.car.permission.CONTROL_CAR_CLIMATE"/>
<permission name="android.car.permission.CONTROL_CAR_DOORS"/>
+ <permission name="android.car.permission.READ_CAR_INTERIOR_LIGHTS"/>
+ <permission name="android.car.permission.CONTROL_CAR_INTERIOR_LIGHTS"/>
<permission name="android.car.permission.CONTROL_CAR_EXTERIOR_LIGHTS"/>
<permission name="android.car.permission.CONTROL_CAR_FEATURES"/>
<permission name="android.car.permission.CONTROL_CAR_MIRRORS"/>
@@ -116,6 +119,11 @@
<permission name="android.car.permission.CONTROL_ADAS_STATES"/>
<permission name="android.car.permission.READ_WINDSHIELD_WIPERS"/>
<permission name="android.car.permission.CONTROL_WINDSHIELD_WIPERS"/>
+ <permission name="android.car.permission.CONTROL_CAR_ENERGY"/>
+ <permission name="android.car.permission.CONTROL_CAR_ENERGY_PORTS"/>
+ <permission name="android.car.permission.CONTROL_CAR_DISPLAY_UNITS"/>
+ <permission name="android.car.permission.ADJUST_RANGE_REMAINING"/>
+ <permission name="android.car.permission.CAR_ENGINE_DETAILED"/>
<permission name="android.car.permission.GET_CAR_VENDOR_CATEGORY_WINDOW"/>
<permission name="android.car.permission.SET_CAR_VENDOR_CATEGORY_WINDOW"/>
<permission name="android.car.permission.GET_CAR_VENDOR_CATEGORY_DOOR"/>
@@ -172,5 +180,7 @@
<permission name="android.permission.ACCESS_BROADCAST_RADIO" />
<!-- use for allowlisting automotive driver assistance packages from camera privacy -->
<permission name="android.permission.CAMERA_PRIVACY_ALLOWLIST" />
+ <!-- use for Camera2 TestFragment -->
+ <permission name="android.permission.SYSTEM_CAMERA"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/com.google.android.car.multidisplaytest.xml b/data/etc/com.google.android.car.multidisplaytest.xml
index 06f012d..95450c7 100644
--- a/data/etc/com.google.android.car.multidisplaytest.xml
+++ b/data/etc/com.google.android.car.multidisplaytest.xml
@@ -19,5 +19,6 @@
<!-- Used for OccupantConnectionFragment -->
<permission name="android.car.permission.MANAGE_OCCUPANT_CONNECTION"/>
<permission name="android.car.permission.MANAGE_REMOTE_DEVICE"/>
+ <permission name="android.car.permission.CAR_POWER"/>
</privapp-permissions>
</permissions>
diff --git a/experimental/experimental_api/Android.bp b/experimental/experimental_api/Android.bp
index cf5edde..067911c 100644
--- a/experimental/experimental_api/Android.bp
+++ b/experimental/experimental_api/Android.bp
@@ -17,6 +17,7 @@
// Experimental API backed by Experimental Car service.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -31,5 +32,4 @@
libs: ["android.car"],
- platform_apis: true,
}
diff --git a/experimental/service/Android.bp b/experimental/service/Android.bp
index 73bbba8..3df68eb 100644
--- a/experimental/service/Android.bp
+++ b/experimental/service/Android.bp
@@ -17,6 +17,7 @@
// Build the Experimental Car service.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/lib/backported-car-property-helper-lib/Android.bp b/libs/backported-car-property-helper-lib/Android.bp
similarity index 95%
rename from lib/backported-car-property-helper-lib/Android.bp
rename to libs/backported-car-property-helper-lib/Android.bp
index 5b8d471..b4b242a 100644
--- a/lib/backported-car-property-helper-lib/Android.bp
+++ b/libs/backported-car-property-helper-lib/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/lib/backported-car-property-helper-lib/AndroidManifest.xml b/libs/backported-car-property-helper-lib/AndroidManifest.xml
similarity index 100%
rename from lib/backported-car-property-helper-lib/AndroidManifest.xml
rename to libs/backported-car-property-helper-lib/AndroidManifest.xml
diff --git a/lib/backported-car-property-helper-lib/src/com/android/car/property/backported/CarPropertyHelper.java b/libs/backported-car-property-helper-lib/src/com/android/car/property/backported/CarPropertyHelper.java
similarity index 100%
rename from lib/backported-car-property-helper-lib/src/com/android/car/property/backported/CarPropertyHelper.java
rename to libs/backported-car-property-helper-lib/src/com/android/car/property/backported/CarPropertyHelper.java
diff --git a/lib/backported-car-property-helper-lib/tests/Android.bp b/libs/backported-car-property-helper-lib/tests/Android.bp
similarity index 100%
rename from lib/backported-car-property-helper-lib/tests/Android.bp
rename to libs/backported-car-property-helper-lib/tests/Android.bp
diff --git a/lib/backported-car-property-helper-lib/tests/AndroidManifest.xml b/libs/backported-car-property-helper-lib/tests/AndroidManifest.xml
similarity index 100%
rename from lib/backported-car-property-helper-lib/tests/AndroidManifest.xml
rename to libs/backported-car-property-helper-lib/tests/AndroidManifest.xml
diff --git a/lib/backported-car-property-helper-lib/tests/src/com/android/car/property/backported/CarPropertyHelperUnitTest.java b/libs/backported-car-property-helper-lib/tests/src/com/android/car/property/backported/CarPropertyHelperUnitTest.java
similarity index 100%
rename from lib/backported-car-property-helper-lib/tests/src/com/android/car/property/backported/CarPropertyHelperUnitTest.java
rename to libs/backported-car-property-helper-lib/tests/src/com/android/car/property/backported/CarPropertyHelperUnitTest.java
diff --git a/car-admin-ui-lib/Android.bp b/libs/car-admin-ui-lib/Android.bp
similarity index 100%
rename from car-admin-ui-lib/Android.bp
rename to libs/car-admin-ui-lib/Android.bp
diff --git a/car-admin-ui-lib/AndroidManifest.xml b/libs/car-admin-ui-lib/AndroidManifest.xml
similarity index 100%
rename from car-admin-ui-lib/AndroidManifest.xml
rename to libs/car-admin-ui-lib/AndroidManifest.xml
diff --git a/car-admin-ui-lib/README.md b/libs/car-admin-ui-lib/README.md
similarity index 100%
rename from car-admin-ui-lib/README.md
rename to libs/car-admin-ui-lib/README.md
diff --git a/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/ManagedDeviceTextView.java b/libs/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/ManagedDeviceTextView.java
similarity index 100%
rename from car-admin-ui-lib/src/main/java/com/android/car/admin/ui/ManagedDeviceTextView.java
rename to libs/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/ManagedDeviceTextView.java
diff --git a/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/UserAvatarView.java b/libs/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/UserAvatarView.java
similarity index 96%
rename from car-admin-ui-lib/src/main/java/com/android/car/admin/ui/UserAvatarView.java
rename to libs/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/UserAvatarView.java
index 3b92265..ff5a541 100644
--- a/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/UserAvatarView.java
+++ b/libs/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/UserAvatarView.java
@@ -29,7 +29,7 @@
// TODO(b/176262528): copied from com.android.systemui, ideally it should be provided by a common
// library like SettingsLib. If not, then this whole project / package should be renamed to
-// "car-user-ui-lib", not "car-admin-ui-lib".
+// "car-user-ui-libs", not "car-admin-ui-libs".
/**
* A view that displays a user image cropped to a circle with an optional frame.
@@ -45,8 +45,8 @@
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.UserAvatarView, defStyleAttr, defStyleRes);
- final int N = a.getIndexCount();
- for (int i = 0; i < N; i++) {
+ final int n = a.getIndexCount();
+ for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
if (attr == R.styleable.UserAvatarView_avatarPadding) {
setAvatarPadding(a.getDimension(attr, 0));
@@ -60,8 +60,7 @@
setBadgeDiameter(a.getDimension(attr, 0));
} else if (attr == R.styleable.UserAvatarView_badgeMargin) {
setBadgeMargin(a.getDimension(attr, 0));
- }
- else {
+ } else {
setBadgeDiameter(a.getDimension(attr, 0));
}
}
diff --git a/car-admin-ui-lib/src/main/res/values-af/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-af/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-af/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-af/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-am/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-am/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-am/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-am/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-ar/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-ar/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-ar/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-ar/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-as/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-as/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-as/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-as/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-az/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-az/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-az/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-az/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-b+sr+Latn/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-b+sr+Latn/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-b+sr+Latn/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-b+sr+Latn/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-be/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-be/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-be/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-be/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-bg/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-bg/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-bg/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-bg/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-bn/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-bn/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-bn/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-bn/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-bs/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-bs/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-bs/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-bs/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-ca/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-ca/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-ca/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-ca/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-cs/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-cs/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-cs/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-cs/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-da/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-da/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-da/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-da/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-de/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-de/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-de/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-de/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-el/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-el/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-el/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-el/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-en-rAU/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-en-rAU/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-en-rAU/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-en-rAU/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-en-rCA/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-en-rCA/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-en-rCA/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-en-rCA/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-en-rGB/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-en-rGB/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-en-rGB/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-en-rGB/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-en-rIN/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-en-rIN/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-en-rIN/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-en-rIN/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-en-rXC/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-en-rXC/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-en-rXC/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-en-rXC/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-es-rUS/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-es-rUS/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-es-rUS/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-es-rUS/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-es/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-es/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-es/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-es/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-et/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-et/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-et/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-et/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-eu/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-eu/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-eu/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-eu/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-fa/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-fa/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-fa/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-fa/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-fi/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-fi/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-fi/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-fi/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-fr-rCA/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-fr-rCA/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-fr-rCA/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-fr-rCA/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-fr/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-fr/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-fr/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-fr/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-gl/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-gl/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-gl/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-gl/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-gu/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-gu/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-gu/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-gu/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-hi/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-hi/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-hi/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-hi/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-hr/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-hr/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-hr/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-hr/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-hu/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-hu/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-hu/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-hu/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-hy/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-hy/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-hy/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-hy/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-in/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-in/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-in/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-in/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-is/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-is/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-is/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-is/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-it/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-it/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-it/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-it/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-iw/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-iw/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-iw/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-iw/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-ja/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-ja/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-ja/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-ja/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-ka/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-ka/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-ka/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-ka/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-kk/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-kk/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-kk/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-kk/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-km/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-km/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-km/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-km/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-kn/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-kn/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-kn/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-kn/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-ko/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-ko/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-ko/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-ko/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-ky/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-ky/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-ky/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-ky/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-lo/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-lo/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-lo/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-lo/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-lt/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-lt/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-lt/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-lt/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-lv/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-lv/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-lv/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-lv/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-mk/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-mk/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-mk/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-mk/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-ml/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-ml/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-ml/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-ml/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-mn/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-mn/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-mn/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-mn/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-mr/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-mr/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-mr/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-mr/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-ms/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-ms/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-ms/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-ms/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-my/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-my/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-my/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-my/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-nb/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-nb/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-nb/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-nb/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-ne/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-ne/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-ne/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-ne/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-nl/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-nl/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-nl/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-nl/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-or/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-or/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-or/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-or/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-pa/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-pa/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-pa/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-pa/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-pl/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-pl/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-pl/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-pl/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-pt-rPT/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-pt-rPT/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-pt-rPT/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-pt-rPT/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-pt/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-pt/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-pt/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-pt/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-ro/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-ro/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-ro/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-ro/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-ru/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-ru/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-ru/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-ru/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-si/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-si/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-si/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-si/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-sk/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-sk/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-sk/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-sk/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-sl/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-sl/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-sl/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-sl/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-sq/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-sq/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-sq/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-sq/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-sr/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-sr/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-sr/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-sr/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-sv/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-sv/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-sv/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-sv/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-sw/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-sw/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-sw/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-sw/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-ta/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-ta/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-ta/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-ta/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-te/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-te/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-te/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-te/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-th/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-th/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-th/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-th/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-tl/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-tl/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-tl/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-tl/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-tr/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-tr/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-tr/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-tr/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-uk/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-uk/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-uk/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-uk/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-ur/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-ur/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-ur/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-ur/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-uz/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-uz/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-uz/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-uz/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-vi/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-vi/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-vi/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-vi/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-zh-rCN/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-zh-rCN/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-zh-rCN/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-zh-rCN/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-zh-rHK/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-zh-rHK/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-zh-rHK/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-zh-rHK/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-zh-rTW/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-zh-rTW/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-zh-rTW/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-zh-rTW/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values-zu/strings.xml b/libs/car-admin-ui-lib/src/main/res/values-zu/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values-zu/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values-zu/strings.xml
diff --git a/car-admin-ui-lib/src/main/res/values/attrs.xml b/libs/car-admin-ui-lib/src/main/res/values/attrs.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values/attrs.xml
rename to libs/car-admin-ui-lib/src/main/res/values/attrs.xml
diff --git a/car-admin-ui-lib/src/main/res/values/strings.xml b/libs/car-admin-ui-lib/src/main/res/values/strings.xml
similarity index 100%
rename from car-admin-ui-lib/src/main/res/values/strings.xml
rename to libs/car-admin-ui-lib/src/main/res/values/strings.xml
diff --git a/car-evs-helper-lib/Android.bp b/libs/car-evs-helper-lib/Android.bp
similarity index 100%
rename from car-evs-helper-lib/Android.bp
rename to libs/car-evs-helper-lib/Android.bp
diff --git a/car-evs-helper-lib/AndroidManifest.xml b/libs/car-evs-helper-lib/AndroidManifest.xml
similarity index 100%
rename from car-evs-helper-lib/AndroidManifest.xml
rename to libs/car-evs-helper-lib/AndroidManifest.xml
diff --git a/car-evs-helper-lib/OWNERS b/libs/car-evs-helper-lib/OWNERS
similarity index 100%
rename from car-evs-helper-lib/OWNERS
rename to libs/car-evs-helper-lib/OWNERS
diff --git a/car-evs-helper-lib/README.md b/libs/car-evs-helper-lib/README.md
similarity index 100%
rename from car-evs-helper-lib/README.md
rename to libs/car-evs-helper-lib/README.md
diff --git a/car-evs-helper-lib/jni/Android.bp b/libs/car-evs-helper-lib/jni/Android.bp
similarity index 100%
rename from car-evs-helper-lib/jni/Android.bp
rename to libs/car-evs-helper-lib/jni/Android.bp
diff --git a/car-evs-helper-lib/jni/CarEvsBufferRenderer.cpp b/libs/car-evs-helper-lib/jni/CarEvsBufferRenderer.cpp
similarity index 100%
rename from car-evs-helper-lib/jni/CarEvsBufferRenderer.cpp
rename to libs/car-evs-helper-lib/jni/CarEvsBufferRenderer.cpp
diff --git a/car-evs-helper-lib/src/com/android/car/internal/evs/CarEvsGLSurfaceView.java b/libs/car-evs-helper-lib/src/com/android/car/internal/evs/CarEvsGLSurfaceView.java
similarity index 94%
rename from car-evs-helper-lib/src/com/android/car/internal/evs/CarEvsGLSurfaceView.java
rename to libs/car-evs-helper-lib/src/com/android/car/internal/evs/CarEvsGLSurfaceView.java
index 2d4ed17..ddb9882 100644
--- a/car-evs-helper-lib/src/com/android/car/internal/evs/CarEvsGLSurfaceView.java
+++ b/libs/car-evs-helper-lib/src/com/android/car/internal/evs/CarEvsGLSurfaceView.java
@@ -21,10 +21,8 @@
import android.car.evs.CarEvsBufferDescriptor;
import android.content.Context;
import android.opengl.GLSurfaceView;
-import android.view.MotionEvent;
import com.android.internal.util.Preconditions;
-import com.android.car.internal.evs.GLES20CarEvsBufferRenderer;
import java.util.ArrayList;
@@ -34,12 +32,12 @@
public final class CarEvsGLSurfaceView extends GLSurfaceView {
private static final String TAG = CarEvsGLSurfaceView.class.getSimpleName();
private static final int DEFAULT_IN_PLANE_ROTATION_ANGLE = 0;
- private static final float DEFAULT_1X1_POSITION[][] = {
+ private static final float[][] DEFAULT_1X1_POSITION = {
{
-1.0f, 1.0f, 0.0f,
- 1.0f, 1.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
- 1.0f, -1.0f, 0.0f,
+ 1.0f, -1.0f, 0.0f,
},
};
private final GLES20CarEvsBufferRenderer mRenderer;
@@ -120,7 +118,7 @@
*
*/
public static CarEvsGLSurfaceView create(Context context, ArrayList<BufferCallback> callbacks,
- float positions[][]) {
+ float[][] positions) {
return create(context, callbacks, DEFAULT_IN_PLANE_ROTATION_ANGLE, positions);
}
@@ -136,7 +134,7 @@
*
*/
public static CarEvsGLSurfaceView create(Context context, ArrayList<BufferCallback> callbacks,
- int angleInDegree, float positions[][]) {
+ int angleInDegree, float[][] positions) {
Preconditions.checkArgument(context != null, "Context cannot be null.");
Preconditions.checkArgument(callbacks != null, "BufferCallback cannot be null.");
diff --git a/car-evs-helper-lib/src/com/android/car/internal/evs/GLES20CarEvsBufferRenderer.java b/libs/car-evs-helper-lib/src/com/android/car/internal/evs/GLES20CarEvsBufferRenderer.java
similarity index 89%
rename from car-evs-helper-lib/src/com/android/car/internal/evs/GLES20CarEvsBufferRenderer.java
rename to libs/car-evs-helper-lib/src/com/android/car/internal/evs/GLES20CarEvsBufferRenderer.java
index be79d71..7e39d2a 100644
--- a/car-evs-helper-lib/src/com/android/car/internal/evs/GLES20CarEvsBufferRenderer.java
+++ b/libs/car-evs-helper-lib/src/com/android/car/internal/evs/GLES20CarEvsBufferRenderer.java
@@ -18,7 +18,6 @@
import static android.opengl.GLU.gluErrorString;
-import android.annotation.NonNull;
import android.car.evs.CarEvsBufferDescriptor;
import android.hardware.HardwareBuffer;
import android.opengl.GLES20;
@@ -48,14 +47,14 @@
private static final float[] sVertPosData = {
-1.0f, 1.0f, 0.0f,
- 1.0f, 1.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
- 1.0f, -1.0f, 0.0f };
+ 1.0f, -1.0f, 0.0f };
private static final float[] sVertTexData = {
- -0.5f, -0.5f,
+ -0.5f, -0.5f,
0.5f, -0.5f,
- -0.5f, 0.5f,
+ -0.5f, 0.5f,
0.5f, 0.5f };
private static final float[] sIdentityMatrix = {
@@ -64,25 +63,26 @@
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f };
- private static final String mVertexShader =
- "attribute vec4 pos; \n" +
- "attribute vec2 tex; \n" +
- "uniform mat4 cameraMat; \n" +
- "varying vec2 uv; \n" +
- "void main() \n" +
- "{ \n" +
- " gl_Position = cameraMat * pos; \n" +
- " uv = tex; \n" +
- "} \n";
+ private static final String sVertexShader =
+ "attribute vec4 pos; \n"
+ + "attribute vec2 tex; \n"
+ + "uniform mat4 cameraMat; \n"
+ + "varying vec2 uv; \n"
+ + "void main() \n"
+ + "{ \n"
+ + " gl_Position = cameraMat * pos; \n"
+ + " uv = tex; \n"
+ + "} \n";
- private static final String mFragmentShader =
- "precision mediump float; \n" +
- "uniform sampler2D tex; \n" +
- "varying vec2 uv; \n" +
- "void main() \n" +
- "{ \n" +
- " gl_FragColor = texture2D(tex, uv); \n" +
- "} \n";
+ private static final String sFragmentShader =
+ "precision mediump float; \n"
+ + "uniform sampler2D tex; \n"
+ + "varying vec2 uv; \n"
+ + "void main() \n"
+ + "{ \n"
+ + " gl_FragColor = texture2D(tex, uv); \n"
+ + "} \n";
+
private static final int INDEX_TO_X_STRIDE = 0;
private static final int INDEX_TO_Y_STRIDE = 1;
@@ -91,8 +91,8 @@
private final ArrayList<CarEvsGLSurfaceView.BufferCallback> mCallbacks;
private final FloatBuffer mVertPos;
private final FloatBuffer mVertTex;
- private final int mTextureId[];
- private final float mPositions[][];
+ private final int[] mTextureId;
+ private final float[][] mPositions;
private int mProgram;
private int mWidth;
@@ -100,7 +100,7 @@
// Hold buffers currently in use.
@GuardedBy("mLock")
- private final CarEvsBufferDescriptor mBufferInUse[];
+ private final CarEvsBufferDescriptor[] mBufferInUse;
/** Load jni on initialization. */
static {
@@ -126,8 +126,8 @@
double angleInRadian = Math.toRadians(angleInDegree);
float[] rotated = {0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f};
- float sin = (float)Math.sin(angleInRadian);
- float cos = (float)Math.cos(angleInRadian);
+ float sin = (float) Math.sin(angleInRadian);
+ float cos = (float) Math.cos(angleInRadian);
rotated[0] += cos * sVertTexData[0] - sin * sVertTexData[1];
rotated[1] += sin * sVertTexData[0] + cos * sVertTexData[1];
@@ -264,7 +264,7 @@
@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
// Use the GLES20 class's static methods instead of a passed GL10 interface.
- mProgram = buildShaderProgram(mVertexShader, mFragmentShader);
+ mProgram = buildShaderProgram(sVertexShader, sFragmentShader);
if (mProgram == 0) {
Log.e(TAG, "Failed to build shader programs");
return;
diff --git a/car-helper-lib/Android.bp b/libs/car-helper-lib/Android.bp
similarity index 94%
rename from car-helper-lib/Android.bp
rename to libs/car-helper-lib/Android.bp
index a07a188..433c26e 100644
--- a/car-helper-lib/Android.bp
+++ b/libs/car-helper-lib/Android.bp
@@ -17,6 +17,7 @@
// NOTE: This library should not be used within p/s/Car
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/car-helper-lib/AndroidManifest.xml b/libs/car-helper-lib/AndroidManifest.xml
similarity index 100%
rename from car-helper-lib/AndroidManifest.xml
rename to libs/car-helper-lib/AndroidManifest.xml
diff --git a/car-helper-lib/README.md b/libs/car-helper-lib/README.md
similarity index 100%
rename from car-helper-lib/README.md
rename to libs/car-helper-lib/README.md
diff --git a/car-helper-lib/res/drawable/car_internal_guest_user_icon.xml b/libs/car-helper-lib/res/drawable/car_internal_guest_user_icon.xml
similarity index 100%
rename from car-helper-lib/res/drawable/car_internal_guest_user_icon.xml
rename to libs/car-helper-lib/res/drawable/car_internal_guest_user_icon.xml
diff --git a/car-helper-lib/res/drawable/car_internal_user_icon_circle_background.xml b/libs/car-helper-lib/res/drawable/car_internal_user_icon_circle_background.xml
similarity index 100%
rename from car-helper-lib/res/drawable/car_internal_user_icon_circle_background.xml
rename to libs/car-helper-lib/res/drawable/car_internal_user_icon_circle_background.xml
diff --git a/car-helper-lib/res/values/colors.xml b/libs/car-helper-lib/res/values/colors.xml
similarity index 100%
rename from car-helper-lib/res/values/colors.xml
rename to libs/car-helper-lib/res/values/colors.xml
diff --git a/car-helper-lib/res/values/dimens.xml b/libs/car-helper-lib/res/values/dimens.xml
similarity index 100%
rename from car-helper-lib/res/values/dimens.xml
rename to libs/car-helper-lib/res/values/dimens.xml
diff --git a/car-helper-lib/src/com/android/car/internal/user/CarUserIconProvider.java b/libs/car-helper-lib/src/com/android/car/internal/user/CarUserIconProvider.java
similarity index 100%
rename from car-helper-lib/src/com/android/car/internal/user/CarUserIconProvider.java
rename to libs/car-helper-lib/src/com/android/car/internal/user/CarUserIconProvider.java
diff --git a/car-helper-lib/src/com/android/car/internal/user/UserHelper.java b/libs/car-helper-lib/src/com/android/car/internal/user/UserHelper.java
similarity index 100%
rename from car-helper-lib/src/com/android/car/internal/user/UserHelper.java
rename to libs/car-helper-lib/src/com/android/car/internal/user/UserHelper.java
diff --git a/lib/car-internal-dep-lib/Android.bp b/libs/car-internal-dep-lib/Android.bp
similarity index 96%
rename from lib/car-internal-dep-lib/Android.bp
rename to libs/car-internal-dep-lib/Android.bp
index 2d7970b..66c4a12 100644
--- a/lib/car-internal-dep-lib/Android.bp
+++ b/libs/car-internal-dep-lib/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/lib/car-internal-dep-lib/src/com/android/car/internal/dep/SystemProperties.java b/libs/car-internal-dep-lib/src/com/android/car/internal/dep/SystemProperties.java
similarity index 100%
rename from lib/car-internal-dep-lib/src/com/android/car/internal/dep/SystemProperties.java
rename to libs/car-internal-dep-lib/src/com/android/car/internal/dep/SystemProperties.java
diff --git a/lib/car-internal-dep-lib/src/com/android/car/internal/dep/Trace.java b/libs/car-internal-dep-lib/src/com/android/car/internal/dep/Trace.java
similarity index 62%
rename from lib/car-internal-dep-lib/src/com/android/car/internal/dep/Trace.java
rename to libs/car-internal-dep-lib/src/com/android/car/internal/dep/Trace.java
index 7cd2e71..0d94bce 100644
--- a/lib/car-internal-dep-lib/src/com/android/car/internal/dep/Trace.java
+++ b/libs/car-internal-dep-lib/src/com/android/car/internal/dep/Trace.java
@@ -42,4 +42,32 @@
public static void endSection() {
android.os.Trace.endSection();
}
+
+ /**
+ * Begins async trace.
+ */
+ public static void asyncTraceBegin(long traceTag, String methodName, int cookie) {
+ android.os.Trace.asyncTraceBegin(traceTag, methodName, cookie);
+ }
+
+ /**
+ * Ends async trace.
+ */
+ public static void asyncTraceEnd(long traceTag, String methodName, int cookie) {
+ android.os.Trace.asyncTraceEnd(traceTag, methodName, cookie);
+ }
+
+ /**
+ * Begins trace.
+ */
+ public static void traceBegin(long traceTag, String methodName) {
+ android.os.Trace.traceBegin(traceTag, methodName);
+ }
+
+ /**
+ * Ends trace.
+ */
+ public static void traceEnd(long traceTag) {
+ android.os.Trace.traceEnd(traceTag);
+ }
}
diff --git a/car-test-lib/Android.bp b/libs/car-test-lib/Android.bp
similarity index 96%
rename from car-test-lib/Android.bp
rename to libs/car-test-lib/Android.bp
index 254d083..a8cd4ed 100644
--- a/car-test-lib/Android.bp
+++ b/libs/car-test-lib/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/car-test-lib/OWNERS b/libs/car-test-lib/OWNERS
similarity index 100%
rename from car-test-lib/OWNERS
rename to libs/car-test-lib/OWNERS
diff --git a/car-test-lib/src/android/car/test/AbstractExpectableTestCase.java b/libs/car-test-lib/src/android/car/test/AbstractExpectableTestCase.java
similarity index 100%
rename from car-test-lib/src/android/car/test/AbstractExpectableTestCase.java
rename to libs/car-test-lib/src/android/car/test/AbstractExpectableTestCase.java
diff --git a/car-test-lib/src/android/car/test/ApiCheckerRule.java b/libs/car-test-lib/src/android/car/test/ApiCheckerRule.java
similarity index 96%
rename from car-test-lib/src/android/car/test/ApiCheckerRule.java
rename to libs/car-test-lib/src/android/car/test/ApiCheckerRule.java
index 80050a7..63fd011 100644
--- a/car-test-lib/src/android/car/test/ApiCheckerRule.java
+++ b/libs/car-test-lib/src/android/car/test/ApiCheckerRule.java
@@ -102,53 +102,53 @@
* </ol>
*
* <p>So, back to the examples above, the tests would be:
- * <pre><code>
-
- @Test
- @ApiTest(apis = {"com.acme.Car#foo"})
- @SupportedVersionTest(unsupportedVersionTest="testFoo_unsupported")
- public void testFoo_supported() {
- baz(); // takes a long time
- foo();
- }
-
- @Test
- @ApiTest(apis = {"com.acme.Car#foo"})
- @UnsupportedVersionTest(supportedVersionTest="testFoo_supported")
- public void testFoo_unsupported() {
- foo(); // should throw PlatformViolationException
- }
-
- @Test
- @ApiTest(apis = {"com.acme.Car#bar"})
- @SupportedVersionTest(unsupportedVersionTest="testBar_unsupported")
- public void testBar_supported() {
- assertWithMessage("bar()").that(bar()).isEqualTo("BehaviorOnSupportedPlatform");
- }
-
- @Test
- @ApiTest(apis = {"com.acme.Car#bar"})
- @UnsupportedVersionTest(supportedVersionTest="testBar_supported", behavior=EXPECT_PASS)
- public void testFoo_unsupported() {
- assertWithMessage("bar()").that(bar()).isEqualTo("BehaviorOnUnsupportedPlatform");
- }
-
- * </code></pre>
+ * <pre>{@code
+ *
+ * @Test
+ * @ApiTest(apis = {"com.acme.Car#foo"})
+ * @SupportedVersionTest(unsupportedVersionTest="testFoo_unsupported")
+ * public void testFoo_supported() {
+ * baz(); // takes a long time
+ * foo();
+ * }
+ *
+ * @Test
+ * @ApiTest(apis = {"com.acme.Car#foo"})
+ * @UnsupportedVersionTest(supportedVersionTest="testFoo_supported")
+ * public void testFoo_unsupported() {
+ * foo(); // should throw PlatformViolationException
+ * }
+ *
+ * @Test
+ * @ApiTest(apis = {"com.acme.Car#bar"})
+ * @SupportedVersionTest(unsupportedVersionTest="testBar_unsupported")
+ * public void testBar_supported() {
+ * assertWithMessage("bar()").that(bar()).isEqualTo("BehaviorOnSupportedPlatform");
+ * }
+ *
+ * @Test
+ * @ApiTest(apis = {"com.acme.Car#bar"})
+ * @UnsupportedVersionTest(supportedVersionTest="testBar_supported", behavior=EXPECT_PASS)
+ * public void testFoo_unsupported() {
+ * assertWithMessage("bar()").that(bar()).isEqualTo("BehaviorOnUnsupportedPlatform");
+ * }
+ *
+ * }</pre>
*
* For nested classes the following annotation should be used for methods:
- * <pre><code>
- @Test
- @ApiTest(apis = {"com.acme.Car$Inner#methodName"})
- public void testMethodName() {}
-
+ * <pre>{@code
+ * @Test
+ * @ApiTest(apis = {"com.acme.Car$Inner#methodName"})
+ * public void testMethodName() {}
+ *
* </code></pre>
* For nested classes the following annotation should be used for fields:
* <pre><code>
- @Test
- @ApiTest(apis = {"com.acme.Car.Inner#fieldName"})
- public void testFieldName() {}
-
- * </code></pre>
+ * @Test
+ * @ApiTest(apis = {"com.acme.Car.Inner#fieldName"})
+ * public void testFieldName() {}
+ *
+ * }</pre>
*/
public final class ApiCheckerRule implements TestRule {
@@ -447,8 +447,8 @@
}
if (!Car.isApiVersionAtLeast(Build.VERSION_CODES.TIRAMISU, /* minor= */ 1)) {
Log.d(TAG, "Running " + description.getDisplayName() + " as-is on pre-TM-QPR1 Car build"
- + " (major=" + Car.API_VERSION_MAJOR_INT
- + ", minor=" + Car.API_VERSION_MINOR_INT + ")");
+ + " (major=" + Car.getCarVersion().getMajorVersion()
+ + ", minor=" + Car.getCarVersion().getMinorVersion() + ")");
return false;
}
return true;
diff --git a/car-test-lib/src/android/car/test/ApiHelper.java b/libs/car-test-lib/src/android/car/test/ApiHelper.java
similarity index 100%
rename from car-test-lib/src/android/car/test/ApiHelper.java
rename to libs/car-test-lib/src/android/car/test/ApiHelper.java
diff --git a/car-test-lib/src/android/car/test/PermissionsCheckerRule.java b/libs/car-test-lib/src/android/car/test/PermissionsCheckerRule.java
similarity index 100%
rename from car-test-lib/src/android/car/test/PermissionsCheckerRule.java
rename to libs/car-test-lib/src/android/car/test/PermissionsCheckerRule.java
diff --git a/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java b/libs/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
similarity index 91%
rename from car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
rename to libs/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
index d8f71c0..df89ad2 100644
--- a/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
+++ b/libs/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
@@ -15,12 +15,8 @@
*/
package android.car.test.mocks;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -37,6 +33,8 @@
import android.os.UserManager;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Log.TerribleFailure;
+import android.util.Log.TerribleFailureHandler;
import android.util.Slog;
import android.util.TimingsTraceLog;
@@ -51,7 +49,6 @@
import org.junit.runners.model.Statement;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
-import org.mockito.invocation.InvocationOnMock;
import org.mockito.quality.Strictness;
import java.lang.annotation.Retention;
@@ -127,6 +124,7 @@
// Tracks (S)Log.wtf() calls made during code execution, then used on verifyWtfNeverLogged()
private final List<RuntimeException> mWtfs = new ArrayList<>();
+ private TerribleFailureHandler mOldWtfHandler;
private MockitoSession mSession;
@@ -179,6 +177,10 @@
Log.v(TAG, "startSession() for " + getTestName() + " on thread "
+ Thread.currentThread() + "; sHighlanderSession=" + sHighlanderSession);
}
+ // Clear all stored mWtfs if any.
+ mWtfs.clear();
+ interceptWtfCalls();
+
finishHighlanderSessionIfNeeded("startSession()");
beginTrace("startSession()");
@@ -189,9 +191,7 @@
.strictness(getSessionStrictness());
CustomMockitoSessionBuilder customBuilder =
- new CustomMockitoSessionBuilder(builder, mStaticSpiedClasses, mStaticMockedClasses)
- .spyStatic(Log.class)
- .spyStatic(Slog.class);
+ new CustomMockitoSessionBuilder(builder, mStaticSpiedClasses, mStaticMockedClasses);
beginTrace("onSessionBuilder()");
onSessionBuilder(customBuilder);
@@ -213,10 +213,6 @@
customBuilder.mCallback.afterSessionStarted();
}
- beginTrace("interceptWtfCalls()");
- interceptWtfCalls();
- endTrace();
-
endTrace(); // startSession
}
@@ -238,8 +234,10 @@
if (VERBOSE) {
Log.v(TAG, "finishSession() for " + getTestName() + " on thread "
+ Thread.currentThread() + "; sHighlanderSession=" + sHighlanderSession);
-
}
+
+ resetWtfCalls();
+
if (false) { // For obvious reasons, should NEVER be merged as true
forceFailure(1, RuntimeException.class, "to simulate an unfinished session");
}
@@ -465,7 +463,7 @@
}
/**
- * Mocks a call to {@link Binder.getCallingUserHandle()}.
+ * Mocks a call to {@link Binder#getCallingUserHandle()}.
*
* @throws IllegalStateException if class didn't override
* {@link #onSessionBuilder(CustomMockitoSessionBuilder)} and called
@@ -508,42 +506,19 @@
}
private void interceptWtfCalls() {
- try {
- doAnswer((invocation) -> {
- return addWtf(invocation);
- }).when(() -> Log.wtf(anyString(), anyString()));
- doAnswer((invocation) -> {
- return addWtf(invocation);
- }).when(() -> Log.wtf(anyString(), any(Throwable.class)));
- doAnswer((invocation) -> {
- return addWtf(invocation);
- }).when(() -> Log.wtf(anyString(), anyString(), any(Throwable.class)));
- doAnswer((invocation) -> {
- return addWtf(invocation);
- }).when(() -> Slog.wtf(anyString(), anyString()));
- doAnswer((invocation) -> {
- return addWtf(invocation);
- }).when(() -> Slog.wtf(anyString(), any(Throwable.class)));
- doAnswer((invocation) -> {
- return addWtf(invocation);
- }).when(() -> Slog.wtf(anyString(), anyString(), any(Throwable.class)));
- // NOTE: android.car.builtin.util.Slogf calls android.util.Slog behind the scenes, so no
- // need to check for calls of the former...
- } catch (Throwable t) {
- Log.e(TAG, "interceptWtfCalls(): failed for test " + getTestName(), t);
- }
+ mOldWtfHandler = Log.setWtfHandler((String tag, TerribleFailure what, boolean system) -> {
+ String message = "Called " + what;
+ Log.d(TAG, message); // Log always, as some test expect it
+ if (mLogTags != null && mLogTags.contains(tag)) {
+ mWtfs.add(new IllegalStateException(message));
+ } else if (VERBOSE) {
+ Log.v(TAG, "ignoring WTF invocation on tag " + tag + ". mLogTags=" + mLogTags);
+ }
+ });
}
- private Object addWtf(InvocationOnMock invocation) {
- String message = "Called " + invocation;
- Log.d(TAG, message); // Log always, as some test expect it
- String actualTag = (String) invocation.getArguments()[0];
- if (mLogTags != null && mLogTags.contains(actualTag)) {
- mWtfs.add(new IllegalStateException(message));
- } else if (VERBOSE) {
- Log.v(TAG, "ignoring WTF invocation on tag " + actualTag + ". mLogTags=" + mLogTags);
- }
- return null;
+ private void resetWtfCalls() {
+ Log.setWtfHandler(mOldWtfHandler);
}
private void verifyWtfLogged() {
@@ -694,7 +669,7 @@
*/
@Retention(RUNTIME)
@Target({METHOD})
- public static @interface ExpectWtf {
+ public @interface ExpectWtf {
}
private static final class SyncRunnable implements Runnable {
diff --git a/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java b/libs/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
similarity index 98%
rename from car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
rename to libs/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
index b0bc111..6c8e88c 100644
--- a/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
+++ b/libs/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
@@ -461,7 +461,7 @@
}
/**
- * Mocks a call to {@link Binder.getCallingUserHandle()}.
+ * Mocks a call to {@link Binder#getCallingUserHandle()}.
*
* <p><b>Note: </b>it must be made inside a
* {@link com.android.dx.mockito.inline.extended.StaticMockitoSession} built with
@@ -474,7 +474,7 @@
}
/**
- * Mocks a call to {@link Car#getCarVersion()
+ * Mocks a call to {@link Car#getCarVersion()}
*/
public static void mockCarGetCarVersion(CarVersion version) {
Log.d(TAG, "mockCarGetCarVersion(): " + version);
@@ -482,7 +482,7 @@
}
/**
- * Mocks a call to {@link Car#getPlatformVersion()
+ * Mocks a call to {@link Car#getPlatformVersion()}
*/
public static void mockCarGetPlatformVersion(PlatformVersion version) {
Log.d(TAG, "mockCarGetPlatformVersion(): " + version);
@@ -490,7 +490,7 @@
}
/**
- * Mocks a call to {@link Car#isApiVersionAtLeast()
+ * Mocks a call to {@link Car#isApiVersionAtLeast()}
*/
public static void mockCarIsApiVersionAtLeast(int major, int minor, boolean isIt) {
Log.d(TAG, "mockCarIsApiVersionAtLeast(" + major + ", " + minor + "): " + isIt);
diff --git a/car-test-lib/src/android/car/test/mocks/BlockingAnswer.java b/libs/car-test-lib/src/android/car/test/mocks/BlockingAnswer.java
similarity index 93%
rename from car-test-lib/src/android/car/test/mocks/BlockingAnswer.java
rename to libs/car-test-lib/src/android/car/test/mocks/BlockingAnswer.java
index e64f8f5..23033b4 100644
--- a/car-test-lib/src/android/car/test/mocks/BlockingAnswer.java
+++ b/libs/car-test-lib/src/android/car/test/mocks/BlockingAnswer.java
@@ -32,17 +32,18 @@
*
* <p>Example:
*
- * <pre><code>
+ * <pre>{@code
*
- * BlockingAnswer<Void> blockingAnswer = BlockingAnswer.forVoidReturn(10_000, (invocation) -> {
- * MyObject obj = (MyObject) invocation.getArguments()[0];
- * obj.doSomething();
+ * BlockingAnswer{@code <Void>} blockingAnswer = BlockingAnswer.forVoidReturn(
+ * 10_000, (invocation) -> {
+ * MyObject obj = (MyObject) invocation.getArguments()[0];
+ * obj.doSomething();
* });
* doAnswer(blockingAnswer).when(mMock).mockSomething();
* doSomethingOnTest();
* blockingAnswer.unblock();
*
- * </code></pre>
+ * }</pre>
*/
public final class BlockingAnswer<T> implements Answer<T> {
diff --git a/car-test-lib/src/android/car/test/mocks/CarArgumentMatchers.java b/libs/car-test-lib/src/android/car/test/mocks/CarArgumentMatchers.java
similarity index 100%
rename from car-test-lib/src/android/car/test/mocks/CarArgumentMatchers.java
rename to libs/car-test-lib/src/android/car/test/mocks/CarArgumentMatchers.java
diff --git a/car-test-lib/src/android/car/testapi/CarMockitoHelper.java b/libs/car-test-lib/src/android/car/test/mocks/CarMockitoHelper.java
similarity index 93%
rename from car-test-lib/src/android/car/testapi/CarMockitoHelper.java
rename to libs/car-test-lib/src/android/car/test/mocks/CarMockitoHelper.java
index 88aab4e..ccb985e 100644
--- a/car-test-lib/src/android/car/testapi/CarMockitoHelper.java
+++ b/libs/car-test-lib/src/android/car/test/mocks/CarMockitoHelper.java
@@ -13,17 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.car.testapi;
+package android.car.test.mock;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doAnswer;
import android.annotation.NonNull;
-import android.car.Car;
import android.os.RemoteException;
import android.util.Log;
+import com.android.car.internal.ICarBase;
+
/**
* Provides common Mockito calls for Car-specific classes.
*/
@@ -36,7 +37,7 @@
* it returns the passed as 2nd argument.
*/
public static void mockHandleRemoteExceptionFromCarServiceWithDefaultValue(
- @NonNull Car car) {
+ @NonNull ICarBase car) {
doAnswer((invocation) -> {
Log.v(TAG, "mocking handleRemoteExceptionFromCarService(): args=" + invocation);
Object returnValue = invocation.getArguments()[1];
diff --git a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java b/libs/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
similarity index 97%
rename from car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
rename to libs/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
index 24ce8e7..f1f7baa 100644
--- a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
+++ b/libs/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
@@ -41,7 +41,7 @@
*
* @param timeoutMs how long to wait for
*
- * @throws {@link IllegalStateException} if it times out.
+ * @throws IllegalStateException if it times out.
*/
public static void await(@NonNull CountDownLatch latch, long timeoutMs)
throws InterruptedException {
@@ -57,7 +57,7 @@
*
* @param timeoutMs how long to wait for
*
- * @throws {@link IllegalStateException} if it times out.
+ * @throws IllegalStateException if it times out.
*/
public static void await(@NonNull Semaphore semaphore, long timeoutMs)
throws InterruptedException {
diff --git a/car-test-lib/src/android/car/test/mocks/MockSettings.java b/libs/car-test-lib/src/android/car/test/mocks/MockSettings.java
similarity index 99%
rename from car-test-lib/src/android/car/test/mocks/MockSettings.java
rename to libs/car-test-lib/src/android/car/test/mocks/MockSettings.java
index 51c026b..0310205 100644
--- a/car-test-lib/src/android/car/test/mocks/MockSettings.java
+++ b/libs/car-test-lib/src/android/car/test/mocks/MockSettings.java
@@ -23,6 +23,7 @@
import android.annotation.Nullable;
import android.car.test.mocks.AbstractExtendedMockitoTestCase.CustomMockitoSessionBuilder;
import android.provider.Settings;
+import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -30,7 +31,6 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import android.util.ArrayMap;
// TODO (b/156033195): Clean settings API. For example, don't mock xyzForUser() methods (as
// they should not be used due to mainline) and explicitly use a MockSettings per user or
diff --git a/car-test-lib/src/android/car/test/mocks/SyncAnswer.java b/libs/car-test-lib/src/android/car/test/mocks/SyncAnswer.java
similarity index 96%
rename from car-test-lib/src/android/car/test/mocks/SyncAnswer.java
rename to libs/car-test-lib/src/android/car/test/mocks/SyncAnswer.java
index 3e885b3..0caa3a0 100644
--- a/car-test-lib/src/android/car/test/mocks/SyncAnswer.java
+++ b/libs/car-test-lib/src/android/car/test/mocks/SyncAnswer.java
@@ -30,7 +30,7 @@
*
* <pre><code>
*
- * SyncAnswer<UserInfo> syncUserInfo = SyncAnswer.forReturn(user);
+ * SyncAnswer{@code <UserInfo>} syncUserInfo = SyncAnswer.forReturn(user);
* when(mMock.preCreateUser(userType)).thenAnswer(syncUserInfo);
* objectUnderTest.preCreate();
* syncUserInfo.await(100);
diff --git a/car-test-lib/src/android/car/test/util/AndroidHelper.java b/libs/car-test-lib/src/android/car/test/util/AndroidHelper.java
similarity index 100%
rename from car-test-lib/src/android/car/test/util/AndroidHelper.java
rename to libs/car-test-lib/src/android/car/test/util/AndroidHelper.java
diff --git a/car-test-lib/src/android/car/test/util/AnnotationHelper.java b/libs/car-test-lib/src/android/car/test/util/AnnotationHelper.java
similarity index 100%
rename from car-test-lib/src/android/car/test/util/AnnotationHelper.java
rename to libs/car-test-lib/src/android/car/test/util/AnnotationHelper.java
diff --git a/car-test-lib/src/android/car/test/util/BlockingResultReceiver.java b/libs/car-test-lib/src/android/car/test/util/BlockingResultReceiver.java
similarity index 100%
rename from car-test-lib/src/android/car/test/util/BlockingResultReceiver.java
rename to libs/car-test-lib/src/android/car/test/util/BlockingResultReceiver.java
diff --git a/car-test-lib/src/android/car/test/util/CarAudioManagerTestUtils.java b/libs/car-test-lib/src/android/car/test/util/CarAudioManagerTestUtils.java
similarity index 100%
rename from car-test-lib/src/android/car/test/util/CarAudioManagerTestUtils.java
rename to libs/car-test-lib/src/android/car/test/util/CarAudioManagerTestUtils.java
diff --git a/car-test-lib/src/android/car/testapi/CarTestingHelper.java b/libs/car-test-lib/src/android/car/test/util/CarTestingHelper.java
similarity index 98%
rename from car-test-lib/src/android/car/testapi/CarTestingHelper.java
rename to libs/car-test-lib/src/android/car/test/util/CarTestingHelper.java
index c47e345..7d565f2 100644
--- a/car-test-lib/src/android/car/testapi/CarTestingHelper.java
+++ b/libs/car-test-lib/src/android/car/test/util/CarTestingHelper.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.car.testapi;
+package android.car.test.util;
import android.annotation.NonNull;
import android.car.util.concurrent.AsyncFuture;
diff --git a/car-test-lib/src/android/car/test/util/DisplayUtils.java b/libs/car-test-lib/src/android/car/test/util/DisplayUtils.java
similarity index 100%
rename from car-test-lib/src/android/car/test/util/DisplayUtils.java
rename to libs/car-test-lib/src/android/car/test/util/DisplayUtils.java
diff --git a/car-test-lib/src/android/car/test/util/ExceptionalFunction.java b/libs/car-test-lib/src/android/car/test/util/ExceptionalFunction.java
similarity index 100%
rename from car-test-lib/src/android/car/test/util/ExceptionalFunction.java
rename to libs/car-test-lib/src/android/car/test/util/ExceptionalFunction.java
diff --git a/car-test-lib/src/android/car/test/util/FakeContext.java b/libs/car-test-lib/src/android/car/test/util/FakeContext.java
similarity index 100%
rename from car-test-lib/src/android/car/test/util/FakeContext.java
rename to libs/car-test-lib/src/android/car/test/util/FakeContext.java
diff --git a/car-test-lib/src/android/car/test/util/TemporaryFile.java b/libs/car-test-lib/src/android/car/test/util/TemporaryFile.java
similarity index 100%
rename from car-test-lib/src/android/car/test/util/TemporaryFile.java
rename to libs/car-test-lib/src/android/car/test/util/TemporaryFile.java
diff --git a/car-test-lib/src/android/car/test/util/UserTestingHelper.java b/libs/car-test-lib/src/android/car/test/util/UserTestingHelper.java
similarity index 100%
rename from car-test-lib/src/android/car/test/util/UserTestingHelper.java
rename to libs/car-test-lib/src/android/car/test/util/UserTestingHelper.java
diff --git a/car-test-lib/src/android/car/test/util/Visitor.java b/libs/car-test-lib/src/android/car/test/util/Visitor.java
similarity index 100%
rename from car-test-lib/src/android/car/test/util/Visitor.java
rename to libs/car-test-lib/src/android/car/test/util/Visitor.java
diff --git a/car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java b/libs/car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java
similarity index 98%
rename from car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java
rename to libs/car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java
index 85daff5..30cb8b3 100644
--- a/car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java
+++ b/libs/car-test-lib/src/android/car/testapi/BlockingUserLifecycleListener.java
@@ -255,8 +255,8 @@
* Blocks until the events specified in the {@link Builder} are received, and returns them.
*
* @throws IllegalStateException if listener was built without any call to
- * {@link Builder#addExpectedEvent(int)} or using {@link #forAnyEvent().
- * @throws IllegalStateException if it times out before all specified events are received.
+ * {@link Builder#addExpectedEvent(int)} or using {@link #forAnyEvent()}.
+ * Also throws if it times out before all specified events are received.
* @throws InterruptedException if interrupted before all specified events are received.
*/
@NonNull
diff --git a/car-test-lib/src/android/car/testapi/CarAppFocusController.java b/libs/car-test-lib/src/android/car/testapi/CarAppFocusController.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/CarAppFocusController.java
rename to libs/car-test-lib/src/android/car/testapi/CarAppFocusController.java
diff --git a/car-test-lib/src/android/car/testapi/CarAudioZoneConfigInfoTestBuilder.java b/libs/car-test-lib/src/android/car/testapi/CarAudioZoneConfigInfoTestBuilder.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/CarAudioZoneConfigInfoTestBuilder.java
rename to libs/car-test-lib/src/android/car/testapi/CarAudioZoneConfigInfoTestBuilder.java
diff --git a/car-test-lib/src/android/car/testapi/CarNavigationStatusController.java b/libs/car-test-lib/src/android/car/testapi/CarNavigationStatusController.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/CarNavigationStatusController.java
rename to libs/car-test-lib/src/android/car/testapi/CarNavigationStatusController.java
diff --git a/car-test-lib/src/android/car/testapi/CarProjectionController.java b/libs/car-test-lib/src/android/car/testapi/CarProjectionController.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/CarProjectionController.java
rename to libs/car-test-lib/src/android/car/testapi/CarProjectionController.java
diff --git a/car-test-lib/src/android/car/testapi/CarPropertyController.java b/libs/car-test-lib/src/android/car/testapi/CarPropertyController.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/CarPropertyController.java
rename to libs/car-test-lib/src/android/car/testapi/CarPropertyController.java
diff --git a/car-test-lib/src/android/car/testapi/CarUxRestrictionsController.java b/libs/car-test-lib/src/android/car/testapi/CarUxRestrictionsController.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/CarUxRestrictionsController.java
rename to libs/car-test-lib/src/android/car/testapi/CarUxRestrictionsController.java
diff --git a/car-test-lib/src/android/car/testapi/FakeAppFocusService.java b/libs/car-test-lib/src/android/car/testapi/FakeAppFocusService.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/FakeAppFocusService.java
rename to libs/car-test-lib/src/android/car/testapi/FakeAppFocusService.java
diff --git a/car-test-lib/src/android/car/testapi/FakeCar.java b/libs/car-test-lib/src/android/car/testapi/FakeCar.java
similarity index 97%
rename from car-test-lib/src/android/car/testapi/FakeCar.java
rename to libs/car-test-lib/src/android/car/testapi/FakeCar.java
index 111825e..66e3b2b 100644
--- a/car-test-lib/src/android/car/testapi/FakeCar.java
+++ b/libs/car-test-lib/src/android/car/testapi/FakeCar.java
@@ -44,9 +44,9 @@
Car Service. Effectively creating a fake version of Car Service that can run under Robolectric
environment (thus running on the desktop rather than on a real device).
- By default all interfaces are mocked out just to allow placeholder implementation and avoid crashes.
- This will allow production code to call into Car*Manager w/o crashes because managers will just
- pass the call into mocked version of the interface. However, in many cases
+ By default all interfaces are mocked out just to allow placeholder implementation and avoid
+ crashes. This will allow production code to call into Car*Manager w/o crashes because managers
+ will just pass the call into mocked version of the interface. However, in many cases
developers would like to have more sophisticated test cases and ability to simulate vehicle as
they need. In this case mocked version of particular service needs to be replaced with the fake
one which will have fake implementation to satisfy test needs and additional interface needs
diff --git a/car-test-lib/src/android/car/testapi/FakeCarAudioService.java b/libs/car-test-lib/src/android/car/testapi/FakeCarAudioService.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/FakeCarAudioService.java
rename to libs/car-test-lib/src/android/car/testapi/FakeCarAudioService.java
diff --git a/car-test-lib/src/android/car/testapi/FakeCarProjectionService.java b/libs/car-test-lib/src/android/car/testapi/FakeCarProjectionService.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/FakeCarProjectionService.java
rename to libs/car-test-lib/src/android/car/testapi/FakeCarProjectionService.java
diff --git a/car-test-lib/src/android/car/testapi/FakeCarPropertyService.java b/libs/car-test-lib/src/android/car/testapi/FakeCarPropertyService.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/FakeCarPropertyService.java
rename to libs/car-test-lib/src/android/car/testapi/FakeCarPropertyService.java
diff --git a/car-test-lib/src/android/car/testapi/FakeCarUxRestrictionsService.java b/libs/car-test-lib/src/android/car/testapi/FakeCarUxRestrictionsService.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/FakeCarUxRestrictionsService.java
rename to libs/car-test-lib/src/android/car/testapi/FakeCarUxRestrictionsService.java
diff --git a/car-test-lib/src/android/car/testapi/FakeInstrumentClusterNavigation.java b/libs/car-test-lib/src/android/car/testapi/FakeInstrumentClusterNavigation.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/FakeInstrumentClusterNavigation.java
rename to libs/car-test-lib/src/android/car/testapi/FakeInstrumentClusterNavigation.java
diff --git a/car-test-lib/src/android/car/testapi/FakeRefactoredCarPowerPolicyDaemon.java b/libs/car-test-lib/src/android/car/testapi/FakeRefactoredCarPowerPolicyDaemon.java
similarity index 90%
rename from car-test-lib/src/android/car/testapi/FakeRefactoredCarPowerPolicyDaemon.java
rename to libs/car-test-lib/src/android/car/testapi/FakeRefactoredCarPowerPolicyDaemon.java
index d2959c8..76965cf 100644
--- a/car-test-lib/src/android/car/testapi/FakeRefactoredCarPowerPolicyDaemon.java
+++ b/libs/car-test-lib/src/android/car/testapi/FakeRefactoredCarPowerPolicyDaemon.java
@@ -19,11 +19,9 @@
import android.annotation.Nullable;
import android.automotive.powerpolicy.internal.ICarPowerPolicyDelegate;
import android.automotive.powerpolicy.internal.ICarPowerPolicyDelegateCallback;
-import android.automotive.powerpolicy.internal.PowerPolicyFailureReason;
import android.automotive.powerpolicy.internal.PowerPolicyInitData;
import android.car.hardware.power.PowerComponent;
import android.car.hardware.power.PowerComponentUtil;
-import android.car.test.util.TemporaryFile;
import android.frameworks.automotive.powerpolicy.CarPowerPolicy;
import android.os.FileObserver;
import android.os.Handler;
@@ -80,24 +78,24 @@
private final Map<String, CarPowerPolicy> mPolicies = new ArrayMap<>();
private final Map<String, SparseArray<CarPowerPolicy>> mPowerPolicyGroups = new ArrayMap<>();
- private String mLastAppliedPowerPolicyId = SYSTEM_POWER_POLICY_INITIAL_ON;
private String mLastSetPowerPolicyGroupId = POLICY_PER_STATE_GROUP_ID;
+ private int mLastNotifiedPowerState;
private boolean mSilentModeOn;
private String mPendingPowerPolicyId;
private String mLastDefinedPolicyId;
+ private String mCurrentPowerPolicyId = SYSTEM_POWER_POLICY_INITIAL_ON;
private Handler mHandler;
private ICarPowerPolicyDelegateCallback mCallback;
- private TemporaryFile mFileKernelSilentMode;
+ private File mFileKernelSilentMode;
- public FakeRefactoredCarPowerPolicyDaemon(@Nullable TemporaryFile fileKernelSilentMode,
+ public FakeRefactoredCarPowerPolicyDaemon(@Nullable File fileKernelSilentMode,
@Nullable int[] customComponents) throws Exception {
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mFileKernelSilentMode = (fileKernelSilentMode == null)
- ? new TemporaryFile("KERNEL_SILENT_MODE") : fileKernelSilentMode;
- mFileObserver = new SilentModeFileObserver(mFileKernelSilentMode.getFile(),
- FileObserver.CLOSE_WRITE);
+ ? new File("KERNEL_SILENT_MODE") : fileKernelSilentMode;
+ mFileObserver = new SilentModeFileObserver(mFileKernelSilentMode, FileObserver.CLOSE_WRITE);
CarPowerPolicy policyInitialOn = createInitialOnPowerPolicy();
CarPowerPolicy policyAllOn = createAllOnPowerPolicy();
CarPowerPolicy policyNoUser = createNoUserPowerPolicy();
@@ -179,14 +177,14 @@
Log.i(TAG, "Fake refactored CPPD was notified that car service is ready");
mCallback = callback;
PowerPolicyInitData initData = new PowerPolicyInitData();
- initData.currentPowerPolicy = mPolicies.get(mLastAppliedPowerPolicyId);
+ initData.currentPowerPolicy = mPolicies.get(mCurrentPowerPolicyId);
initData.registeredPolicies = new CarPowerPolicy[]{
mPolicies.get(SYSTEM_POWER_POLICY_INITIAL_ON),
mPolicies.get(SYSTEM_POWER_POLICY_ALL_ON),
mPolicies.get(SYSTEM_POWER_POLICY_NO_USER_INTERACTION),
mPolicies.get(SYSTEM_POWER_POLICY_SUSPEND_PREP)};
initData.registeredCustomComponents = mCustomComponents;
- mComponentHandler.applyPolicy(mPolicies.get(mLastAppliedPowerPolicyId));
+ mComponentHandler.applyPolicy(mPolicies.get(mCurrentPowerPolicyId));
return initData;
}
@@ -198,7 +196,8 @@
throw new IllegalStateException("Fake refactored CPPD callback is null, was "
+ "notifyCarServiceReady() called?");
}
- mLastAppliedPowerPolicyId = policyId;
+ boolean deferred = isPreemptivePolicy(mCurrentPowerPolicyId)
+ && !isPreemptivePolicy(policyId);
CarPowerPolicy currentPolicy = mPolicies.get(policyId);
if (currentPolicy == null) {
throw new IllegalArgumentException("Power policy " + policyId + " is invalid");
@@ -206,7 +205,10 @@
mComponentHandler.applyPolicy(currentPolicy);
CarPowerPolicy accumulatedPolicy = mComponentHandler.getAccumulatedPolicy(policyId);
mCallback.updatePowerComponents(accumulatedPolicy);
- mCallback.onApplyPowerPolicySucceeded(requestId, accumulatedPolicy);
+ mCallback.onApplyPowerPolicySucceeded(requestId, accumulatedPolicy, deferred);
+ if (!deferred) {
+ mCurrentPowerPolicyId = policyId;
+ }
}
@Override
@@ -218,18 +220,18 @@
}
if (mSilentModeOn) {
mPendingPowerPolicyId = policy.policyId;
- Log.d(TAG, "Silent mode is on, so cannot apply power policy for state " + state
- + ", setting pending power policy to " + mPendingPowerPolicyId);
- mCallback.onApplyPowerPolicyFailed(requestId,
- PowerPolicyFailureReason.POWER_POLICY_FAILURE_CANNOT_OVERRIDE);
+ Log.d(TAG, "Silent mode is on, so applying power policy for state " + state
+ + " is deferred, setting pending power policy to " + mPendingPowerPolicyId);
+ mCallback.onApplyPowerPolicySucceeded(requestId, policy, /* deferred= */ true);
return;
}
mHandler.post(() -> {
try {
- mCallback.onApplyPowerPolicySucceeded(requestId, policy);
mCallback.updatePowerComponents(policy);
- mLastAppliedPowerPolicyId = policy.policyId;
+ mCallback.onApplyPowerPolicySucceeded(requestId, policy, /* deferred= */ false);
+ mLastNotifiedPowerState = state;
mComponentHandler.applyPolicy(policy);
+ mCurrentPowerPolicyId = policy.policyId;
} catch (Exception e) {
Log.w(TAG, "Cannot call onApplyPowerPolicySucceeded", e);
}
@@ -242,7 +244,7 @@
try {
mCallback.onPowerPolicyChanged(accumulatedPolicy);
mCallback.updatePowerComponents(accumulatedPolicy);
- mLastAppliedPowerPolicyId = policyId;
+ mCurrentPowerPolicyId = policyId;
} catch (RemoteException e) {
Log.d(TAG, errMsg, e);
}
@@ -297,14 +299,22 @@
mPowerPolicyGroups.put(policyGroupId, policyGroup);
}
- public String getLastAppliedPowerPolicyId() {
- return mLastAppliedPowerPolicyId;
+ /**
+ * Get the last power state notified to the daemon
+ * @return Last notified power state
+ */
+ public int getLastNotifiedPowerState() {
+ return mLastNotifiedPowerState;
}
public String getLastDefinedPolicyId() {
return mLastDefinedPolicyId;
}
+ public String getCurrentPowerPolicyId() {
+ return mCurrentPowerPolicyId;
+ }
+
/**
* Begin observing the silent mode file for changes in silent mode state. Should be called
* at the beginning of a test case that involves silent mode and power policy.
@@ -331,6 +341,11 @@
return ICarPowerPolicyDelegate.HASH;
}
+ private boolean isPreemptivePolicy(String policyId) {
+ return Objects.equals(policyId, SYSTEM_POWER_POLICY_NO_USER_INTERACTION)
+ || Objects.equals(policyId, SYSTEM_POWER_POLICY_SUSPEND_PREP);
+ }
+
private final class SilentModeFileObserver extends FileObserver {
SilentModeFileObserver(File file, int mask) {
super(file, mask);
diff --git a/car-test-lib/src/android/car/testapi/FakeSystemActivityMonitoringService.java b/libs/car-test-lib/src/android/car/testapi/FakeSystemActivityMonitoringService.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/FakeSystemActivityMonitoringService.java
rename to libs/car-test-lib/src/android/car/testapi/FakeSystemActivityMonitoringService.java
diff --git a/car-test-lib/src/android/car/testapi/OccupantZoneInfoTestBuilder.java b/libs/car-test-lib/src/android/car/testapi/OccupantZoneInfoTestBuilder.java
similarity index 100%
rename from car-test-lib/src/android/car/testapi/OccupantZoneInfoTestBuilder.java
rename to libs/car-test-lib/src/android/car/testapi/OccupantZoneInfoTestBuilder.java
diff --git a/obd2-lib/Android.bp b/libs/obd2-lib/Android.bp
similarity index 100%
rename from obd2-lib/Android.bp
rename to libs/obd2-lib/Android.bp
diff --git a/obd2-lib/AndroidManifest.xml b/libs/obd2-lib/AndroidManifest.xml
similarity index 100%
rename from obd2-lib/AndroidManifest.xml
rename to libs/obd2-lib/AndroidManifest.xml
diff --git a/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java b/libs/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java
similarity index 100%
rename from obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java
rename to libs/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2Command.java b/libs/obd2-lib/src/com/android/car/obd2/Obd2Command.java
similarity index 99%
rename from obd2-lib/src/com/android/car/obd2/Obd2Command.java
rename to libs/obd2-lib/src/com/android/car/obd2/Obd2Command.java
index 30fca0c..e5fa321 100644
--- a/obd2-lib/src/com/android/car/obd2/Obd2Command.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/Obd2Command.java
@@ -31,6 +31,7 @@
import com.android.car.obd2.commands.RPM;
import com.android.car.obd2.commands.Speed;
import com.android.car.obd2.commands.ThrottlePosition;
+
import java.io.IOException;
import java.util.HashMap;
import java.util.Objects;
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2Connection.java b/libs/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
similarity index 94%
rename from obd2-lib/src/com/android/car/obd2/Obd2Connection.java
rename to libs/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
index cef1539..7efbeea 100644
--- a/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
@@ -30,7 +30,7 @@
/** This class represents a connection between Java code and a "vehicle" that talks OBD2. */
public class Obd2Connection {
private static final String TAG = Obd2Connection.class.getSimpleName();
- private static final boolean DBG = false;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
/**
* The transport layer that moves OBD2 requests from us to the remote entity and viceversa. It
@@ -50,7 +50,7 @@
private final UnderlyingTransport mConnection;
- private static final String[] initCommands =
+ private static final String[] INIT_COMMANDS =
new String[] {"ATD", "ATZ", "AT E0", "AT L0", "AT S0", "AT H0", "AT SP 0"};
public Obd2Connection(UnderlyingTransport connection) {
@@ -63,7 +63,7 @@
}
private void runInitCommands() {
- for (final String initCommand : initCommands) {
+ for (final String initCommand : INIT_COMMANDS) {
try {
runImpl(initCommand);
} catch (IOException | InterruptedException e) {
@@ -169,8 +169,9 @@
String responseValue = runImpl(command);
String originalResponseValue = responseValue;
String unspacedCommand = command.replaceAll(" ", "");
- if (responseValue.startsWith(unspacedCommand))
+ if (responseValue.startsWith(unspacedCommand)) {
responseValue = responseValue.substring(unspacedCommand.length());
+ }
responseValue = unpackLongFrame(responseValue);
if (DBG) {
@@ -208,7 +209,7 @@
}
static class FourByteBitSet {
- private static final int[] masks =
+ private static final int[] MASKS =
new int[] {
0b0000_0001,
0b0000_0010,
@@ -248,9 +249,10 @@
}
private boolean getBit(byte b, int index) {
- if (index < 0 || index >= masks.length)
+ if (index < 0 || index >= MASKS.length) {
throw new IllegalArgumentException(index + " is not a valid bit index");
- return 0 != (b & masks[index]);
+ }
+ return 0 != (b & MASKS[index]);
}
public boolean getBit(int b, int index) {
@@ -271,7 +273,7 @@
byte byte3 = (byte) (responseData[5] & 0xFF);
if (DBG) {
Log.i(TAG, String.format("supported PID at base %d payload %02X%02X%02X%02X",
- basePid, byte0, byte1, byte2, byte3));
+ basePid, byte0, byte1, byte2, byte3));
}
FourByteBitSet fourByteBitSet = new FourByteBitSet(byte0, byte1, byte2, byte3);
for (int byteIndex = 0; byteIndex < 4; ++byteIndex) {
@@ -324,9 +326,12 @@
List<String> result = new ArrayList<>();
int[] response = run("03");
IntegerArrayStream stream = new IntegerArrayStream(response);
- if (stream.isEmpty()) return result;
- if (!stream.expect(0x43))
+ if (stream.isEmpty()) {
+ return result;
+ }
+ if (!stream.expect(0x43)) {
throw new IllegalArgumentException("data from remote end not a mode 3 response");
+ }
int count = stream.consume();
for (int i = 0; i < count; ++i) {
result.add(getDiagnosticTroubleCode(stream));
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2FrameGeneratorUtils.java b/libs/obd2-lib/src/com/android/car/obd2/Obd2FrameGeneratorUtils.java
similarity index 100%
rename from obd2-lib/src/com/android/car/obd2/Obd2FrameGeneratorUtils.java
rename to libs/obd2-lib/src/com/android/car/obd2/Obd2FrameGeneratorUtils.java
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java b/libs/obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java
similarity index 100%
rename from obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java
rename to libs/obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2LiveFrameGenerator.java b/libs/obd2-lib/src/com/android/car/obd2/Obd2LiveFrameGenerator.java
similarity index 100%
rename from obd2-lib/src/com/android/car/obd2/Obd2LiveFrameGenerator.java
rename to libs/obd2-lib/src/com/android/car/obd2/Obd2LiveFrameGenerator.java
diff --git a/obd2-lib/src/com/android/car/obd2/commands/AmbientAirTemperature.java b/libs/obd2-lib/src/com/android/car/obd2/commands/AmbientAirTemperature.java
similarity index 99%
rename from obd2-lib/src/com/android/car/obd2/commands/AmbientAirTemperature.java
rename to libs/obd2-lib/src/com/android/car/obd2/commands/AmbientAirTemperature.java
index d864855..931d2a8 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/AmbientAirTemperature.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/commands/AmbientAirTemperature.java
@@ -18,6 +18,7 @@
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
+
import java.util.Optional;
public class AmbientAirTemperature implements Obd2Command.OutputSemanticHandler<Float> {
diff --git a/obd2-lib/src/com/android/car/obd2/commands/CalculatedEngineLoad.java b/libs/obd2-lib/src/com/android/car/obd2/commands/CalculatedEngineLoad.java
similarity index 99%
rename from obd2-lib/src/com/android/car/obd2/commands/CalculatedEngineLoad.java
rename to libs/obd2-lib/src/com/android/car/obd2/commands/CalculatedEngineLoad.java
index ab40df0..ed67641 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/CalculatedEngineLoad.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/commands/CalculatedEngineLoad.java
@@ -18,6 +18,7 @@
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
+
import java.util.Optional;
public class CalculatedEngineLoad implements Obd2Command.OutputSemanticHandler<Float> {
diff --git a/obd2-lib/src/com/android/car/obd2/commands/EngineCoolantTemperature.java b/libs/obd2-lib/src/com/android/car/obd2/commands/EngineCoolantTemperature.java
similarity index 99%
rename from obd2-lib/src/com/android/car/obd2/commands/EngineCoolantTemperature.java
rename to libs/obd2-lib/src/com/android/car/obd2/commands/EngineCoolantTemperature.java
index 7cd2f9a..963c48c 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/EngineCoolantTemperature.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/commands/EngineCoolantTemperature.java
@@ -18,6 +18,7 @@
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
+
import java.util.Optional;
public class EngineCoolantTemperature implements Obd2Command.OutputSemanticHandler<Integer> {
diff --git a/obd2-lib/src/com/android/car/obd2/commands/EngineOilTemperature.java b/libs/obd2-lib/src/com/android/car/obd2/commands/EngineOilTemperature.java
similarity index 99%
rename from obd2-lib/src/com/android/car/obd2/commands/EngineOilTemperature.java
rename to libs/obd2-lib/src/com/android/car/obd2/commands/EngineOilTemperature.java
index e47b339..dc560d4 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/EngineOilTemperature.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/commands/EngineOilTemperature.java
@@ -18,6 +18,7 @@
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
+
import java.util.Optional;
public class EngineOilTemperature implements Obd2Command.OutputSemanticHandler<Integer> {
diff --git a/obd2-lib/src/com/android/car/obd2/commands/EngineRuntime.java b/libs/obd2-lib/src/com/android/car/obd2/commands/EngineRuntime.java
similarity index 99%
rename from obd2-lib/src/com/android/car/obd2/commands/EngineRuntime.java
rename to libs/obd2-lib/src/com/android/car/obd2/commands/EngineRuntime.java
index a1b153a..b0a05c5 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/EngineRuntime.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/commands/EngineRuntime.java
@@ -18,6 +18,7 @@
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
+
import java.util.Optional;
public class EngineRuntime implements Obd2Command.OutputSemanticHandler<Integer> {
diff --git a/obd2-lib/src/com/android/car/obd2/commands/FuelGaugePressure.java b/libs/obd2-lib/src/com/android/car/obd2/commands/FuelGaugePressure.java
similarity index 99%
rename from obd2-lib/src/com/android/car/obd2/commands/FuelGaugePressure.java
rename to libs/obd2-lib/src/com/android/car/obd2/commands/FuelGaugePressure.java
index c3b49fd..69bd8f2 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/FuelGaugePressure.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/commands/FuelGaugePressure.java
@@ -18,6 +18,7 @@
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
+
import java.util.Optional;
public class FuelGaugePressure implements Obd2Command.OutputSemanticHandler<Integer> {
diff --git a/obd2-lib/src/com/android/car/obd2/commands/FuelSystemStatus.java b/libs/obd2-lib/src/com/android/car/obd2/commands/FuelSystemStatus.java
similarity index 99%
rename from obd2-lib/src/com/android/car/obd2/commands/FuelSystemStatus.java
rename to libs/obd2-lib/src/com/android/car/obd2/commands/FuelSystemStatus.java
index 67ca622..a38cd0a 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/FuelSystemStatus.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/commands/FuelSystemStatus.java
@@ -18,6 +18,7 @@
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
+
import java.util.Optional;
public class FuelSystemStatus implements Obd2Command.OutputSemanticHandler<Integer> {
diff --git a/obd2-lib/src/com/android/car/obd2/commands/FuelTankLevel.java b/libs/obd2-lib/src/com/android/car/obd2/commands/FuelTankLevel.java
similarity index 99%
rename from obd2-lib/src/com/android/car/obd2/commands/FuelTankLevel.java
rename to libs/obd2-lib/src/com/android/car/obd2/commands/FuelTankLevel.java
index d3e4efc..fe08572 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/FuelTankLevel.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/commands/FuelTankLevel.java
@@ -18,6 +18,7 @@
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
+
import java.util.Optional;
public class FuelTankLevel implements Obd2Command.OutputSemanticHandler<Float> {
diff --git a/obd2-lib/src/com/android/car/obd2/commands/FuelTrimCommand.java b/libs/obd2-lib/src/com/android/car/obd2/commands/FuelTrimCommand.java
similarity index 99%
rename from obd2-lib/src/com/android/car/obd2/commands/FuelTrimCommand.java
rename to libs/obd2-lib/src/com/android/car/obd2/commands/FuelTrimCommand.java
index 5ceb002..ca1f1b1 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/FuelTrimCommand.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/commands/FuelTrimCommand.java
@@ -18,6 +18,7 @@
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
+
import java.util.Optional;
public abstract class FuelTrimCommand implements Obd2Command.OutputSemanticHandler<Float> {
diff --git a/obd2-lib/src/com/android/car/obd2/commands/RPM.java b/libs/obd2-lib/src/com/android/car/obd2/commands/RPM.java
similarity index 99%
rename from obd2-lib/src/com/android/car/obd2/commands/RPM.java
rename to libs/obd2-lib/src/com/android/car/obd2/commands/RPM.java
index a062e97..a9dc53f 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/RPM.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/commands/RPM.java
@@ -18,6 +18,7 @@
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
+
import java.util.Optional;
public class RPM implements Obd2Command.OutputSemanticHandler<Integer> {
diff --git a/obd2-lib/src/com/android/car/obd2/commands/Speed.java b/libs/obd2-lib/src/com/android/car/obd2/commands/Speed.java
similarity index 99%
rename from obd2-lib/src/com/android/car/obd2/commands/Speed.java
rename to libs/obd2-lib/src/com/android/car/obd2/commands/Speed.java
index b110887..346ee7c 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/Speed.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/commands/Speed.java
@@ -18,6 +18,7 @@
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
+
import java.util.Optional;
public class Speed implements Obd2Command.OutputSemanticHandler<Integer> {
diff --git a/obd2-lib/src/com/android/car/obd2/commands/ThrottlePosition.java b/libs/obd2-lib/src/com/android/car/obd2/commands/ThrottlePosition.java
similarity index 99%
rename from obd2-lib/src/com/android/car/obd2/commands/ThrottlePosition.java
rename to libs/obd2-lib/src/com/android/car/obd2/commands/ThrottlePosition.java
index fea44df..084a573 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/ThrottlePosition.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/commands/ThrottlePosition.java
@@ -18,6 +18,7 @@
import com.android.car.obd2.IntegerArrayStream;
import com.android.car.obd2.Obd2Command;
+
import java.util.Optional;
public class ThrottlePosition implements Obd2Command.OutputSemanticHandler<Float> {
diff --git a/obd2-lib/src/com/android/car/obd2/connections/BluetoothConnection.java b/libs/obd2-lib/src/com/android/car/obd2/connections/BluetoothConnection.java
similarity index 98%
rename from obd2-lib/src/com/android/car/obd2/connections/BluetoothConnection.java
rename to libs/obd2-lib/src/com/android/car/obd2/connections/BluetoothConnection.java
index 2d1a13e..3ead6cd 100644
--- a/obd2-lib/src/com/android/car/obd2/connections/BluetoothConnection.java
+++ b/libs/obd2-lib/src/com/android/car/obd2/connections/BluetoothConnection.java
@@ -20,7 +20,9 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;
+
import com.android.car.obd2.Obd2Connection;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -32,7 +34,8 @@
/**
* This is the well-known UUID for the Bluetooth SPP (Serial Port Profile)
*/
- private static final UUID SERIAL_PORT_PROFILE = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
+ private static final UUID SERIAL_PORT_PROFILE = UUID.fromString(
+ "00001101-0000-1000-8000-00805F9B34FB");
private final BluetoothDevice mDevice;
private BluetoothSocket mSocket = null;
diff --git a/procfs-inspector/client/Android.bp b/libs/procfs-inspector/client/Android.bp
similarity index 100%
rename from procfs-inspector/client/Android.bp
rename to libs/procfs-inspector/client/Android.bp
diff --git a/procfs-inspector/client/src/com/android/car/procfsinspector/IProcfsInspector.aidl b/libs/procfs-inspector/client/src/com/android/car/procfsinspector/IProcfsInspector.aidl
similarity index 100%
rename from procfs-inspector/client/src/com/android/car/procfsinspector/IProcfsInspector.aidl
rename to libs/procfs-inspector/client/src/com/android/car/procfsinspector/IProcfsInspector.aidl
diff --git a/procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.aidl b/libs/procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.aidl
similarity index 100%
rename from procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.aidl
rename to libs/procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.aidl
diff --git a/procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.java b/libs/procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.java
similarity index 82%
rename from procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.java
rename to libs/procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.java
index 413e380..1808c9a 100644
--- a/procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.java
+++ b/libs/procfs-inspector/client/src/com/android/car/procfsinspector/ProcessInfo.java
@@ -27,15 +27,15 @@
@Deprecated
public class ProcessInfo implements Parcelable {
public static final Parcelable.Creator<ProcessInfo> CREATOR =
- new Parcelable.Creator<ProcessInfo>() {
- public ProcessInfo createFromParcel(Parcel in) {
- return new ProcessInfo(in);
- }
+ new Parcelable.Creator<ProcessInfo>() {
+ public ProcessInfo createFromParcel(Parcel in) {
+ return new ProcessInfo(in);
+ }
- public ProcessInfo[] newArray(int size) {
- return new ProcessInfo[size];
- }
- };
+ public ProcessInfo[] newArray(int size) {
+ return new ProcessInfo[size];
+ }
+ };
public final int pid;
public final int uid;
@@ -64,7 +64,7 @@
@Override
public boolean equals(Object other) {
if (other instanceof ProcessInfo) {
- ProcessInfo processInfo = (ProcessInfo)other;
+ ProcessInfo processInfo = (ProcessInfo) other;
return processInfo.pid == pid && processInfo.uid == uid;
}
diff --git a/procfs-inspector/client/src/com/android/car/procfsinspector/ProcfsInspector.java b/libs/procfs-inspector/client/src/com/android/car/procfsinspector/ProcfsInspector.java
similarity index 100%
rename from procfs-inspector/client/src/com/android/car/procfsinspector/ProcfsInspector.java
rename to libs/procfs-inspector/client/src/com/android/car/procfsinspector/ProcfsInspector.java
diff --git a/procfs-inspector/server/Android.bp b/libs/procfs-inspector/server/Android.bp
similarity index 100%
rename from procfs-inspector/server/Android.bp
rename to libs/procfs-inspector/server/Android.bp
diff --git a/procfs-inspector/server/com.android.car.procfsinspector.rc b/libs/procfs-inspector/server/com.android.car.procfsinspector.rc
similarity index 100%
rename from procfs-inspector/server/com.android.car.procfsinspector.rc
rename to libs/procfs-inspector/server/com.android.car.procfsinspector.rc
diff --git a/procfs-inspector/server/directory.cpp b/libs/procfs-inspector/server/directory.cpp
similarity index 100%
rename from procfs-inspector/server/directory.cpp
rename to libs/procfs-inspector/server/directory.cpp
diff --git a/procfs-inspector/server/directory.h b/libs/procfs-inspector/server/directory.h
similarity index 82%
rename from procfs-inspector/server/directory.h
rename to libs/procfs-inspector/server/directory.h
index 19528fe..902f371 100644
--- a/procfs-inspector/server/directory.h
+++ b/libs/procfs-inspector/server/directory.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef CAR_PROCFS_DIRECTORY
-#define CAR_PROCFS_DIRECTORY
+#ifndef LIBS_PROCFS_INSPECTOR_SERVER_DIRECTORY_H_
+#define LIBS_PROCFS_INSPECTOR_SERVER_DIRECTORY_H_
#include <dirent.h>
#include <sys/stat.h>
@@ -26,12 +26,11 @@
#include <string>
namespace procfsinspector {
-
class Directory {
public:
class Entry {
public:
- Entry(std::string parent = "", std::string child = "");
+ explicit Entry(std::string parent = "", std::string child = "");
const std::string& getChild() { return mChild; }
std::string str();
@@ -49,7 +48,7 @@
std::string mChild;
};
- Directory(const char* path);
+ explicit Directory(const char* path);
Entry next(unsigned char type = DT_UNKNOWN);
@@ -64,6 +63,6 @@
std::unique_ptr<DIR, Deleter> mDirectory;
};
-}
+} // namespace procfsinspector
-#endif // CAR_PROCFS_DIRECTORY
\ No newline at end of file
+#endif // LIBS_PROCFS_INSPECTOR_SERVER_DIRECTORY_H_
diff --git a/procfs-inspector/server/impl.cpp b/libs/procfs-inspector/server/impl.cpp
similarity index 100%
rename from procfs-inspector/server/impl.cpp
rename to libs/procfs-inspector/server/impl.cpp
diff --git a/procfs-inspector/server/main.cpp b/libs/procfs-inspector/server/main.cpp
similarity index 97%
rename from procfs-inspector/server/main.cpp
rename to libs/procfs-inspector/server/main.cpp
index 1555aa3..3e835f9 100644
--- a/procfs-inspector/server/main.cpp
+++ b/libs/procfs-inspector/server/main.cpp
@@ -23,9 +23,7 @@
#include <utils/Log.h>
using namespace android;
-
-int main(int, char**)
-{
+int main(int, char **) {
ALOGI("starting " LOG_TAG);
signal(SIGPIPE, SIG_IGN);
diff --git a/libs/procfs-inspector/server/process.cpp b/libs/procfs-inspector/server/process.cpp
new file mode 100644
index 0000000..26af398
--- /dev/null
+++ b/libs/procfs-inspector/server/process.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "process.h"
+
+#include <binder/Parcel.h>
+
+status_t procfsinspector::ProcessInfo::writeToParcel(Parcel* parcel) const {
+ parcel->writeUint32(mPid);
+ parcel->writeUint32(mUid);
+ return android::OK;
+}
+
+status_t procfsinspector::ProcessInfo::readFromParcel(const Parcel* parcel) {
+ mPid = parcel->readUint32();
+ mUid = parcel->readUint32();
+ return android::OK;
+}
diff --git a/libs/procfs-inspector/server/process.h b/libs/procfs-inspector/server/process.h
new file mode 100644
index 0000000..ea2b77b
--- /dev/null
+++ b/libs/procfs-inspector/server/process.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LIBS_PROCFS_INSPECTOR_SERVER_PROCESS_H_
+#define LIBS_PROCFS_INSPECTOR_SERVER_PROCESS_H_
+
+#include <sys/types.h>
+
+#include <binder/Parcelable.h>
+
+using namespace android;
+
+namespace procfsinspector {
+class ProcessInfo : public Parcelable {
+public:
+ pid_t getPid() { return mPid; }
+ uid_t getUid() { return mUid; }
+
+ // default initialize to invalid values
+ explicit ProcessInfo(pid_t pid = -1, uid_t uid = -1) : mPid(pid), mUid(uid) {}
+
+ status_t writeToParcel(Parcel *parcel) const override;
+ status_t readFromParcel(const Parcel *parcel) override;
+
+private:
+ pid_t mPid;
+ uid_t mUid;
+};
+} // namespace procfsinspector
+
+#endif // LIBS_PROCFS_INSPECTOR_SERVER_PROCESS_H_
diff --git a/procfs-inspector/server/server.cpp b/libs/procfs-inspector/server/server.cpp
similarity index 89%
rename from procfs-inspector/server/server.cpp
rename to libs/procfs-inspector/server/server.cpp
index 402b37b..5eb59ed 100644
--- a/procfs-inspector/server/server.cpp
+++ b/libs/procfs-inspector/server/server.cpp
@@ -38,17 +38,16 @@
namespace procfsinspector {
class BpProcfsInspector: public BpInterface<IProcfsInspector> {
public:
- BpProcfsInspector(sp<IBinder> binder) : BpInterface<IProcfsInspector>(binder) {}
+ explicit BpProcfsInspector(sp<IBinder> binder) : BpInterface<IProcfsInspector>(binder) {}
- virtual std::vector<ProcessInfo> readProcessTable() override {
+ std::vector<ProcessInfo> readProcessTable() override {
Parcel data, reply;
remote()->transact((uint32_t)IProcfsInspector::Call::READ_PROCESS_TABLE, data, &reply);
- std::vector<procfsinspector::ProcessInfo> result;
+ std::vector<ProcessInfo> result;
reply.readParcelableVector(&result);
return result;
}
-
};
IMPLEMENT_META_INTERFACE(ProcfsInspector, "com.android.car.procfsinspector.IProcfsInspector");
@@ -70,4 +69,4 @@
return BBinder::onTransact(code, data, reply, flags);
}
-}
+} // namespace procfsinspector
diff --git a/libs/procfs-inspector/server/server.h b/libs/procfs-inspector/server/server.h
new file mode 100644
index 0000000..70b9b35
--- /dev/null
+++ b/libs/procfs-inspector/server/server.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LIBS_PROCFS_INSPECTOR_SERVER_SERVER_H_
+#define LIBS_PROCFS_INSPECTOR_SERVER_SERVER_H_
+
+#define LOG_TAG "com.android.car.procfsinspector"
+#define SERVICE_NAME "com.android.car.procfsinspector"
+
+#include <vector>
+
+#include <binder/Parcel.h>
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+#include <binder/IBinder.h>
+#include <binder/IInterface.h>
+
+#include <utils/Log.h>
+#include <utils/String16.h>
+
+#include "process.h"
+
+using namespace android;
+
+namespace procfsinspector {
+class IProcfsInspector : public IInterface {
+public:
+DECLARE_META_INTERFACE(ProcfsInspector);
+
+ enum class Call : uint32_t {
+ READ_PROCESS_TABLE = IBinder::FIRST_CALL_TRANSACTION,
+ };
+
+ // API declarations start here
+ virtual std::vector<ProcessInfo> readProcessTable() = 0;
+};
+
+class Impl : public BnInterface<IProcfsInspector> {
+public:
+ status_t onTransact(uint32_t code,
+ const Parcel &data,
+ Parcel *reply,
+ uint32_t flags) override;
+
+ std::vector<ProcessInfo> readProcessTable() override;
+};
+} // namespace procfsinspector
+#endif // LIBS_PROCFS_INSPECTOR_SERVER_SERVER_H_
diff --git a/vehicle-hal-support-lib/Android.bp b/libs/vehicle-hal-support-lib/Android.bp
similarity index 95%
rename from vehicle-hal-support-lib/Android.bp
rename to libs/vehicle-hal-support-lib/Android.bp
index 2478d1e..fa953fe 100644
--- a/vehicle-hal-support-lib/Android.bp
+++ b/libs/vehicle-hal-support-lib/Android.bp
@@ -14,6 +14,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/vehicle-hal-support-lib/src/com/android/car/hal/test/AidlVehiclePropConfigBuilder.java b/libs/vehicle-hal-support-lib/src/com/android/car/hal/test/AidlVehiclePropConfigBuilder.java
similarity index 100%
rename from vehicle-hal-support-lib/src/com/android/car/hal/test/AidlVehiclePropConfigBuilder.java
rename to libs/vehicle-hal-support-lib/src/com/android/car/hal/test/AidlVehiclePropConfigBuilder.java
diff --git a/vehicle-hal-support-lib/src/com/android/car/hal/test/AidlVehiclePropValueBuilder.java b/libs/vehicle-hal-support-lib/src/com/android/car/hal/test/AidlVehiclePropValueBuilder.java
similarity index 100%
rename from vehicle-hal-support-lib/src/com/android/car/hal/test/AidlVehiclePropValueBuilder.java
rename to libs/vehicle-hal-support-lib/src/com/android/car/hal/test/AidlVehiclePropValueBuilder.java
diff --git a/vehicle-hal-support-lib/src/com/android/car/hal/test/DiagnosticEventBuilder.java b/libs/vehicle-hal-support-lib/src/com/android/car/hal/test/DiagnosticEventBuilder.java
similarity index 100%
rename from vehicle-hal-support-lib/src/com/android/car/hal/test/DiagnosticEventBuilder.java
rename to libs/vehicle-hal-support-lib/src/com/android/car/hal/test/DiagnosticEventBuilder.java
diff --git a/vehicle-hal-support-lib/src/com/android/car/hal/test/DiagnosticJson.java b/libs/vehicle-hal-support-lib/src/com/android/car/hal/test/DiagnosticJson.java
similarity index 100%
rename from vehicle-hal-support-lib/src/com/android/car/hal/test/DiagnosticJson.java
rename to libs/vehicle-hal-support-lib/src/com/android/car/hal/test/DiagnosticJson.java
diff --git a/vehicle-hal-support-lib/src/com/android/car/hal/test/DiagnosticJsonReader.java b/libs/vehicle-hal-support-lib/src/com/android/car/hal/test/DiagnosticJsonReader.java
similarity index 100%
rename from vehicle-hal-support-lib/src/com/android/car/hal/test/DiagnosticJsonReader.java
rename to libs/vehicle-hal-support-lib/src/com/android/car/hal/test/DiagnosticJsonReader.java
diff --git a/vehicle-hal-support-lib/src/com/android/car/hal/test/HidlVehiclePropConfigBuilder.java b/libs/vehicle-hal-support-lib/src/com/android/car/hal/test/HidlVehiclePropConfigBuilder.java
similarity index 100%
rename from vehicle-hal-support-lib/src/com/android/car/hal/test/HidlVehiclePropConfigBuilder.java
rename to libs/vehicle-hal-support-lib/src/com/android/car/hal/test/HidlVehiclePropConfigBuilder.java
diff --git a/vehicle-hal-support-lib/src/com/android/car/hal/test/HidlVehiclePropValueBuilder.java b/libs/vehicle-hal-support-lib/src/com/android/car/hal/test/HidlVehiclePropValueBuilder.java
similarity index 100%
rename from vehicle-hal-support-lib/src/com/android/car/hal/test/HidlVehiclePropValueBuilder.java
rename to libs/vehicle-hal-support-lib/src/com/android/car/hal/test/HidlVehiclePropValueBuilder.java
diff --git a/vehicle-hal-support-lib/src/com/android/car/hal/test/Utils.java b/libs/vehicle-hal-support-lib/src/com/android/car/hal/test/Utils.java
similarity index 100%
rename from vehicle-hal-support-lib/src/com/android/car/hal/test/Utils.java
rename to libs/vehicle-hal-support-lib/src/com/android/car/hal/test/Utils.java
diff --git a/packages/CarActivityResolver/AndroidManifest.xml b/packages/CarActivityResolver/AndroidManifest.xml
index 9ffb5b1..9105e7c 100644
--- a/packages/CarActivityResolver/AndroidManifest.xml
+++ b/packages/CarActivityResolver/AndroidManifest.xml
@@ -41,7 +41,8 @@
flow. -->
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
- <application android:label="@string/app_name">
+ <application android:label="@string/app_name"
+ android:theme="@style/Theme.Transparent">
<activity
android:name=".CarResolverActivity"
diff --git a/car_product/rro/overlay-visual/androidRRO/res/layout/resolver_list.xml b/packages/CarActivityResolver/res/layout/resolver_list.xml
similarity index 100%
rename from car_product/rro/overlay-visual/androidRRO/res/layout/resolver_list.xml
rename to packages/CarActivityResolver/res/layout/resolver_list.xml
diff --git a/car_product/rro/overlay-visual/androidRRO/res/layout/resolver_list_with_default.xml b/packages/CarActivityResolver/res/layout/resolver_list_with_default.xml
similarity index 100%
rename from car_product/rro/overlay-visual/androidRRO/res/layout/resolver_list_with_default.xml
rename to packages/CarActivityResolver/res/layout/resolver_list_with_default.xml
diff --git a/packages/CarActivityResolver/res/values/dimens.xml b/packages/CarActivityResolver/res/values/dimens.xml
new file mode 100644
index 0000000..53e646d
--- /dev/null
+++ b/packages/CarActivityResolver/res/values/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+-->
+<resources>
+ <dimen name="background_blur_radius">14dp</dimen>
+
+ <!-- These values are copied from the framework so they may be referenced in this app -->
+ <dimen name="resolver_list_item_height">@*android:dimen/car_activity_resolver_list_item_height</dimen>
+ <dimen name="resolver_list_max_height">@*android:dimen/car_activity_resolver_list_max_height</dimen>
+</resources>
diff --git a/packages/CarActivityResolver/res/values/themes.xml b/packages/CarActivityResolver/res/values/themes.xml
new file mode 100644
index 0000000..d3a1ed8
--- /dev/null
+++ b/packages/CarActivityResolver/res/values/themes.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Theme for transparent activity -->
+ <style name="Theme.Transparent" parent="Theme.CarUi.NoToolbar">
+ <item name="carUiBaseLayout">false</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowTranslucentStatus">true</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ </style>
+</resources>
diff --git a/packages/CarActivityResolver/src/com/android/car/activityresolver/CarResolverActivity.java b/packages/CarActivityResolver/src/com/android/car/activityresolver/CarResolverActivity.java
index 5642867..0462de2 100644
--- a/packages/CarActivityResolver/src/com/android/car/activityresolver/CarResolverActivity.java
+++ b/packages/CarActivityResolver/src/com/android/car/activityresolver/CarResolverActivity.java
@@ -21,9 +21,15 @@
import android.view.ViewTreeObserver;
import android.widget.ListView;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowCompat;
+import androidx.core.view.WindowInsetsCompat;
+
import com.android.internal.R;
import com.android.internal.app.ResolverActivity;
import com.android.internal.app.ResolverViewPager;
+import com.android.internal.widget.ResolverDrawerLayout;
/**
* An automotive variant of the resolver activity which does not use the safe forwarding mode and
@@ -42,12 +48,35 @@
mProfilePager = findViewById(R.id.profile_pager);
mProfilePager.getViewTreeObserver().addOnGlobalLayoutListener(this);
+
+ ((ResolverDrawerLayout) findViewById(R.id.contentPanel)).setShowAtTop(true);
+ setupSystemBarInsets();
+ getWindow().setBackgroundBlurRadius(getResources().getDimensionPixelSize(
+ com.android.car.activityresolver.R.dimen.background_blur_radius));
+ }
+
+ /**
+ * Override to use corresponding Car optimized layouts (supporting rotary) for content view.
+ */
+ @Override
+ public void setContentView(int layoutResID) {
+ int carLayoutResId = layoutResID;
+ switch (layoutResID) {
+ case R.layout.resolver_list:
+ carLayoutResId = com.android.car.activityresolver.R.layout.resolver_list;
+ break;
+ case R.layout.resolver_list_with_default:
+ carLayoutResId =
+ com.android.car.activityresolver.R.layout.resolver_list_with_default;
+ break;
+ }
+
+ super.setContentView(carLayoutResId);
}
@Override
protected void onDestroy() {
mProfilePager.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-
super.onDestroy();
}
@@ -67,7 +96,35 @@
listView.performItemClick(view, position, id);
});
}
+
+ // Set a max height on the list of apps so that the list does not cut off the "Just
+ // Once"/"Always" buttons of the activity
+ int resolverListMaxHeight = getResources().getDimensionPixelSize(
+ com.android.car.activityresolver.R.dimen.resolver_list_max_height);
+ // The activity has a preselected default app option, so subract that height from max
+ // height which is applied to the list of app choices
+ if (useLayoutWithDefault()) {
+ resolverListMaxHeight -= getResources().getDimensionPixelSize(
+ com.android.car.activityresolver.R.dimen.resolver_list_item_height);
+ }
+ if (listView.getHeight() > resolverListMaxHeight) {
+ listView.getLayoutParams().height = resolverListMaxHeight;
+ listView.setLayoutParams(listView.getLayoutParams());
+ }
}
}
-}
+ private void setupSystemBarInsets() {
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content),
+ (v, windowInsets) -> {
+ Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
+ // Apply the insets paddings to the view.
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+
+ // Return CONSUMED if you don't want the window insets to keep being
+ // passed down to descendant views.
+ return WindowInsetsCompat.CONSUMED;
+ });
+ }
+}
diff --git a/packages/CarDeveloperOptions/AndroidManifest.xml b/packages/CarDeveloperOptions/AndroidManifest.xml
index d0edf46..393bc82 100644
--- a/packages/CarDeveloperOptions/AndroidManifest.xml
+++ b/packages/CarDeveloperOptions/AndroidManifest.xml
@@ -35,7 +35,6 @@
<activity
android:name=".CarDevelopmentSettingsDashboardActivity"
- android:enabled="false"
android:exported="true"
android:icon="@drawable/ic_settings_development"
android:label="@string/development_settings_title"
@@ -2881,6 +2880,79 @@
<intent-filter tools:node="removeAll"/>
</activity>
+ <activity
+ android:name="com.android.settings.Settings$CellularSecuritySettingsActivity"
+ android:enabled="false"
+ android:exported="false"
+ tools:node="merge"
+ tools:replace="android:exported">
+ <intent-filter tools:node="removeAll"/>
+ </activity>
+
+ <activity
+ android:name="com.android.settings.Settings$HearingDevicesActivity"
+ android:enabled="false"
+ android:exported="false"
+ tools:node="merge"
+ tools:replace="android:exported">
+ <intent-filter tools:node="removeAll"/>
+ </activity>
+
+ <activity
+ android:name="com.android.settings.Settings$HearingDevicesPairingActivity"
+ android:enabled="false"
+ android:exported="false"
+ tools:node="merge"
+ tools:replace="android:exported">
+ <intent-filter tools:node="removeAll"/>
+ </activity>
+
+ <activity
+ android:name="com.android.settings.Settings$ColorContrastActivity"
+ android:enabled="false"
+ android:exported="false"
+ tools:node="merge"
+ tools:replace="android:exported">
+ <intent-filter tools:node="removeAll"/>
+ </activity>
+
+
+ <activity
+ android:name="com.android.settings.Settings$BluetoothDashboardActivity"
+ android:enabled="false"
+ android:exported="false"
+ tools:node="merge"
+ tools:replace="android:exported">
+ <intent-filter tools:node="removeAll"/>
+ </activity>
+
+ <activity
+ android:name="com.android.settings.connecteddevice.audiosharing.AudioSharingActivity"
+ android:enabled="false"
+ android:exported="false"
+ tools:node="merge"
+ tools:replace="android:exported">
+ <intent-filter tools:node="removeAll"/>
+ </activity>
+
+ <activity
+ android:name="com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode.QrCodeScanModeActivity"
+ android:enabled="false"
+ android:exported="false"
+ tools:node="merge"
+ tools:replace="android:exported">
+ <intent-filter tools:node="removeAll"/>
+ </activity>
+
+ <activity
+ android:name="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamConfirmDialogActivity"
+ android:enabled="false"
+ android:exported="false"
+ tools:node="merge"
+ tools:replace="android:exported">
+ <intent-filter tools:node="removeAll"/>
+ </activity>
+
<activity-alias
android:name="com.android.settings.ConfirmDeviceCredentialActivity"
android:enabled="false"
@@ -3186,6 +3258,32 @@
<intent-filter tools:node="removeAll"/>
</receiver>
+ <receiver
+ android:name="com.android.settings.privatespace.PrivateSpaceBroadcastReceiver"
+ android:enabled="false"
+ android:exported="false"
+ tools:node="merge"
+ tools:replace="android:exported">
+ <intent-filter tools:node="removeAll"/>
+ </receiver>
+
+ <receiver
+ android:name="com.android.settings.development.Enable16KBootReceiver"
+ android:enabled="false"
+ android:exported="false"
+ tools:node="merge"
+ tools:replace="android:enabled,android:exported">
+ <intent-filter tools:node="removeAll"/>
+ </receiver>
+
+ <receiver android:name="com.android.settings.connecteddevice.audiosharing.AudioSharingReceiver"
+ android:exported="false"
+ android:enabled="false"
+ tools:node="merge"
+ tools:replace="android:exported">
+ <intent-filter tools:node="removeAll"/>
+ </receiver>
+
<service
android:name="com.android.settings.wifi.tether.TetherService"
android:enabled="false"
@@ -3312,5 +3410,14 @@
<intent-filter tools:node="removeAll"/>
</service>
+ <service
+ android:name="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService"
+ android:enabled="false"
+ android:exported="false"
+ tools:node="merge"
+ tools:replace="android:exported,android:enabled">
+ <intent-filter tools:node="removeAll"/>
+ </service>
+
</application>
</manifest>
diff --git a/packages/CarDeveloperOptions/OWNERS b/packages/CarDeveloperOptions/OWNERS
index d635a9e..33a35a9 100644
--- a/packages/CarDeveloperOptions/OWNERS
+++ b/packages/CarDeveloperOptions/OWNERS
@@ -1,7 +1,9 @@
# People who can approve changes for submission.
# Primary
-alexstetson@google.com
+eschiang@google.com
+cassieyw@google.com
# Secondary (only if people in Primary are unreachable)
-nehah@google.com
+dnek@google.com
+alexstetson@google.com
diff --git a/packages/CarDeveloperOptions/tests/unit/Android.bp b/packages/CarDeveloperOptions/tests/unit/Android.bp
index 153016d..fec5027 100644
--- a/packages/CarDeveloperOptions/tests/unit/Android.bp
+++ b/packages/CarDeveloperOptions/tests/unit/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_system_experience",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/packages/CarDeveloperOptions/tests/unit/src/com/android/car/developeroptions/CarDeveloperOptionsIntentTest.java b/packages/CarDeveloperOptions/tests/unit/src/com/android/car/developeroptions/CarDeveloperOptionsIntentTest.java
index 066af56..cf1db82 100644
--- a/packages/CarDeveloperOptions/tests/unit/src/com/android/car/developeroptions/CarDeveloperOptionsIntentTest.java
+++ b/packages/CarDeveloperOptions/tests/unit/src/com/android/car/developeroptions/CarDeveloperOptionsIntentTest.java
@@ -37,8 +37,7 @@
@RunWith(AndroidJUnit4.class)
public class CarDeveloperOptionsIntentTest {
private static final List<String> ACTIVITY_ALLOWLIST = Arrays.asList(
- "com.android.car.developeroptions.CarDevelopmentSettingsDashboardActivity",
- "com.android.settings.development.DevelopmentSettingsDisabledActivity");
+ "com.android.car.developeroptions.CarDevelopmentSettingsDashboardActivity");
private Context mContext = ApplicationProvider.getApplicationContext();
private PackageManager mPm;
diff --git a/packages/CarDocumentsUI/Android.bp b/packages/CarDocumentsUI/Android.bp
new file mode 100644
index 0000000..b7c3398
--- /dev/null
+++ b/packages/CarDocumentsUI/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_app {
+ name: "CarDocumentsUI",
+ defaults: ["documentsui_defaults"],
+ overrides: [
+ "DocumentsUI",
+ ],
+ manifest: "AndroidManifest.xml",
+ required: ["allowed_privapp_com.android.car.documentsui"],
+ resource_dirs: [],
+ static_libs: [
+ "DocumentsUI-lib",
+ "DocumentsUIManifestLib",
+ ],
+
+ package_name: "com.android.car.documentsui",
+ updatable: true,
+}
diff --git a/packages/CarDocumentsUI/AndroidManifest.xml b/packages/CarDocumentsUI/AndroidManifest.xml
new file mode 100644
index 0000000..8708262
--- /dev/null
+++ b/packages/CarDocumentsUI/AndroidManifest.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.car.documentsui"
+ android:versionCode="0">
+ <!-- Use AdaptApp for Back button and better DPI -->
+ <uses-feature
+ android:name="android.software.car.display_compatibility"
+ android:required="true" />
+
+ <application
+ tools:node="merge">
+ <!-- Remove launcher icons -->
+ <activity
+ android:name="com.android.documentsui.files.FilesActivity"
+ android:enabled="false"
+ android:exported="false"
+ tools:node="merge"
+ tools:replace="android:exported">
+ <intent-filter tools:node="removeAll"/>
+ </activity>
+
+ <activity
+ android:name="com.android.documentsui.files.LauncherActivity"
+ android:enabled="false"
+ android:exported="false"
+ tools:node="merge"
+ tools:replace="android:exported">
+ <intent-filter tools:node="removeAll"/>
+ </activity>
+
+ <activity-alias
+ android:name="com.android.documentsui.LauncherActivity"
+ android:targetActivity="com.android.documentsui.files.LauncherActivity"
+ tools:node="remove">
+ </activity-alias>
+ </application>
+ <uses-sdk
+ tools:overrideLibrary="com.android.documentsui" />
+</manifest>
diff --git a/packages/CarDocumentsUI/proguard.flags b/packages/CarDocumentsUI/proguard.flags
new file mode 100644
index 0000000..ce87e59
--- /dev/null
+++ b/packages/CarDocumentsUI/proguard.flags
@@ -0,0 +1,3 @@
+# include DocumentsUI proguard file
+-include ../../../../../packages/apps/DocumentsUI/proguard.flags
+
diff --git a/packages/CarManagedProvisioning/Android.bp b/packages/CarManagedProvisioning/Android.bp
index 5d6ee9a..28da3c6 100644
--- a/packages/CarManagedProvisioning/Android.bp
+++ b/packages/CarManagedProvisioning/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/packages/CarShell/AndroidManifest.xml b/packages/CarShell/AndroidManifest.xml
index 08ed097..d9ff896 100644
--- a/packages/CarShell/AndroidManifest.xml
+++ b/packages/CarShell/AndroidManifest.xml
@@ -18,6 +18,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.shell"
+ updatableSystem="false"
coreApp="true"
android:sharedUserId="android.uid.shell"
>
@@ -166,7 +167,7 @@
<!-- Permissions required for CarRemoteDeviceManagerPermissionTest -->
<uses-permission android:name="android.car.permission.MANAGE_REMOTE_DEVICE" />
<!-- Permissions required for CarPackageManagerPermissionTest -->
- <uses-permission android:name="android.car.permission.QUERY_DISPLAY_COMPATIBILITY" />
+ <uses-permission android:name="android.car.permission.MANAGE_DISPLAY_COMPATIBILITY" />
<!-- permissions required for CarRemoteAccessManagerTest -->
<uses-permission android:name="android.car.permission.CONTROL_REMOTE_ACCESS" />
<!-- permissions required for ClusterHomeManagerTest -->
@@ -175,4 +176,6 @@
<uses-permission android:name="android.car.permission.CAR_NAVIGATION_MANAGER" />
<!-- permissions required for CarProjectionManagerTest -->
<uses-permission android:name="android.car.permission.CAR_PROJECTION" />
+ <!-- permissions required for CarWifiManagerTest -->
+ <uses-permission android:name="android.car.permission.READ_PERSIST_TETHERING_SETTINGS" />
</manifest>
diff --git a/packages/ScriptExecutor/Android.bp b/packages/ScriptExecutor/Android.bp
index 2efbcfb..0771c1a 100644
--- a/packages/ScriptExecutor/Android.bp
+++ b/packages/ScriptExecutor/Android.bp
@@ -87,6 +87,7 @@
jni_uses_platform_apis: true,
+ use_embedded_native_libs: true,
apex_available: [
"//apex_available:platform",
"com.android.car.framework",
diff --git a/packages/ScriptExecutor/tests/functional/Android.bp b/packages/ScriptExecutor/tests/functional/Android.bp
index accfe45..791df88 100644
--- a/packages/ScriptExecutor/tests/functional/Android.bp
+++ b/packages/ScriptExecutor/tests/functional/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_automotive",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/packages/ScriptExecutor/tests/nonsystemuser/Android.bp b/packages/ScriptExecutor/tests/nonsystemuser/Android.bp
index d68f340..d03c0b2 100644
--- a/packages/ScriptExecutor/tests/nonsystemuser/Android.bp
+++ b/packages/ScriptExecutor/tests/nonsystemuser/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/packages/ScriptExecutor/tests/unit/Android.bp b/packages/ScriptExecutor/tests/unit/Android.bp
index 7fb2cb0..1a33b41 100644
--- a/packages/ScriptExecutor/tests/unit/Android.bp
+++ b/packages/ScriptExecutor/tests/unit/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_automotive",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/car-lib/api/released/3.txt b/prebuilts/1/public/api/android.car-removed.txt
similarity index 100%
copy from car-lib/api/released/3.txt
copy to prebuilts/1/public/api/android.car-removed.txt
diff --git a/car-lib/api/released/1.txt b/prebuilts/1/public/api/android.car.txt
similarity index 100%
rename from car-lib/api/released/1.txt
rename to prebuilts/1/public/api/android.car.txt
diff --git a/car-lib/api/released/3.txt b/prebuilts/2/public/api/android.car-removed.txt
similarity index 100%
copy from car-lib/api/released/3.txt
copy to prebuilts/2/public/api/android.car-removed.txt
diff --git a/car-lib/api/released/2.txt b/prebuilts/2/public/api/android.car.txt
similarity index 100%
rename from car-lib/api/released/2.txt
rename to prebuilts/2/public/api/android.car.txt
diff --git a/car-lib/api/released/3.txt b/prebuilts/2/system/api/android.car-removed.txt
similarity index 100%
copy from car-lib/api/released/3.txt
copy to prebuilts/2/system/api/android.car-removed.txt
diff --git a/car-lib/api/released/3.txt b/prebuilts/2/system/api/android.car.txt
similarity index 100%
rename from car-lib/api/released/3.txt
rename to prebuilts/2/system/api/android.car.txt
diff --git a/car-lib/api/released/3.txt b/prebuilts/3/module-lib/api/android.car-removed.txt
similarity index 100%
copy from car-lib/api/released/3.txt
copy to prebuilts/3/module-lib/api/android.car-removed.txt
diff --git a/car-lib/api/released/3.txt b/prebuilts/3/module-lib/api/android.car.txt
similarity index 100%
copy from car-lib/api/released/3.txt
copy to prebuilts/3/module-lib/api/android.car.txt
diff --git a/prebuilts/3/public/api/android.car-removed.txt b/prebuilts/3/public/api/android.car-removed.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/prebuilts/3/public/api/android.car-removed.txt
@@ -0,0 +1 @@
+
diff --git a/car-lib/api/released/3.txt b/prebuilts/3/public/api/android.car.txt
similarity index 100%
copy from car-lib/api/released/3.txt
copy to prebuilts/3/public/api/android.car.txt
diff --git a/car-lib/api/released/3.txt b/prebuilts/3/system/api/android.car-removed.txt
similarity index 100%
copy from car-lib/api/released/3.txt
copy to prebuilts/3/system/api/android.car-removed.txt
diff --git a/car-lib/api/released/3.txt b/prebuilts/3/system/api/android.car.txt
similarity index 100%
copy from car-lib/api/released/3.txt
copy to prebuilts/3/system/api/android.car.txt
diff --git a/car-helper-lib/Android.bp b/prebuilts/Android.bp
similarity index 67%
copy from car-helper-lib/Android.bp
copy to prebuilts/Android.bp
index a07a188..5ebb5ea 100644
--- a/car-helper-lib/Android.bp
+++ b/prebuilts/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 The Android Open Source Project
+// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,19 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// This library has common code which is used by other apps.
-//
-// NOTE: This library should not be used within p/s/Car
-
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_library {
- name: "car-helper-lib",
- srcs: [
- "src/**/*.java",
+prebuilt_apis {
+ name: "car_sdk",
+ api_dirs: [
+ "1",
+ "2",
+ "3",
],
- resource_dirs: ["res"],
- platform_apis: true,
}
diff --git a/procfs-inspector/server/process.cpp b/procfs-inspector/server/process.cpp
deleted file mode 100644
index 3c25ce2..0000000
--- a/procfs-inspector/server/process.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#include "process.h"
-
-#include <binder/Parcel.h>
-
-status_t procfsinspector::ProcessInfo::writeToParcel(Parcel* parcel) const {
- parcel->writeUint32(mPid);
- parcel->writeUint32(mUid);
- return android::OK;
-}
-
-status_t procfsinspector::ProcessInfo::readFromParcel(const Parcel* parcel) {
- mPid = parcel->readUint32();
- mUid = parcel->readUint32();
- return android::OK;
-}
diff --git a/procfs-inspector/server/process.h b/procfs-inspector/server/process.h
deleted file mode 100644
index 75b76c7..0000000
--- a/procfs-inspector/server/process.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef CAR_PROCFS_PROCESS
-#define CAR_PROCFS_PROCESS
-
-#include <sys/types.h>
-
-#include <binder/Parcelable.h>
-
-using namespace android;
-
-namespace procfsinspector {
- class ProcessInfo : public Parcelable {
- public:
- pid_t getPid() { return mPid; }
- uid_t getUid() { return mUid; }
-
- // default initialize to invalid values
- ProcessInfo(pid_t pid = -1, uid_t uid = -1) : mPid(pid), mUid(uid) {}
-
- virtual status_t writeToParcel(Parcel* parcel) const override;
- virtual status_t readFromParcel(const Parcel* parcel) override;
-
- private:
- pid_t mPid;
- uid_t mUid;
- };
-}
-
-#endif // CAR_PROCFS_PROCESS
diff --git a/procfs-inspector/server/server.h b/procfs-inspector/server/server.h
deleted file mode 100644
index ae529ff..0000000
--- a/procfs-inspector/server/server.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef CAR_PROCFS_SERVER
-#define CAR_PROCFS_SERVER
-
-#define LOG_TAG "com.android.car.procfsinspector"
-#define SERVICE_NAME "com.android.car.procfsinspector"
-
-#include <vector>
-
-#include <binder/Parcel.h>
-#include <binder/IPCThreadState.h>
-#include <binder/ProcessState.h>
-#include <binder/IServiceManager.h>
-#include <binder/IBinder.h>
-#include <binder/IInterface.h>
-
-#include <utils/Log.h>
-#include <utils/String16.h>
-
-#include "process.h"
-
-using namespace android;
-
-namespace procfsinspector {
- class IProcfsInspector : public IInterface {
- public:
- DECLARE_META_INTERFACE(ProcfsInspector);
-
- enum class Call : uint32_t {
- READ_PROCESS_TABLE = IBinder::FIRST_CALL_TRANSACTION,
- };
-
- // API declarations start here
- virtual std::vector<ProcessInfo> readProcessTable() = 0;
- };
-
- class Impl : public BnInterface<IProcfsInspector> {
- public:
- virtual status_t onTransact(uint32_t code,
- const Parcel& data,
- Parcel *reply,
- uint32_t flags) override;
- virtual std::vector<ProcessInfo> readProcessTable() override;
- };
-}
-
-#endif // CAR_PROCFS_SERVER
diff --git a/service-builtin/Android.bp b/service-builtin/Android.bp
index fcfa89c..9d5ce43 100644
--- a/service-builtin/Android.bp
+++ b/service-builtin/Android.bp
@@ -17,6 +17,7 @@
// Build the Builtin Car service. This one is not updated from mainline.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/service-builtin/res/xml/car_safety_accessibility_service_config.xml b/service-builtin/res/xml/car_safety_accessibility_service_config.xml
index 9029ec2..75c014f 100644
--- a/service-builtin/res/xml/car_safety_accessibility_service_config.xml
+++ b/service-builtin/res/xml/car_safety_accessibility_service_config.xml
@@ -16,5 +16,5 @@
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeWindowStateChanged"
- android:accessibilityFlags="flagDefault"
+ android:accessibilityFlags="flagDefault|flagEnableAccessibilityVolume"
android:accessibilityFeedbackType="feedbackAllMask"/>
\ No newline at end of file
diff --git a/service-builtin/src/com/android/car/CarPerUserService.java b/service-builtin/src/com/android/car/CarPerUserService.java
index b39d29d..6bdb21e 100644
--- a/service-builtin/src/com/android/car/CarPerUserService.java
+++ b/service-builtin/src/com/android/car/CarPerUserService.java
@@ -20,9 +20,6 @@
/** Proxy service for CarPerUserServiceImpl */
public class CarPerUserService extends ServiceProxy {
- private static final boolean DBG = false;
- private static final String TAG = CarPerUserService.class.getSimpleName();
-
public CarPerUserService() {
super(UpdatablePackageDependency.CAR_USER_PER_SERVICE_IMPL_CLASS);
}
diff --git a/service/Android.bp b/service/Android.bp
index 16d7880..78b0075 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -157,11 +157,12 @@
defaults: [
"car-framework-aconfig-libraries",
+ "latest_android_hardware_automotive_audiocontrol_java_static",
],
srcs: [
// add full source for all codes under p/s/Car to reduce unnecessary library allow listing.
- ":IVehicleGeneratedJavaFiles",
+ ":IVehicleGeneratedJavaFiles-V3",
":android.car.cluster.navigation-src",
":android.car.watchdoglib-src",
":cartelemetry-cardata-proto-srcs",
@@ -177,7 +178,7 @@
aidl: {
include_dirs: [
- "packages/services/Car/procfs-inspector/client/src",
+ "packages/services/Car/libs/procfs-inspector/client/src",
],
},
@@ -209,11 +210,10 @@
"android.automotive.telemetry.internal-V2-java", // ICarTelemetryInternal
"android.automotive.watchdog.internal-V3-java",
"android.frameworks.automotive.powerpolicy.internal-V1-java",
- "android.frameworks.automotive.powerpolicy-V2-java",
+ "android.frameworks.automotive.powerpolicy-V3-java",
"android.hidl.base-V1.0-java",
"android.hardware.automotive.audiocontrol-V1.0-java",
"android.hardware.automotive.audiocontrol-V2.0-java",
- "android.hardware.automotive.audiocontrol-V3-java",
"android.hardware.automotive.remoteaccess-V2-java",
"android.hardware.automotive.vehicle-V3-java",
"android.hardware.automotive.vehicle.property-V3-java",
@@ -324,6 +324,14 @@
defaults: ["carservice-updatable-min-java-defaults"],
manifest: "EmptyAndroidManifest.xml",
+
+ // Avoid using the java_sdk_library version for android.car.builtin since it cannot be found
+ // during runtime on host. Use a java_library version instead.
+ exclude_uses_libs: ["android.car.builtin"],
+
+ static_libs: [
+ "android.car.builtin.testonly",
+ ],
}
filegroup {
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 2e3510e..d65667f 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -1167,14 +1167,13 @@
android:label="@string/car_permission_label_control_windshield_wipers"
android:description="@string/car_permission_desc_control_windshield_wipers"/>
- <!-- Allows an application to query if a package requires display compatibility treatment or
- not.
+ <!-- Allows an application to manage packages that require display compatibility mode.
<p>Protection level: signature|privileged
-->
- <permission android:name="android.car.permission.QUERY_DISPLAY_COMPATIBILITY"
+ <permission android:name="android.car.permission.MANAGE_DISPLAY_COMPATIBILITY"
android:protectionLevel="signature|privileged"
- android:label="@string/car_permission_label_query_display_compatibility"
- android:description="@string/car_permission_desc_query_display_compatibility"/>
+ android:label="@string/car_permission_label_manage_display_compatibility"
+ android:description="@string/car_permission_desc_manage_display_compatibility"/>
<!-- Allows an application to read the vehicle's persist tethering settings.
<p>Protection level: signature|privileged
@@ -1185,10 +1184,23 @@
android:label="@string/car_permission_label_read_persist_tethering_settings"
android:description="@string/car_permission_desc_read_persist_tethering_settings" />
+ <!-- Allows the holder of this permission to bind with app card providers.
+ <p>Protection level: signature|privileged
+
+ NOTE: This permission isn't tested on the platform instead it is tested in an
+ unbundled library.
+ -->
+ <permission
+ android:name="android.car.permission.BIND_APP_CARD_PROVIDER"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_bind_app_card_provider"
+ android:description="@string/car_permission_desc_bind_app_card_provider" />
+
<!-- NOTE: when you're adding a new permission, you should edit
- cts/tests/tests/permission2/res/raw/automotive_android_manifest.xml accordingly and run
- the test with 'atest android.permission2.cts.PermissionPolicyTest' to verify it.
+ packages/modules/Permission/tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml
+ accordingly and run the test with 'atest android.permission2.cts.PermissionPolicyTest'
+ to verify it.
-->
<application android:label="@string/app_title"
diff --git a/service/OWNERS b/service/OWNERS
index 435e2e9..3a5e3bc 100644
--- a/service/OWNERS
+++ b/service/OWNERS
@@ -3,9 +3,9 @@
include platform/packages/services/Car:/OWNERS
# ActivityManager
-per-file src/com/android/car/SystemActivityMonitoringService.java = ycheo@google.com
-per-file src/com/android/car/am/* = ycheo@google.com
-per-file src/com/android/car/systeminterface/ActivityManagerInterface.java = ycheo@google.com
+per-file src/com/android/car/SystemActivityMonitoringService.java = gauravbhola@google.com
+per-file src/com/android/car/am/* = gauravbhola@google.com
+per-file src/com/android/car/systeminterface/ActivityManagerInterface.java = gauravbhola@google.com
# Audio
per-file proto/android/car/audio/* = oscarazu@google.com, ericjeong@google.com
@@ -16,18 +16,19 @@
per-file src/com/android/car/bluetooth/* = salsavage@google.com, chengandrew@google.com
# Cluster
-per-file src/com/android/car/cluster/* = ycheo@google.com
-per-file src/com/android/car/hal/ClusterHalService.java = ycheo@google.com
+per-file src/com/android/car/cluster/* = bkchoi@google.com
+per-file src/com/android/car/hal/ClusterHalService.java = bkchoi@google.com
# Input
-per-file src/com/android/car/CarInputService.java = ycheo@google.com, kanant@google.com
-per-file src/com/android/car/hal/InputHalService.java = ycheo@google.com, kanant@google.com
+per-file src/com/android/car/CarInputService.java = kanant@google.com
+per-file src/com/android/car/hal/InputHalService.java = kanant@google.com
# PackageManager
-per-file src/com/android/car/pm/AppBlockingPolicyProxy.java = ycheo@google.com
-per-file src/com/android/car/pm/Car*.java = ycheo@google.com
-per-file src/com/android/car/pm/TEST_MAPPING = ycheo@google.com
-per-file src/com/android/car/pm/WindowDumpParser.java = ycheo@google.com
+per-file src/com/android/car/pm/AppBlockingPolicyProxy.java = gauravbhola@google.com
+per-file src/com/android/car/pm/Car*.java = gauravbhola@google.com, gargmayank@google.com
+per-file src/com/android/car/pm/TEST_MAPPING = gauravbhola@google.com
+per-file src/com/android/car/pm/Vendor*.java = gargmayank@google.com
+per-file src/com/android/car/pm/WindowDumpParser.java = gauravbhola@google.com
# Power
per-file src/com/android/car/garagemode/* = ericjeong@google.com
@@ -58,6 +59,6 @@
per-file src/com/android/car/hal/fakevhal/*.java = ericjeong@google.com
# Watchdog
-per-file proto/android/car/watchdog/* = lakshmana@google.com
-per-file src/com/android/car/os/* = lakshmana@google.com
-per-file src/com/android/car/watchdog/* = lakshmana@google.com
+per-file proto/android/car/watchdog/* = lakshmana@google.com, jahdiel@google.com
+per-file src/com/android/car/os/* = lakshmana@google.com, jahdiel@google.com
+per-file src/com/android/car/watchdog/* = lakshmana@google.com, jahdiel@google.com
diff --git a/service/proto/android/car/audio/audio_dump.proto b/service/proto/android/car/audio/audio_dump.proto
index f1b59ee..4f67d48 100644
--- a/service/proto/android/car/audio/audio_dump.proto
+++ b/service/proto/android/car/audio/audio_dump.proto
@@ -79,6 +79,7 @@
optional int32 current_gain_index = 4;
optional int32 min_activation_gain_index = 5;
optional int32 max_activation_gain_index = 6;
+ optional int32 activation_invocation_type = 7;
}
message CarAudioDeviceInfoProto {
optional string address = 1;
diff --git a/service/proto/android/car/power/power_dump.proto b/service/proto/android/car/power/power_dump.proto
index 4a99728..6daa28e 100644
--- a/service/proto/android/car/power/power_dump.proto
+++ b/service/proto/android/car/power/power_dump.proto
@@ -101,6 +101,7 @@
optional bool is_monitoring_hw_state_signal = 3;
optional bool silent_mode_by_hw_state = 4;
optional bool forced_silent_mode = 5;
+ optional bool is_silent_mode_supported = 6;
}
message ScreenOffHandlerProto {
diff --git a/service/res/values-af/strings.xml b/service/res/values-af/strings.xml
index 3bc3134..98cceca 100644
--- a/service/res/values-af/strings.xml
+++ b/service/res/values-af/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Lees motor se ruitveërs."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"beheer motor se ruitveërs"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Beheer motor se ruitveërs."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"vraagskermversoenbaarheidpakkette"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Vraagskermversoenbaarheidpakkette."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"bestuur pakkette vir skermversoenbaarheid"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"bestuur pakkette vir skermversoenbaarheid."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"bind aan appkaartverskaffers"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"bind aan appkaartverskaffers."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"monitor die toestande van ander insittendesones in die motor en eweknie-apps (apps wat dieselfde pakketnaam as die beller het) wat in daardie sones geïnstalleer is, en bestuur die vermoëns van daardie sones."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Monitor die toestande van ander insittendesones in die motor en eweknie-apps (apps wat dieselfde pakketnaam as die beller het) wat in daardie sones geïnstalleer is, en bestuur die vermoëns van daardie sones."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"bewerkstellig ’n verbinding en kommunikeer met eweknie-apps (apps wat dieselfde pakketnaam as die beller het) wat in ander insittendesones in die motor geïnstalleer is"</string>
diff --git a/service/res/values-am/strings.xml b/service/res/values-am/strings.xml
index 03513dc..ee798f9 100644
--- a/service/res/values-am/strings.xml
+++ b/service/res/values-am/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"የመኪና የዝናብ መጥረጊያዎችን ያንብቡ።"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"የመኪና የዝናብ መጥረጊያዎችን ይቆጣጠሩ"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"የመኪና የዝናብ መጥረጊያዎችን ይቆጣጥሩ።"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"መጠይቅ የተኳዃኝነት ጥቅሎችን ያሳያል"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"መጠይቅ የተኳዃኝነት ጥቅሎችን ያሳያል።"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"የማሳያ ተኳዃኝነት ጥቅሎችን ያስተዳድሩ"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"የማሳያ ተኳዃኝነት ጥቅሎችን ያስተዳድሩ።"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"ከመተግበሪያ ካርድ አቅራቢዎች ጋር ማሰር"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"ከመተግበሪያ ካርድ አቅራቢዎች ጋር ማሰር።"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"በመኪናው ውስጥ ያሉ የሌሎች የተጠቃሚ ሰው ዞኖችን እና በእነሱ ዞኖች ውስጥ የተጫኑ የአቻ መተግበሪያዎችን (ከደዋዩ ጋር ተመሳሳይ የጥቅል ስም ያላቸው መተግበሪያዎች) ሁኔታዎች ይከታተሉ እና የእነሱን ዞኖች ኃይል ያስተዳድሩ።"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"በመኪናው ውስጥ ያሉ የሌሎች የተጠቃሚ ሰው ዞኖችን እና በእነሱ ዞኖች ውስጥ የተጫኑ የአቻ መተግበሪያዎችን (ከደዋዩ ጋር ተመሳሳይ የጥቅል ስም ያላቸው መተግበሪያዎች) ሁኔታዎች ይከታተሉ እና የእነሱን ዞኖች ኃይል ያስተዳድሩ።"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"ግንኙነት ይፍጠሩ እና በመኪናው ውስጥ ያሉ ሌሎች የተጠቃሚ ሰው ዞኖች ውስጥ ከተጫኑ አቻ መተግበሪያዎች (ከደዋዩ ጋር ተመሳሳይ የጥቅል ስም ያላቸው መተግበሪያዎች) ጋር ይገናኙ"</string>
diff --git a/service/res/values-ar/strings.xml b/service/res/values-ar/strings.xml
index c57b982..6fcf20e 100644
--- a/service/res/values-ar/strings.xml
+++ b/service/res/values-ar/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"قراءة معلومات مسّاحات الزجاج الأمامي"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"التحكّم في مسّاحات الزجاج الأمامي"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"التحكّم في مسّاحات الزجاج الأمامي"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"الحصول على قائمة بالتطبيقات المطلوب توافقها مع الشاشة"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"الحصول على قائمة بالتطبيقات المطلوب توافقها مع الشاشة"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"إدارة الحِزم المطلوب توافقها مع الشاشة"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"إدارة الحِزم المطلوب توافقها مع الشاشة"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"الربط بموفِّري بطاقات التطبيقات"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"الربط بموفِّري بطاقات التطبيقات"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"مراقبة حالات مناطق الركاب الأخرى في السيارة والتطبيقات المشابهة (التطبيقات التي لها اسم الحزمة نفسه مثل تطبيق الاتصال) المثبَّتة في تلك المناطق، وإدارة مستوى الطاقة في تلك المناطق"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"مراقبة حالات مناطق الركاب الأخرى في السيارة والتطبيقات المشابهة (التطبيقات التي لها اسم الحزمة نفسه مثل تطبيق الاتصال) المثبَّتة في تلك المناطق، وإدارة مستوى الطاقة في تلك المناطق"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"الاتصال بالتطبيقات المشابهة والتواصل معها (التطبيقات التي لها اسم الحزمة نفسه مثل تطبيق الاتصال) المثبَّتة في مناطق الركاب الأخرى في السيارة"</string>
diff --git a/service/res/values-as/strings.xml b/service/res/values-as/strings.xml
index a6a8b08..b1c0cdc 100644
--- a/service/res/values-as/strings.xml
+++ b/service/res/values-as/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"গাড়ীৰ ৱিণ্ডশ্বিল্ড ৱাইপাৰ পঢ়ক।"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"গাড়ীৰ ৱিণ্ডশ্বিল্ড ৱাইপাৰ নিয়ন্ত্ৰণ কৰক"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"গাড়ীৰ ৱিণ্ডশ্বিল্ড ৱাইপাৰ নিয়ন্ত্ৰণ কৰক।"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"ডিছপ্লে’ৰ সুসংগতা পেকেজৰ বিষয়ে কুৱেৰী কৰক"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"ডিছপ্লে’ৰ সুসংগতা পেকেজৰ বিষয়ে কুৱেৰী কৰক।"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ডিছপ্লে’ৰ সুসংগতা পেকেজ পৰিচালনা কৰক"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ডিছপ্লে’ৰ সুসংগতা পেকেজ পৰিচালনা কৰক।"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"এপ্ কাৰ্ড প্ৰদানকাৰীৰ সৈতে সংযুক্ত হ’ব পাৰিব"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"এপ্ কাৰ্ড প্ৰদানকাৰীৰ সৈতে সংযুক্ত হ’ব পাৰিব।"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"গাড়ীখনৰ অন্য আৰোহীৰ স্থানৰ আৰু সেই স্থানত ইনষ্টল কৰি থোৱা সহযোগী এপ্সমূহ (কলাৰগৰাকীৰ সৈতে একে নামৰ পেকেজ থকা এপ্সমূহ)ৰ স্থিতি নিৰীক্ষণ কৰক আৰু সেই স্থানসমূহৰ পাৱাৰ পৰিচালনা কৰক।"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"গাড়ীখনৰ অন্য আৰোহীৰ স্থানৰ আৰু সেই স্থানত ইনষ্টল কৰি থোৱা সহযোগী এপ্সমূহ (কলাৰগৰাকীৰ সৈতে একে নামৰ পেকেজ থকা এপ্সমূহ)ৰ স্থিতি নিৰীক্ষণ কৰক আৰু সেই স্থানসমূহৰ পাৱাৰ পৰিচালনা কৰক।"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"গাড়ীখনৰ অন্য আৰোহীৰ স্থানত ইনষ্টল কৰি থোৱা সহযোগী এপ্সমূহ (কলাৰগৰাকীৰ সৈতে একে নামৰ পেকেজ থকা এপ্সমূহ)ৰ সৈতে সংযোগ স্থাপন কৰক আৰু যোগাযোগ কৰক"</string>
diff --git a/service/res/values-az/strings.xml b/service/res/values-az/strings.xml
index 2453be9..885fd4f 100644
--- a/service/res/values-az/strings.xml
+++ b/service/res/values-az/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Avtomobil şüşəsilənlərinin oxunması."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"avtomobil şüşəsilənlərinin idarə edilməsi"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Avtomobil şüşəsilənlərinin idarə edilməsi."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"displey uyğunluğu paketləri üçün sorğu göndərin"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Displey uyğunluğu paketləri üçün sorğu göndərin."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"displey uyğunluğu paketlərini idarə edin"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"displey uyğunluğu paketlərini idarə edin."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"tətbiq kartı provayderlərinə bağlayın"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"tətbiq kartı provayderlərinə bağlayın."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"avtomobildə digər sərnişin zonalarının və orada quraşdırılmış analoq tətbiqlərin (zəng edənlə eyni paket adına malik tətbiqlər) vəziyyətini müşahidə edin və həmin zonaların gücünü idarə edin."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Avtomobildə digər sərnişin zonalarının və orada quraşdırılmış analoq tətbiqlərin (zəng edənlə eyni paket adına malik tətbiqlər) vəziyyətini müşahidə edin və həmin zonaların gücünü idarə edin."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"avtomobildə digər sərnişin zonalarında quraşdırılmış analoq tətbiqləri (zəng edənlə eyni paket adına malik tətbiqlər) ilə bağlantı yaradın qurun və əlaqə saxlayın."</string>
diff --git a/service/res/values-b+sr+Latn/strings.xml b/service/res/values-b+sr+Latn/strings.xml
index e4fa217..50c3149 100644
--- a/service/res/values-b+sr+Latn/strings.xml
+++ b/service/res/values-b+sr+Latn/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Čita brisače vetrobranskog stakla automobila."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"kontrola brisača vetrobranskog stakla automobila"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Kontroliše brisače vetrobranskog stakla automobila."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"utvrđivanje paketa za kompatibilnost prikaza"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Utvrđivanje paketa za kompatibilnost prikaza."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"upravljanje paketima za kompatibilnost prikaza"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"upravljanje paketima za kompatibilnost prikaza."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"povezivanje sa dobavljačima kartica u aplikacijama"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"povezivanje sa dobavljačima kartica u aplikacijama."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"nadgledaju stanja drugih prostora za putnike u automobilu i relevantnih aplikacija (aplikacije koje imaju isti naziv paketa kao i pozivalac) instaliranih u tim prostorima i upravljaju energijom tih prostora"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Nadgledaju stanja drugih prostora za putnike u automobilu i relevantnih aplikacija (aplikacije koje imaju isti naziv paketa kao i pozivalac) instaliranih u tim prostorima i upravljaju energijom tih prostora."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"uspostavljaju vezu i komuniciraju sa relevantnim aplikacijama (aplikacije koje imaju isti naziv paketa kao i pozivalac) instaliranim u drugim prostorima za putnike u automobilu"</string>
diff --git a/service/res/values-be/strings.xml b/service/res/values-be/strings.xml
index 0adc48d..10df727 100644
--- a/service/res/values-be/strings.xml
+++ b/service/res/values-be/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Праглядаць інфармацыю пра шклоачышчальнікі аўтамабіля."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"кіраваць шклоачышчальнікамі аўтамабіля"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Кіраваць шклоачышчальнікамі аўтамабіля."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"атрымаць спіс пакетаў, якім для сумяшчальнасці з дысплэем патрабуецца карэкціроўка"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Атрымаць спіс пакетаў, якім для сумяшчальнасці з дысплэем патрабуецца карэкціроўка"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"кіраванне пакетамі, якім для сумяшчальнасці з дысплэем патрабуецца карэкціроўка"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"кіраванне пакетамі, якім для сумяшчальнасці з дысплэем патрабуецца карэкціроўка."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"падключэнне да пастаўшчыкоў картак для праграм"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"падключэнне да пастаўшчыкоў картак для праграм."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"сачыць за станам іншых зон пасажыраў у аўтамабілі і аналагічных праграм (праграм, якія маюць такую ж назву пакета, што і ініцыятар выкліку), усталяваных у гэтых зонах, а таксама кіраваць сілкаваннем гэтых зон."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Сачыць за станам іншых зон пасажыраў у аўтамабілі і аналагічных праграм (праграм, якія маюць такую ж назву пакета, што і ініцыятар выкліку), усталяваных у гэтых зонах, а таксама кіраваць сілкаваннем гэтых зон."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"наладжваць падключэнне і абменьвацца данымі з аналагічнымі праграмамі (праграмамі, якія маюць такую ж назву пакета, што і ініцыятар выкліку), усталяванымі ў іншых зонах пасажыраў у аўтамабілі"</string>
diff --git a/service/res/values-bg/strings.xml b/service/res/values-bg/strings.xml
index f09e1b3..c35514a 100644
--- a/service/res/values-bg/strings.xml
+++ b/service/res/values-bg/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Четене на чистачките на автомобила."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"управление на чистачките на автомобила"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Управление на чистачките на автомобила."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"пакети за съвместимост с екрана за заявката"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Пакети за съвместимост с екрана за заявката."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"управление на пакетите за съвместимост с екрана"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"управление на пакетите за съвместимост с екрана."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"обвързване с доставчици на карти за приложения"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"обвързване с доставчици на карти за приложения."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"наблюдаване на състоянията на другите пътнически зони в автомобила и тези на подобните приложения (имат същото име на пакета като това на извикващото приложение), инсталирани за съответните зони, и управление на захранването на пътническите зони"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Наблюдаване на състоянията на другите пътнически зони в автомобила и тези на подобните приложения (имат същото име на пакета като това на извикващото приложение), инсталирани за съответните зони, и управление на захранването на пътническите зони."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"установяване на връзка и комуникация с подобните приложения (имат същото име на пакета като това на извикващото приложение), инсталирани за другите пътнически зони в автомобила"</string>
diff --git a/service/res/values-bn/strings.xml b/service/res/values-bn/strings.xml
index b4e82d1..04367ee 100644
--- a/service/res/values-bn/strings.xml
+++ b/service/res/values-bn/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"গাড়ির উইন্ডশিল্ড ওয়াইপার সংক্রান্ত তথ্য দেখতে পারে।"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"গাড়ির উইন্ডশিল্ড ওয়াইপার, নিয়ন্ত্রণ করতে পারে"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"গাড়ির উইন্ডশিল্ড ওয়াইপার, নিয়ন্ত্রণ করতে পারে।"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"কোয়েরি ডিসপ্লের জন্য মানানসই প্যাকেজ"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"কোয়েরি ডিসপ্লের জন্য মানানসই প্যাকেজ।"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ডিসপ্লে কম্প্যাটিবিলিটি প্যাকেজ ম্যানেজ করুন"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ডিসপ্লে কম্প্যাটিবিলিটি প্যাকেজ ম্যানেজ করুন।"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"অ্যাপ কার্ড প্রদানকারীর সাথে কানেক্ট করুন"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"অ্যাপ কার্ড প্রদানকারীর সাথে কানেক্ট করুন।"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"গাড়িতে থাকা অন্যান্য সিটিং জোন এবং সেই জোনে ইনস্টল করা পিয়ার অ্যাপগুলির (কলারের মতো একই প্যাকেজ নাম আছে এমন অ্যাপ) স্ট্যাটাস মনিটর করুন, এছাড়া সেই জোনগুলির পাওয়ার ম্যানেজ করুন।"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"গাড়িতে থাকা অন্যান্য সিটিং জোন এবং সেই জোনে ইনস্টল করা পিয়ার অ্যাপগুলির (কলারের মতো একই প্যাকেজ নাম আছে এমন অ্যাপ) স্ট্যাটাস মনিটর করুন, এছাড়া সেই জোনগুলির পাওয়ার ম্যানেজ করুন।"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"কানেক্ট করুন এবং গাড়ির অন্যান্য সিটিং জোনে ইনস্টল করা পিয়ার অ্যাপগুলির (কলারের মতো একই প্যাকেজ নাম আছে এমন অ্যাপ) সাথে যোগাযোগ করুন"</string>
diff --git a/service/res/values-bs/strings.xml b/service/res/values-bs/strings.xml
index 707f83d..01a49dd 100644
--- a/service/res/values-bs/strings.xml
+++ b/service/res/values-bs/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Očitavaj brisače vjetrobrana automobila."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"kontrola brisača vjetrobrana automobila."</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Kontroliraj brisače vjetrobrana automobila."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"upit za pakete za kompatibilnost s ekranom"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Upit za pakete za kompatibilnost s ekranom."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"upravljanje paketima za kompatibilnost s ekranom"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"upravljanje paketima za kompatibilnost s ekranom."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"povezivanje s pružaocima kartica u aplikaciji"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"povezivanje s pružaocima kartica u aplikaciji."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"prati stanja drugih zona prisustva u automobilu i aplikacijama iste kategorije (aplikacije koje imaju isti naziv paketa kao i pozivalac) koje su instalirane u tim zonama te upravljaj napajanjem tih zona."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Prati stanja drugih zona prisustva u automobilu i aplikacijama iste kategorije (aplikacije koje imaju isti naziv paketa kao i pozivalac) koje su instalirane u tim zonama te upravljaj napajanjem tih zona."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"uspostavi vezu i komuniciraj s aplikacijama iste kategorije (aplikacije koje imaju isti naziv paketa kao i pozivalac) koje su instalirane na drugim zonama prisustva u automobilu"</string>
diff --git a/service/res/values-ca/strings.xml b/service/res/values-ca/strings.xml
index 7e28e35..e78cac6 100644
--- a/service/res/values-ca/strings.xml
+++ b/service/res/values-ca/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Llegir els eixugaparabrises del cotxe."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"controlar els eixugaparabrises del cotxe"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Controlar els eixugaparabrises del cotxe."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"consultar paquets de compatibilitat de pantalla"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Consultar paquets de compatibilitat de pantalla."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"gestiona paquets de compatibilitat de pantalla"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"gestiona paquets de compatibilitat de pantalla."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"vincular als proveïdors de targetes de l\'aplicació"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"vincular als proveïdors de targetes de l\'aplicació."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"supervisar els estats de les zones d\'altres ocupants del cotxe i de les aplicacions semblants (aplicacions que tenen el mateix nom de paquet que la persona que truca) instal·lades en aquestes zones, i gestionar l\'energia en aquestes zones"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Supervisar els estats de les zones d\'altres ocupants del cotxe i de les aplicacions semblants (aplicacions que tenen el mateix nom de paquet que la persona que truca) instal·lades en aquestes zones, i gestionar l\'energia en aquestes zones."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"establir la connexió i comunicar-se amb aplicacions semblants (aplicacions que tenen el mateix nom de paquet que la persona que truca) instal·lades en les zones d\'altres ocupants del cotxe"</string>
diff --git a/service/res/values-cs/strings.xml b/service/res/values-cs/strings.xml
index fe4623a..bbcb2fe 100644
--- a/service/res/values-cs/strings.xml
+++ b/service/res/values-cs/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Čtení informací o stěračích auta."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"ovládání stěračů auta"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Ovládání stěračů auta."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"vyžádání seznamu balíčků s kompatibilitou displeje"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Vyžádání seznamu balíčků s kompatibilitou displeje."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"správa balíčků s kompatibilitou displeje"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"správa balíčků s kompatibilitou displeje."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"navázání se na poskytovatele karty aplikace"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"navázání se na poskytovatele karty aplikace."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"sledovat stavy ostatních zón cestujících v autě a partnerských aplikací (aplikací, které mají stejný název balíčku jako volající aplikace) nainstalovaných v těchto zónách a spravovat napájení těchto zón"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Sledovat stavy ostatních zón cestujících v autě a partnerských aplikací (aplikací, které mají stejný název balíčku jako volající aplikace) nainstalovaných v těchto zónách a spravovat napájení těchto zón."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"navázat spojení a komunikovat s partnerskými aplikacemi (aplikacemi, které mají stejný název balíčku jako volající aplikace) nainstalovanými v ostatních zónách cestujících v autě"</string>
diff --git a/service/res/values-da/strings.xml b/service/res/values-da/strings.xml
index fe2dadd..78239a5 100644
--- a/service/res/values-da/strings.xml
+++ b/service/res/values-da/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Læse oplysninger om bilens vinduesviskere."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"kontrollere bilens vinduesviskere"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Kontrollere bilens vinduesviskere."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"sende forespørgsler om pakker vedrørende skærmkompatibilitet"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Sende forespørgsler om pakker vedrørende skærmkompatibilitet."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"administrer pakker til skærmkompatibilitet"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"administrer pakker til skærmkompatibilitet."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"knyt til udbydere af appkort"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"knyt til udbydere af appkort."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"holde øje med tilstanden af andre tilstedeværelseszoner i bilen og lignende apps (apps, der har samme pakkenavn som den, der ringer), som er installeret i de pågældende zoner, og styre strømforsyningen i zonerne."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Holde øje med tilstanden af andre tilstedeværelseszoner i bilen og lignende apps (apps, der har samme pakkenavn som den, der ringer), som er installeret i de pågældende zoner, og styre strømforsyningen i zonerne."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"oprette forbindelse og kommunikere med lignende apps (app, der har samme pakkenavn som den, der ringer), som er installeret i andre tilstedeværelseszoner i bilen"</string>
diff --git a/service/res/values-de/strings.xml b/service/res/values-de/strings.xml
index 3abac22..5215048 100644
--- a/service/res/values-de/strings.xml
+++ b/service/res/values-de/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Informationen zu den Scheibenwischern des Autos auslesen."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"Scheibenwischer des Autos steuern"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Scheibenwischer des Autos steuern."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"Pakete, für die Anzeigekompatibilität erforderlich ist, abfragen"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Pakete, für die Anzeigekompatibilität erforderlich ist, abfragen."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"Pakete, für die Anzeigekompatibilität erforderlich ist, verwalten"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"Pakete, für die Anzeigekompatibilität erforderlich ist, verwalten."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"Verbindung zu einem Anbieter von App-Karten herstellen"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"Verbindung zu einem Anbieter von App-Karten herstellen."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"Den Status von anderen Insassenbereichen im Auto und von ähnlichen Apps – d. h. Apps, die denselben Paketnamen wie der Aufrufer haben –, die in diesen Bereichen installiert sind, überwachen und die Energieeinstellungen dieser Bereiche verwalten."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Den Status von anderen Insassenbereichen im Auto und von ähnlichen Apps – d. h. Apps, die denselben Paketnamen wie der Aufrufer haben –, die in diesen Bereichen installiert sind, überwachen und die Energieeinstellungen dieser Bereiche verwalten."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"Verbindung mit ähnlichen Apps – d. h. Apps, die denselben Paketnamen wie der Aufrufer haben –, die in anderen Insassenbereichen im Auto installiert sind, herstellen und mit diesen kommunizieren"</string>
diff --git a/service/res/values-el/strings.xml b/service/res/values-el/strings.xml
index 91395e6..e5955a3 100644
--- a/service/res/values-el/strings.xml
+++ b/service/res/values-el/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Ανάγνωση των υαλοκαθαριστήρων του αυτοκινήτου."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"έλεγχος των υαλοκαθαριστήρων του αυτοκινήτου"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Έλεγχος των υαλοκαθαριστήρων του αυτοκινήτου."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"ερώτημα για πακέτα συμβατότητας προβολής"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Ερώτημα για πακέτα συμβατότητας προβολής."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"διαχείριση πακέτων συμβατότητας προβολής"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"διαχείριση πακέτων συμβατότητας προβολής."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"σύνδεση με παρόχους κάρτας εφαρμογών"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"σύνδεση με παρόχους κάρτας εφαρμογών."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"παρακολούθηση των καταστάσεων των άλλων ζωνών επιβατών στο αυτοκίνητο και των ομότιμων εφαρμογών (εφαρμογών που έχουν το ίδιο όνομα πακέτου με τον καλούντα) οι οποίες είναι εγκατεστημένες στις συγκεκριμένες ζώνες, καθώς και διαχείριση της ενέργειας αυτών των ζωνών."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Παρακολούθηση των καταστάσεων των άλλων ζωνών επιβατών στο αυτοκίνητο και των ομότιμων εφαρμογών (εφαρμογών που έχουν το ίδιο όνομα πακέτου με τον καλούντα) οι οποίες είναι εγκατεστημένες στις συγκεκριμένες ζώνες, καθώς και διαχείριση της ενέργειας αυτών των ζωνών."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"δημιουργία σύνδεσης και επικοινωνία με ομότιμες εφαρμογές (εφαρμογές που έχουν το ίδιο όνομα πακέτου με τον καλούντα) οι οποίες είναι εγκατεστημένες σε άλλες ζώνες επιβατών στο αυτοκίνητο"</string>
diff --git a/service/res/values-en-rAU/strings.xml b/service/res/values-en-rAU/strings.xml
index d98cc96..13c2ea5 100644
--- a/service/res/values-en-rAU/strings.xml
+++ b/service/res/values-en-rAU/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Read car’s windscreen wipers."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"control car’s windscreen wipers"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Control car’s windscreen wipers."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"query display compatibility packages"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Query display compatibility packages."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"manage display compatibility packages"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"manage display compatibility packages."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"bind to app card providers"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"bind to app card providers."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"monitor the states of other occupant zones in the car and peer apps (apps that have the same package name as the caller) installed in those zones, and manage the power of those zones."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Monitor the states of other occupant zones in the car and peer apps (apps that have the same package name as the caller) installed in those zones, and manage the power of those zones."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"establish connection and communicate to peer apps (apps that have the same package name as the caller) installed in other occupant zones in the car"</string>
diff --git a/service/res/values-en-rCA/strings.xml b/service/res/values-en-rCA/strings.xml
index eeaeb62..e6f797b 100644
--- a/service/res/values-en-rCA/strings.xml
+++ b/service/res/values-en-rCA/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Read car’s windshield wipers."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"control car’s windshield wipers"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Control car’s windshield wipers."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"query display compatibility packages"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Query display compatibility packages."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"manage display compatibility packages"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"manage display compatibility packages."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"bind to app card providers"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"bind to app card providers."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"monitor the states of other occupant zones in the car and peer apps (apps that have the same package name as the caller) installed in those zones, and manage the power of those zones."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Monitor the states of other occupant zones in the car and peer apps (apps that have the same package name as the caller) installed in those zones, and manage the power of those zones."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"establish connection and communicate to peer apps (apps that have the same package name as the caller) installed in other occupant zones in the car"</string>
diff --git a/service/res/values-en-rGB/strings.xml b/service/res/values-en-rGB/strings.xml
index d98cc96..13c2ea5 100644
--- a/service/res/values-en-rGB/strings.xml
+++ b/service/res/values-en-rGB/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Read car’s windscreen wipers."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"control car’s windscreen wipers"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Control car’s windscreen wipers."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"query display compatibility packages"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Query display compatibility packages."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"manage display compatibility packages"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"manage display compatibility packages."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"bind to app card providers"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"bind to app card providers."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"monitor the states of other occupant zones in the car and peer apps (apps that have the same package name as the caller) installed in those zones, and manage the power of those zones."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Monitor the states of other occupant zones in the car and peer apps (apps that have the same package name as the caller) installed in those zones, and manage the power of those zones."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"establish connection and communicate to peer apps (apps that have the same package name as the caller) installed in other occupant zones in the car"</string>
diff --git a/service/res/values-en-rIN/strings.xml b/service/res/values-en-rIN/strings.xml
index d98cc96..13c2ea5 100644
--- a/service/res/values-en-rIN/strings.xml
+++ b/service/res/values-en-rIN/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Read car’s windscreen wipers."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"control car’s windscreen wipers"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Control car’s windscreen wipers."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"query display compatibility packages"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Query display compatibility packages."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"manage display compatibility packages"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"manage display compatibility packages."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"bind to app card providers"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"bind to app card providers."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"monitor the states of other occupant zones in the car and peer apps (apps that have the same package name as the caller) installed in those zones, and manage the power of those zones."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Monitor the states of other occupant zones in the car and peer apps (apps that have the same package name as the caller) installed in those zones, and manage the power of those zones."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"establish connection and communicate to peer apps (apps that have the same package name as the caller) installed in other occupant zones in the car"</string>
diff --git a/service/res/values-en-rXC/strings.xml b/service/res/values-en-rXC/strings.xml
index 654356e..d933166 100644
--- a/service/res/values-en-rXC/strings.xml
+++ b/service/res/values-en-rXC/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Read car’s windshield wipers."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"control car’s windshield wipers"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Control car’s windshield wipers."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"query display compatibility packages"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Query display compatibility packages."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"manage display compatibility packages"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"manage display compatibility packages."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"bind to app card providers"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"bind to app card providers."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"monitor the states of other occupant zones in the car and peer apps (apps that have the same package name as the caller) installed in those zones, and manage the power of those zones."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Monitor the states of other occupant zones in the car and peer apps (apps that have the same package name as the caller) installed in those zones, and manage the power of those zones."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"establish connection and communicate to peer apps (apps that have the same package name as the caller) installed in other occupant zones in the car"</string>
diff --git a/service/res/values-es-rUS/strings.xml b/service/res/values-es-rUS/strings.xml
index 6e208c3..68d4d2d 100644
--- a/service/res/values-es-rUS/strings.xml
+++ b/service/res/values-es-rUS/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Acceder a la información del limpiaparabrisas del vehículo"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"controlar el limpiaparabrisas del vehículo"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Controlar el limpiaparabrisas del vehículo"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"paquetes compatibles para la muestra de consultas"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Paquetes compatibles para la muestra de consultas."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"administrar paquetes para la compatibilidad de pantalla"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"administrar paquetes para la compatibilidad de pantalla."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"enlazarse con proveedores de tarjetas para apps"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"enlazarse con proveedores de tarjetas para apps."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"supervisa los estados de las otras zonas de ocupantes en el vehículo y apps similares (que tienen el mismo nombre de paquete que el llamador) instaladas en esas zonas, y administra la energía de esas áreas."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Supervisa los estados de las otras zonas de ocupantes en el vehículo y apps similares (que tienen el mismo nombre de paquete que el llamador) instaladas en esas zonas, y administra la energía de esas áreas."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"establece una conexión y se comunica con apps similares (que tienen el mismo nombre de paquete que el llamador) instaladas en otras zonas de ocupantes en el vehículo."</string>
diff --git a/service/res/values-es/strings.xml b/service/res/values-es/strings.xml
index 4d1b397..dc8a238 100644
--- a/service/res/values-es/strings.xml
+++ b/service/res/values-es/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Leer los limpiaparabrisas del coche."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"controlar los limpiaparabrisas del coche"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Controlar los limpiaparabrisas del coche."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"consultar paquetes de compatibilidad de pantalla"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Consultar paquetes de compatibilidad de pantalla"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"gestionar paquetes de compatibilidad de pantalla"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"gestionar paquetes de compatibilidad de pantalla."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"vincular con proveedores de la tarjeta de la aplicación"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"vincular con proveedores de la tarjeta de la aplicación."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"monitorizar el estado de las zonas de otros ocupantes del coche y de las aplicaciones similares (aplicaciones que tienen el mismo nombre de paquete que el llamador) instaladas en esas zonas, así como gestionar el suministro de energía de esas zonas"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Monitorizar el estado de las zonas de otros ocupantes del coche y de las aplicaciones similares (aplicaciones que tienen el mismo nombre de paquete que el llamador) instaladas en esas zonas, así como gestionar el suministro de energía de esas zonas."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"establecer conexión y comunicarse con aplicaciones similares (aplicaciones que tienen el mismo nombre de paquete que el llamador) instaladas en las zonas de otros ocupantes del coche"</string>
diff --git a/service/res/values-et/strings.xml b/service/res/values-et/strings.xml
index 1ae72da..2c1a99d 100644
--- a/service/res/values-et/strings.xml
+++ b/service/res/values-et/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Auto klaasipuhastite oleku lugemine."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"auto klaasipuhastite juhtimine"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Auto klaasipuhastite juhtimine."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"päring kuvaühilduvuse töötlemise pakettide kohta"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Päring kuvaühilduvuse töötlemise pakettide kohta."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"haldavad kuvaühilduvuse töötlemise pakette"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"haldavad kuvaühilduvuse töötlemise pakette."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"ühendada rakenduse kaardi pakkujatega"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"ühendada rakenduse kaardi pakkujatega."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"auto muude reisijatsoonide ja nendes installitud sarnaste rakenduste (rakendused, millel on kutsuva programmiga sama paketinimi) olekute jälgimine ning nende tsoonide toite haldamine."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Auto muude reisijatsoonide ja nendes installitud sarnaste rakenduste (rakendused, millel on kutsuva programmiga sama paketinimi) olekute jälgimine ning nende tsoonide toite haldamine."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"auto muudes reisijatsoonides installitud sarnaste rakendustega (rakendused, millel on kutsuva programmiga sama paketinimi) ühenduse loomine ja suhtlemine."</string>
diff --git a/service/res/values-eu/strings.xml b/service/res/values-eu/strings.xml
index 25ff361..8a0d644 100644
--- a/service/res/values-eu/strings.xml
+++ b/service/res/values-eu/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Irakurri autoaren haizetako-garbigailuak."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"kontrolatu autoaren haizetako-garbigailuak"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Kontrolatu autoaren haizetako-garbigailuak."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"egin kontsultak bistaratze-bateragarritasunaren paketeei buruz"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Egin kontsultak bistaratze-bateragarritasunaren paketeei buruz."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"kudeatu bistaratze-bateragarritasunaren paketeak"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"kudeatu bistaratze-bateragarritasunaren paketeak."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"lotu aplikazio-txartelen hornitzaileekin"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"lotu aplikazio-txartelen hornitzaileekin."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"kontrolatu ea autoko beste eserlekuetan inor baden eta eserleku horietan daudenen instalatutatako pareko aplikazioen (deitzailearen pakete-izen bereko aplikazioak) egoerak, eta kudeatu eserleku horietako korrontea."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Kontrolatu ea autoko beste eserlekuetan inor baden eta eserleku horietan daudenen instalatutatako pareko aplikazioen (deitzailearen pakete-izen bereko aplikazioak) egoerak, eta kudeatu eserleku horietako korrontea."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"konektatu eta komunikatu autoko beste eserleku batzuetan instalatuta dauden pareko aplikazioekin; hau da, deitzailearen pakete-izen bereko aplikazioekin"</string>
diff --git a/service/res/values-fa/strings.xml b/service/res/values-fa/strings.xml
index 6252e55..2a563bb 100644
--- a/service/res/values-fa/strings.xml
+++ b/service/res/values-fa/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"خواندن برفپاککنهای خودرو."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"کنترل برفپاککنهای خودرو"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"کنترل برفپاککنهای خودرو."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"بستههای سازگاری با نمایش پُرسمان"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"بستههای سازگاری با نمایش پُرسمان."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"مدیریت بستههای سازگاری نمایشگر"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"مدیریت بستههای سازگاری نمایشگر."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"ترابست به ارائهدهندگان کارت برنامه."</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"ترابست به ارائهدهندگان کارت برنامه"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"نظارت بر وضعیت دیگر مناطق سرنشین در خودرو و برنامههای همتای نصبشده در این مناطق، و مدیریت انرژی این مناطق (برنامههای همتا یعنی برنامههایی که نام بسته مشابه با تماسگیرنده دارند)."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"نظارت بر وضعیت دیگر مناطق سرنشین در خودرو و برنامههای همتای نصبشده در این مناطق، و مدیریت انرژی این مناطق (برنامههای همتا یعنی برنامههایی که نام بسته مشابه با تماسگیرنده دارند)."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"برقراری اتصال و ارتباط با برنامههای همتای نصبشده در سایر مناطق سرنشین در خودرو (برنامههای همتا یعنی برنامههایی که نام بسته مشابه با تماسگیرنده دارند)."</string>
diff --git a/service/res/values-fi/strings.xml b/service/res/values-fi/strings.xml
index b9f20b9..df04dad 100644
--- a/service/res/values-fi/strings.xml
+++ b/service/res/values-fi/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Lue tiedot auton tuulilasinpyyhkijöistä."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"ohjata auton tuulilasinpyyhkijöitä"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Ohjaa auton tuulilasinpyyhkijöitä."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"kyselynäyttö yhteensopivuuspaketeista"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Kyselynäyttö yhteensopivuuspaketeista."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ylläpitää näytön yhteensopivuuspaketteja"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ylläpitää näytön yhteensopivuuspaketteja."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"yhdistää sovelluksen korttien tarjoajiin"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"yhdistää sovelluksen korttien tarjoajiin."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"seuraa auton muiden paikallaoloalueiden sekä niille asennettujen vertaissovellusten (sovellusten, joilla on sama pakettinimi kuin kutsujalla) tiloja ja hallitsee virran käyttöä kyseisillä alueilla."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Seuraa auton muiden paikallaoloalueiden sekä niille asennettujen vertaissovellusten (sovellusten, joilla on sama pakettinimi kuin kutsujalla) tiloja ja hallitsee virran käyttöä kyseisillä alueilla."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"muodostaa yhteyden ja on yhteydessä vertaissovelluksiin (sovelluksiin, joilla on sama pakettinimi kuin kutsujalla), jotka on asennettu muille auton paikallaoloalueille."</string>
diff --git a/service/res/values-fr-rCA/strings.xml b/service/res/values-fr-rCA/strings.xml
index d8facc3..6f81d09 100644
--- a/service/res/values-fr-rCA/strings.xml
+++ b/service/res/values-fr-rCA/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Lecture des données relatives aux essuie-glaces de la voiture."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"contrôle des essuie-glaces de la voiture"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Contrôle des essuie-glaces de la voiture."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"lancer une requête sur les paquets ayant des problèmes de compatibilité d\'écran"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Lancer une requête sur les paquets ayant des problèmes de compatibilité d\'écran."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"gérer les paquets de compatibilité d\'affichage"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"gérer les paquets de compatibilité d\'affichage."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"lier aux fournisseurs de données pour cartes d\'application"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"liez aux fournisseurs de données pour cartes d\'application."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"surveiller l\'état des autres zones pour passagers dans la voiture et des applications similaires (applications qui ont le même nom de l\'ensemble que l\'appelant) installées dans ces zones, et gérer leur alimentation."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Surveiller l\'état des autres zones pour passagers dans la voiture et des applications similaires (applications qui ont le même nom de l\'ensemble que l\'appelant) installées dans ces zones, et gérer leur alimentation."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"établir une connexion et communiquer avec des applications similaires (applications qui ont le même nom de l\'ensemble que l\'appelant) installées dans d\'autres zones pour passagers de la voiture"</string>
diff --git a/service/res/values-fr/strings.xml b/service/res/values-fr/strings.xml
index e3abd77..f65b82f 100644
--- a/service/res/values-fr/strings.xml
+++ b/service/res/values-fr/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Lire les infos concernant les essuie-glaces de la voiture."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"contrôler les essuie-glaces de la voiture"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Contrôler les essuie-glaces de la voiture."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"packages de compatibilité d\'affichage des requêtes"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Packages de compatibilité d\'affichage des requêtes."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"gérer les paquets de compatibilité d\'affichage"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"gérer les paquets de compatibilité d\'affichage."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"lier aux fournisseurs de cartes pour applications"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"lier aux fournisseurs de cartes pour applications."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"Surveiller les états d\'autres zones de l\'occupant dans la voiture et d\'applis similaires (applis ayant le même nom de package que l\'appelant) installées dans ces zones, et gérer l\'alimentation de ces zones."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Surveiller les états d\'autres zones de l\'occupant dans la voiture et d\'applis similaires (applis ayant le même nom de package que l\'appelant) installées dans ces zones, et gérer l\'alimentation de ces zones."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"Établir une connexion et communiquer avec des applis similaires (applis ayant le même nom de package que l\'appelant) installées dans d\'autres zones de l\'occupant dans la voiture."</string>
diff --git a/service/res/values-gl/strings.xml b/service/res/values-gl/strings.xml
index 397da15..faead4b 100644
--- a/service/res/values-gl/strings.xml
+++ b/service/res/values-gl/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Ler información sobre os limpaparabrisas do coche."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"controlar os limpaparabrisas do coche"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Controlar os limpaparabrisas do coche"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"consultar paquetes de compatibilidade de pantalla"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Consultar paquetes de compatibilidade de pantalla."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"xestionar os paquetes de compatibilidade da pantalla"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"xestionar os paquetes de compatibilidade da pantalla."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"vincular a provedores de aplicacións de tarxetas"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"vincular a provedores de aplicacións de tarxetas."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"supervisar o estado doutras zonas de pasaxeiros do coche e das aplicacións semellantes instaladas nelas (aquelas que teñen o mesmo nome de paquete que a persoa que realiza a chamada), así como xestionar a enerxía desas zonas."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Supervisar o estado doutras zonas de pasaxeiros do coche e das aplicacións semellantes instaladas nelas (aquelas que teñen o mesmo nome de paquete que a persoa que realiza a chamada), así como xestionar a enerxía desas zonas."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"establecer conexión e comunicarse con aplicacións semellantes (aquelas que teñen o mesmo nome de paquete que a persoa que realiza a chamada) instaladas noutras zonas de pasaxeiros do coche"</string>
diff --git a/service/res/values-gu/strings.xml b/service/res/values-gu/strings.xml
index 106928c..ee4e628 100644
--- a/service/res/values-gu/strings.xml
+++ b/service/res/values-gu/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"કારના વિન્ડશીલ્ડ વાઇપરની માહિતી વાંચો."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"કારના વિન્ડશીલ્ડ વાઇપર નિયંત્રિત કરો"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"કારના વિન્ડશીલ્ડ વાઇપર નિયંત્રિત કરો."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"ક્વેરી બતાવવા માટેના સુસંગત પૅકેજ"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"ક્વેરી બતાવવા માટેના સુસંગત પૅકેજ."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"બતાવવા માટેના સુસંગત પૅકેજ મેનેજ કરો"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"બતાવવા માટેના સુસંગત પૅકેજ મેનેજ કરો."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"ઍપ કાર્ડના પ્રદાતાઓ સાથે જોડાઓ"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"ઍપ કાર્ડના પ્રદાતાઓ સાથે જોડાઓ."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"કારમાંના અન્ય ઑક્યુપન્ટ ઝોન અને તે ઝોનમાં ઇન્સ્ટૉલ કરેલી પીઅર ઍપ (કૉલરના જેવું પૅકેજનું નામ ધરાવતી ઍપ)ના સ્ટેટસનું નિરીક્ષણ કરો અને આ બધા ઝોનના પાવરને મેનેજ કરો."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"કારમાંના અન્ય ઑક્યુપન્ટ ઝોન અને તે ઝોનમાં ઇન્સ્ટૉલ કરેલી પીઅર ઍપ (કૉલરના જેવું પૅકેજનું નામ ધરાવતી ઍપ)ના સ્ટેટસનું નિરીક્ષણ કરો અને આ બધા ઝોનના પાવરને મેનેજ કરો."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"કનેક્શન સ્થાપિત કરો અને કારમાં અન્ય ઑક્યુપન્ટ ઝોનમાં ઇન્સ્ટૉલ કરેલી પીઅર ઍપ (કૉલરના જેવું પૅકેજનું નામ ધરાવતી ઍપ)નો સંપર્ક કરો"</string>
diff --git a/service/res/values-hi/strings.xml b/service/res/values-hi/strings.xml
index 5102742..af19a23 100644
--- a/service/res/values-hi/strings.xml
+++ b/service/res/values-hi/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"कार के विंडशील्ड वाइपर को चालू या बंद करने की अनुमति दें."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"कार के विंडशील्ड वाइपर कंट्रोल करने की अनुमति दें"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"कार के विंडशील्ड वाइपर कंट्रोल करने की अनुमति दें."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"इस एपीआई की मदद से, ऐसे पैकेज के लिए क्वेरी की जा सकेगी जिनके लिए, डिसप्ले के साथ काम करने से जुड़ा ट्रीटमेंट ज़रूरी है"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"इस एपीआई की मदद से, ऐसे पैकेज के लिए क्वेरी की जा सकेगी जिनके लिए, डिसप्ले के साथ काम करने से जुड़ा ट्रीटमेंट ज़रूरी है."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"इस एपीआई की मदद से, ऐसे पैकेज मैनेज किए जा सकेंगे जिनके लिए, डिसप्ले के साथ काम करने से जुड़ा ट्रीटमेंट ज़रूरी है"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"इस एपीआई की मदद से, ऐसे पैकेज मैनेज किए जा सकेंगे जिनके लिए, डिसप्ले के साथ काम करने से जुड़ा ट्रीटमेंट ज़रूरी है."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"कार्ड देने वाली सेवा या कंपनी के ऐप्लिकेशन से बाइंड करें"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"कार्ड देने वाली सेवा या कंपनी के ऐप्लिकेशन से बाइंड करें."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"यह अनुमति मिलने पर ऐप्लिकेशन, कार के दूसरे ऑक्यूपंट ज़ोन की स्थितियों और उन ज़ोन में इंस्टॉल किए गए मिलते-जुलते ऐप्लिकेशन (इन ऐप्लिकेशन और कॉलर ऐप्लिकेशन का पैकेज एक ही होता है) की स्थितियों को मॉनिटर कर सकते हैं. साथ ही, वे इन ज़ोन में पावर को मैनेज कर सकते हैं."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"कार के दूसरे ऑक्यूपंट ज़ोन की स्थितियों और उन ज़ोन में इंस्टॉल किए गए मिलते-जुलते ऐप्लिकेशन (इन ऐप्लिकेशन और कॉलर के ऐप्लिकेशन के पैकेज का नाम एक ही होता है) की स्थितियों को मॉनिटर करने की अनुमति दें. साथ ही, इन ज़ोन में पावर को मैनेज करने की अनुमति दें."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"यह अनुमति मिलने पर ऐप्लिकेशन, कार के दूसरे ऑक्यूपंट ज़ोन में इंस्टॉल किए गए मिलते-जुलते ऐप्लिकेशन (इन ऐप्लिकेशन और कॉलर ऐप्लिकेशन का पैकेज एक ही होता है) से कनेक्ट और इंटरैक्ट कर सकते हैं"</string>
diff --git a/service/res/values-hr/strings.xml b/service/res/values-hr/strings.xml
index 2600e46..0a9371e 100644
--- a/service/res/values-hr/strings.xml
+++ b/service/res/values-hr/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Čitanje brisača vjetrobrana automobila."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"upravljanje brisačima vjetrobrana automobila"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Upravljanje brisačima vjetrobrana automobila."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"paketi kompatibilnosti prikaza upita"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Paketi kompatibilnosti prikaza upita."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"upravljanje paketima kompatibilnosti prikaza"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"upravljanje paketima kompatibilnosti prikaza."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"povežite se s widgetima kartice s predloškom aplikacije koje se dohvaćaju pomoću pružatelja sadržaja"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"Povežite se s widgetima kartice s predloškom aplikacije koje se dohvaćaju pomoću pružatelja sadržaja."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"nadzirati stanja drugih putničkih zona u automobilu i aplikacija iste kategorije (aplikacije s istim nazivom paketa kao pozivatelj) koje su instalirane u tim zonama te upravljati napajanjem u tim zonama."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Nadzire stanja drugih putničkih zona u automobilu i aplikacija iste kategorije (aplikacije s istim nazivom paketa kao pozivatelj) koje su instalirane u tim zonama te upravlja napajanjem u tim zonama."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"uspostaviti vezu i komunicirati s aplikacijama iste kategorije (aplikacije s istim nazivom paketa kao pozivatelj) koje su instalirane u drugim putničkim zonama u automobilu"</string>
diff --git a/service/res/values-hu/strings.xml b/service/res/values-hu/strings.xml
index 7acbb40..126b8e5 100644
--- a/service/res/values-hu/strings.xml
+++ b/service/res/values-hu/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Az autó ablaktörlőit érintő információk olvasása."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"az autó ablaktörlőinek vezérlése"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Az autó ablaktörlőinek vezérlése."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"kijelzőkompatibilis csomagok lekérdezése"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Kijelzőkompatibilis csomagok lekérdezése."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"kijelzőkompatibilitási csomagok kezelése"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"kijelzőkompatibilitási csomagok kezelése."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"csatlakozás kártyaalkalmazás-szolgáltatókhoz"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"csatlakozás kártyaalkalmazás-szolgáltatókhoz."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"az autó utastere más zónái állapotának és az ezekbe a zónákba telepített hasonló alkalmazásoknak (olyan alkalmazásoknak, amelyek ugyanazzal a csomagnévvel rendelkeznek, mint a hívó) a megfigyelése, valamint a zónák áramellátásának kezelése."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Az autó utastere más zónái állapotának és az ezekbe a zónákba telepített hasonló alkalmazásoknak (olyan alkalmazásoknak, amelyek ugyanazzal a csomagnévvel rendelkeznek, mint a hívó) a megfigyelése, valamint a zónák áramellátásának kezelése."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"kapcsolat létrehozása és kommunikáció hasonló alkalmazásokkal (olyan alkalmazásokkal, amelyek ugyanazzal a csomagnévvel rendelkeznek, mint a hívó), amelyek az autó utasterének más zónáiba vannak telepítve."</string>
diff --git a/service/res/values-hy/strings.xml b/service/res/values-hy/strings.xml
index d14270d..557d642 100644
--- a/service/res/values-hy/strings.xml
+++ b/service/res/values-hy/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Ընթերցել մեքենայի դիմապակու մաքրիչների մասին տվյալները։"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"կառավարել մեքենայի դիմապակու մաքրիչները"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Կառավարել մեքենայի դիմապակու մաքրիչները։"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"հարցումների ցուցադրման համատեղելիության փաթեթներ"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Հարցումների ցուցադրման համատեղելիության փաթեթներ։"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"կառավարել ցուցադրման համատեղելիության փաթեթները"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"կառավարել ցուցադրման համատեղելիության փաթեթները։"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"միանալ հավելվածի համար քարտերի մատակարարներին"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"միանալ հավելվածի համար քարտերի մատակարարներին։"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"վերահսկել մեքենայի ուղևորային այլ գոտիների և այդ գոտիներում տեղադրված հավասարազոր հավելվածների (հավելվածներ, որոնք ունեն նույն փաթեթի անվանումը, ինչ զանգողը) կարգավիճակը և կառավարել այդ գոտիների էներգասնուցումը։"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Վերահսկել մեքենայի ուղևորային այլ գոտիների և այդ գոտիներում տեղադրված հավասարազոր հավելվածների (հավելվածներ, որոնք ունեն նույն փաթեթի անվանումը, ինչ զանգողը) կարգավիճակը և կառավարել այդ գոտիների էներգասնուցումը։"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"կապ հաստատել և տեղեկություններ փոխանակել մեքենայի ուղևորային այլ գոտիներում տեղադրված հավասարազոր հավելվածների հետ (հավելվածներ, որոնք ունեն նույն փաթեթի անվանումը, ինչ զանգողը)"</string>
diff --git a/service/res/values-in/strings.xml b/service/res/values-in/strings.xml
index 2df59ce..3edaebf 100644
--- a/service/res/values-in/strings.xml
+++ b/service/res/values-in/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Membaca penyeka kaca mobil."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"mengontrol penyeka kaca mobil"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Mengontrol penyeka kaca mobil."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"paket kompatibilitas tampilan kueri"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Paket kompatibilitas tampilan kueri."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"mengelola paket kompatibilitas layar"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"mengelola paket kompatibilitas layar."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"terhubung dengan penyedia kartu aplikasi"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"terhubung dengan penyedia kartu aplikasi."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"memantau status zona penumpang lainnya di mobil dan aplikasi pembanding (aplikasi yang memiliki nama paket yang sama dengan pemanggil) yang terinstal di zona tersebut, serta mengelola penggunaan daya di zona tersebut."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Memantau status zona penumpang lainnya di mobil dan aplikasi pembanding (aplikasi yang memiliki nama paket yang sama dengan pemanggil) yang terinstal di zona tersebut, serta mengelola penggunaan daya di zona tersebut."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"menghubungkan dan berkomunikasi dengan aplikasi pembanding (aplikasi yang memiliki nama paket yang sama dengan pemanggil) yang terinstal di zona penumpang lainnya dalam mobil"</string>
diff --git a/service/res/values-is/strings.xml b/service/res/values-is/strings.xml
index e903319..7696c40 100644
--- a/service/res/values-is/strings.xml
+++ b/service/res/values-is/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Lesa rúðuþurrkur bíls."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"stjórna rúðuþurrkum bíls"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Stjórna rúðuþurrkum bíls."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"fyrirspurnarpakkar fyrir skjásamhæfi"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Fyrirspurnarpakkar fyrir skjásamhæfi."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"stjórnunarpakkar fyrir skjásamhæfi"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"stjórnunarpakkar fyrir skjásamhæfi."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"binda við veitur forritaspjalds"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"binda við veitur forritaspjalds."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"fylgjast með stöðu annarra farþegasvæða í bílnum og samskonar forrita (forrit með sama pakkaheiti og hjá hringjanda) sem eru sett upp á viðkomandi svæðum og stjórna rafmagnsnotkun á svæðunum."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Fylgjast með stöðu annarra farþegasvæða í bílnum og samskonar forrita (forrit með sama pakkaheiti og hjá hringjanda) sem eru sett upp á viðkomandi svæðum og stjórna rafmagnsnotkun á svæðunum."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"koma á tengingu og eiga samskipti við samskonar forrit (forrit með sama pakkaheiti og hjá hringjanda) sem eru sett upp á öðrum farþegasvæðum í bílnum"</string>
diff --git a/service/res/values-it/strings.xml b/service/res/values-it/strings.xml
index 8a7252a..14bd153 100644
--- a/service/res/values-it/strings.xml
+++ b/service/res/values-it/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Consente di leggere lo stato dei tergicristalli dell\'auto."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"Controllo dei tergicristalli dell\'auto"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Consente di controllare i tergicristalli dell\'auto."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"invia una query relativa a pacchetti che richiedono un intervento per la compatibilità del display"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Invia una query relativa a pacchetti che richiedono un intervento per la compatibilità del display."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"Gestisci pacchetti che richiedono un intervento per la compatibilità del display"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"gestisci pacchetti che richiedono un intervento per la compatibilità del display."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"eseguire l\'associazione agli emittenti di carte delle app"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"eseguire l\'associazione agli emittenti di carte delle app."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"Monitoraggio degli stati delle altre zone degli occupanti dell\'auto e delle app peer (app che hanno lo stesso nome di pacchetto del chiamante) installate in quelle zone, nonché gestione dell\'alimentazione di quelle zone"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Consente di monitorare gli stati delle altre zone degli occupanti dell\'auto e delle app peer (app che hanno lo stesso nome di pacchetto del chiamante) installate in quelle zone, nonché di gestire l\'alimentazione di quelle zone."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"Creazione di una connessione e comunicazione con app peer (app che hanno lo stesso nome di pacchetto del chiamante) installate in altre zone degli occupanti dell\'auto"</string>
diff --git a/service/res/values-iw/strings.xml b/service/res/values-iw/strings.xml
index 0a5eb52..021a53b 100644
--- a/service/res/values-iw/strings.xml
+++ b/service/res/values-iw/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"קריאה של המגבים של הרכב."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"שליטה במגבים של הרכב"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"שליטה במגבים של הרכב."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"חבילות תאימות להצגת שאילתות"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"חבילות תאימות להצגת שאילתות."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ניהול חבילות תאימות להצגה"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ניהול חבילות תאימות להצגה."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"קישור לספקי כרטיסים של אפליקציות"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"קישור לספקי כרטיסים של אפליקציות."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"מעקב אחר המצבים של אזורי הנוסעים האחרים ברכב ואפליקציות שנבחרו לצורך השוואה (אפליקציות שיש להן שם חבילה זהה למתקשר) המותקנות באזורים האלה, וניהול העוצמה באזורים האלה."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"מעקב אחר המצבים של אזורי הנוסעים האחרים ברכב ואפליקציות שנבחרו לצורך השוואה (אפליקציות שיש להן שם חבילה זהה למתקשר) המותקנות באזורים האלה, וניהול העוצמה באזורים האלה."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"יצירת חיבור ותקשורת עם אפליקציות שנבחרו לצורך השוואה (אפליקציות שיש להן שם חבילה זהה למתקשר) המותקנות באזורי הנוסעים האחרים ברכב."</string>
diff --git a/service/res/values-ja/strings.xml b/service/res/values-ja/strings.xml
index c4ba929..101ec07 100644
--- a/service/res/values-ja/strings.xml
+++ b/service/res/values-ja/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"車のワイパー情報の読み取り。"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"車のワイパーの制御"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"車のワイパーの制御。"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"ディスプレイ対応パッケージのクエリ。"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"ディスプレイ対応パッケージのクエリ。"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ディスプレイ対応パッケージの管理"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ディスプレイ対応パッケージの管理。"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"アプリカード プロバイダとのバインド"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"アプリカード プロバイダとのバインド。"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"車内の別の乗員ゾーンとそのゾーンにインストールされた類似アプリ(発信者と同じパッケージ名を持つアプリ)の状態を監視して、ゾーンの電力を管理する。"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"車内の別の乗員ゾーンとそのゾーンにインストールされた類似アプリ(発信者と同じパッケージ名を持つアプリ)の状態を監視して、ゾーンの電力を管理する。"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"車内の別の乗員ゾーンにインストールされた類似アプリ(発信者と同じパッケージ名を持つアプリ)との接続を確立して通信する"</string>
diff --git a/service/res/values-ka/strings.xml b/service/res/values-ka/strings.xml
index 86b099c..49df597 100644
--- a/service/res/values-ka/strings.xml
+++ b/service/res/values-ka/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"წაიკითხეთ მანქანის საქარე მინის საწმენდების შესახებ."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"აკონტროლეთ მანქანის საქარე მინის საწმენდები"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"აკონტროლეთ მანქანის საქარე მინის საწმენდები."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"მოთხოვნის ჩვენების თავსებადობის პაკეტები"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"მოთხოვნის ჩვენების თავსებადობის პაკეტები."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ეკრანის თავსებადობის პაკეტების მართვა"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ეკრანის თავსებადობის პაკეტების მართვა."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"დაკავშირება აპის ბარათების პროვაიდერებთან"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"დაკავშირება აპის ბარათების პროვაიდერებთან."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"დააკვირდით სხვა ოკუპანტთა ზონების მდგომარეობას მანქანისა და ამ ზონებში დაყენებულ თანატოლ აპებში (აპები, რომლებსაც აქვთ იგივე პაკეტის სახელი აბონენტთან) და მართეთ ამ ზონების სიმძლავრე."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"დააკვირდით სხვა ოკუპანტთა ზონების მდგომარეობას მანქანისა და ამ ზონებში დაყენებულ თანატოლ აპებში (აპები, რომლებსაც აქვთ იგივე პაკეტის სახელი აბონენტთან) და მართეთ ამ ზონების სიმძლავრე."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"დაამყარეთ კავშირი და დაუკავშირდით თანატოლ აპებს (აპები, რომლებსაც აქვთ იგივე პაკეტის სახელი აბონენტთან) დაინსტალირებული მანქანის სხვა ზონებში"</string>
diff --git a/service/res/values-kk/strings.xml b/service/res/values-kk/strings.xml
index 4101758..0060741 100644
--- a/service/res/values-kk/strings.xml
+++ b/service/res/values-kk/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Көлік терезе тазалағыштарының дерегін қарау"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"көліктің терезе тазалағыштарын басқару"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Көліктің терезе тазалағыштарын басқару"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"cұрау дисплейінің үйлесімділік пакеттері"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Сұрау дисплейінің үйлесімділік пакеттері."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"дисплейдің үйлесімділік пакеттерін басқару"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"дисплейдің үйлесімділік пакеттерін басқару."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"қолданба картасының провайдерлеріне байланыстыру"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"қолданба картасының провайдерлеріне байланыстыру."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"көліктегі басқа жолаушы орындарының және сол орындарда орнатылған түйіндес қолданбалардың (пакет атаулары абоненттікімен бірдей қолданбалар) жағдайын бақылап, аталған орындардағы электр қуат көзін басқарыңыз."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Көліктегі басқа жолаушы орындарының және сол орындарда орнатылған түйіндес қолданбалардың (пакет атаулары абоненттікімен бірдей қолданбалар) жағдайын бақылап, аталған орындардағы электр қуат көзін басқарыңыз."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"көліктегі басқа жолаушы орындарында орнатылған түйіндес қолданбалармен (пакет атаулары абоненттікімен бірдей қолданбалар) байланыс орнатыңыз және деректермен алмасыңыз"</string>
diff --git a/service/res/values-km/strings.xml b/service/res/values-km/strings.xml
index a69b9bb..19883a5 100644
--- a/service/res/values-km/strings.xml
+++ b/service/res/values-km/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"អានព័ត៌មានទាក់ទងនឹងផ្លិតទឹករបស់រថយន្ត។"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"គ្រប់គ្រងផ្លិតទឹករបស់រថយន្ត"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"គ្រប់គ្រងផ្លិតទឹករបស់រថយន្ត។"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"កញ្ចប់នៃភាពត្រូវគ្នាក្នុងការបង្ហាញសំណួរ"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"កញ្ចប់នៃភាពត្រូវគ្នាក្នុងការបង្ហាញសំណួរ។"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"គ្រប់គ្រងកញ្ចប់នៃភាពត្រូវគ្នាក្នុងការបង្ហាញ"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"គ្រប់គ្រងកញ្ចប់នៃភាពត្រូវគ្នាក្នុងការបង្ហាញ។"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"ភ្ជាប់ទៅក្រុមហ៊ុនផ្ដល់កាតកម្មវិធី"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"ភ្ជាប់ទៅក្រុមហ៊ុនផ្ដល់កាតកម្មវិធី។"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"តាមដានស្ថានភាពនៃតំបន់អ្នកជិះផ្សេងទៀតក្នុងរថយន្ត និងកម្មវិធីដូចគ្នា (កម្មវិធីដែលមានឈ្មោះកញ្ចប់ដូចគ្នានឹងខាងហៅ) ដែលបានដំឡើងនៅក្នុងតំបន់ទាំងនោះ និងគ្រប់គ្រងថាមពលនៃតំបន់ទាំងនោះ។"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"តាមដានស្ថានភាពនៃតំបន់អ្នកជិះផ្សេងទៀតក្នុងរថយន្ត និងកម្មវិធីដូចគ្នា (កម្មវិធីដែលមានឈ្មោះកញ្ចប់ដូចគ្នានឹងខាងហៅ) ដែលបានដំឡើងនៅក្នុងតំបន់ទាំងនោះ និងគ្រប់គ្រងថាមពលនៃតំបន់ទាំងនោះ។"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"បង្កើតការតភ្ជាប់ និងភ្ជាប់ទំនាក់ទំនងជាមួយកម្មវិធីដូចគ្នា (កម្មវិធីដែលមានឈ្មោះកញ្ចប់ដូចគ្នានឹងខាងហៅ) ដែលបានដំឡើងនៅក្នុងតំបន់អ្នកជិះផ្សេងទៀតក្នុងរថយន្ត"</string>
diff --git a/service/res/values-kn/strings.xml b/service/res/values-kn/strings.xml
index ce37a16..a09a9ce 100644
--- a/service/res/values-kn/strings.xml
+++ b/service/res/values-kn/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"ಕಾರಿನ ವಿಂಡ್ಶೀಲ್ಡ್ ವೈಪರ್ಗಳ ಮಾಹಿತಿಯನ್ನು ಓದಿ."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"ಕಾರಿನ ವಿಂಡ್ಶೀಲ್ಡ್ ವೈಪರ್ಗಳನ್ನು ನಿಯಂತ್ರಿಸಿ"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"ಕಾರಿನ ವಿಂಡ್ಶೀಲ್ಡ್ ವೈಪರ್ಗಳನ್ನು ನಿಯಂತ್ರಿಸಿ."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"ಪ್ರಶ್ನೆ ಡಿಸ್ಪ್ಲೇ ಹೊಂದಾಣಿಕೆಯ ಪ್ಯಾಕೇಜ್ಗಳು"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"ಪ್ರಶ್ನೆ ಡಿಸ್ಪ್ಲೇ ಹೊಂದಾಣಿಕೆಯ ಪ್ಯಾಕೇಜ್ಗಳು."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ಡಿಸ್ಪ್ಲೇ ಹೊಂದಾಣಿಕೆಯ ಪ್ಯಾಕೇಜ್ಗಳನ್ನು ನಿರ್ವಹಿಸಿ"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ಪ್ರದರ್ಶನ ಹೊಂದಾಣಿಕೆಯ ಪ್ಯಾಕೇಜ್ಗಳನ್ನು ನಿರ್ವಹಿಸಿ."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"ಆ್ಯಪ್ ಕಾರ್ಡ್ ಪೂರೈಕೆದಾರರಿಗೆ ಬೈಂಡ್ ಮಾಡಿ"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"ಆ್ಯಪ್ ಕಾರ್ಡ್ ಪೂರೈಕೆದಾರರಿಗೆ ಬೈಂಡ್ ಮಾಡಿ."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"ಕಾರ್ನಲ್ಲಿನ ಇತರ ಲಭ್ಯವಿರುವ ವಲಯಗಳ ಸ್ಥಿತಿಯನ್ನು ಮತ್ತು ಆ ವಲಯಗಳಲ್ಲಿ ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲಾದ ಪೀಯರ್ ಆ್ಯಪ್ಗಳನ್ನು (ಕಾಲರ್ ಆ್ಯಪ್ಗಳ ಅದೇ ಪ್ಯಾಕೇಜ್ ಹೆಸರನ್ನು ಹೊಂದಿರುವ ಆ್ಯಪ್ಗಳು) ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಿ ಮತ್ತು ಆ ವಲಯಗಳ ಪವರ್ ಅನ್ನು ನಿರ್ವಹಿಸಿ."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"ಕಾರ್ನಲ್ಲಿನ ಇತರ ಲಭ್ಯವಿರುವ ವಲಯಗಳ ಸ್ಥಿತಿಯನ್ನು ಮತ್ತು ಆ ವಲಯಗಳಲ್ಲಿ ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲಾದ ಪೀಯರ್ ಆ್ಯಪ್ಗಳನ್ನು (ಕಾಲರ್ ಆ್ಯಪ್ಗಳ ಅದೇ ಪ್ಯಾಕೇಜ್ ಹೆಸರನ್ನು ಹೊಂದಿರುವ ಆ್ಯಪ್ಗಳು) ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಿ ಮತ್ತು ಆ ವಲಯಗಳ ಪವರ್ ಅನ್ನು ನಿರ್ವಹಿಸಿ."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"ಕಾರ್ನಲ್ಲಿನ ಇತರ ಲಭ್ಯವಿರುವ ವಲಯಗಳಲ್ಲಿ ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲಾದ ಪೀಯರ್ ಆ್ಯಪ್ಗಳಿಗೆ (ಕಾಲರ್ ಆ್ಯಪ್ಗಳ ಅದೇ ಪ್ಯಾಕೇಜ್ ಹೆಸರನ್ನು ಹೊಂದಿರುವ ಆ್ಯಪ್ಗಳು) ಕನೆಕ್ಷನ್ ಅನ್ನು ಸ್ಥಾಪಿಸಿ ಮತ್ತು ಅವುಗಳ ಜೊತೆಗೆ ಸಂವಹನ ನಡೆಸಿ"</string>
diff --git a/service/res/values-ko/strings.xml b/service/res/values-ko/strings.xml
index 7778e87..73816e4 100644
--- a/service/res/values-ko/strings.xml
+++ b/service/res/values-ko/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"차량 와이퍼를 읽습니다."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"차량 와이퍼를 제어합니다."</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"차량 와이퍼를 제어합니다."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"쿼리 디스플레이 호환성 패키지"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"쿼리 디스플레이 호환성 패키지"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"디스플레이 호환성 패키지 관리"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"디스플레이 호환성 패키지 관리"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"앱 카드 제공업체에 바인드합니다"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"앱 카드 제공업체에 바인드합니다."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"차량 내 다른 승객 공간의 상태와 해당 공간에 설치된 동종 앱(호출자와 같은 패키지 이름을 가진 앱)의 상태를 모니터링하고 승객 공간의 출력을 관리합니다."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"차량 내 다른 승객 공간의 상태와 해당 공간에 설치된 동종 앱(호출자와 같은 패키지 이름을 가진 앱)의 상태를 모니터링하고 승객 공간의 출력을 관리합니다."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"차량 내 다른 승객 공간에 설치된 동종 앱(호출자와 같은 패키지 이름을 가진 앱)과 연결을 설정하고 통신합니다."</string>
diff --git a/service/res/values-ky/strings.xml b/service/res/values-ky/strings.xml
index 46de37b..78117a8 100644
--- a/service/res/values-ky/strings.xml
+++ b/service/res/values-ky/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Унаанын айнек тазалагычтары тууралуу маалыматты окуу."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"унаанын айнек тазалагычтарын башкаруу"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Унаанын айнек тазалагычтарын башкаруу."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"дисплей менен шайкештикти талап кылган топтомдор тууралуу сурамдарды жөнөтүү"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Дисплей менен шайкештикти талап кылган топтомдор тууралуу сурамдарды жөнөтүү."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"дисплей менен шайкештикти талап кылган топтомдорду тескөө"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"дисплей менен шайкештикти талап кылган топтомдорду тескөө."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"колдонмонун картасын камсыздоочулар менен байланыштыруу"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"колдонмонун картасын камсыздоочулар менен байланыштыруу."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"башка аймактарда орнотулган унаадагы бир деңгээлдеги колдонмолорду (чалып жаткан абоненттин топтомуна окшош колдонмолор) көзөмөлдөп, ал аймактардын күчүн тескөө."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Башка аймактарда орнотулган унаадагы бир деңгээлдеги колдонмолорду (чалып жаткан абоненттин топтомуна окшош колдонмолор) көзөмөлдөп, ал аймактардын күчүн тескеңиз."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"унаанын башка жүргүнчүлөр аймактарында орнотулган бир деңгээлдеги колдонмолор (чалып жаткан абоненттин топтомуна окшош колдонмолор) менен байланышуу"</string>
diff --git a/service/res/values-lo/strings.xml b/service/res/values-lo/strings.xml
index 0684f16..f1152e9 100644
--- a/service/res/values-lo/strings.xml
+++ b/service/res/values-lo/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"ອ່ານຄ່າເຄື່ອງປັດນ້ຳຝົນຂອງລົດ."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"ຄວບຄຸມເຄື່ອງປັດນ້ຳຝົນຂອງລົດ"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"ຄວບຄຸມເຄື່ອງປັດນ້ຳຝົນຂອງລົດ."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"ຄຳຊອກຫາສະແດງແພັກເກດຄວາມເຂົ້າກັນໄດ້"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"ຄຳຊອກຫາສະແດງແພັກເກດຄວາມເຂົ້າກັນໄດ້."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ຈັດການແພັກເກດຄວາມເຂົ້າກັນໄດ້ຂອງການສະແດງຜົນ"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ຈັດການແພັກເກດຄວາມເຂົ້າກັນໄດ້ຂອງການສະແດງຜົນ."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"ເຊື່ອມໂຍງກັບຜູ້ໃຫ້ບໍລິການບັດແອັບ"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"ເຊື່ອມໂຍງກັບຜູ້ໃຫ້ບໍລິການບັດແອັບ."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"ເຝົ້າຕິດຕາມສະຖານະຂອງເຂດທີ່ມີການເຂົ້າໃຊ້ອື່ນໆໃນລົດ ແລະ ແອັບທຽບເທົ່າ (ແອັບທີ່ມີຊື່ແພັກເກດດຽວກັນກັບຜູ້ເອີ້ນໃຊ້) ເຊິ່ງຕິດຕັ້ງໄວ້ໃນເຂດເຫຼົ່ານັ້ນ ແລະ ຈັດການກຳລັງໄຟຟ້າຂອງເຂດເຫຼົ່ານັ້ນ."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"ເຝົ້າຕິດຕາມສະຖານະຂອງເຂດທີ່ມີການເຂົ້າໃຊ້ອື່ນໆໃນລົດ ແລະ ແອັບທຽບເທົ່າ (ແອັບທີ່ມີຊື່ແພັກເກດດຽວກັນກັບຜູ້ເອີ້ນໃຊ້) ເຊິ່ງຕິດຕັ້ງໄວ້ໃນເຂດເຫຼົ່ານັ້ນ ແລະ ຈັດການກຳລັງໄຟຟ້າຂອງເຂດເຫຼົ່ານັ້ນ."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"ສ້າງການເຊື່ອມຕໍ່ ແລະ ສື່ສານກັບແອັບທຽບເທົ່າ (ແອັບທີ່ມີຊື່ແພັກເກດດຽວກັນກັບຜູ້ເອີ້ນໃຊ້) ເຊິ່ງຕິດຕັ້ງໄວ້ໃນເຂດທີ່ມີການເຂົ້າໃຊ້ອື່ນໆໃນລົດ"</string>
diff --git a/service/res/values-lt/strings.xml b/service/res/values-lt/strings.xml
index ce9493b..16f2232 100644
--- a/service/res/values-lt/strings.xml
+++ b/service/res/values-lt/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Nuskaityti priekinio stiklo valytuvų informaciją."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"valdyti automobilio priekinio stiklo valytuvus"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Valdyti automobilio priekinio stiklo valytuvus."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"užklausa apie ekrano suderinamumo paketus"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Užklausa apie ekrano suderinamumo paketus."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"tvarkyti ekrano suderinamumo paketus"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"tvarkyti ekrano suderinamumo paketus."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"susieti su programų kortelių teikėjais"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"susieti su programų kortelių teikėjais."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"stebėti kitų buvimo namuose zonų būseną automobilyje ir lygiavertes programas (programas, kurių paketo pavadinimas toks pat kaip skambintojo), įdiegtas šiose zonose, bei valdyti šių zonų galią."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Stebėti kitų buvimo namuose zonų būseną automobilyje ir lygiavertes programas (programas, kurių paketo pavadinimas toks pat kaip skambintojo), įdiegtas šiose zonose, bei valdyti šių zonų galią."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"užmegzti ryšį ir susisiekti su lygiavertėmis programomis (programomis, kurių paketo pavadinimas toks pat kaip skambintojo), įdiegtomis kitose buvimo namuose zonose, automobilyje"</string>
diff --git a/service/res/values-lv/strings.xml b/service/res/values-lv/strings.xml
index 517d608..04fe39b 100644
--- a/service/res/values-lv/strings.xml
+++ b/service/res/values-lv/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Nolasīt informāciju par automašīnas vējstikla tīrītājiem."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"kontrolēt automašīnas vējstikla tīrītājus"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Kontrolēt automašīnas vējstikla tīrītājus."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"sūtīt pieprasījumus par pakotnēm, kam nepieciešama saderība ar displejiem"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Sūtīt pieprasījumus par pakotnēm, kam nepieciešama saderība ar displejiem."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"pārvaldīt pakotnes, kam nepieciešama saderība ar displejiem"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"pārvaldīt pakotnes, kam nepieciešama saderība ar displejiem."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"saistīšana ar lietotņu kartītēm, kuru izgūšanai izmanto satura nodrošinātājus"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"saistīšana ar lietotņu kartītēm, kuru izgūšanai izmanto satura nodrošinātājus."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"uzraudzīt automašīnas pārējo pasažieru zonu stāvokli un šajās zonās instalētās līdzīgās lietotnes (lietotnes, kam ir tāds pats pakotnes nosaukums kā izsaucējam), kā arī pārvaldīt enerģijas lietojumu šajās zonās"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Uzraudzīt automašīnas pārējo pasažieru zonu stāvokli un šajās zonās instalētās līdzīgās lietotnes (lietotnes, kam ir tāds pats pakotnes nosaukums kā izsaucējam), kā arī pārvaldīt enerģijas lietojumu šajās zonās."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"veidot savienojumus un uzturēt sakarus ar pārējās automašīnas pasažieru zonās instalētajām līdzīgajām lietotnēm (lietotnēm, kam ir tāds pats pakotnes nosaukums kā izsaucējam)"</string>
diff --git a/service/res/values-mk/strings.xml b/service/res/values-mk/strings.xml
index 105a7f9..2703aa4 100644
--- a/service/res/values-mk/strings.xml
+++ b/service/res/values-mk/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Читање на состојбата на бришачите на автомобилот."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"контрола на бришачите на автомобилот"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Контрола на бришачите на автомобилот."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"пакети за компатибилност на екранот за барања"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Пакети за компатибилност на екранот за барања."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"управување со пакети за компатибилност на екранот"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"управување со пакети што бараат компатибилност на екранот."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"да се поврзе со давателите на картички за апликации"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"да се поврзе со давателите на картички за апликации."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"да ја следи состојбата на другите зони на присуство во автомобилот и на споредбените апликации (апликации што ги имаат истото име на пакет како повикувачот) инсталирани во тие зони и да управува со напојувањето на тие зони"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"да ја следи состојбата на другите зони на присуство во автомобилот и на споредбените апликации (апликации што ги имаат истото име на пакет како повикувачот) инсталирани во тие зони и да управува со напојувањето на тие зони"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"да утврди врска и да комуницира со споредбени апликации (апликации што ги имаат истото име на пакет како повикувачот) инсталирани во други зони на присуство во автомобилот"</string>
diff --git a/service/res/values-ml/strings.xml b/service/res/values-ml/strings.xml
index aae0594..8beb981 100644
--- a/service/res/values-ml/strings.xml
+++ b/service/res/values-ml/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"കാറിന്റെ വിൻഡ്ഷീൽഡ് വെെപ്പറുകൾ വായിക്കുക."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"കാറിന്റെ വിൻഡ്ഷീൽഡ് വെെപ്പറുകൾ നിയന്ത്രിക്കുക"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"കാറിന്റെ വിൻഡ്ഷീൽഡ് വെെപ്പറുകൾ നിയന്ത്രിക്കുക."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"ചോദ്യം പ്രദർശിപ്പിക്കുന്നതിന് അനുയോജ്യമായ പാക്കേജുകൾ"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"ചോദ്യം പ്രദർശിപ്പിക്കുന്നതിന് അനുയോജ്യമായ പാക്കേജുകൾ."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ഡിസ്പ്ലേ അനുയോജ്യതാ പാക്കേജുകൾ മാനേജ് ചെയ്യുക"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ഡിസ്പ്ലേ അനുയോജ്യതാ പാക്കേജുകൾ മാനേജ് ചെയ്യുക."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"ആപ്പ് കാർഡ് ദാതാക്കളുമായി ബന്ധിപ്പിക്കുക"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"ആപ്പ് കാർഡ് ദാതാക്കളുമായി ബന്ധിപ്പിക്കുക."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"കാറിലെ മറ്റ് ഒക്യുപന്റ് സോണുകളുടെയും ആ സോണുകളിൽ ഇൻസ്റ്റാൾ ചെയ്തിരിക്കുന്ന പിയർ ആപ്പുകളുടെയും (കോളറുടെ അതേ പാക്കേജ് പേരുള്ള ആപ്പുകൾ) നിലകൾ നിരീക്ഷിച്ച് ആ സോണുകളുടെ പവർ മാനേജ് ചെയ്യുക."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"കാറിലെ മറ്റ് ഒക്യുപന്റ് സോണുകളുടെയും ആ സോണുകളിൽ ഇൻസ്റ്റാൾ ചെയ്തിരിക്കുന്ന പിയർ ആപ്പുകളുടെയും (കോളറുടെ അതേ പാക്കേജ് പേരുള്ള ആപ്പുകൾ) നിലകൾ നിരീക്ഷിച്ച് ആ സോണുകളുടെ പവർ മാനേജ് ചെയ്യുക."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"കാറിലെ മറ്റ് ഒക്യുപന്റ് സോണുകളിൽ ഇൻസ്റ്റാൾ ചെയ്തിരിക്കുന്ന പിയർ ആപ്പുകളുമായി (കോളറുടെ അതേ പാക്കേജ് പേരുള്ള ആപ്പുകൾ) കണക്ഷൻ സ്ഥാപിച്ച് ആശയവിനിമയം നടത്തുക"</string>
diff --git a/service/res/values-mn/strings.xml b/service/res/values-mn/strings.xml
index 4b05c55..1b2dfb5 100644
--- a/service/res/values-mn/strings.xml
+++ b/service/res/values-mn/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Машины салхины шил арчигчийг унших."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"машины салхины шил арчигчийг хянах"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Машины салхины шил арчигчийг хянах."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"дэлгэцийн тохирох багцыг асуух"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Дэлгэцийн тохирох багцыг асууна уу."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"дэлгэцийн тохирох багцуудыг удирдах"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"дэлгэцийн тохирох багцуудыг удирдах."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"аппын карт нийлүүлэгчдэд холбоно уу"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"аппын карт нийлүүлэгчдэд холбоно уу."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"Машины бусад суух бүс болон эдгээр бүсэд суулгасан төстэй аппуудын (дуудлага хийгчтэй ижил багцын нэртэй аппууд) төлөвийг хянаж, эдгээр бүсийн цахилгааныг удирдана."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Машины бусад суух бүс болон эдгээр бүсэд суулгасан төстэй аппуудын (дуудлага хийгчтэй ижил багцын нэртэй аппууд) төлөвийг хянаж, эдгээр бүсийн цахилгааныг удирдана."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"машины бусад суух бүсэд суулгасан төстэй аппуудтай (дуудлага хийгчтэй ижил багцын нэртэй аппууд) холболт тогтоож, харилцана"</string>
diff --git a/service/res/values-mr/strings.xml b/service/res/values-mr/strings.xml
index 419fbb3..90435fa 100644
--- a/service/res/values-mr/strings.xml
+++ b/service/res/values-mr/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"कारच्या विंडशिल्ड वायपरशी संबंधित डेटा वाचा."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"कारची विंडशिल्ड वायपर नियंत्रित करा"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"कारची विंडशिल्ड वायपर नियंत्रित करा."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"क्वेरी डिस्प्ले कंपॅटिबिलिटी पॅकेज"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"क्वेरी डिस्प्ले कंपॅटिबिलिटी पॅकेज."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"डिस्प्ले कंपॅटिबिलिटी पॅकेज व्यवस्थापित करा"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"डिस्प्ले कंपॅटिबिलिटी पॅकेज व्यवस्थापित करा."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"अॅप कार्ड पुरवठादारांशी प्रतिबद्ध करा"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"अॅप कार्ड पुरवठादारांशी प्रतिबद्ध करा."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"कारमधील इतर ऑक्युपंट झोन आणि त्या झोनमध्ये इंस्टॉल केलेल्या पीअर ॲप्सच्या (कॉलरचेच पॅकेज नाव असलेली ॲप्स) स्थितींचे निरीक्षण करा आणि त्या झोनची पॉवर व्यवस्थापित करा."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"कारमधील इतर ऑक्युपंट झोन आणि त्या झोनमध्ये इंस्टॉल केलेल्या पीअर ॲप्सच्या (कॉलरचेच पॅकेज नाव असलेली ॲप्स) स्थितींचे निरीक्षण करा आणि त्या झोनची पॉवर व्यवस्थापित करा."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"कारमधील इतर ऑक्युपंट झोनमध्ये इंस्टॉल केलेल्या पीअर ॲप्ससोबत (कॉलरचेच पॅकेज नाव असलेली ॲप्स) कनेक्ट करा आणि संवाद साधा"</string>
diff --git a/service/res/values-ms/strings.xml b/service/res/values-ms/strings.xml
index 2147658..77b2d9d 100644
--- a/service/res/values-ms/strings.xml
+++ b/service/res/values-ms/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Baca pengelap cermin depan kereta."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"kawal pengelap cermin depan kereta"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Kawal pengelap cermin depan kereta."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"pakej keserasian paparan pertanyaan"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Pakej keserasian paparan pertanyaan"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"urus pakej keserasian paparan"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"urus pakej keserasian paparan."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"bersambung dengan penyedia kad apl"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"bersambung dengan penyedia kad apl."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"pantau keadaan zon penumpang lain di dalam kereta dan apl setara (apl yang mempunyai nama pakej yang sama dengan pemanggil) yang dipasang dalam zon tersebut, serta urus kuasa zon tersebut."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Pantau keadaan zon penumpang lain di dalam kereta dan apl setara (apl yang mempunyai nama pakej yang sama dengan pemanggil) yang dipasang dalam zon tersebut, serta urus kuasa zon tersebut."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"wujudkan sambungan dan berkomunikasi dengan apl setara (apl yang mempunyai nama pakej yang sama dengan pemanggil) yang dipasang dalam zon penumpang lain di dalam kereta"</string>
diff --git a/service/res/values-my/strings.xml b/service/res/values-my/strings.xml
index 28303cc..8c8d245 100644
--- a/service/res/values-my/strings.xml
+++ b/service/res/values-my/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"ကားလေကာမှန်ရေယက်များ၏ အခြေအနေကို ဖတ်ပါ။"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"ကား၏ လေကာမှန်ရေယက်များကို ထိန်းချုပ်ပါ"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"ကား၏ လေကာမှန်ရေယက်များကို ထိန်းချုပ်ပါ။"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"မေးမြန်းချက်ဖန်သားပြင် ကိုက်ညီမှုဆိုင်ရာပက်ကေ့ဂျ်များ"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"မေးမြန်းချက်ဖန်သားပြင် ကိုက်ညီမှုဆိုင်ရာပက်ကေ့ဂျ်များ။"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ဖန်သားပြင် ကိုက်ညီမှုဆိုင်ရာပက်ကေ့ဂျ်များ စီမံခြင်း"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ဖန်သားပြင် ကိုက်ညီမှုဆိုင်ရာပက်ကေ့ဂျ်များ စီမံခြင်း။"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"အက်ပ်ကတ် ဝန်ဆောင်မှုပေးသူများနှင့် ချိတ်ဆက်ပါ"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"အက်ပ်ကတ် ဝန်ဆောင်မှုပေးသူများနှင့် ချိတ်ဆက်ပါ။"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"ကားထဲရှိ အခြားလိုက်ပါသူရှိသည့် နေရာများနှင့် ထိုနေရာများတွင် တပ်ဆင်ထားသည့် အမျိုးအစားတူ အက်ပ်များ (ခေါ်ဆိုသူနှင့် ပက်ကေ့ချ်အမည်တူညီသော အက်ပ်များ) ၏ အခြေအနေကို စောင့်ကြည့်စစ်ဆေးပြီး ထိုနေရာများ၏ စွမ်းအင်ပါဝါကို စီမံပါ။"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"ကားထဲရှိ အခြားလိုက်ပါသူရှိသည့် နေရာများနှင့် ထိုနေရာများတွင် တပ်ဆင်ထားသည့် အမျိုးအစားတူ အက်ပ်များ (ခေါ်ဆိုသူနှင့် ပက်ကေ့ချ်အမည်တူညီသော အက်ပ်များ) ၏ အခြေအနေကို စောင့်ကြည့်စစ်ဆေးပြီး ထိုနေရာများ၏ စွမ်းအင်ပါဝါကို စီမံပါ။"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"ချိတ်ဆက်မှုပြုပြီး ကားထဲရှိ အခြားလိုက်ပါသူရှိသည့် နေရာများတွင် ထည့်သွင်းထားသည့် အမျိုးအစားတူ အက်ပ်များ (ခေါ်ဆိုသူနှင့် ပက်ကေ့ချ်အမည်တူညီသော အက်ပ်များ) နှင့် ဆက်သွယ်ပါ"</string>
diff --git a/service/res/values-nb/strings.xml b/service/res/values-nb/strings.xml
index d6de27f..279abac 100644
--- a/service/res/values-nb/strings.xml
+++ b/service/res/values-nb/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"lese informasjon om vindusviskerne på bilen"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"kontrollere vindusviskerne på bilen"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"kontrollere vindusviskerne på bilen"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"pakker for kompatibilitet med visning av søk"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Pakker for kompatibilitet med visning av søk."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"administrere pakker for kompatibilitet med visning"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"administrere pakker for kompatibilitet med visning"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"knytte til leverandører av appkort"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"knytte til leverandører av appkort"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"overvåke tilstandene til andre passasjersoner i bilen og likestilte apper (apper med samme pakkenavn som startappen) som er installert i de aktuelle sonene, samt administrere strømbruken i disse sonene"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"overvåke tilstandene til andre passasjersoner i bilen og likestilte apper (apper med samme pakkenavn som startappen) som er installert i de aktuelle sonene, samt administrere strømbruken i disse sonene"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"opprette en tilkobling og kommunisere med likestilte apper (apper med samme pakkenavn som startappen) som er installert i andre passasjersoner i bilen"</string>
diff --git a/service/res/values-ne/strings.xml b/service/res/values-ne/strings.xml
index 4718f79..f190802 100644
--- a/service/res/values-ne/strings.xml
+++ b/service/res/values-ne/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"कारको विन्डसिल्ड वाइपरहरू रिड गर्ने।"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"कारको विन्डसिल्ड वाइपरहरू प्रयोग गर्ने"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"कारको विन्डसिल्ड वाइपरहरू प्रयोग गर्ने।"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"क्वेरीमा देखाइने कम्प्याटिबिलिटीसम्बन्धी प्याकेजहरू"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"क्वेरीमा देखाइने कम्प्याटिबिलिटीसम्बन्धी प्याकेजहरू।"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"डिस्प्ले कम्प्याटिबिलिटी प्याकेजहरू व्यवस्थापन गर्नुहोस्"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"डिस्प्ले कम्प्याटिबिलिटी प्याकेजहरू व्यवस्थापन गर्नुहोस्।"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"एप कार्ड जारी गर्ने कम्पनीसँग कनेक्ट गर्नुहोस्"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"एप कार्ड जारी गर्ने कम्पनीसँग कनेक्ट गर्नुहोस्।"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"कारमा यात्रु बसेका अन्य सिट र ती सिटसँग कनेक्ट गरिएका डिभाइसहरूमा इन्स्टल गरिएका पियर एप (कल गर्न प्रयोग गरिने एपको जस्तै प्याकेजको नाम भएका एपहरू) को स्थिति जाँच्नुहोस् र ती सिटहरूलाई कति ऊर्जा खपत गर्न दिने भन्ने कुरा व्यवस्थापन गर्नुहोस्।"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"कारमा यात्रु बसेका अन्य सिट र ती सिटसँग कनेक्ट गरिएका डिभाइसहरूमा इन्स्टल गरिएका पियर एप (कल गर्न प्रयोग गरिने एपको जस्तै प्याकेजको नाम भएका एपहरू) को स्थिति जाँच्नुहोस् र ती सिटहरूलाई कति ऊर्जा खपत गर्न दिने भन्ने कुरा व्यवस्थापन गर्नुहोस्।"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"कारमा यात्रु बसेका अन्य सिटसँग कनेक्ट गरिएका डिभाइसहरूमा इन्स्टल गरिएका पियर एपहरू (कल गर्न प्रयोग गरिने एपको जस्तै प्याकेजको नाम भएका एपहरू) सँग कनेक्ट गर्नुहोस् र ती एपहरूसँग अन्तर्क्रिया गर्नुहोस्"</string>
diff --git a/service/res/values-nl/strings.xml b/service/res/values-nl/strings.xml
index 27e0b4d..3003c36 100644
--- a/service/res/values-nl/strings.xml
+++ b/service/res/values-nl/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Ruitenwissers van auto lezen."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"ruitenwissers van auto bedienen"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Ruitenwissers van auto bedienen."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"weergavecompatibiliteitspakketten opvragen"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Weergavecompatibiliteitspakketten opvragen"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"weergavecompatibiliteitspakketten beheren"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"weergavecompatibiliteitspakketten beheren."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"binden aan providers van app-kaarten"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"binden aan providers van app-kaarten."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"de statussen bijhouden van andere passagierszones in de auto en vergelijkbare apps (apps die dezelfde pakketnaam hebben als de beller) die zijn geïnstalleerd in die zones, en de stroomvoorziening van die zones beheren"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"De statussen bijhouden van andere passagierszones in de auto en vergelijkbare apps (apps die dezelfde pakketnaam hebben als de beller) die zijn geïnstalleerd in die zones, en de stroomvoorziening van die zones beheren."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"verbinding maken en communiceren met vergelijkbare apps (apps die dezelfde pakketnaam hebben als de beller) die zijn geïnstalleerd in andere passagierszones in de auto"</string>
diff --git a/service/res/values-or/strings.xml b/service/res/values-or/strings.xml
index b299683..ba09721 100644
--- a/service/res/values-or/strings.xml
+++ b/service/res/values-or/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"କାରର ୱିଣ୍ଡସିଲ୍ଡ ୱାଇପରଗୁଡ଼ିକୁ ପଢ଼ିପାରିବ।"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"କାରର ୱିଣ୍ଡସିଲ୍ଡ ୱାଇପରଗୁଡ଼ିକୁ ନିୟନ୍ତ୍ରଣ କରିପାରିବ"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"କାରର ୱିଣ୍ଡସିଲ୍ଡ ୱାଇପରଗୁଡ଼ିକୁ ନିୟନ୍ତ୍ରଣ କରିପାରିବ।"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"କ୍ୱେରୀ ଡିସପ୍ଲେ କମ୍ପାଟିବିଲିଟୀ ପେକେଜଗୁଡ଼ିକ"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"କ୍ୱେରୀ ଡିସପ୍ଲେ କମ୍ପାଟିବିଲିଟୀ ପେକେଜଗୁଡ଼ିକ।"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ଡିସପ୍ଲେ କମ୍ପାଟିବିଲିଟୀ ପେକେଜଗୁଡ଼ିକୁ ପରିଚାଳନା କରନ୍ତୁ"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ଡିସପ୍ଲେ କମ୍ପାଟିବିଲିଟୀ ପେକେଜଗୁଡ଼ିକୁ ପରିଚାଳନା କରନ୍ତୁ।"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"ଆପ କାର୍ଡ ପ୍ରଦାନକାରୀମାନଙ୍କ ସହ ଯୋଡ଼ି ହୁଅନ୍ତୁ"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"ଆପ କାର୍ଡ ପ୍ରଦାନକାରୀମାନଙ୍କ ସହ ଯୋଡ଼ି ହୁଅନ୍ତୁ।"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"କାରରେ ଥିବା ଅନ୍ୟ ଅକ୍ୟୁପେଣ୍ଟ ଜୋନଗୁଡ଼ିକର ସ୍ଥିତି ଏବଂ ସେହି ଜୋନଗୁଡ଼ିକରେ ଇନଷ୍ଟଲ କରାଯାଇଥିବା ପିଅର ଆପ୍ସ (କଲରଙ୍କ ପରି ସମାନ ପେକେଜ ନାମ ଥିବା ଆପ୍ସ)କୁ ମନିଟର କରନ୍ତୁ ଏବଂ ସେହି ଜୋନଗୁଡ଼ିକର ପାୱାରକୁ ପରିଚାଳନା କରନ୍ତୁ।"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"କାରରେ ଥିବା ଅନ୍ୟ ଅକ୍ୟୁପେଣ୍ଟ ଜୋନଗୁଡ଼ିକର ସ୍ଥିତି ଏବଂ ସେହି ଜୋନଗୁଡ଼ିକରେ ଇନଷ୍ଟଲ କରାଯାଇଥିବା ପିଅର ଆପ୍ସ (କଲରଙ୍କ ପରି ସମାନ ପେକେଜ ନାମ ଥିବା ଆପ୍ସ)କୁ ମନିଟର କରନ୍ତୁ ଏବଂ ସେହି ଜୋନଗୁଡ଼ିକର ପାୱାରକୁ ପରିଚାଳନା କରନ୍ତୁ।"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"କନେକ୍ସନ ସ୍ଥାପନ କରନ୍ତୁ ଏବଂ କାରରେ ଥିବା ଅନ୍ୟ ଅକ୍ୟୁପେଣ୍ଟ ଜୋନଗୁଡ଼ିକରେ ଇନଷ୍ଟଲ କରାଯାଇଥିବା ପିଅର ଆପ୍ସ (କଲରଙ୍କ ପରି ସମାନ ପେକେଜ ନାମ ଥିବା ଆପ୍ସ) ସହ କମ୍ୟୁନିକେଟ କରନ୍ତୁ"</string>
diff --git a/service/res/values-pa/strings.xml b/service/res/values-pa/strings.xml
index b806199..a1366dc 100644
--- a/service/res/values-pa/strings.xml
+++ b/service/res/values-pa/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"ਕਾਰ ਦੇ ਵਿੰਡਸ਼ੀਲਡ ਵਾਈਪਰਾਂ ਦੀ ਜਾਣਕਾਰੀ ਨੂੰ ਪੜ੍ਹਨ ਦਿਓ।"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"ਕਾਰ ਦੇ ਵਿੰਡਸ਼ੀਲਡ ਵਾਈਪਰਾਂ ਨੂੰ ਕੰਟਰੋਲ ਕਰਨ ਦਿਓ"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"ਕਾਰ ਦੇ ਵਿੰਡਸ਼ੀਲਡ ਵਾਈਪਰਾਂ ਨੂੰ ਕੰਟਰੋਲ ਕਰਨ ਦਿਓ।"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"ਪੁੱਛਗਿੱਛ ਦਿਖਾਉਣ ਸੰਬੰਧੀ ਅਨੁਰੂਪਤਾ ਪੈਕੇਜ"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"ਪੁੱਛਗਿੱਛ ਦਿਖਾਉਣ ਸੰਬੰਧੀ ਅਨੁਰੂਪਤਾ ਪੈਕੇਜ।"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ਡਿਸਪਲੇ ਅਨੁਰੂਪਤਾ ਪੈਕੇਜਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ਡਿਸਪਲੇ ਅਨੁਰੂਪਤਾ ਪੈਕੇਜਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ।"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"ਐਪ ਕਾਰਡ ਪ੍ਰਦਾਨਕਾਂ ਨਾਲ ਜੁੜੋ"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"ਐਪ ਕਾਰਡ ਪ੍ਰਦਾਨਕਾਂ ਨਾਲ ਜੁੜੋ।"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"ਕਾਰ ਦੇ ਹੋਰ ਸਵਾਰੀ ਖੇਤਰਾਂ ਦੀਆਂ ਸਥਿਤੀਆਂ ਅਤੇ ਉਨ੍ਹਾਂ ਖੇਤਰਾਂ ਵਿੱਚ ਸਥਾਪਤ ਪੀਅਰ ਐਪਾਂ (ਉਹ ਐਪਾਂ ਜਿਨ੍ਹਾਂ ਦਾ ਕਾਲਰ ਨਾਲ ਦਾ ਇੱਕੋ ਜਿਹਾ ਪੈਕੇਜ ਨਾਮ ਹੁੰਦਾ ਹੈ) ਦੀ ਨਿਗਰਾਨੀ ਕਰੋ, ਅਤੇ ਉਨ੍ਹਾਂ ਖੇਤਰਾਂ ਦੀ ਪਾਵਰ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ।"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"ਕਾਰ ਦੇ ਹੋਰ ਸਵਾਰੀ ਖੇਤਰਾਂ ਦੀਆਂ ਸਥਿਤੀਆਂ ਅਤੇ ਉਨ੍ਹਾਂ ਖੇਤਰਾਂ ਵਿੱਚ ਸਥਾਪਤ ਪੀਅਰ ਐਪਾਂ (ਉਹ ਐਪਾਂ ਜਿਨ੍ਹਾਂ ਦਾ ਕਾਲਰ ਨਾਲ ਦਾ ਇੱਕੋ ਜਿਹਾ ਪੈਕੇਜ ਨਾਮ ਹੁੰਦਾ ਹੈ) ਦੀ ਨਿਗਰਾਨੀ ਕਰੋ, ਅਤੇ ਉਨ੍ਹਾਂ ਖੇਤਰਾਂ ਦੀ ਪਾਵਰ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ।"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"ਕਾਰ ਦੇ ਹੋਰ ਸਵਾਰੀ ਖੇਤਰਾਂ ਵਿੱਚ ਸਥਾਪਤ ਪੀਅਰ ਐਪਾਂ (ਉਹ ਐਪਾਂ ਜਿਨ੍ਹਾਂ ਦਾ ਕਾਲਰ ਨਾਲ ਦਾ ਇੱਕੋ ਜਿਹਾ ਪੈਕੇਜ ਨਾਮ ਹੁੰਦਾ ਹੈ) ਨਾਲ ਕਨੈਕਟ ਕਰੋ ਅਤੇ ਸੰਚਾਰ ਕਰੋ"</string>
diff --git a/service/res/values-pl/strings.xml b/service/res/values-pl/strings.xml
index 5dedd80..dd226b2 100644
--- a/service/res/values-pl/strings.xml
+++ b/service/res/values-pl/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Odczytywanie przednich wycieraczek."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"sterowanie przednimi wycieraczkami"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Sterowanie przednimi wycieraczkami."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"wysyłanie zapytań o pakiety zgodności z ekranem"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Wysyłanie zapytań o pakiety zgodności z ekranem."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"zarządzanie pakietami wymagającymi dostosowania do ekranu"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"zarządzanie pakietami wymagającymi dostosowania do ekranu."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"powiązanie z dostawcami kart aplikacji"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"powiązanie z dostawcami kart aplikacji"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"obserwuj stany innych stref pasażerów w samochodzie i aplikacji równorzędnych (czyli takich, które mają tę samą nazwę pakietu co aplikacja wywołująca) zainstalowanych w tych strefach oraz zarządzaj zasilaniem w tych strefach."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Obserwuj stany innych stref pasażerów w samochodzie i aplikacji równorzędnych (czyli takich, które mają tę samą nazwę pakietu co aplikacja wywołująca) zainstalowanych w tych strefach oraz zarządzaj zasilaniem w tych strefach."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"łącz się i komunikuj z aplikacjami równorzędnymi (czyli takimi, które mają tę samą nazwę pakietu co aplikacja wywołująca) zainstalowanymi w innych strefach pasażerów w samochodzie"</string>
diff --git a/service/res/values-pt-rPT/strings.xml b/service/res/values-pt-rPT/strings.xml
index 2fbe939..2cd486f 100644
--- a/service/res/values-pt-rPT/strings.xml
+++ b/service/res/values-pt-rPT/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Leia os limpa-para-brisas do carro."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"controlar os limpa-para-brisas do carro"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Controle os limpa-para-brisas do carro."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"pacotes de compatibilidade do ecrã de consulta"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Pacotes de compatibilidade do ecrã de consulta."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"gerir pacotes de compatibilidade do ecrã"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"faça a gestão de pacotes de compatibilidade do ecrã."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"vincular-se a fornecedores de cartões de apps"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"vincular-se a fornecedores de cartões de apps."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"monitorizar os estados das zonas de outros ocupantes no carro e das apps de pares (apps com o mesmo nome de pacote do autor da chamada) instaladas nessas zonas e gerir a alimentação dessas zonas."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Monitorize os estados das zonas de outros ocupantes no carro e das apps de pares (apps com o mesmo nome de pacote do autor da chamada) instaladas nessas zonas e faça a gestão da alimentação dessas zonas."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"estabelecer ligação e comunicar com apps de pares (apps com o mesmo nome de pacote do autor da chamada) instaladas nas zonas de outros ocupantes no carro"</string>
diff --git a/service/res/values-pt/strings.xml b/service/res/values-pt/strings.xml
index 0a200e2..6438dd6 100644
--- a/service/res/values-pt/strings.xml
+++ b/service/res/values-pt/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Ler o limpador de para-brisa do carro."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"controlar o limpador de para-brisa do carro"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Controlar o limpador de para-brisa do carro."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"consultar pacotes de compatibilidade de exibição"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Consultar pacotes de compatibilidade de exibição."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"gerenciar pacotes de compatibilidade de exibição"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"gerenciar pacotes de compatibilidade de exibição."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"vincular a provedores de cards de apps"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"vincular a provedores de cards de apps."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"monitorar os estados de outras zonas de ocupantes no carro e apps semelhantes (que têm o mesmo nome de pacote que o autor da chamada) instalados nessas zonas e gerenciar a energia delas."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Monitorar os estados de outras zonas de ocupantes no carro e apps semelhantes (que têm o mesmo nome de pacote que o autor da chamada) instalados nessas zonas e gerenciar a energia delas."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"estabelecer conexão e se comunicar com apps semelhantes (que têm o mesmo nome de pacote que o autor da chamada) instalados em outras zonas de ocupantes no carro"</string>
diff --git a/service/res/values-ro/strings.xml b/service/res/values-ro/strings.xml
index 61e4344..14084d6 100644
--- a/service/res/values-ro/strings.xml
+++ b/service/res/values-ro/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Să citească datele despre ștergătoarele de parbriz ale mașinii."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"să controleze ștergătoarele de parbriz ale mașinii"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Să controleze ștergătoarele de parbriz ale mașinii."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"pachete de compatibilitate pentru afișarea interogărilor"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Pachete de compatibilitate pentru afișarea interogărilor."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"gestionează pachetele de compatibilitate pentru afișare"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"gestionează pachetele de compatibilitate pentru afișare."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"să se conecteze la furnizori de carduri din aplicație"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"să se conecteze la furnizori de carduri din aplicație."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"Să monitorizeze stările altor zone pentru ocupanți din mașină și ale aplicațiilor peer (aplicații care au același nume de pachet ca apelantul) instalate în zonele respective și să gestioneze alimentarea pentru acele zone."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Să monitorizeze stările altor zone pentru ocupanți din mașină și ale aplicațiilor peer (aplicații care au același nume de pachet ca apelantul) instalate în zonele respective și să gestioneze alimentarea pentru acele zone."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"Să stabilească o conexiune și să comunice cu aplicații peer (aplicații care au același nume de pachet ca apelantul) instalate în alte zone pentru ocupanți din mașină."</string>
diff --git a/service/res/values-ru/strings.xml b/service/res/values-ru/strings.xml
index d587180..fddd3fd 100644
--- a/service/res/values-ru/strings.xml
+++ b/service/res/values-ru/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Информация о стеклоочистителях."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"управление стеклоочистителями"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Управление стеклоочистителями."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"отправка запросов о пакетах, которые требуют совместимости с дисплеем"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Отправка запросов о пакетах, которые требуют совместимости с дисплеем"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"управление пакетами, для которых нужно обеспечить совместимость с дисплеем"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"управление пакетами, для которых нужно обеспечить совместимость с дисплеем."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"подключение к поставщикам карточек для приложения"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"подключение к поставщикам карточек для приложения."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"отслеживать состояние других зон пассажиров и приложений с таким же названием пакета, установленных в этих зонах, и управлять электропитанием таких зон."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Отслеживать состояние других зон пассажиров и приложений с таким же названием пакета, установленных в этих зонах, и управлять электропитанием таких зон."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"подключаться к приложениям с таким же названием пакета, установленным в других зонах пассажиров, и обмениваться данными с ними"</string>
diff --git a/service/res/values-si/strings.xml b/service/res/values-si/strings.xml
index 0873a6f..60e4b64 100644
--- a/service/res/values-si/strings.xml
+++ b/service/res/values-si/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"මෝටර් රථයේ හුළං පලිහ පිසින කියවන්න."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"මෝටර් රථයේ හුළං පලිහ පිසින පාලනය කරන්න"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"මෝටර් රථයේ හුළං පලිහ පිසින පාලනය කරන්න."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"විමසුම් සංදර්ශක අනුකූලතා පැකේජ"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"විමසුම් සංදර්ශක අනුකූලතා පැකේජ."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"සංදර්ශක අනුකූලතා පැකේජ කළමනාකරණය කරන්න"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"සංදර්ශක අනුකූලතා පැකේජ කළමනාකරණය කරන්න."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"යෙදුම් කාඩ්පත් සපයන්නන් වෙත බැඳීම"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"යෙදුම් කාඩ්පත් සපයන්නන් වෙත බැඳීම."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"එම කලාප තුළ ස්ථාපනය කර ඇති මෝටර් රථ සහ සම වයසේ යෙදුම් (ඇමතුම්කරු මෙන් එකම පැකේජ නාමය ඇති යෙදුම්) තුළ ඇති අනෙකුත් පදිංචිකරුවන්ගේ කලාපවල තත්ත්වයන් අධීක්ෂණය කරන්න, සහ එම කලාපවල බලය කළමනාකරණය කරන්න."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"එම කලාප තුළ ස්ථාපනය කර ඇති මෝටර් රථ සහ සම වයසේ යෙදුම් (ඇමතුම්කරු මෙන් එකම පැකේජ නාමය ඇති යෙදුම්) තුළ ඇති අනෙකුත් පදිංචිකරුවන්ගේ කලාපවල තත්ත්වයන් අධීක්ෂණය කරන්න, සහ එම කලාපවල බලය කළමනාකරණය කරන්න."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"මෝටර් රථයේ වෙනත් පදිංචිකරුවන්ගේ කලාපවල ස්ථාපනය කර ඇති සම වයසේ යෙදුම් (ඇමතුම්කරු මෙන් එකම පැකේජ නම ඇති යෙදුම්) වෙත සම්බන්ධතාවය ස්ථාපිත කර සන්නිවේදනය කරන්න."</string>
diff --git a/service/res/values-sk/strings.xml b/service/res/values-sk/strings.xml
index 5a67eb0..77ede58 100644
--- a/service/res/values-sk/strings.xml
+++ b/service/res/values-sk/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Čítanie informácií o stieračoch auta."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"ovládanie stieračov auta"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Ovládanie stieračov auta."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"balíky dopytov kompatibility obrazovky"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Balíky dopytov kompatibility obrazovky."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"správa balíčkov s kompatibilitou obrazovky"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"správa balíčkov s kompatibilitou obrazovky."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"naviazanie sa na poskytovateľa karty aplikácie"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"naviazanie sa na poskytovateľa karty aplikácie."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"monitorujte stav ďalších miest pre spolucestujúcich v aute a podobných aplikácií (aplikácie, ktoré majú rovnaký názov balíka ako zdroj spätného volania) nainštalovaných na týchto miestach a spravujte výkon týchto miest."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Monitorujte stav ďalších miest pre spolucestujúcich v aute a podobných aplikácií (aplikácie, ktoré majú rovnaký názov balíka ako zdroj spätného volania) nainštalovaných na týchto miestach a spravujte výkon týchto miest."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"nadviažte spojenie a komunikujte s podobnými aplikáciami (aplikácie, ktoré majú rovnaký názov balíka ako zdroj spätného volania) nainštalovanými na ďalších miestach pre spolucestujúcich v aute"</string>
diff --git a/service/res/values-sl/strings.xml b/service/res/values-sl/strings.xml
index b2e6a2ab..b8ebdc5 100644
--- a/service/res/values-sl/strings.xml
+++ b/service/res/values-sl/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Branje brisalcev vetrobranskega stekla avtomobila."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"nadziranje brisalcev vetrobranskega stekla avtomobila"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Nadziranje brisalcev vetrobranskega stekla avtomobila."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"poizvedovanje glede paketov za združljivost z zaslonom"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Poizvedovanje glede paketov za združljivost z zaslonom."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"upravljanje paketov, ki zahtevajo združljivost z zaslonom"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"upravljanje paketov, ki zahtevajo združljivost z zaslonom."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"povezovanje s ponudniki kartic v aplikacijah"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"povezovanje s ponudniki kartic v aplikacijah."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"nadziranje stanj drugih območij potnikov v avtomobilu in primerljivih aplikacij (aplikacije, ki imajo isto ime paketa kot klicatelj), nameščenih na teh območjih, ter upravljanje napajanja teh območij"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Nadziranje stanj drugih območij potnikov v avtomobilu in primerljivih aplikacij (aplikacije, ki imajo isto ime paketa kot klicatelj), nameščenih na teh območjih, ter upravljanje napajanja teh območij."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"vzpostavitev povezave in komuniciranje s primerljivimi aplikacijami (aplikacije, ki imajo isto ime paketa kot klicatelj), nameščenimi na drugih območjih potnikov v avtomobilu"</string>
diff --git a/service/res/values-sq/strings.xml b/service/res/values-sq/strings.xml
index 351692e..7dae26c 100644
--- a/service/res/values-sq/strings.xml
+++ b/service/res/values-sq/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Lexo fshirëset e xhamave të makinës."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"kontrollo fshirëset e xhamave të makinës"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Kontrollo fshirëset e xhamave të makinës."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"të kërkojë paketat e përputhshmërisë së ekranit"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Të kërkojë paketat e përputhshmërisë së ekranit."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"të menaxhojë paketat e përputhshmërisë së ekranit"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"të menaxhojë paketat e përputhshmërisë së ekranit."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"të lidhet me ofruesit e kartave të aplikacioneve"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"të lidhet me ofruesit e kartave të aplikacioneve."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"të monitorojnë gjendjet e zonave të tjera të pasagjerëve në makinë dhe aplikacionet homologe (aplikacionet që kanë të njëjtin emër pakete me thirrësin) të instaluara në këto zona dhe të menaxhojë energjinë e këtyre zonave."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Të monitorojnë gjendjet e zonave të tjera të pasagjerëve në makinë dhe aplikacionet homologe (aplikacionet që kanë të njëjtin emër pakete me thirrësin) të instaluara në këto zona dhe të menaxhojë energjinë e këtyre zonave."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"të vendosin lidhje dhe të komunikojnë me aplikacionet homologe (aplikacionet që kanë të njëjtin emër pakete me thirrësin) të instaluara në zona të tjera të pasagjerëve në makinë."</string>
diff --git a/service/res/values-sr/strings.xml b/service/res/values-sr/strings.xml
index ade8dc5..380ba37 100644
--- a/service/res/values-sr/strings.xml
+++ b/service/res/values-sr/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Чита брисаче ветробранског стакла аутомобила."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"контрола брисача ветробранског стакла аутомобила"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Контролише брисаче ветробранског стакла аутомобила."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"утврђивање пакета за компатибилност приказа"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Утврђивање пакета за компатибилност приказа."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"управљање пакетима за компатибилност приказа"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"управљање пакетима за компатибилност приказа."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"повезивање са добављачима картица у апликацијама"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"повезивање са добављачима картица у апликацијама."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"надгледају стања других простора за путнике у аутомобилу и релевантних апликација (апликације које имају исти назив пакета као и позивалац) инсталираних у тим просторима и управљају енергијом тих простора"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Надгледају стања других простора за путнике у аутомобилу и релевантних апликација (апликације које имају исти назив пакета као и позивалац) инсталираних у тим просторима и управљају енергијом тих простора."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"успостављају везу и комуницирају са релевантним апликацијама (апликације које имају исти назив пакета као и позивалац) инсталираним у другим просторима за путнике у аутомобилу"</string>
diff --git a/service/res/values-sv/strings.xml b/service/res/values-sv/strings.xml
index d9e9459..6dbff5c 100644
--- a/service/res/values-sv/strings.xml
+++ b/service/res/values-sv/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Läsa av bilens vindrutetorkare."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"styra bilens vindrutetorkare"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Styra bilens vindrutetorkare."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"fråga paketen om skärmkompatibilitet"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Fråga paketen om skärmkompatibilitet"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"hantera paket för skärmkompatibilitet"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"hantera paket för skärmkompatibilitet."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"kopplas till appkortsleverantörer"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"kopplas till appkortsleverantörer."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"övervaka statusen för andra närvarozoner i bilen och jämförbara appar (appar som har samma paketnamn som uppringaren) som är installerade i de zonerna, samt hantera kraften hos de zonerna."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Övervaka statusen för andra närvarozoner i bilen och jämförbara appar (appar som har samma paketnamn som uppringaren) som är installerade i de zonerna, samt hantera kraften hos de zonerna."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"upprätta anslutning och kommunicera med jämförbara appar (appar som har samma paketnamn som uppringaren) som är installerade i andra närvarozoner i bilen"</string>
diff --git a/service/res/values-sw/strings.xml b/service/res/values-sw/strings.xml
index f856696..ba4d44f 100644
--- a/service/res/values-sw/strings.xml
+++ b/service/res/values-sw/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Fikia maelezo ya waipa za gari."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"dhibiti waipa za gari"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Dhibiti waipa za gari."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"vifurushi vya uoanifu wa skrini ya hoja"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Vifurushi vya uoanifu wa skrini ya hoja."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"vifurushi vya kudhibiti uoanifu wa skrini"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"vifurushi vya kudhibiti uoanifu wa skrini."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"kuunganisha na watoa huduma wa kadi ya programu"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"kuunganisha na watoa huduma wa kadi ya programu."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"fuatilia hali za maeneo mengine alipo mwenye gari na programu zingine (programu ambazo zina jina la kifurushi sawa na anayepiga) zilizosakinishwa katika maeneo hayo na udhibiti matumizi katika maeneo hayo."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Fuatilia hali za maeneo mengine alipo mwenye gari na programu zingine (programu ambazo zina jina la kifurushi sawa na anayepiga) zilizosakinishwa katika maeneo hayo na udhibiti matumizi katika maeneo hayo."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"anzisha muunganisho na uwasiliane na programu zingine (programu ambazo zina jina la kifurushi sawa na anayepiga) zilizosakinishwa katika maeneo mengine alipo mwenye gari."</string>
diff --git a/service/res/values-ta/strings.xml b/service/res/values-ta/strings.xml
index a202718..6c5083b 100644
--- a/service/res/values-ta/strings.xml
+++ b/service/res/values-ta/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"காரின் கண்ணாடித் துடைப்பான் தொடர்பான தரவைப் படிக்கும்."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"காரின் கண்ணாடித் துடைப்பானைக் கட்டுப்படுத்துதல்"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"காரின் கண்ணாடித் துடைப்பானைக் கட்டுப்படுத்தும்."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"வினவல் காட்சியின் இணக்கத்தன்மைத் தொகுப்புகள்"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"வினவல் காட்சியின் இணக்கத்தன்மைத் தொகுப்புகள்."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"காட்சியின் இணக்கத்தன்மைத் தொகுப்புகளை நிர்வகியுங்கள்"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"காட்சியின் இணக்கத்தன்மைத் தொகுப்புகளை நிர்வகிக்கலாம்."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"ஆப்ஸ் கார்டு வழங்குநர்களுடன் இணை"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"ஆப்ஸ் கார்டு வழங்குநர்களுடன் இணைக்கும்."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"காரிலுள்ள பிற பயணிப் பகுதிகளின் நிலைகளையும் அப்பகுதிகளில் நிறுவப்பட்டிருக்கும் பியர் ஆப்ஸையும் (ஒரே பேக்கேஜ் பெயரை உடைய ஆப்ஸ்) கண்காணித்தல், அப்பகுதிகளின் பவரை நிர்வகித்தல்."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"காரிலுள்ள பிற பயணிப் பகுதிகளின் நிலைகளையும் அப்பகுதிகளில் நிறுவப்பட்டிருக்கும் பியர் ஆப்ஸையும் (ஒரே பேக்கேஜ் பெயரை உடைய ஆப்ஸ்) கண்காணிக்கும், அப்பகுதிகளின் பவரை நிர்வகிக்கும்."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"காரின் பிற பயணிப் பகுதிகளில் நிறுவப்பட்டுள்ள பியர் ஆப்ஸுடன் (ஒரே பேக்கேஜ் பெயரை உடைய ஆப்ஸ்) இணைப்பை ஏற்படுத்தி அவற்றுடன் தரவுப் பரிமாற்றம் செய்தல்"</string>
diff --git a/service/res/values-te/strings.xml b/service/res/values-te/strings.xml
index 20ab03a..e4afe91 100644
--- a/service/res/values-te/strings.xml
+++ b/service/res/values-te/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"కారు విండ్షీల్డ్ వైపర్ల గురించి చదవండి."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"కారు విండ్షీల్డ్ వైపర్లను కంట్రోల్ చేయండి"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"కారు విండ్షీల్డ్ వైపర్లను కంట్రోల్ చేయండి."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"క్వెరీ డిస్ప్లే అనుకూలత ప్యాకేజీలు"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"క్వెరీ డిస్ప్లే అనుకూలత ప్యాకేజీలు."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"డిస్ప్లే అనుకూలత ప్యాకేజీలను మేనేజ్ చేయండి"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"డిస్ప్లే అనుకూలత ప్యాకేజీలను మేనేజ్ చేయండి."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"యాప్ కార్డ్ ప్రొవైడర్లతో కనెక్ట్ చేయండి"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"యాప్ కార్డ్ ప్రొవైడర్లతో కనెక్ట్ చేయండి."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"కారులోని ఇతర బస చేసే జోన్ల స్టేట్లను పర్యవేక్షించండి, ఆ జోన్లలో ఇన్స్టాల్ చేయబడిన పీర్ యాప్లు (కాలర్ లాగా ఒకే ప్యాకేజీ పేరు ఉన్న యాప్లు), ఆ జోన్ల పవర్ను మేనేజ్ చేయండి."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"కారులోని ఇతర బస చేసే జోన్ల స్టేట్లను పర్యవేక్షించండి, ఆ జోన్లలో ఇన్స్టాల్ చేయబడిన పీర్ యాప్లు (కాలర్ లాగా ఒకే ప్యాకేజీ పేరు ఉన్న యాప్లు), ఆ జోన్ల పవర్ను మేనేజ్ చేయండి."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"కారులోని ఇతర బస చేసే జోన్లలో ఇన్స్టాల్ చేయబడిన పీర్ యాప్లకు (కాలర్ లాగా ఒకే ప్యాకేజీ పేరు ఉన్న యాప్లు) కనెక్షన్ను ఏర్పాటు చేయండి, కమ్యూనికేట్ చేయండి"</string>
diff --git a/service/res/values-th/strings.xml b/service/res/values-th/strings.xml
index a3058af..9988124 100644
--- a/service/res/values-th/strings.xml
+++ b/service/res/values-th/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"อ่านที่ปัดน้ำฝนรถ"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"ควบคุมที่ปัดน้ำฝนรถ"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"ควบคุมที่ปัดน้ำฝนรถ"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"คำถามแสดงแพ็กเกจความเข้ากันได้"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"คำถามแสดงแพ็กเกจความเข้ากันได้"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"จัดการการแสดงแพ็กเกจความเข้ากันได้"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"จัดการการแสดงแพ็กเกจความเข้ากันได้"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"เชื่อมโยงกับผู้ให้บริการการ์ดแอป"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"เชื่อมโยงกับผู้ให้บริการการ์ดแอป"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"ตรวจสอบสถานะของโซนที่มีการเข้าใช้อื่นๆ ในรถและแอปเทียบเท่า (แอปที่มีชื่อแพ็กเกจเดียวกันกับผู้เรียกใช้) ซึ่งติดตั้งในโซนดังกล่าว รวมถึงจัดการกำลังไฟฟ้าของโซนเหล่านั้น"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"ตรวจสอบสถานะของโซนที่มีการเข้าใช้อื่นๆ ในรถและแอปเทียบเท่า (แอปที่มีชื่อแพ็กเกจเดียวกันกับผู้เรียกใช้) ซึ่งติดตั้งในโซนดังกล่าว รวมถึงจัดการกำลังไฟฟ้าของโซนเหล่านั้น"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"ทำการเชื่อมต่อและสื่อสารกับแอปเทียบเท่า (แอปที่มีชื่อแพ็กเกจเดียวกันกับผู้เรียกใช้) ซึ่งติดตั้งในโซนที่มีการเข้าใช้อื่นๆ ในรถ"</string>
diff --git a/service/res/values-tl/strings.xml b/service/res/values-tl/strings.xml
index e6a0a60..db68081 100644
--- a/service/res/values-tl/strings.xml
+++ b/service/res/values-tl/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Tingnan ang mga windshield wiper ng kotse."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"kontrolin ang mga windshield wiper ng kotse"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Kontrolin ang mga windshield wiper ng kotse."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"mga compatibility package ng query display"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Mga compatibility package ng query display."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"pamahalaan ang mga package ng compatibility sa display"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"pamahalaan ang mga package ng compatibility sa display."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"i-bind sa mga provider ng app card"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"i-bind sa mga provider ng app card."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"subaybayan ang mga status ng iba pang occupant zone sa kotse at mga peer app (mga app na may kaparehong pangalan ng package ng caller) na naka-install sa mga zone na iyon, at pamahalaan ang power ng mga zone na iyon."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Subaybayan ang mga status ng iba pang occupant zone sa kotse at mga peer app (mga app na may kaparehong pangalan ng package ng caller) na naka-install sa mga zone na iyon, at pamahalaan ang power ng mga zone na iyon."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"bumuo ng koneksyon at makipag-ugnayan sa mga peer app (mga app na may kaparehong pangalan ng package ng caller) na naka-install sa iba pang occupant zone sa kotse"</string>
diff --git a/service/res/values-tr/strings.xml b/service/res/values-tr/strings.xml
index 7b22b38..b378cba 100644
--- a/service/res/values-tr/strings.xml
+++ b/service/res/values-tr/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Aracın silecek bilgilerini okuyabilir."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"aracın sileceklerini kontrol edebilir"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Aracın sileceklerini kontrol edebilir."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"ekran uyumluluğu paketlerinin sorgulanması"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Ekran uyumluluğu paketlerinin sorgulanması."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ekran uyumluluk paketlerini yönetme"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ekran uyumluluk paketlerini yönetme"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"uygulama kartı sağlayıcılarına bağlanma"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"uygulama kartı sağlayıcılarına bağlanma."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"arabadaki diğer yolcu bölgelerinin durumunu ve bu bölgelere yüklenmiş benzer uygulamaları (arayanla aynı paket adına sahip uygulamalar) izleyip bu bölgelerin güç kullanımını yönetme"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Arabadaki diğer yolcu bölgelerinin durumunu ve bu bölgelere yüklenmiş benzer uygulamaları (arayanla aynı paket adına sahip uygulamalar) izleyip bu bölgelerin güç kullanımını yönetme"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"arabadaki diğer yolcu bölgelerine yüklenmiş benzer uygulamalarla (arayanla aynı paket adına sahip uygulamalar) bağlantı ve iletişim kurma"</string>
diff --git a/service/res/values-uk/strings.xml b/service/res/values-uk/strings.xml
index b8e0c76..cab165c 100644
--- a/service/res/values-uk/strings.xml
+++ b/service/res/values-uk/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Зчитування даних про склоочисники вітрового скла автомобіля."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"керувати склоочисниками вітрового скла автомобіля"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Керування склоочисниками вітрового скла автомобіля."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"надсилати запити на пакети сумісності з екраном"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Надсилати запити на пакети сумісності з дисплеєм"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"керувати пакетами сумісності з екраном"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"керувати пакетами сумісності з екраном."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"зв’язуватися з постачальниками карток додатків"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"зв’язуватися з постачальниками карток додатків."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"відстежувати стан інших зон пасажирів в автомобілі й аналогічних додатків (що мають таку саму назву пакета, як додаток, що викликає), установлених у цих зонах, а також керувати живленням цих зон"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Відстеження стану інших зон пасажирів в автомобілі й аналогічних додатків (що мають таку саму назву пакета, як додаток, що викликає), установлених у цих зонах, а також керування живленням цих зон."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"встановлювати з’єднання й обмінюватися даними з аналогічними додатками (що мають таку саму назву пакета, як додаток, що викликає), установленими в інших зонах пасажирів в автомобілі"</string>
diff --git a/service/res/values-ur/strings.xml b/service/res/values-ur/strings.xml
index 5a4e39d..2de72d4 100644
--- a/service/res/values-ur/strings.xml
+++ b/service/res/values-ur/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"کار کے ونڈشیلڈ وائپرز کو پڑھیں۔"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"کار کے ونڈشیلڈ وائپرز کو کنٹرول کریں"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"کار کے ونڈشیلڈ وائپرز کو کنٹرول کریں۔"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"استفسار ڈسپلے کی موافقت کے پیکیجز"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"استفسار ڈسپلے کی موافقت کے پیکیجز۔"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ڈسپلے کی موافقت کے پیکیجز کا نظم کریں"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ڈسپلے کی موافقت کے پیکیجز کا نظم کریں۔"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"ایپ کارڈ فراہم کنندگان سے منسلک کریں"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"ایپ کارڈ فراہم کنندگان سے منسلک کریں۔"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"کار میں ساکن کے دیگر زونز کی حالتوں اور ان زونز میں انسٹال کردہ پیئر ایپس (ایسی ایپس جن کے کالر اور پیکیج کا نام ایک ہو) کو مانیٹر کریں اور ان زونز کے پاور کا نظم کریں۔"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"کار میں ساکن کے دیگر زونز کی حالتوں اور ان زونز میں انسٹال کردہ پیئر ایپس (ایسی ایپس جن کے کالر اور پیکیج کا نام ایک ہو) کو مانیٹر کریں اور ان زونز کے پاور کا نظم کریں۔"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"پیئر ایپس (ایسی ایپس جن کے کالر اور پیکیج کا نام ایک ہو) سے کنکشن قائم کریں اور کار میں ساکن کے دیگر زونز میں انسٹال کردہ پیئر ایپس کے ساتھ مواصلت کریں"</string>
diff --git a/service/res/values-uz/strings.xml b/service/res/values-uz/strings.xml
index 89077a4..86fa74e 100644
--- a/service/res/values-uz/strings.xml
+++ b/service/res/values-uz/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Avtomobilning oyna tozalagichlari axborotini oʻqish."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"avtomobilning oyna tozalagichlarini boshqarish"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Avtomobilning oyna tozalagichlarini boshqarish."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"ekran bilan moslik paketlarini talab qilish"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Ekran bilan moslik paketlarini talab qilish"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"ekran bilan moslik paketlarini boshqarish"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"ekran bilan moslik paketlarini boshqarish."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"ilova uchun karta taʼminotchilariga ulanish"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"ilova uchun karta taʼminotchilariga ulanish."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"avtomobildagi boshqa yoʻlovchi joylari holatini va ushbu joylarda oʻrnatilgan tengdosh ilovalarni (chaqiruvchi sifatida bir xil paket nomiga ega ilovalar) nazorat qilish hamda bu joylarning quvvatini boshqarish."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Avtomobildagi boshqa yoʻlovchi joylari holatini va ushbu joylarda oʻrnatilgan tengdosh ilovalarni (chaqiruvchi sifatida bir xil paket nomiga ega ilovalar) nazorat qiling hamda bu joylarning quvvatini boshqaring."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"avtomobildagi boshqa yoʻlovchi joylarida oʻrnatilgan tengdosh ilovalar (chaqiruvchi sifatida bir xil paket nomiga ega ilovalar) bilan aloqa qurish va muloqot qilish"</string>
diff --git a/service/res/values-vi/strings.xml b/service/res/values-vi/strings.xml
index 8093d40..be9af8e 100644
--- a/service/res/values-vi/strings.xml
+++ b/service/res/values-vi/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Đọc dữ liệu của cần gạt nước ô tô."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"kiểm soát cần gạt nước của ô tô"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Kiểm soát cần gạt nước của ô tô."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"Truy vấn gói về khả năng tương thích với màn hình"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Truy vấn gói về khả năng tương thích với màn hình."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"quản lý gói về khả năng tương thích với màn hình"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"quản lý gói về khả năng tương thích với màn hình."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"kết nối với nhà cung cấp thẻ dạng ứng dụng"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"kết nối với nhà cung cấp thẻ dạng ứng dụng."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"theo dõi trạng thái và quản lý điện năng ở khu vực của những người khác trong xe cũng như các ứng dụng ngang hàng (ứng dụng có cùng tên gói với ứng dụng gọi) được cài đặt trong những khu vực đó."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Theo dõi trạng thái và quản lý điện năng ở khu vực của những người khác trong xe cũng như các ứng dụng ngang hàng (ứng dụng có cùng tên gói với ứng dụng gọi) được cài đặt trong những khu vực đó."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"thiết lập kết nối và giao tiếp với các ứng dụng ngang hàng (ứng dụng có cùng tên gói với ứng dụng gọi) được cài đặt ở khu vực của những người khác trong xe."</string>
diff --git a/service/res/values-zh-rCN/strings.xml b/service/res/values-zh-rCN/strings.xml
index 24d0889..8a6e5f5 100644
--- a/service/res/values-zh-rCN/strings.xml
+++ b/service/res/values-zh-rCN/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"读取汽车雨刷的数据。"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"控制汽车的雨刷"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"控制汽车的雨刷。"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"查询需进行显示屏兼容性处理的软件包"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"查询需进行显示屏兼容性处理的软件包。"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"管理需在显示屏兼容模式下运行的软件包"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"管理需在显示屏兼容模式下运行的软件包。"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"绑定到应用卡片提供程序"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"绑定到应用卡片提供程序。"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"监控安装在车内其他乘员区中的类似应用(与调用方拥有相同软件包名称的应用)的状态及这些区域本身的状态,以及管理这些区域的电源供应。"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"监控安装在车内其他乘员区中的类似应用(与调用方拥有相同软件包名称的应用)的状态及这些区域本身的状态,以及管理这些区域的电源供应。"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"与安装在车内其他乘员区中的类似应用(与调用方拥有相同软件包名称的应用)建立连接及进行通信"</string>
diff --git a/service/res/values-zh-rHK/strings.xml b/service/res/values-zh-rHK/strings.xml
index f0def53..6053601 100644
--- a/service/res/values-zh-rHK/strings.xml
+++ b/service/res/values-zh-rHK/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"讀取汽車的擋風玻璃水撥。"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"控制汽車的擋風玻璃水撥"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"控制汽車的擋風玻璃水撥。"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"查詢有顯示兼容性問題的應用程式"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"查詢有顯示兼容性問題的應用程式。"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"管理有顯示兼容性問題的應用程式"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"管理有顯示兼容性問題的應用程式。"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"繫結至應用程式資訊卡供應商"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"繫結至應用程式資訊卡供應商。"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"監控汽車中其他乘客區域的狀態和安裝在這些區域的同類應用程式 (與來電者具有相同套件名稱的應用程式),並管理這些區域的電源。"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"監控汽車中其他乘客區域的狀態和安裝在這些區域的同類應用程式 (與來電者具有相同套件名稱的應用程式),並管理這些區域的電源。"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"與安裝在汽車中其他乘客區域的同類應用程式 (與來電者具有相同套件名稱的應用程式) 建立連接及進行通訊"</string>
diff --git a/service/res/values-zh-rTW/strings.xml b/service/res/values-zh-rTW/strings.xml
index e493335..c48fe1a 100644
--- a/service/res/values-zh-rTW/strings.xml
+++ b/service/res/values-zh-rTW/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"讀取車輛雨刷。"</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"控制車輛雨刷"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"控制車輛雨刷。"</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"查詢顯示相容性套件"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"查詢顯示相容性套件。"</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"管理螢幕相容性套件"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"管理螢幕相容性套件。"</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"繫結至應用程式資訊卡供應商"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"繫結至應用程式資訊卡供應商。"</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"監控安裝在車內其他乘客區域的同類應用程式 (套件名稱與呼叫端相同的應用程式) 和這些區域本身的狀態,以及管理這些區域的電源供應。"</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"監控安裝在車內其他乘客區域的同類應用程式 (套件名稱與呼叫端相同的應用程式) 和這些區域本身的狀態,以及管理這些區域的電源供應。"</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"建立連線並與安裝在車內其他乘客區域的同類應用程式 (套件名稱與呼叫端相同的應用程式) 進行通訊"</string>
diff --git a/service/res/values-zu/strings.xml b/service/res/values-zu/strings.xml
index 85a1d99..342ceb0 100644
--- a/service/res/values-zu/strings.xml
+++ b/service/res/values-zu/strings.xml
@@ -214,8 +214,10 @@
<string name="car_permission_desc_read_windshield_wipers" msgid="597300271560195986">"Funda ama-windshield wiper emoto."</string>
<string name="car_permission_label_control_windshield_wipers" msgid="7971852482734087493">"lawula ama-windshield wiper emoto"</string>
<string name="car_permission_desc_control_windshield_wipers" msgid="3803429825323199728">"Lawula ama-windshield wiper emoto."</string>
- <string name="car_permission_label_query_display_compatibility" msgid="8846515818036686896">"umbuzo ngokubonisa amaphakheji ahambisanayo"</string>
- <string name="car_permission_desc_query_display_compatibility" msgid="5344194045180717300">"Umbuzo ngokubonisa amaphakheji ahambisanayo."</string>
+ <string name="car_permission_label_manage_display_compatibility" msgid="52642673039197383">"phatha amaphakheji ahambelana nesibonisi"</string>
+ <string name="car_permission_desc_manage_display_compatibility" msgid="903128563662035449">"phatha amaphakheji ahambelana nesibonisi."</string>
+ <string name="car_permission_label_bind_app_card_provider" msgid="4905839485679347567">"bophezela kubahlinzeki bekahdi le-app"</string>
+ <string name="car_permission_desc_bind_app_card_provider" msgid="3392070358840581524">"bophezela kubahlinzeki bekhadi le-app."</string>
<string name="car_permission_label_manage_remote_device" msgid="1144368625081074994">"qapha izimo zezinye izindawo ezihlala emotweni kanye nama-app ontanga (ama-app anegama lephakheji elifanayo nelomuntu ofonayo) elifakwe kulawo mazoni, futhi ulawule amandla alawo mazoni."</string>
<string name="car_permission_desc_manage_remote_device" msgid="4802785901453919948">"Qapha izimo zezinye izindawo ezihlala emotweni kanye nama-app ontanga (ama-app anegama lephakheji elifanayo nelomuntu ofonayo) elifakwe kulawo mazoni, futhi ulawule amandla alawo mazoni."</string>
<string name="car_permission_label_manage_occupant_connection" msgid="6007579379849984187">"sungula uxhumo futhi uxhumane nama-app ontanga (ama-app anegama lephakheji elifanayo nelomuntu ofonayo) afakwe kwamanye amazoni asemotweni"</string>
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 530695a..dd0ecca 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -114,6 +114,14 @@
the focus requests should interact only if the requests are for the same audio zone.-->
<bool name="audioUseIsolatedAudioFocusForDynamicDevices">false</bool>
+ <!-- Configuration to enable volume key events affecting volume changes on dynamic devices.
+ If this is set to true, volume changes via key events will also consider volume groups
+ containing dynamic devices for the volume/mute change. The actual volume group selected
+ will be determined by the volume priority list configured with
+ audioVolumeAdjustmentContextsVersion. Otherwise, the volume group selection via key events
+ will not consider volume groups containing dynamic devices. -->
+ <bool name="audioEnableVolumeKeyEventsToDynamicDevices">false</bool>
+
<!-- Whether to block other audio while media audio is muted with display off. When set to true,
other sounds cannot be played either while display is off. If false, only media is muted
and other sounds can be still played. -->
@@ -154,10 +162,10 @@
<!-- Activity to be presented when un-safe activity is launched. Take a look at the javadoc of the
default implementation. -->
<string name="activityBlockingActivity" translatable="false">
- com.android.systemui/com.android.systemui.car.activity.ActivityBlockingActivity
+ com.android.systemui/com.android.systemui.car.wm.activity.ActivityBlockingActivity
</string>
<string name="continuousBlankActivity" translatable="false">
- com.android.systemui/com.android.systemui.car.activity.ContinuousBlankActivity
+ com.android.systemui/com.android.systemui.car.wm.activity.ContinuousBlankActivity
</string>
<!-- Comma separated list of activities that need to be exempted from getting
blocked in a UX restricted state.
@@ -425,6 +433,7 @@
-->
<string-array translatable="false" name="config_occupant_display_mapping">
+ <item>displayPort=0,displayType=MAIN,occupantZoneId=0,inputTypes=TOUCH_SCREEN|DPAD_KEYS|NAVIGATE_KEYS|ROTARY_NAVIGATION</item>
</string-array>
<!-- Specifies notice UI that will be launched when user starts a car or do user
@@ -642,4 +651,18 @@
<!-- A configuration flag to adjust the enabling of persistent tethering within
CarWifiService. -->
<bool name="config_enablePersistTetheringCapabilities">false</bool>
+
+ <!-- The packages that should not be stopped when suspend to disk is initiated. -->
+ <string-array translatable="false" name="config_packages_not_to_stop_during_suspend">
+ </string-array>
+
+ <!-- The value to be set that describes how much memory will be saved when going into suspend
+ to disk. There are 4 values that can be set:
+ none: no memory savings will take place
+ low: cached processes will be stopped.
+ medium: services, cached, and can't save state processes will be stopped.
+ high: foreground services, background services, cached, and can't saved state processes
+ will be stopped.
+ -->
+ <string name="config_suspend_to_disk_memory_savings" translatable="false">low</string>
</resources>
diff --git a/service/res/values/overlayable.xml b/service/res/values/overlayable.xml
index c00ac2b..a7abc22 100644
--- a/service/res/values/overlayable.xml
+++ b/service/res/values/overlayable.xml
@@ -40,6 +40,7 @@
<item type="bool" name="audioUseFadeManagerConfiguration"/>
<item type="bool" name="audioUseMinMaxActivationVolume"/>
<item type="bool" name="audioUseIsolatedAudioFocusForDynamicDevices"/>
+ <item type="bool" name="audioEnableVolumeKeyEventsToDynamicDevices"/>
<item type="bool" name="displayOffMuteLockAllAudio"/>
<item type="bool" name="useDefaultBluetoothConnectionPolicy"/>
<item type="bool" name="useDefaultBluetoothPowerPolicy"/>
@@ -131,6 +132,8 @@
<item type="fraction" name="config_clusterHomeVisibility_minRendered"/>
<item type="integer" name="config_clusterHomeVisibility_stabilityMs"/>
<item type="bool" name="config_enablePersistTetheringCapabilities" />
+ <item type="array" name="config_packages_not_to_stop_during_suspend" translatable="false"/>
+ <item type="string" name="config_suspend_to_disk_memory_savings" translatable="false"/>
<!-- Params from config.xml that can be overlaid -->
<!-- XML files that can be overlaid -->
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index d1c8626..ee7456c 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -663,9 +663,14 @@
<string name="car_permission_desc_control_windshield_wipers">Control car\u2019s windshield wipers.</string>
<!-- Permission text: apps that need to run in display compatibility mode [CHAR LIMIT=NONE] -->
- <string name="car_permission_label_query_display_compatibility">query display compatibility packages</string>
+ <string name="car_permission_label_manage_display_compatibility">manage display compatibility packages</string>
<!-- Permission text: apps that need to run in display compatibility mode [CHAR LIMIT=NONE] -->
- <string name="car_permission_desc_query_display_compatibility">Query display compatibility packages.</string>
+ <string name="car_permission_desc_manage_display_compatibility">manage display compatibility packages.</string>
+
+ <!-- Permission text: system app that needs to bind with app card providers [CHAR LIMIT=NONE] -->
+ <string name="car_permission_label_bind_app_card_provider">bind to app card providers</string>
+ <!-- Permission text: system app that needs to bind with app card providers [CHAR LIMIT=NONE] -->
+ <string name="car_permission_desc_bind_app_card_provider">bind to app card providers.</string>
<!-- Permission text: apps can monitor the states of other occupant zones in the car and peer
apps (apps that have the same package name as the caller) installed in those zones,
diff --git a/service/res/xml/car_ux_restrictions_map.xml b/service/res/xml/car_ux_restrictions_map.xml
index 9511c35..9d14060 100644
--- a/service/res/xml/car_ux_restrictions_map.xml
+++ b/service/res/xml/car_ux_restrictions_map.xml
@@ -106,7 +106,7 @@
NOTE:
1. The speed units is in meters per second to be in accordance with the
PERF_VEHICLE SPEED definition in hardware/interfaces/automotive/vehicle/2.0/types.hal
- 2. The speed ranges should be non overlapping, i.e one speed value cannot be in more
+ 2. The speed ranges should be non overlapping, i.e. one speed value cannot be in more
than one range. It is denoted in a left closed, right open interval [minSpeed, maxSpeed).
For ex: [0,5) indicates speed >= 0 && speed < 5.0m/s.
3. For a speed range with no high limit, maxSpeed is not filled. For ex., if the speed
diff --git a/service/src/com/android/car/AidlVehicleStub.java b/service/src/com/android/car/AidlVehicleStub.java
index 62bad6c..74ab31a 100644
--- a/service/src/com/android/car/AidlVehicleStub.java
+++ b/service/src/com/android/car/AidlVehicleStub.java
@@ -514,9 +514,6 @@
* An abstract interface for handling async get/set value requests from vehicle stub.
*/
private abstract static class AsyncRequestsHandler<VhalRequestType, VhalRequestsType> {
- protected LongSparseArray<List<Long>> mVhalRequestIdsByTimeoutInMs =
- new LongSparseArray<>();
-
/**
* Preallocsate size array for storing VHAL requests.
*/
diff --git a/service/src/com/android/car/AppFocusService.java b/service/src/com/android/car/AppFocusService.java
index a9ff95d..eeb74df 100644
--- a/service/src/com/android/car/AppFocusService.java
+++ b/service/src/com/android/car/AppFocusService.java
@@ -24,17 +24,19 @@
import android.car.IAppFocusOwnershipCallback;
import android.car.builtin.util.Slogf;
import android.content.Context;
-import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.ArraySet;
+import android.util.Log;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+import com.android.car.internal.StaticBinderInterface;
+import com.android.car.internal.SystemStaticBinder;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -51,7 +53,7 @@
*/
public class AppFocusService extends IAppFocus.Stub implements CarServiceBase,
BinderInterfaceContainer.BinderEventHandler<IAppFocusOwnershipCallback> {
- private static final boolean DBG = false;
+ private static final boolean DBG = Slogf.isLoggable(CarLog.TAG_APP_FOCUS, Log.DEBUG);
private static final boolean DBG_EVENT = false;
// This constant should be equal to PermissionChecker.PERMISSION_GRANTED.
@@ -59,6 +61,7 @@
static final int PERMISSION_CHECKER_PERMISSION_GRANTED = 0;
private final SystemActivityMonitoringService mSystemActivityMonitoringService;
+ private final StaticBinderInterface mBinderInterface;
private final Object mLock = new Object();
@@ -75,7 +78,7 @@
private final SparseArray<OwnershipClientInfo> mFocusOwners = new SparseArray<>();
@GuardedBy("mLock")
- private final Set<Integer> mActiveAppTypes = new ArraySet<>();
+ private final ArraySet<Integer> mActiveAppTypes = new ArraySet<>();
@GuardedBy("mLock")
private final List<FocusOwnershipCallback> mFocusOwnershipCallbacks = new ArrayList<>();
@@ -91,7 +94,15 @@
public AppFocusService(Context context,
SystemActivityMonitoringService systemActivityMonitoringService) {
+ this(context, systemActivityMonitoringService, new SystemStaticBinder());
+ }
+
+ @VisibleForTesting
+ AppFocusService(Context context,
+ SystemActivityMonitoringService systemActivityMonitoringService,
+ StaticBinderInterface binderInterface) {
mContext = context;
+ mBinderInterface = binderInterface;
mSystemActivityMonitoringService = systemActivityMonitoringService;
mAllChangeClients = new ClientHolder(mAllBinderEventHandler);
mAllOwnershipClients = new OwnershipClientHolder(this);
@@ -102,8 +113,8 @@
synchronized (mLock) {
ClientInfo info = (ClientInfo) mAllChangeClients.getBinderInterface(listener);
if (info == null) {
- info = new ClientInfo(mAllChangeClients, listener, Binder.getCallingUid(),
- Binder.getCallingPid(), appType);
+ info = new ClientInfo(mAllChangeClients, listener, mBinderInterface.getCallingUid(),
+ mBinderInterface.getCallingPid(), appType);
mAllChangeClients.addBinderInterface(info);
} else {
info.addAppType(appType);
@@ -128,7 +139,7 @@
@Override
public int[] getActiveAppTypes() {
synchronized (mLock) {
- return mActiveAppTypes.stream().mapToInt(Integer::intValue).toArray();
+ return CarServiceUtils.toIntArray(mActiveAppTypes);
}
}
@@ -172,7 +183,7 @@
(OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(callback);
if (info == null) {
info = new OwnershipClientInfo(mAllOwnershipClients, callback,
- Binder.getCallingUid(), Binder.getCallingPid());
+ mBinderInterface.getCallingUid(), mBinderInterface.getCallingPid());
mAllOwnershipClients.addBinderInterface(info);
}
Set<Integer> alreadyOwnedAppTypes = info.getOwnedAppTypes();
@@ -280,7 +291,6 @@
@VisibleForTesting
public Looper getLooper() {
return mHandlerThread.getLooper();
-
}
@Override
diff --git a/service/src/com/android/car/CarDiagnosticService.java b/service/src/com/android/car/CarDiagnosticService.java
index 94341d2..8131d99 100644
--- a/service/src/com/android/car/CarDiagnosticService.java
+++ b/service/src/com/android/car/CarDiagnosticService.java
@@ -30,6 +30,8 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import com.android.car.Listeners.ClientWithRate;
@@ -42,12 +44,10 @@
import java.util.Arrays;
import java.util.ConcurrentModificationException;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
-import java.util.Set;
/** @hide */
public class CarDiagnosticService extends ICarDiagnostic.Stub
@@ -60,8 +60,8 @@
/** key: diagnostic type. */
@GuardedBy("mLock")
- private final HashMap<Integer, Listeners<DiagnosticClient>> mDiagnosticListeners =
- new HashMap<>();
+ private final SparseArray<Listeners<DiagnosticClient>> mDiagnosticListeners =
+ new SparseArray();
/** the latest live frame data. */
@GuardedBy("mLock")
@@ -141,9 +141,9 @@
@Override
public void release() {
synchronized (mLock) {
- mDiagnosticListeners.forEach(
- (Integer frameType, Listeners diagnosticListeners) ->
- diagnosticListeners.release());
+ for (int i = 0; i < mDiagnosticListeners.size(); i++) {
+ mDiagnosticListeners.valueAt(i).release();
+ }
mDiagnosticListeners.clear();
mLiveFrameDiagnosticRecord.disableIfNeeded();
mFreezeFrameDiagnosticRecords.disableIfNeeded();
@@ -184,7 +184,7 @@
}
}
- for (ArrayMap.Entry<CarDiagnosticService.DiagnosticClient, List<CarDiagnosticEvent>> entry :
+ for (Map.Entry<CarDiagnosticService.DiagnosticClient, List<CarDiagnosticEvent>> entry :
eventsByClient.entrySet()) {
CarDiagnosticService.DiagnosticClient client = entry.getKey();
List<CarDiagnosticEvent> clientEvents = entry.getValue();
@@ -491,7 +491,7 @@
/** callback for diagnostic events */
private final ICarDiagnosticEventListener mListener;
- private final Set<Integer> mActiveDiagnostics = new HashSet<>();
+ private final ArraySet<Integer> mActiveDiagnostics = new ArraySet<>();
/** when false, it is already released */
private volatile boolean mActive = true;
@@ -530,7 +530,7 @@
}
int[] getDiagnosticArray() {
- return mActiveDiagnostics.stream().mapToInt(Integer::intValue).toArray();
+ return CarServiceUtils.toIntArray(mActiveDiagnostics);
}
ICarDiagnosticEventListener getICarDiagnosticEventListener() {
@@ -606,7 +606,7 @@
private static class FreezeFrameRecord extends DiagnosticRecord {
/** Store the timestamp --> freeze frame mapping. */
- HashMap<Long, CarDiagnosticEvent> mEvents = new HashMap<>();
+ ArrayMap<Long, CarDiagnosticEvent> mEvents = new ArrayMap<>();
@Override
boolean disableIfNeeded() {
@@ -627,7 +627,11 @@
}
long[] getFreezeFrameTimestamps() {
- return mEvents.keySet().stream().mapToLong(Long::longValue).toArray();
+ long[] freezeFrameTimestamps = new long[mEvents.size()];
+ for (int i = 0; i < mEvents.size(); i++) {
+ freezeFrameTimestamps[i] = mEvents.keyAt(i);
+ }
+ return freezeFrameTimestamps;
}
CarDiagnosticEvent getEvent(long timestamp) {
@@ -673,12 +677,12 @@
}
writer.println("**diagnostic listeners**");
try {
- for (int diagnostic : mDiagnosticListeners.keySet()) {
- Listeners diagnosticListeners = mDiagnosticListeners.get(diagnostic);
+ for (int i = 0; i < mDiagnosticListeners.size(); i++) {
+ Listeners diagnosticListeners = mDiagnosticListeners.valueAt(i);
if (diagnosticListeners != null) {
writer.println(
" Diagnostic:"
- + diagnostic
+ + mDiagnosticListeners.keyAt(i)
+ " num client:"
+ diagnosticListeners.getNumberOfClients()
+ " rate:"
diff --git a/service/src/com/android/car/CarDrivingStateService.java b/service/src/com/android/car/CarDrivingStateService.java
index b6af47f..636890b 100644
--- a/service/src/com/android/car/CarDrivingStateService.java
+++ b/service/src/com/android/car/CarDrivingStateService.java
@@ -33,12 +33,15 @@
import android.car.hardware.property.ICarPropertyEventListener;
import android.content.Context;
import android.hardware.automotive.vehicle.VehicleGear;
+import android.hardware.automotive.vehicle.VehicleIgnitionState;
import android.hardware.automotive.vehicle.VehicleProperty;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
@@ -57,10 +60,9 @@
*/
public class CarDrivingStateService extends ICarDrivingState.Stub implements CarServiceBase {
private static final String TAG = CarLog.tagFor(CarDrivingStateService.class);
- private static final boolean DBG = false;
+ private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
private static final int MAX_TRANSITION_LOG_SIZE = 20;
private static final int PROPERTY_UPDATE_RATE = 5; // Update rate in Hz
- private static final int NOT_RECEIVED = -1;
private final Context mContext;
private final CarPropertyService mPropertyService;
// List of clients listening to driving state events.
@@ -71,7 +73,13 @@
private static final int[] REQUIRED_PROPERTIES = {
VehicleProperty.PERF_VEHICLE_SPEED,
VehicleProperty.GEAR_SELECTION,
- VehicleProperty.PARKING_BRAKE_ON};
+ VehicleProperty.PARKING_BRAKE_ON,
+ };
+ // Array of optional properties that the service needs to listen to from CarPropertyService
+ // for deriving the driving state.
+ private static final int[] OPTIONAL_PROPERTIES = {
+ VehicleProperty.IGNITION_STATE
+ };
private final HandlerThread mClientDispatchThread = CarServiceUtils.getHandlerThread(
getClass().getSimpleName());
private final Handler mClientDispatchHandler = new Handler(mClientDispatchThread.getLooper());
@@ -81,23 +89,19 @@
@GuardedBy("mLock")
private final LinkedList<TransitionLog> mTransitionLogs = new LinkedList<>();
+ /**
+ * A set of optional {@link VehicleProperty} that are supported.
+ */
@GuardedBy("mLock")
- private int mLastGear;
-
+ private SparseBooleanArray mOptionalPropertiesSupported;
@GuardedBy("mLock")
- private long mLastGearTimestamp = NOT_RECEIVED;
-
+ private CarPropertyValue mLastParkingBrake;
@GuardedBy("mLock")
- private float mLastSpeed;
-
+ private CarPropertyValue mLastGear;
@GuardedBy("mLock")
- private long mLastSpeedTimestamp = NOT_RECEIVED;
-
+ private CarPropertyValue mLastSpeed;
@GuardedBy("mLock")
- private boolean mLastParkingBrakeState;
-
- @GuardedBy("mLock")
- private long mLastParkingBrakeTimestamp = NOT_RECEIVED;
+ private CarPropertyValue mLastIgnitionState;
@GuardedBy("mLock")
private List<Integer> mSupportedGears;
@@ -131,6 +135,13 @@
for (int property : REQUIRED_PROPERTIES) {
mPropertyService.unregisterListenerSafe(property, mICarPropertyEventListener);
}
+ for (int property : OPTIONAL_PROPERTIES) {
+ synchronized (mLock) {
+ if (isOptionalPropertySupportedLocked(property)) {
+ mPropertyService.unregisterListenerSafe(property, mICarPropertyEventListener);
+ }
+ }
+ }
while (mDrivingStateClients.getRegisteredCallbackCount() > 0) {
for (int i = mDrivingStateClients.getRegisteredCallbackCount() - 1; i >= 0; i--) {
ICarDrivingStateChangeListener client =
@@ -144,6 +155,9 @@
synchronized (mLock) {
mCurrentDrivingState = createDrivingStateEvent(
CarDrivingStateEvent.DRIVING_STATE_UNKNOWN);
+ if (mOptionalPropertiesSupported != null) {
+ mOptionalPropertiesSupported.clear();
+ }
}
}
@@ -168,10 +182,32 @@
return false;
}
}
+ configs = mPropertyService
+ .getPropertyConfigList(OPTIONAL_PROPERTIES).carPropertyConfigList.getConfigs();
+ if (configs != null) {
+ SparseBooleanArray optionalPropertiesSupported = new SparseBooleanArray();
+ for (CarPropertyConfig config : configs) {
+ optionalPropertiesSupported.put(config.getPropertyId(), true);
+ }
+ synchronized (mLock) {
+ mOptionalPropertiesSupported = optionalPropertiesSupported;
+ }
+ }
return true;
}
/**
+ * Check if the optional property is supported.
+ */
+ @GuardedBy("mLock")
+ private boolean isOptionalPropertySupportedLocked(int propertyId) {
+ if (mOptionalPropertiesSupported != null) {
+ return mOptionalPropertiesSupported.get(propertyId);
+ }
+ return false;
+ }
+
+ /**
* Subscribe to the {@link CarPropertyService} for required sensors.
*/
private void subscribeToProperties() {
@@ -179,7 +215,14 @@
mPropertyService.registerListenerSafe(propertyId, PROPERTY_UPDATE_RATE,
mICarPropertyEventListener);
}
-
+ for (int propertyId : OPTIONAL_PROPERTIES) {
+ synchronized (mLock) {
+ if (isOptionalPropertySupportedLocked(propertyId)) {
+ mPropertyService.registerListenerSafe(propertyId, PROPERTY_UPDATE_RATE,
+ mICarPropertyEventListener);
+ }
+ }
+ }
}
// Binder methods
@@ -313,54 +356,40 @@
return;
}
CarPropertyValue value = event.getCarPropertyValue();
- int propId = value.getPropertyId();
- long curTimestamp = value.getTimestamp();
if (DBG) {
- Slogf.d(TAG, "Property Changed: propId=" + propId);
+ Slogf.d(TAG, "Property Changed: " + value);
}
- switch (propId) {
+ switch (value.getPropertyId()) {
case VehicleProperty.PERF_VEHICLE_SPEED:
- float curSpeed = (Float) value.getValue();
+ mLastSpeed = value;
if (DBG) {
- Slogf.d(TAG, "Speed: " + curSpeed + "@" + curTimestamp);
- }
- if (curTimestamp > mLastSpeedTimestamp) {
- mLastSpeedTimestamp = curTimestamp;
- mLastSpeed = curSpeed;
- } else if (DBG) {
- Slogf.d(TAG, "Ignoring speed with older timestamp:" + curTimestamp);
+ Slogf.d(TAG, "mLastSpeed: " + mLastSpeed);
}
break;
case VehicleProperty.GEAR_SELECTION:
if (mSupportedGears == null) {
mSupportedGears = getSupportedGears();
}
- int curGear = (Integer) value.getValue();
+ mLastGear = value;
if (DBG) {
- Slogf.d(TAG, "Gear: " + curGear + "@" + curTimestamp);
- }
- if (curTimestamp > mLastGearTimestamp) {
- mLastGearTimestamp = curTimestamp;
- mLastGear = (Integer) value.getValue();
- } else if (DBG) {
- Slogf.d(TAG, "Ignoring Gear with older timestamp:" + curTimestamp);
+ Slogf.d(TAG, "mLastGear: " + mLastGear);
}
break;
case VehicleProperty.PARKING_BRAKE_ON:
- boolean curParkingBrake = (boolean) value.getValue();
+ mLastParkingBrake = value;
if (DBG) {
- Slogf.d(TAG, "Parking Brake: " + curParkingBrake + "@" + curTimestamp);
+ Slogf.d(TAG, "mLastParkingBrake: " + mLastParkingBrake);
}
- if (curTimestamp > mLastParkingBrakeTimestamp) {
- mLastParkingBrakeTimestamp = curTimestamp;
- mLastParkingBrakeState = curParkingBrake;
- } else if (DBG) {
- Slogf.d(TAG, "Ignoring Parking Brake status with an older timestamp:"
- + curTimestamp);
+ break;
+ case VehicleProperty.IGNITION_STATE:
+ mLastIgnitionState = value;
+ if (DBG) {
+ Slogf.d(TAG, "mLastIgnitionState: " + mLastIgnitionState);
}
break;
default:
- Slogf.e(TAG, "Received property event for unhandled propId=" + propId);
+ Slogf.e(TAG,
+ "Received property event for unhandled propId=" + value.getPropertyId());
break;
}
@@ -407,6 +436,11 @@
mTransitionLogs.add(tLog);
}
+ private static boolean isPropertyStatusAvailable(CarPropertyValue<?> carPropertyValue) {
+ return carPropertyValue != null
+ && carPropertyValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE;
+ }
+
/**
* Infers the current driving state of the car from the other Car Sensor properties like
* Current Gear, Speed etc.
@@ -418,7 +452,9 @@
private int inferDrivingStateLocked() {
updateVehiclePropertiesIfNeededLocked();
if (DBG) {
- Slogf.d(TAG, "Last known Gear:" + mLastGear + " Last known speed:" + mLastSpeed);
+ Slogf.d(TAG,
+ "inferDrivingStateLocked mLastGear: " + mLastGear + "mLastSpeed: " + mLastSpeed
+ + "mLastIgnitionState: " + mLastIgnitionState);
}
/*
@@ -431,7 +467,10 @@
3. If driving state is unknown at the end of step 2,
3a. if speed == 0, then driving state is idling
3b. if speed != 0, then driving state is moving
- 3c. if speed unavailable, then driving state is unknown
+ 3c. if speed is unavailable,
+ 3ca. If ignition state is supported and ignition state == lock or acc or
+ off, then driving state is parked
+ 3cb. else driving state is unknown
*/
if (isVehicleKnownToBeParkedLocked()) {
@@ -439,9 +478,13 @@
}
// We don't know if the vehicle is parked, let's look at the speed.
- if (mLastSpeedTimestamp == NOT_RECEIVED) {
+ if (!isPropertyStatusAvailable(mLastSpeed)) {
+ if (isOptionalPropertySupportedLocked(VehicleProperty.IGNITION_STATE)
+ && isVehicleKnownToBeInLockOrAccOrOffIgnitionStateLocked()) {
+ return CarDrivingStateEvent.DRIVING_STATE_PARKED;
+ }
return CarDrivingStateEvent.DRIVING_STATE_UNKNOWN;
- } else if (mLastSpeed == 0f) {
+ } else if (mLastSpeed.getValue().equals(0f)) {
return CarDrivingStateEvent.DRIVING_STATE_IDLING;
} else {
return CarDrivingStateEvent.DRIVING_STATE_MOVING;
@@ -449,6 +492,21 @@
}
/**
+ * Find if we have signals to know if the vehicle is in ignition state lock or acc or off.
+ *
+ * @return true if we have enough information to say the vehicle is in ignition state acc or
+ * off, false if the vehicle is either not in ignition state acc or off or if we don't have any
+ * information.
+ */
+ @GuardedBy("mLock")
+ private boolean isVehicleKnownToBeInLockOrAccOrOffIgnitionStateLocked() {
+ return isPropertyStatusAvailable(mLastIgnitionState)
+ && (mLastIgnitionState.getValue().equals(VehicleIgnitionState.ACC)
+ || mLastIgnitionState.getValue().equals(VehicleIgnitionState.OFF)
+ || mLastIgnitionState.getValue().equals(VehicleIgnitionState.LOCK));
+ }
+
+ /**
* Find if we have signals to know if the vehicle is parked
*
* @return true if we have enough information to say the vehicle is parked.
@@ -457,13 +515,14 @@
@GuardedBy("mLock")
private boolean isVehicleKnownToBeParkedLocked() {
// If we know the gear is in park, return true
- if (mLastGearTimestamp != NOT_RECEIVED && mLastGear == VehicleGear.GEAR_PARK) {
+ if (isPropertyStatusAvailable(mLastGear) && mLastGear.getValue()
+ .equals(VehicleGear.GEAR_PARK)) {
return true;
- } else if (mLastParkingBrakeTimestamp != NOT_RECEIVED) {
+ } else if (isPropertyStatusAvailable(mLastParkingBrake)) {
// if gear is not in park or unknown, look for status of parking brake if transmission
// type is manual.
if (isCarManualTransmissionTypeLocked()) {
- return mLastParkingBrakeState;
+ return (boolean) mLastParkingBrake.getValue();
}
}
// if neither information is available, return false to indicate we can't determine
@@ -491,49 +550,57 @@
*/
@GuardedBy("mLock")
private void updateVehiclePropertiesIfNeededLocked() {
- if (mLastGearTimestamp == NOT_RECEIVED) {
+ if (mLastGear == null) {
CarPropertyValue propertyValue = mPropertyService.getPropertySafe(
VehicleProperty.GEAR_SELECTION,
VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
if (propertyValue != null) {
- mLastGear = (Integer) propertyValue.getValue();
- mLastGearTimestamp = propertyValue.getTimestamp();
+ mLastGear = propertyValue;
if (DBG) {
Slogf.d(TAG, "updateVehiclePropertiesIfNeeded: gear:" + mLastGear);
}
}
}
- if (mLastParkingBrakeTimestamp == NOT_RECEIVED) {
+ if (mLastParkingBrake == null) {
CarPropertyValue propertyValue = mPropertyService.getPropertySafe(
VehicleProperty.PARKING_BRAKE_ON,
VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
if (propertyValue != null) {
- mLastParkingBrakeState = (boolean) propertyValue.getValue();
- mLastParkingBrakeTimestamp = propertyValue.getTimestamp();
+ mLastParkingBrake = propertyValue;
if (DBG) {
- Slogf.d(TAG, "updateVehiclePropertiesIfNeeded: brake:"
- + mLastParkingBrakeState);
+ Slogf.d(TAG, "updateVehiclePropertiesIfNeeded: brake:" + mLastParkingBrake);
}
}
}
- if (mLastSpeedTimestamp == NOT_RECEIVED) {
+ if (mLastSpeed == null) {
CarPropertyValue propertyValue = mPropertyService.getPropertySafe(
VehicleProperty.PERF_VEHICLE_SPEED,
VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
if (propertyValue != null) {
- mLastSpeed = (float) propertyValue.getValue();
- mLastSpeedTimestamp = propertyValue.getTimestamp();
+ mLastSpeed = propertyValue;
if (DBG) {
Slogf.d(TAG, "updateVehiclePropertiesIfNeeded: speed:" + mLastSpeed);
}
}
}
+
+ if (mLastIgnitionState == null) {
+ CarPropertyValue propertyValue = mPropertyService.getPropertySafe(
+ VehicleProperty.IGNITION_STATE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
+ if (propertyValue != null) {
+ mLastIgnitionState = propertyValue;
+ if (DBG) {
+ Slogf.d(TAG, "updateVehiclePropertiesIfNeeded: ignition state:"
+ + mLastIgnitionState);
+ }
+ }
+ }
}
private static CarDrivingStateEvent createDrivingStateEvent(int eventValue) {
return new CarDrivingStateEvent(eventValue, SystemClock.elapsedRealtimeNanos());
}
-
}
diff --git a/service/src/com/android/car/CarFeatureController.java b/service/src/com/android/car/CarFeatureController.java
index 3bb6c4a..8fb2b81 100644
--- a/service/src/com/android/car/CarFeatureController.java
+++ b/service/src/com/android/car/CarFeatureController.java
@@ -27,6 +27,8 @@
import android.car.builtin.util.Slogf;
import android.car.feature.Flags;
import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.automotive.vehicle.VehicleProperty;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.ArraySet;
@@ -34,6 +36,8 @@
import android.util.Pair;
import android.util.proto.ProtoOutputStream;
+import com.android.car.hal.HalPropValue;
+import com.android.car.hal.VehicleHal;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
@@ -52,7 +56,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
/**
@@ -61,6 +64,7 @@
public final class CarFeatureController implements CarServiceBase {
private static final String TAG = CarLog.tagFor(CarFeatureController.class);
+ private static final int INITIAL_VHAL_GET_RETRY = 2;
// We define this here for compatibility with older feature lists only
private static final String BLUETOOTH_SERVICE = "car_bluetooth";
@@ -130,9 +134,11 @@
Car.CAR_INSTRUMENT_CLUSTER_SERVICE
);
- private static final ArraySet<String> FLAGGED_OPTIONAL_FEATURES = new ArraySet<>();
+ private static final ArraySet<String> FLAGGED_OPTIONAL_FEATURES = new ArraySet<>(1);
static {
+ // TODO(b/327682912): Move to packages/services/Car/service/res/values/config.xml,
+ // when removing the feature flag
if (Flags.displayCompatibility()) {
FLAGGED_OPTIONAL_FEATURES.add(Car.CAR_DISPLAY_COMPAT_SERVICE);
}
@@ -189,15 +195,28 @@
@GuardedBy("mLock")
private ArraySet<String> mAvailableExperimentalFeatures = new ArraySet<>();
- public CarFeatureController(@NonNull Context context,
- @NonNull String[] defaultEnabledFeaturesFromConfig,
- @NonNull String[] disabledFeaturesFromVhal, @NonNull File dataDir) {
+ public CarFeatureController(@NonNull Context context, @NonNull File dataDir, VehicleHal hal) {
if (!BuildHelper.isUserBuild()) {
OPTIONAL_FEATURES.addAll(NON_USER_ONLY_FEATURES);
}
mContext = context;
- Arrays.sort(defaultEnabledFeaturesFromConfig);
- mDefaultEnabledFeaturesFromConfig = Arrays.asList(defaultEnabledFeaturesFromConfig);
+ String[] disabledFeaturesFromVhal = null;
+ HalPropValue disabledOptionalFeatureValue = hal.getIfSupportedOrFailForEarlyStage(
+ VehicleProperty.DISABLED_OPTIONAL_FEATURES, INITIAL_VHAL_GET_RETRY);
+ if (disabledOptionalFeatureValue != null) {
+ String disabledFeatures = disabledOptionalFeatureValue.getStringValue();
+ if (disabledFeatures != null && !disabledFeatures.isEmpty()) {
+ disabledFeaturesFromVhal = disabledFeatures.split(",");
+ }
+ }
+ if (disabledFeaturesFromVhal == null) {
+ disabledFeaturesFromVhal = new String[0];
+ }
+ Resources res = mContext.getResources();
+ String[] defaultEnabledFeatures = res.getStringArray(
+ R.array.config_allowed_optional_car_features);
+ Arrays.sort(defaultEnabledFeatures);
+ mDefaultEnabledFeaturesFromConfig = Arrays.asList(defaultEnabledFeatures);
mDisabledFeaturesFromVhal = Arrays.asList(disabledFeaturesFromVhal);
Slogf.i(TAG, "mDefaultEnabledFeaturesFromConfig:" + mDefaultEnabledFeaturesFromConfig
+ ",mDisabledFeaturesFromVhal:" + mDisabledFeaturesFromVhal);
@@ -459,12 +478,12 @@
/** Returns currently enabled experimental features */
public @NonNull List<String> getEnabledExperimentalFeatures() {
+ ArrayList<String> experimentalFeature = new ArrayList<>();
if (BuildHelper.isUserBuild()) {
Slogf.e(TAG, "getEnabledExperimentalFeatures called in USER build",
new RuntimeException());
- return Collections.emptyList();
+ return experimentalFeature;
}
- ArrayList<String> experimentalFeature = new ArrayList<>();
for (int i = 0; i < mEnabledFeatures.size(); i++) {
String enabledFeature = mEnabledFeatures.valueAt(i);
if (MANDATORY_FEATURES.contains(enabledFeature)) {
@@ -617,24 +636,27 @@
Slogf.e(TAG, "config_default_enabled_optional_car_features including "
+ "user build only feature, will be ignored:" + defaultEnabledFeature);
} else {
- throw new IllegalArgumentException(
- "config_default_enabled_optional_car_features include non-optional "
- + "features:" + defaultEnabledFeature);
+ Slogf.e(TAG, "config_default_enabled_optional_car_features include "
+ + "non-optional features:" + defaultEnabledFeature);
}
}
Slogf.i(TAG, "Loaded default features:" + mEnabledFeatures);
}
private static void addSupportFeatures(Collection<String> features) {
- SUPPORT_FEATURES.stream()
- .filter(entry -> features.contains(entry.first))
- .forEach(entry -> features.add(entry.second));
+ for (int index = 0; index < SUPPORT_FEATURES.size(); index++) {
+ if (features.contains(SUPPORT_FEATURES.get(index).first)) {
+ features.add(SUPPORT_FEATURES.get(index).second);
+ }
+ }
}
private static void removeSupportFeatures(Collection<String> features) {
- SUPPORT_FEATURES.stream()
- .filter(entry -> features.contains(entry.first))
- .forEach(entry -> features.remove(entry.second));
+ for (int index = 0; index < SUPPORT_FEATURES.size(); index++) {
+ if (features.contains(SUPPORT_FEATURES.get(index).first)) {
+ features.remove(SUPPORT_FEATURES.get(index).second);
+ }
+ }
}
private static ArraySet<String> combineFeatures(List<String> features,
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index eedb10d..618630c 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -17,7 +17,6 @@
package com.android.car;
import static android.car.CarOccupantZoneManager.DisplayTypeEnum;
-import static android.car.PlatformVersion.VERSION_CODES.UPSIDE_DOWN_CAKE_0;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
import static com.android.car.BuiltinPackageDependency.CAR_ACCESSIBILITY_SERVICE_CLASS;
@@ -31,7 +30,6 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.car.Car;
import android.car.CarOccupantZoneManager;
import android.car.CarOccupantZoneManager.OccupantZoneInfo;
import android.car.CarProjectionManager;
@@ -541,12 +539,6 @@
@Override
public void onMotionEvent(MotionEvent event, @DisplayTypeEnum int targetDisplayType,
@VehicleAreaSeat.Enum int seat) {
- if (!Car.getPlatformVersion().isAtLeast(UPSIDE_DOWN_CAKE_0)) {
- Slogf.e(TAG, "Motion event for passenger is only supported from %s",
- UPSIDE_DOWN_CAKE_0);
- return;
- }
-
if (seat == VehicleAreaSeat.SEAT_UNKNOWN) {
throw new IllegalArgumentException("Unknown seat");
}
diff --git a/service/src/com/android/car/CarLocalServices.java b/service/src/com/android/car/CarLocalServices.java
index 1921a61..a890a67 100644
--- a/service/src/com/android/car/CarLocalServices.java
+++ b/service/src/com/android/car/CarLocalServices.java
@@ -22,6 +22,7 @@
import android.car.hardware.power.CarPowerManager;
import android.content.Context;
import android.util.ArrayMap;
+import android.util.Log;
import com.android.car.power.CarPowerManagementService;
import com.android.internal.annotations.VisibleForTesting;
@@ -31,10 +32,11 @@
* This is for accessing other car service components.
*/
public final class CarLocalServices {
- private static final boolean DBG = false;
private static final String TAG = CarLog.tagFor(CarLocalServices.class);
+ private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
+
private CarLocalServices() {}
private static final ArrayMap<Class<?>, Object> sLocalServiceObjects =
diff --git a/service/src/com/android/car/CarLocationService.java b/service/src/com/android/car/CarLocationService.java
index cd53e98..6921a2b 100644
--- a/service/src/com/android/car/CarLocationService.java
+++ b/service/src/com/android/car/CarLocationService.java
@@ -144,22 +144,32 @@
boolean isOn =
accumulatedPolicy.isComponentEnabled(PowerComponent.LOCATION);
- if (isOn) {
- logd("Resume GNSS requests.");
- locationManager.setAutomotiveGnssSuspended(false);
- if (locationManager.isAutomotiveGnssSuspended()) {
- Slogf.w(TAG,
- "Failed - isAutomotiveGnssSuspended is true. "
- + "GNSS should NOT be suspended.");
+ try {
+ if (isOn) {
+ logd("Resume GNSS requests.");
+ locationManager.setAutomotiveGnssSuspended(false);
+ if (locationManager.isAutomotiveGnssSuspended()) {
+ Slogf.w(
+ TAG,
+ "Failed - isAutomotiveGnssSuspended is true. "
+ + "GNSS should NOT be suspended.");
+ }
+ } else {
+ logd("Suspend GNSS requests.");
+ locationManager.setAutomotiveGnssSuspended(true);
+ if (!locationManager.isAutomotiveGnssSuspended()) {
+ Slogf.w(
+ TAG,
+ "Failed - isAutomotiveGnssSuspended is false. "
+ + "GNSS should be suspended.");
+ }
}
- } else {
- logd("Suspend GNSS requests.");
- locationManager.setAutomotiveGnssSuspended(true);
- if (!locationManager.isAutomotiveGnssSuspended()) {
- Slogf.w(TAG,
- "Failed - isAutomotiveGnssSuspended is false. "
- + "GNSS should be suspended.");
- }
+ } catch (NullPointerException e) {
+ Slogf.w(
+ TAG,
+ "The device might not support GNSS thus GNSSManagerService may be"
+ + " null",
+ e);
}
}
};
@@ -606,7 +616,7 @@
logd("Failed to inject stored location on attempt %s.", attemptCount);
asyncOperation(() -> {
injectLocation(location, attemptCount + 1);
- }, 200 * attemptCount);
+ }, 200L * attemptCount);
} else {
logd("No location injected.");
}
diff --git a/service/src/com/android/car/CarMediaService.java b/service/src/com/android/car/CarMediaService.java
index 674d2f9..88a4700 100644
--- a/service/src/com/android/car/CarMediaService.java
+++ b/service/src/com/android/car/CarMediaService.java
@@ -69,6 +69,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.PersistableBundle;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -360,6 +361,15 @@
};
private final UserHandleHelper mUserHandleHelper;
+ private static final PersistableBundle USER_INTERACTION_EXTRAS;
+
+ static {
+ USER_INTERACTION_EXTRAS = new PersistableBundle();
+ USER_INTERACTION_EXTRAS.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY,
+ CarMediaService.class.getCanonicalName());
+ USER_INTERACTION_EXTRAS.putString(UsageStatsManager.EXTRA_EVENT_ACTION,
+ "setPrimaryMediaSource");
+ }
public CarMediaService(Context context, CarOccupantZoneService occupantZoneService,
CarUserService userService, CarPowerManagementService powerManagementService) {
@@ -776,7 +786,7 @@
}
/**
- * @see {@link CarMediaManager#setMediaSource(ComponentName)}
+ * @see CarMediaManager#setMediaSource(ComponentName, int)
*/
@Override
public void setMediaSource(@NonNull ComponentName componentName,
@@ -792,7 +802,7 @@
}
/**
- * @see {@link CarMediaManager#getMediaSource()}
+ * @see CarMediaManager#getMediaSource
*/
@Override
public ComponentName getMediaSource(@CarMediaManager.MediaSourceMode int mode,
@@ -816,7 +826,8 @@
}
/**
- * @see {@link CarMediaManager#registerMediaSourceListener(MediaSourceChangedListener)}
+ * @see
+ * CarMediaManager#removeMediaSourceListener(CarMediaManager.MediaSourceChangedListener, int)
*/
@Override
public void registerMediaSourceListener(ICarMediaSourceListener callback,
@@ -832,7 +843,7 @@
}
/**
- * @see {@link CarMediaManager#unregisterMediaSourceListener(ICarMediaSourceListener)}
+ * @see CarMediaManager#removeMediaSourceListener
*/
@Override
public void unregisterMediaSourceListener(ICarMediaSourceListener callback,
@@ -1300,7 +1311,7 @@
// attribution of non-foreground media app interactions to the app's package name
if (componentName != null) {
UsageStatsManagerHelper.reportUserInteraction(mUsageStatsManager,
- componentName.getPackageName(), userId);
+ componentName.getPackageName(), userId, USER_INTERACTION_EXTRAS);
}
}
diff --git a/service/src/com/android/car/CarNightService.java b/service/src/com/android/car/CarNightService.java
index 65b526d..b09bb40 100644
--- a/service/src/com/android/car/CarNightService.java
+++ b/service/src/com/android/car/CarNightService.java
@@ -32,6 +32,7 @@
import android.hardware.automotive.vehicle.VehicleProperty;
import android.os.RemoteException;
import android.provider.Settings;
+import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
@@ -47,7 +48,7 @@
*/
public class CarNightService implements CarServiceBase {
- public static final boolean DBG = false;
+ public static final boolean DBG = Slogf.isLoggable(CarLog.TAG_SENSOR, Log.DEBUG);
@IntDef({FORCED_SENSOR_MODE, FORCED_DAY_MODE, FORCED_NIGHT_MODE})
@Retention(RetentionPolicy.SOURCE)
diff --git a/service/src/com/android/car/CarOccupantZoneService.java b/service/src/com/android/car/CarOccupantZoneService.java
index 80cad79..729055e 100644
--- a/service/src/com/android/car/CarOccupantZoneService.java
+++ b/service/src/com/android/car/CarOccupantZoneService.java
@@ -37,7 +37,6 @@
import android.car.CarOccupantZoneManager.OccupantZoneInfo;
import android.car.ICarOccupantZone;
import android.car.ICarOccupantZoneCallback;
-import android.car.PlatformVersion;
import android.car.VehicleAreaSeat;
import android.car.builtin.util.Slogf;
import android.car.builtin.view.DisplayHelper;
@@ -128,8 +127,7 @@
DisplayConfig(int displayType, int occupantZoneId, IntArray inputTypes) {
this.displayType = displayType;
this.occupantZoneId = occupantZoneId;
- if (inputTypes == null && Car.getPlatformVersion().isAtLeast(
- PlatformVersion.VERSION_CODES.UPSIDE_DOWN_CAKE_0)) {
+ if (inputTypes == null) {
Slogf.w(TAG, "No input type was defined for displayType:%d "
+ " and occupantZoneId:%d", displayType, occupantZoneId);
}
@@ -1381,6 +1379,21 @@
case "AUXILIARY":
type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY;
break;
+ case "AUXILIARY_2":
+ type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY_2;
+ break;
+ case "AUXILIARY_3":
+ type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY_3;
+ break;
+ case "AUXILIARY_4":
+ type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY_4;
+ break;
+ case "AUXILIARY_5":
+ type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY_5;
+ break;
+ case "DISPLAY_COMPATIBILITY":
+ type = CarOccupantZoneManager.DISPLAY_TYPE_DISPLAY_COMPATIBILITY;
+ break;
default:
throwFormatErrorInDisplayMapping(
"Unrecognized display type:" + entry);
diff --git a/service/src/com/android/car/CarPerUserServiceHelper.java b/service/src/com/android/car/CarPerUserServiceHelper.java
index 9110907..7da0bf3 100644
--- a/service/src/com/android/car/CarPerUserServiceHelper.java
+++ b/service/src/com/android/car/CarPerUserServiceHelper.java
@@ -33,6 +33,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.UserHandle;
+import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
@@ -52,7 +53,7 @@
public class CarPerUserServiceHelper implements CarServiceBase {
private static final String TAG = CarLog.tagFor(CarPerUserServiceHelper.class);
- private static boolean DBG = false;
+ private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
private final Context mContext;
private final CarUserService mUserService;
diff --git a/service/src/com/android/car/CarPropertyService.java b/service/src/com/android/car/CarPropertyService.java
index eecc9cd..c2b476b 100644
--- a/service/src/com/android/car/CarPropertyService.java
+++ b/service/src/com/android/car/CarPropertyService.java
@@ -33,7 +33,6 @@
import android.car.builtin.util.Slogf;
import android.car.feature.FeatureFlags;
import android.car.feature.FeatureFlagsImpl;
-import android.car.feature.Flags;
import android.car.hardware.CarHvacFanDirection;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
@@ -74,6 +73,8 @@
import com.android.car.internal.util.ArrayUtils;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.car.internal.util.IntArray;
+import com.android.car.logging.HistogramFactoryInterface;
+import com.android.car.logging.SystemHistogramFactory;
import com.android.car.property.CarPropertyServiceClient;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -85,8 +86,11 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* This class implements the binder interface for ICarProperty.aidl to make it easier to create
@@ -154,34 +158,15 @@
WINDSHIELD_WIPERS_SWITCH_UNWRITABLE_STATES);
}
- private static final Histogram sConcurrentSyncOperationHistogram = new Histogram(
- "automotive_os.value_concurrent_sync_operations",
- new Histogram.UniformOptions(/* binCount= */ 17, /* minValue= */ 0,
- /* exclusiveMaxValue= */ 17));
+ private final FeatureFlags mFeatureFlags;
+ private final HistogramFactoryInterface mHistogramFactory;
- private static final Histogram sGetPropertySyncLatencyHistogram = new Histogram(
- "automotive_os.value_sync_get_property_latency",
- new Histogram.ScaledRangeOptions(/* binCount= */ 20, /* minValue= */ 0,
- /* firstBinWidth= */ 2, /* scaleFactor= */ 1.5f));
-
- private static final Histogram sSetPropertySyncLatencyHistogram = new Histogram(
- "automotive_os.value_sync_set_property_latency",
- new Histogram.ScaledRangeOptions(/* binCount= */ 20, /* minValue= */ 0,
- /* firstBinWidth= */ 2, /* scaleFactor= */ 1.5f));
-
- private static final Histogram sSubscriptionUpdateRateHistogram = new Histogram(
- "automotive_os.value_subscription_update_rate",
- new Histogram.UniformOptions(/* binCount= */ 101, /* minValue= */ 0,
- /* exclusiveMaxValue= */ 101));
- private static final Histogram sGetAsyncLatencyHistogram = new Histogram(
- "automotive_os.value_get_async_latency",
- new Histogram.UniformOptions(/* binCount= */ 20, /* minValue= */ 0,
- /* exclusiveMaxValue= */ 1000));
-
- private static final Histogram sSetAsyncLatencyHistogram = new Histogram(
- "automotive_os.value_set_async_latency",
- new Histogram.UniformOptions(/* binCount= */ 20, /* minValue= */ 0,
- /* exclusiveMaxValue= */ 1000));
+ private Histogram mConcurrentSyncOperationHistogram;
+ private Histogram mGetPropertySyncLatencyHistogram;
+ private Histogram mSetPropertySyncLatencyHistogram;
+ private Histogram mSubscriptionUpdateRateHistogram;
+ private Histogram mGetAsyncLatencyHistogram;
+ private Histogram mSetAsyncLatencyHistogram;
private final Context mContext;
private final PropertyHalService mPropertyHalService;
@@ -203,20 +188,74 @@
@GuardedBy("mLock")
private int mSyncGetSetPropertyOpCount;
- private FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
+ /**
+ * The builder for {@link com.android.car.CarPropertyService}.
+ */
+ public static final class Builder {
+ private Context mContext;
+ private PropertyHalService mPropertyHalService;
+ private @Nullable FeatureFlags mFeatureFlags;
+ private @Nullable HistogramFactoryInterface mHistogramFactory;
+ private boolean mBuilt;
- public CarPropertyService(Context context, PropertyHalService propertyHalService) {
+ /** Sets the context. */
+ public Builder setContext(Context context) {
+ mContext = context;
+ return this;
+ }
+
+ /** Sets the {@link PropertyHalService}. */
+ public Builder setPropertyHalService(PropertyHalService propertyHalService) {
+ mPropertyHalService = propertyHalService;
+ return this;
+ }
+
+ /**
+ * Builds the {@link com.android.car.CarPropertyService}.
+ */
+ public CarPropertyService build() {
+ if (mBuilt) {
+ throw new IllegalStateException("Only allowed to be built once");
+ }
+ mBuilt = true;
+ return new CarPropertyService(this);
+ }
+
+ /** Sets fake feature flag for unit testing. */
+ @VisibleForTesting
+ Builder setFeatureFlags(FeatureFlags fakeFeatureFlags) {
+ mFeatureFlags = fakeFeatureFlags;
+ return this;
+ }
+
+ /** Sets fake histogram builder for unit testing. */
+ @VisibleForTesting
+ Builder setHistogramFactory(HistogramFactoryInterface histogramFactory) {
+ mHistogramFactory = histogramFactory;
+ return this;
+ }
+ }
+
+ private CarPropertyService(Builder builder) {
if (DBG) {
Slogf.d(TAG, "CarPropertyService started!");
}
- mPropertyHalService = propertyHalService;
- mContext = context;
+ mPropertyHalService = Objects.requireNonNull(builder.mPropertyHalService);
+ mContext = Objects.requireNonNull(builder.mContext);
+ mFeatureFlags = Objects.requireNonNullElseGet(builder.mFeatureFlags,
+ () -> new FeatureFlagsImpl());
+ mHistogramFactory = Objects.requireNonNullElseGet(builder.mHistogramFactory,
+ () -> new SystemHistogramFactory());
+ initializeHistogram();
}
- /** Sets fake feature flag for unit testing. */
@VisibleForTesting
- void setFeatureFlags(FeatureFlags fakeFeatureFlags) {
- mFeatureFlags = fakeFeatureFlags;
+ void finishHandlerTasks(int timeoutInMs) throws InterruptedException {
+ CountDownLatch cdLatch = new CountDownLatch(1);
+ mHandler.post(() -> {
+ cdLatch.countDown();
+ });
+ cdLatch.await(timeoutInMs, TimeUnit.MILLISECONDS);
}
@Override
@@ -402,7 +441,7 @@
for (int i = 0; i < sanitizedOptions.size(); i++) {
CarSubscription option = sanitizedOptions.get(i);
- sSubscriptionUpdateRateHistogram.logSample(option.updateRateHz);
+ mSubscriptionUpdateRateHistogram.logSample(option.updateRateHz);
if (DBG) {
Slogf.d(TAG, "registerListener after update rate sanitization, options: "
+ sanitizedOptions.get(i));
@@ -711,11 +750,11 @@
private <V> V runSyncOperationCheckLimit(Callable<V> c) {
synchronized (mLock) {
if (mSyncGetSetPropertyOpCount >= SYNC_GET_SET_PROPERTY_OP_LIMIT) {
- sConcurrentSyncOperationHistogram.logSample(mSyncGetSetPropertyOpCount);
+ mConcurrentSyncOperationHistogram.logSample(mSyncGetSetPropertyOpCount);
throw new ServiceSpecificException(SYNC_OP_LIMIT_TRY_AGAIN);
}
mSyncGetSetPropertyOpCount += 1;
- sConcurrentSyncOperationHistogram.logSample(mSyncGetSetPropertyOpCount);
+ mConcurrentSyncOperationHistogram.logSample(mSyncGetSetPropertyOpCount);
if (DBG) {
Slogf.d(TAG, "mSyncGetSetPropertyOpCount: %d", mSyncGetSetPropertyOpCount);
}
@@ -754,7 +793,7 @@
Slogf.d(TAG, "Latency of getPropertySync is: %f", (float) (System
.currentTimeMillis() - currentTimeMs));
}
- sGetPropertySyncLatencyHistogram.logSample((float) (System.currentTimeMillis()
+ mGetPropertySyncLatencyHistogram.logSample((float) (System.currentTimeMillis()
- currentTimeMs));
Trace.traceEnd(TRACE_TAG);
}
@@ -838,7 +877,7 @@
Slogf.d(TAG, "Latency of setPropertySync is: %f", (float) (System
.currentTimeMillis() - currentTimeMs));
}
- sSetPropertySyncLatencyHistogram.logSample((float) (System.currentTimeMillis()
+ mSetPropertySyncLatencyHistogram.logSample((float) (System.currentTimeMillis()
- currentTimeMs));
}
}
@@ -989,7 +1028,7 @@
Slogf.d(TAG, "Latency of getPropertyAsync is: %f", (float) (System
.currentTimeMillis() - currentTime));
}
- sGetAsyncLatencyHistogram.logSample((float) (System.currentTimeMillis() - currentTime));
+ mGetAsyncLatencyHistogram.logSample((float) (System.currentTimeMillis() - currentTime));
}
/**
@@ -1038,7 +1077,7 @@
Slogf.d(TAG, "Latency of setPropertyAsync is: %f", (float) (System
.currentTimeMillis() - currentTime));
}
- sSetAsyncLatencyHistogram.logSample((float) (System.currentTimeMillis() - currentTime));
+ mSetAsyncLatencyHistogram.logSample((float) (System.currentTimeMillis() - currentTime));
}
@Override
@@ -1073,9 +1112,9 @@
mPropertyHalService.cancelRequests(serviceRequestIds);
}
- private static void assertPropertyIsReadable(CarPropertyConfig<?> carPropertyConfig,
+ private void assertPropertyIsReadable(CarPropertyConfig<?> carPropertyConfig,
int areaId) {
- int accessLevel = Flags.areaIdConfigAccess()
+ int accessLevel = mFeatureFlags.areaIdConfigAccess()
? carPropertyConfig.getAreaIdConfig(areaId).getAccess()
: carPropertyConfig.getAccess();
Preconditions.checkArgument(
@@ -1092,6 +1131,29 @@
+ VehiclePropertyIds.toString(carPropertyConfig.getPropertyId()));
}
+ private void initializeHistogram() {
+ mConcurrentSyncOperationHistogram = mHistogramFactory.newUniformHistogram(
+ "automotive_os.value_concurrent_sync_operations",
+ /* binCount= */ 17, /* minValue= */ 0, /* exclusiveMaxValue= */ 17);
+ mGetPropertySyncLatencyHistogram = mHistogramFactory.newScaledRangeHistogram(
+ "automotive_os.value_sync_get_property_latency",
+ /* binCount= */ 20, /* minValue= */ 0,
+ /* firstBinWidth= */ 2, /* scaleFactor= */ 1.5f);
+ mSetPropertySyncLatencyHistogram = mHistogramFactory.newScaledRangeHistogram(
+ "automotive_os.value_sync_set_property_latency",
+ /* binCount= */ 20, /* minValue= */ 0,
+ /* firstBinWidth= */ 2, /* scaleFactor= */ 1.5f);
+ mSubscriptionUpdateRateHistogram = mHistogramFactory.newUniformHistogram(
+ "automotive_os.value_subscription_update_rate",
+ /* binCount= */ 101, /* minValue= */ 0, /* exclusiveMaxValue= */ 101);
+ mGetAsyncLatencyHistogram = mHistogramFactory.newUniformHistogram(
+ "automotive_os.value_get_async_latency",
+ /* binCount= */ 20, /* minValue= */ 0, /* exclusiveMaxValue= */ 1000);
+ mSetAsyncLatencyHistogram = mHistogramFactory.newUniformHistogram(
+ "automotive_os.value_set_async_latency",
+ /* binCount= */ 20, /* minValue= */ 0, /* exclusiveMaxValue= */ 1000);
+ }
+
@Nullable
private CarPropertyConfig<?> getCarPropertyConfig(int propertyId) {
CarPropertyConfig<?> carPropertyConfig;
@@ -1150,7 +1212,7 @@
assertAreaIdIsSupported(carPropertyConfig, areaId);
// Assert property is writable.
- int accessLevel = Flags.areaIdConfigAccess()
+ int accessLevel = mFeatureFlags.areaIdConfigAccess()
? carPropertyConfig.getAreaIdConfig(areaId).getAccess()
: carPropertyConfig.getAccess();
Preconditions.checkArgument(
diff --git a/service/src/com/android/car/CarServiceImpl.java b/service/src/com/android/car/CarServiceImpl.java
index 6c9b31b..a930151 100644
--- a/service/src/com/android/car/CarServiceImpl.java
+++ b/service/src/com/android/car/CarServiceImpl.java
@@ -24,6 +24,7 @@
import android.car.builtin.os.TraceHelper;
import android.car.builtin.util.EventLogHelper;
import android.car.builtin.util.Slogf;
+import android.car.builtin.util.TimingsTraceLog;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
@@ -31,7 +32,6 @@
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.ProxiedService;
import com.android.car.systeminterface.SystemInterface;
-import com.android.car.util.LimitedTimingsTraceLog;
import com.android.internal.annotations.Keep;
import java.io.FileDescriptor;
@@ -52,7 +52,7 @@
@Override
public void onCreate() {
- LimitedTimingsTraceLog initTiming = new LimitedTimingsTraceLog(CAR_SERVICE_INIT_TIMING_TAG,
+ TimingsTraceLog initTiming = new TimingsTraceLog(CAR_SERVICE_INIT_TIMING_TAG,
TraceHelper.TRACE_TAG_CAR_SERVICE, CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS);
initTiming.traceBegin("CarService.onCreate");
diff --git a/service/src/com/android/car/CarServiceUtils.java b/service/src/com/android/car/CarServiceUtils.java
index d6c15f8..5abf3af 100644
--- a/service/src/com/android/car/CarServiceUtils.java
+++ b/service/src/com/android/car/CarServiceUtils.java
@@ -72,9 +72,9 @@
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.StringJoiner;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
-import java.util.stream.Collectors;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
@@ -268,9 +268,12 @@
return true;
}
}
+ StringJoiner expectedTyepsStringJoiner = new StringJoiner(",");
+ for (int index = 0; index < expectedTypes.length; index++) {
+ expectedTyepsStringJoiner.add(lifecycleEventTypeToString(expectedTypes[index]));
+ }
Slogf.wtf(tag, "Received an unexpected event: %s. Expected types: [%s]", event,
- Arrays.stream(expectedTypes).mapToObj(t -> lifecycleEventTypeToString(t)).collect(
- Collectors.joining(",")));
+ expectedTyepsStringJoiner.toString());
return false;
}
@@ -481,6 +484,20 @@
}
/**
+ * Converts int-value array set to values array
+ */
+ public static int[] toIntArray(ArraySet<Integer> set) {
+ Preconditions.checkArgument(set != null,
+ "Int array set to converted to array must not be null");
+ int size = set.size();
+ int[] array = new int[size];
+ for (int i = 0; i < size; ++i) {
+ array[i] = set.valueAt(i);
+ }
+ return array;
+ }
+
+ /**
* Returns delta between elapsed time to uptime = {@link SystemClock#elapsedRealtime()} -
* {@link SystemClock#uptimeMillis()}. Note that this value will be always >= 0.
*/
@@ -527,29 +544,33 @@
}
/**
- * Finishes all queued {@code Handler} tasks for {@code HandlerThread} created via
+ * Quits all the {@code HandlerThread} created via
* {@link#getHandlerThread(String)}. This is useful only for testing.
*/
@VisibleForTesting
- public static void finishAllHandlerTasks() {
+ public static void quitHandlerThreads() throws InterruptedException {
ArrayList<HandlerThread> threads;
synchronized (sHandlerThreads) {
threads = new ArrayList<>(sHandlerThreads.values());
}
- ArrayList<SyncRunnable> syncs = new ArrayList<>(threads.size());
for (int i = 0; i < threads.size(); i++) {
- if (!threads.get(i).isAlive()) {
+ var thread = threads.get(i);
+ if (!thread.isAlive()) {
continue;
}
- Handler handler = new Handler(threads.get(i).getLooper());
- SyncRunnable sr = new SyncRunnable(() -> { });
- if (handler.post(sr)) {
- // Track the threads only where SyncRunnable is posted successfully.
- syncs.add(sr);
+ if (thread.quitSafely()) {
+ thread.join();
}
}
- for (int i = 0; i < syncs.size(); i++) {
- syncs.get(i).waitForComplete();
+ synchronized (sHandlerThreads) {
+ for (int i = 0; i < sHandlerThreads.size(); i++) {
+ if (sHandlerThreads.valueAt(i).isAlive()) {
+ throw new IllegalStateException(
+ "Thread: " + sHandlerThreads.keyAt(i) + " is still alive after "
+ + "finishing all the tasks in the handler threads, maybe one of the "
+ + " pending task is creating a new handler thread?");
+ }
+ }
}
}
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index 50ed3de..a2732a5 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -53,6 +53,8 @@
import android.car.SyncResultCallback;
import android.car.VehiclePropertyIds;
import android.car.builtin.content.pm.PackageManagerHelper;
+import android.car.builtin.display.DisplayManagerHelper;
+import android.car.builtin.input.InputManagerHelper;
import android.car.builtin.os.BuildHelper;
import android.car.builtin.os.UserManagerHelper;
import android.car.builtin.util.Slogf;
@@ -104,6 +106,8 @@
import android.hardware.automotive.vehicle.VehicleDisplay;
import android.hardware.automotive.vehicle.VehicleGear;
import android.hardware.automotive.vehicle.VehiclePropError;
+import android.hardware.display.DisplayManager;
+import android.hardware.input.InputManager;
import android.os.Binder;
import android.os.FileUtils;
import android.os.IBinder;
@@ -149,7 +153,6 @@
import com.android.car.user.CarUserService;
import com.android.car.user.UserHandleHelper;
import com.android.car.watchdog.CarWatchdogService;
-import com.android.car.wifi.CarWifiService;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.BasicShellCommandHandler;
@@ -163,6 +166,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
@@ -207,6 +211,7 @@
private static final String PARAM_SKIP_GARAGEMODE = "--skip-garagemode";
private static final String PARAM_REBOOT = "--reboot";
private static final String PARAM_WAKEUP_AFTER = "--wakeup-after";
+ private static final String PARAM_CANCEL_AFTER = "--cancel-after";
private static final String PARAM_FREE_MEMORY = "--free-memory";
private static final String COMMAND_SET_UID_TO_ZONE = "set-audio-zone-for-uid";
private static final String COMMAND_RESET_VOLUME_CONTEXT = "reset-selected-volume-context";
@@ -308,13 +313,17 @@
private static final String COMMAND_GENERATE_TEST_VENDOR_CONFIGS = "gen-test-vendor-configs";
private static final String COMMAND_RESTORE_TEST_VENDOR_CONFIGS = "restore-vendor-configs";
- private static final String COMMAND_GET_TETHERING_CAPABILITY = "get-tethering-capability";
private static final String COMMAND_GET_CURRENT_UX_RESTRICTIONS = "get-current-ux-restrictions";
private static final String COMMAND_SET_CURRENT_UXR_MODE = "set-current-uxr-mode";
private static final String COMMAND_GET_CURRENT_UXR_MODE = "get-current-uxr-mode";
private static final String COMMAND_GET_SUPPORTED_UXR_MODES = "get-supported-uxr-modes";
private static final String COMMAND_GET_UXR_CONFIG = "get-uxr-config";
+ private static final String COMMAND_GET_INPUT_AND_DISPLAY_INFO = "get-input-and-display-info";
+ private static final String COMMAND_ADD_INPUT_DESCRIPTOR_ASSOCIATION_TO_DISPLAY_UNIQUE_ID =
+ "add-input-descriptor-association-to-display-unique-id";
+ private static final String COMMAND_REMOVE_INPUT_DESCRIPTOR_ASSOCIATION =
+ "remove-input-descriptor-association";
private static final String[] CREATE_OR_MANAGE_USERS_PERMISSIONS = new String[] {
android.Manifest.permission.CREATE_USERS,
@@ -456,8 +465,6 @@
private static final ArrayMap<String, Integer> CUSTOM_INPUT_FUNCTION_ARGS;
- private static final String INVALID_DISPLAY_ARGUMENTS =
- "Error: Invalid arguments for display ID.";
private static final int DEFAULT_DEVICE_ID = 0;
private static final float DEFAULT_PRESSURE = 1.0f;
private static final float NO_PRESSURE = 0.0f;
@@ -545,7 +552,6 @@
private final CarWatchdogService mCarWatchdogService;
private final CarTelemetryService mCarTelemetryService;
private final CarUxRestrictionsManagerService mCarUxRestrictionsManagerService;
- private final CarWifiService mCarWifiService;
private final Map<Class, CarSystemService> mAllServicesByClazz;
private long mKeyDownTime;
private long mMotionDownTime;
@@ -580,7 +586,6 @@
allServicesByClazz.get(CarTelemetryService.class);
mCarUxRestrictionsManagerService = (CarUxRestrictionsManagerService)
allServicesByClazz.get(CarUxRestrictionsManagerService.class);
- mCarWifiService = (CarWifiService) allServicesByClazz.get(CarWifiService.class);
mAllServicesByClazz = allServicesByClazz;
}
@@ -659,6 +664,10 @@
pw.printf("\t %s skips Garage Mode before going into sleep.\n", PARAM_SKIP_GARAGEMODE);
pw.printf("\t %s [RESUME_DELAY] wakes up the device RESUME_DELAY seconds after suspend.\n",
PARAM_WAKEUP_AFTER);
+ pw.printf("\t %s [RESUME_DELAY] cancels the wake up after RESUME_DELAY seconds, if this"
+ + " flag is set, device will not go into suspend mode and wait in shutdown"
+ + " prepare for RESUME_DELAY seconds.\n",
+ PARAM_CANCEL_AFTER);
pw.printf("\t%s\n", getSuspendCommandUsage(COMMAND_HIBERNATE));
pw.println("\t Suspend the system to disk.");
pw.printf("\t %s forces the device to perform suspend-to-disk.\n", PARAM_REAL);
@@ -971,9 +980,15 @@
pw.println("\t Gets all supported UX restrictions modes.");
pw.printf("\t%s", COMMAND_GET_UXR_CONFIG);
pw.println("\t Gets UX restrictions configuration.");
-
- pw.printf("\t%s", COMMAND_GET_TETHERING_CAPABILITY);
- pw.println("\t Gets the current tethering persistence capability.");
+ pw.printf("\t%s", COMMAND_GET_INPUT_AND_DISPLAY_INFO);
+ pw.println("\t Gets input devices & their descriptor; and gets display devices & their "
+ + "uniqueId");
+ pw.printf("\t%s <input descriptor> <display unique id>",
+ COMMAND_ADD_INPUT_DESCRIPTOR_ASSOCIATION_TO_DISPLAY_UNIQUE_ID);
+ pw.println("\t Add association of the input device to the particular display by using "
+ + "input descriptor.");
+ pw.printf("\t%s <input descriptor>", COMMAND_REMOVE_INPUT_DESCRIPTOR_ASSOCIATION);
+ pw.println("\t Remove association of the input device descriptor to any display.");
}
private static int showInvalidArguments(IndentingPrintWriter pw) {
@@ -1218,7 +1233,7 @@
if (args.length != 2) {
return showInvalidArguments(writer);
}
- String pkgName = args[1].toLowerCase();
+ String pkgName = args[1].toLowerCase(Locale.US);
if (mCarPackageManagerService != null) {
String[] doActivities =
mCarPackageManagerService.getDistractionOptimizedActivities(
@@ -1518,9 +1533,6 @@
case COMMAND_GET_USER_BY_DISPLAY:
getUserByDisplay(args, writer);
break;
- case COMMAND_GET_TETHERING_CAPABILITY:
- getTetheringCapability(writer);
- break;
case COMMAND_GET_CURRENT_UX_RESTRICTIONS:
getCurrentUxRestrictions(args, writer);
break;
@@ -1536,6 +1548,15 @@
case COMMAND_GET_UXR_CONFIG:
getUxrConfig(writer);
break;
+ case COMMAND_GET_INPUT_AND_DISPLAY_INFO:
+ getInputAndDisplayInfo(writer);
+ break;
+ case COMMAND_ADD_INPUT_DESCRIPTOR_ASSOCIATION_TO_DISPLAY_UNIQUE_ID:
+ addInputDescriptorAssociationToDisplayUniqueId(args, writer);
+ break;
+ case COMMAND_REMOVE_INPUT_DESCRIPTOR_ASSOCIATION:
+ removeInputDescriptorAssociation(args, writer);
+ break;
default:
writer.println("Unknown command: \"" + cmd + "\"");
showHelp(writer);
@@ -1579,6 +1600,49 @@
writer.printf("Current Restrictions:\n %s", restrictions.getActiveRestrictionsString());
}
+ private void getInputAndDisplayInfo(IndentingPrintWriter writer) {
+ InputManager inputManager = mContext.getSystemService(InputManager.class);
+ int[] inputDeviceIds = inputManager.getInputDeviceIds();
+
+ for (int inputDeviceId : inputDeviceIds) {
+ InputDevice device = inputManager.getInputDevice(inputDeviceId);
+ String deviceInfo = "Input Device " + device.getId() + ": " + device.getName() + "\n"
+ + "\tDescriptor: " + device.getDescriptor() + "\n";
+ writer.printf(deviceInfo);
+ }
+
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ Display[] displays = displayManager.getDisplays();
+ for (Display display : displays) {
+ String displayInfo = "Display Devices " + display.getDisplayId() + ": \n"
+ + "\tUniqueId: " + DisplayManagerHelper.getUniqueId(display) + " \n";
+ writer.printf(displayInfo);
+ }
+ }
+
+ private void addInputDescriptorAssociationToDisplayUniqueId(String[] args,
+ IndentingPrintWriter writer) {
+ InputManager inputManager = mContext.getSystemService(InputManager.class);
+ if (args.length == 3) {
+ InputManagerHelper.addUniqueIdAssociationByDescriptor(inputManager, args[1], args[2]);
+ writer.println("Associated input " + args[1] + " with display " + args[2] + "\n");
+ } else {
+ writer.printf("Incorrect Usage.\nUsage: %s <inputDeviceDescriptor> <displayUniqueId>\n",
+ COMMAND_ADD_INPUT_DESCRIPTOR_ASSOCIATION_TO_DISPLAY_UNIQUE_ID);
+ }
+ }
+
+ private void removeInputDescriptorAssociation(String[] args, IndentingPrintWriter writer) {
+ InputManager inputManager = mContext.getSystemService(InputManager.class);
+ if (args.length == 2) {
+ InputManagerHelper.removeUniqueIdAssociationByDescriptor(inputManager, args[1]);
+ writer.println("Input " + args[1] + " association removed.\n");
+ } else {
+ writer.printf("Incorrect Usage.\nUsage: %s <inputDeviceDescriptor>\n",
+ COMMAND_REMOVE_INPUT_DESCRIPTOR_ASSOCIATION);
+ }
+ }
+
private void setStartBackgroundUsersOnGarageMode(String[] args, IndentingPrintWriter writer) {
if (args.length < 2) {
writer.println("Insufficient number of args");
@@ -2087,7 +2151,7 @@
// Processing the last remaining argument. Argument is expected one of the tem functions
// ('f1', 'f2', ..., 'f10') or just a plain integer representing the custom input event.
- String eventValue = args[argIdx].toLowerCase();
+ String eventValue = args[argIdx].toLowerCase(Locale.US);
Integer inputCode;
if (eventValue.startsWith("f")) {
inputCode = CUSTOM_INPUT_FUNCTION_ARGS.get(eventValue);
@@ -2199,7 +2263,6 @@
Thread.currentThread().interrupt();
writer.println("Interrupted waiting for HAL");
}
- return;
}
private void switchUser(String[] args, IndentingPrintWriter writer) {
@@ -2876,6 +2939,7 @@
boolean skipGarageMode = false;
boolean freeMemory = false;
int resumeDelay = CarPowerManagementService.NO_WAKEUP_BY_TIMER;
+ int cancelDelay = CarPowerManagementService.NO_WAKEUP_BY_TIMER;
int index = 1;
while (index < args.length) {
switch (args[index]) {
@@ -2918,6 +2982,15 @@
}
resumeDelay = Integer.parseInt(args[index]);
break;
+ case PARAM_CANCEL_AFTER:
+ index++;
+ if (index >= args.length) {
+ writer.printf("Invalid command syntax.\nUsage: %s\n",
+ getSuspendCommandUsage(command));
+ return;
+ }
+ cancelDelay = Integer.parseInt(args[index]);
+ break;
case PARAM_FREE_MEMORY:
freeMemory = true;
break;
@@ -2928,8 +3001,14 @@
}
index++;
}
- if (resumeDelay >= 0 && !simulate) {
- writer.printf("Wake up by timer is available only with simulated suspend.\n");
+ if ((cancelDelay >= 0 || resumeDelay >= 0) && !simulate) {
+ writer.printf("Wake up and cancel by timer is available only with simulated suspend."
+ + "\n");
+ return;
+ }
+
+ if (cancelDelay >= 0 && resumeDelay >= 0 && simulate) {
+ writer.printf("Cancel and resume cannot be set at the same time.\n");
return;
}
@@ -2942,10 +3021,18 @@
if (simulate) {
try {
writer.printf("Suspend: simulating suspend-to-%s.\n", suspendType);
- mCarPowerManagementService.simulateSuspendAndMaybeReboot(
- isHibernation ? PowerHalService.PowerState.SHUTDOWN_TYPE_HIBERNATION
- : PowerHalService.PowerState.SHUTDOWN_TYPE_DEEP_SLEEP,
- /* shouldReboot= */ false, skipGarageMode, resumeDelay, freeMemory);
+ if (Flags.carPowerCancelShellCommand()) {
+ mCarPowerManagementService.simulateSuspendAndMaybeReboot(
+ isHibernation ? PowerHalService.PowerState.SHUTDOWN_TYPE_HIBERNATION
+ : PowerHalService.PowerState.SHUTDOWN_TYPE_DEEP_SLEEP,
+ /* shouldReboot= */ false, skipGarageMode, resumeDelay, cancelDelay,
+ freeMemory);
+ } else {
+ mCarPowerManagementService.simulateSuspendAndMaybeReboot(
+ isHibernation ? PowerHalService.PowerState.SHUTDOWN_TYPE_HIBERNATION
+ : PowerHalService.PowerState.SHUTDOWN_TYPE_DEEP_SLEEP,
+ /* shouldReboot= */ false, skipGarageMode, resumeDelay, freeMemory);
+ }
} catch (Exception e) {
writer.printf("Simulating suspend-to-%s failed: %s\n", suspendType, e.getMessage());
}
@@ -3047,11 +3134,11 @@
private void emulateDrive() {
Slogf.i(TAG, "Emulating driving mode (speed=80mph, gear=8)");
mHal.injectVhalEvent(VehiclePropertyIds.PERF_VEHICLE_SPEED,
- /* zone= */ 0, /* value= */ "80", /* delayTime= */ 2000);
+ /* areaId= */ 0, /* value= */ "80", /* delayTimeSeconds= */ 2000);
mHal.injectVhalEvent(VehiclePropertyIds.GEAR_SELECTION,
- /* zone= */ 0, Integer.toString(VehicleGear.GEAR_8), /* delayTime= */ 0);
+ /* areaId= */ 0, Integer.toString(VehicleGear.GEAR_8), /* delayTimeSeconds= */ 0);
mHal.injectVhalEvent(VehiclePropertyIds.PARKING_BRAKE_ON,
- /* zone= */ 0, /* value= */ "false", /* delayTime= */ 0);
+ /* areaId= */ 0, /* value= */ "false", /* delayTimeSeconds= */ 0);
}
/**
@@ -3061,11 +3148,12 @@
private void emulateReverse() {
Slogf.i(TAG, "Emulating reverse driving mode (speed=5mph)");
mHal.injectVhalEvent(VehiclePropertyIds.PERF_VEHICLE_SPEED,
- /* zone= */ 0, /* value= */ "5", /* delayTime= */ 2000);
+ /* areaId= */ 0, /* value= */ "5", /* delayTimeSeconds= */ 2000);
mHal.injectVhalEvent(VehiclePropertyIds.GEAR_SELECTION,
- /* zone= */ 0, Integer.toString(VehicleGear.GEAR_REVERSE), /* delayTime= */ 0);
+ /* areaId= */ 0, Integer.toString(VehicleGear.GEAR_REVERSE),
+ /* delayTimeSeconds= */ 0);
mHal.injectVhalEvent(VehiclePropertyIds.PARKING_BRAKE_ON,
- /* zone= */ 0, /* value= */ "false", /* delayTime= */ 0);
+ /* areaId= */ 0, /* value= */ "false", /* delayTimeSeconds= */ 0);
}
/**
@@ -3075,9 +3163,10 @@
private void emulatePark() {
Slogf.i(TAG, "Emulating parking mode");
mHal.injectVhalEvent(VehiclePropertyIds.PERF_VEHICLE_SPEED,
- /* zone= */ 0, /* value= */ "0", /* delayTime= */ 0);
+ /* areaId= */ 0, /* value= */ "0", /* delayTimeSeconds= */ 0);
mHal.injectVhalEvent(VehiclePropertyIds.GEAR_SELECTION,
- /* zone= */ 0, Integer.toString(VehicleGear.GEAR_PARK), /* delayTime= */ 0);
+ /* areaId= */ 0, Integer.toString(VehicleGear.GEAR_PARK),
+ /* delayTimeSeconds= */ 0);
}
/**
@@ -3087,11 +3176,12 @@
private void emulateNeutral() {
Slogf.i(TAG, "Emulating neutral driving mode");
mHal.injectVhalEvent(VehiclePropertyIds.PERF_VEHICLE_SPEED,
- /* zone= */ 0, /* value= */ "0", /* delayTime= */ 0);
+ /* areaId= */ 0, /* value= */ "0", /* delayTimeSeconds= */ 0);
mHal.injectVhalEvent(VehiclePropertyIds.GEAR_SELECTION,
- /* zone= */ 0, Integer.toString(VehicleGear.GEAR_NEUTRAL), /* delayTime= */ 0);
+ /* areaId= */ 0, Integer.toString(VehicleGear.GEAR_NEUTRAL),
+ /* delayTimeSeconds= */ 0);
mHal.injectVhalEvent(VehiclePropertyIds.PARKING_BRAKE_ON,
- /* zone= */ 0, /* value= */ "true", /* delayTime= */ 0);
+ /* areaId= */ 0, /* value= */ "true", /* delayTimeSeconds= */ 0);
}
private int definePowerPolicy(String[] args, IndentingPrintWriter writer) {
@@ -4290,27 +4380,14 @@
writer.println(userId);
}
- private void getTetheringCapability(IndentingPrintWriter writer) {
- writer.printf("Persist tethering capabilities enabled: %b\n",
- mCarWifiService.canControlPersistTetheringSettings());
- }
-
- // Check if the given property is global
- private static boolean isPropertyAreaTypeGlobal(@Nullable String property) {
- if (property == null) {
- return false;
- }
- return isPropertyAreaTypeGlobal(Integer.decode(property));
- }
-
private static boolean isPropertyAreaTypeGlobal(int propertyId) {
return (propertyId & VehicleArea.MASK) == VehicleArea.GLOBAL;
}
private static String getSuspendCommandUsage(String command) {
return command + " [" + PARAM_AUTO + "|" + PARAM_SIMULATE + "|" + PARAM_REAL + "] ["
- + PARAM_SKIP_GARAGEMODE + "] [" + PARAM_WAKEUP_AFTER + " RESUME_DELAY]" + "["
- + PARAM_FREE_MEMORY + "]";
+ + PARAM_SKIP_GARAGEMODE + "] [" + PARAM_WAKEUP_AFTER + " RESUME_DELAY | "
+ + PARAM_CANCEL_AFTER + " CANCEL_DELAY]" + "[" + PARAM_FREE_MEMORY + "]";
}
private static final class AudioZoneMirrorStatusCallbackImpl extends
diff --git a/service/src/com/android/car/CarStatsLogHelper.java b/service/src/com/android/car/CarStatsLogHelper.java
index c9e6334..116aad1 100644
--- a/service/src/com/android/car/CarStatsLogHelper.java
+++ b/service/src/com/android/car/CarStatsLogHelper.java
@@ -16,6 +16,10 @@
package com.android.car;
+import static com.android.car.CarStatsLog.CAR_WAKEUP_FROM_SUSPEND_REPORTED__RESUME_TYPE__DISK;
+import static com.android.car.CarStatsLog.CAR_WAKEUP_FROM_SUSPEND_REPORTED__RESUME_TYPE__RAM;
+import static com.android.car.CarStatsLog.CAR_WAKEUP_FROM_SUSPEND_REPORTED__RESUME_TYPE__UNSPECIFIED;
+
/**
* CarStatsLogHelper provides API to send Car events to statd.
*
@@ -41,4 +45,18 @@
public static void logGarageModeStop() {
CarStatsLog.write(CarStatsLog.GARAGE_MODE_INFO, false);
}
+
+ /** Logs resume from suspend event */
+ public static void logResumeFromSuspend(String type, long kernelStartTimeMillis,
+ long carServiceStartTimeMillis, long userPerceivedStartTimeMillis,
+ long deviceStartTimeMillis) {
+ int statsType = switch(type) {
+ case "Suspend-to-Disk" -> CAR_WAKEUP_FROM_SUSPEND_REPORTED__RESUME_TYPE__DISK;
+ case "Suspend-to-RAM" -> CAR_WAKEUP_FROM_SUSPEND_REPORTED__RESUME_TYPE__RAM;
+ default -> CAR_WAKEUP_FROM_SUSPEND_REPORTED__RESUME_TYPE__UNSPECIFIED;
+ };
+ CarStatsLog.write(CarStatsLog.CAR_WAKEUP_FROM_SUSPEND_REPORTED, statsType,
+ kernelStartTimeMillis, carServiceStartTimeMillis, userPerceivedStartTimeMillis,
+ deviceStartTimeMillis);
+ }
}
diff --git a/service/src/com/android/car/CarStorageMonitoringService.java b/service/src/com/android/car/CarStorageMonitoringService.java
index 2c24907..aa32cce 100644
--- a/service/src/com/android/car/CarStorageMonitoringService.java
+++ b/service/src/com/android/car/CarStorageMonitoringService.java
@@ -74,6 +74,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.StringJoiner;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -89,8 +90,10 @@
public static final long SHUTDOWN_COST_INFO_MISSING =
CarStorageMonitoringManager.SHUTDOWN_COST_INFO_MISSING;
- private static final boolean DBG = false;
private static final String TAG = CarLog.tagFor(CarStorageMonitoringService.class);
+
+ private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
+
private static final int MIN_WEAR_ESTIMATE_OF_CONCERN = 80;
static final String UPTIME_TRACKER_FILENAME = "service_uptime";
@@ -464,7 +467,7 @@
private List<LifetimeWriteInfo> loadLifetimeWrites() {
if (!mLifetimeWriteFile.exists() || !mLifetimeWriteFile.isFile()) {
Slogf.d(TAG, "lifetime write file missing or inaccessible " + mLifetimeWriteFile);
- return Collections.emptyList();
+ return new ArrayList<>();
}
try {
JSONObject jsonObject = new JSONObject(
@@ -479,7 +482,7 @@
return result;
} catch (JSONException | IOException e) {
Slogf.e(TAG, "lifetime write file does not contain valid JSON", e);
- return Collections.emptyList();
+ return new ArrayList<>();
}
}
@@ -521,14 +524,9 @@
doInitServiceIfNeededLocked();
writer.println("last wear information retrieved: "
+ mWearInformation.map(WearInformation::toString).orElse("missing"));
- writer.println("wear change history: "
- + mWearEstimateChanges.stream()
- .map(WearEstimateChange::toString)
- .collect(Collectors.joining("\n")));
- writer.println("boot I/O stats: "
- + mBootIoStats.stream()
- .map(IoStatsEntry::toString)
- .collect(Collectors.joining("\n")));
+ writer.println(listToString(mWearEstimateChanges, "\n",
+ "wear change history: "));
+ writer.println(listToString(mBootIoStats, "\n", "boot I/O stats: "));
writer.println("aggregate I/O stats: "
+ SparseArrayStream.valueStream(mIoStatsTracker.getTotal())
.map(IoStatsEntry::toString)
@@ -536,9 +534,7 @@
writer.println("I/O stats snapshots: ");
writer.println(
mIoStatsSamples.stream().map(
- sample -> sample.getStats().stream()
- .map(IoStatsEntry::toString)
- .collect(Collectors.joining("\n")))
+ sample -> listToString(sample.getStats(), "\n", ""))
.collect(Collectors.joining("\n------\n")));
if (mShutdownCostInfo < 0) {
writer.print("last shutdown cost: missing. ");
@@ -551,6 +547,14 @@
}
}
+ private <T> String listToString(List<T> list, CharSequence delimiter, String prefix) {
+ StringJoiner stringJoiner = new StringJoiner(delimiter, prefix, /* suffix= */ "");
+ for (int index = 0; index < list.size(); index++) {
+ stringJoiner.add(list.get(index).toString());
+ }
+ return stringJoiner.toString();
+ }
+
@Override
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
public void dumpProto(ProtoOutputStream proto) {}
@@ -709,12 +713,12 @@
Configuration(Resources resources) throws Resources.NotFoundException {
ioStatsNumSamplesToStore = resources.getInteger(R.integer.ioStatsNumSamplesToStore);
acceptableBytesWrittenPerSample =
- 1024 * resources.getInteger(R.integer.acceptableWrittenKBytesPerSample);
+ 1024L * resources.getInteger(R.integer.acceptableWrittenKBytesPerSample);
acceptableFsyncCallsPerSample =
resources.getInteger(R.integer.acceptableFsyncCallsPerSample);
maxExcessiveIoSamplesInWindow =
resources.getInteger(R.integer.maxExcessiveIoSamplesInWindow);
- uptimeIntervalBetweenUptimeDataWriteMs = 60 * 60 * 1000
+ uptimeIntervalBetweenUptimeDataWriteMs = 60L * 60 * 1000
* resources.getInteger(R.integer.uptimeHoursIntervalBetweenUptimeDataWrite);
acceptableHoursPerOnePercentFlashWear =
resources.getInteger(R.integer.acceptableHoursPerOnePercentFlashWear);
diff --git a/service/src/com/android/car/CarUxRestrictionsConfigurationXmlParser.java b/service/src/com/android/car/CarUxRestrictionsConfigurationXmlParser.java
index 25905da..3167549 100644
--- a/service/src/com/android/car/CarUxRestrictionsConfigurationXmlParser.java
+++ b/service/src/com/android/car/CarUxRestrictionsConfigurationXmlParser.java
@@ -27,7 +27,6 @@
import android.car.drivingstate.CarDrivingStateEvent;
import android.car.drivingstate.CarUxRestrictions;
import android.car.drivingstate.CarUxRestrictionsConfiguration;
-import android.car.drivingstate.CarUxRestrictionsConfiguration.Builder;
import android.car.drivingstate.CarUxRestrictionsConfiguration.DrivingStateRestrictions;
import android.content.Context;
import android.content.res.XmlResourceParser;
@@ -360,7 +359,8 @@
}
// 3. Parse the restrictions for this driving state
- Builder.SpeedRange speedRange = parseSpeedRange(minSpeed, maxSpeed);
+ CarUxRestrictionsConfiguration.Builder.SpeedRange speedRange = parseSpeedRange(
+ minSpeed, maxSpeed);
if (!parseAllRestrictions(parser, drivingState, speedRange)) {
Slogf.e(TAG, "Could not parse restrictions for driving state:" + drivingState);
return false;
@@ -392,7 +392,7 @@
* Parses all <restrictions> tags nested with <drivingState> tag.
*/
private boolean parseAllRestrictions(XmlResourceParser parser,
- int drivingState, Builder.SpeedRange speedRange)
+ int drivingState, CarUxRestrictionsConfiguration.Builder.SpeedRange speedRange)
throws IOException, XmlPullParserException {
if (parser == null) {
Slogf.e(TAG, "Invalid arguments");
@@ -542,7 +542,8 @@
}
@Nullable
- private Builder.SpeedRange parseSpeedRange(float minSpeed, float maxSpeed) {
+ private CarUxRestrictionsConfiguration.Builder.SpeedRange parseSpeedRange(float minSpeed,
+ float maxSpeed) {
if (Float.compare(minSpeed, 0) < 0 || Float.compare(maxSpeed, 0) < 0) {
return null;
}
diff --git a/service/src/com/android/car/CarUxRestrictionsManagerService.java b/service/src/com/android/car/CarUxRestrictionsManagerService.java
index a6d4845..f7fac6f 100644
--- a/service/src/com/android/car/CarUxRestrictionsManagerService.java
+++ b/service/src/com/android/car/CarUxRestrictionsManagerService.java
@@ -68,6 +68,7 @@
import android.util.JsonReader;
import android.util.JsonToken;
import android.util.JsonWriter;
+import android.util.Log;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -122,7 +123,7 @@
public class CarUxRestrictionsManagerService extends ICarUxRestrictionsManager.Stub implements
CarServiceBase {
private static final String TAG = CarLog.tagFor(CarUxRestrictionsManagerService.class);
- private static final boolean DBG = false;
+ private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
private static final int MAX_TRANSITION_LOG_SIZE = 20;
private static final int PROPERTY_UPDATE_RATE = 5; // Update rate in Hz
diff --git a/service/src/com/android/car/CommonThread.java b/service/src/com/android/car/CommonThread.java
index f659482..babfe63 100644
--- a/service/src/com/android/car/CommonThread.java
+++ b/service/src/com/android/car/CommonThread.java
@@ -37,7 +37,7 @@
* </code>
* </pre>
*
- * @memberDoc This method must be called on the common car thread. This is typically the thread
+ * <p>This method must be called on the common car thread. This is typically the thread
* which the {@link CarServiceUtils#getCommonHandlerThread()} is bound to.
*/
@Retention(SOURCE)
diff --git a/service/src/com/android/car/HidlVehicleStub.java b/service/src/com/android/car/HidlVehicleStub.java
index 3213f5e..ec9971b 100644
--- a/service/src/com/android/car/HidlVehicleStub.java
+++ b/service/src/com/android/car/HidlVehicleStub.java
@@ -125,7 +125,7 @@
@Override
public void linkToDeath(IVehicleDeathRecipient recipient) throws IllegalStateException {
try {
- mHidlVehicle.linkToDeath(recipient, /*flag=*/ 0);
+ mHidlVehicle.linkToDeath(recipient, /* cookie= */ 0);
} catch (RemoteException e) {
throw new IllegalStateException("Failed to linkToDeath Vehicle HAL");
}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index b08b0f1..463f34c 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -40,15 +40,14 @@
import android.car.builtin.os.UserManagerHelper;
import android.car.builtin.util.EventLogHelper;
import android.car.builtin.util.Slogf;
-import android.car.feature.Flags;
+import android.car.builtin.util.TimingsTraceLog;
+import android.car.feature.FeatureFlags;
+import android.car.feature.FeatureFlagsImpl;
import android.car.user.CarUserManager;
import android.content.Context;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManager;
import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.hardware.automotive.vehicle.VehicleProperty;
-import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -74,11 +73,13 @@
import com.android.car.cluster.InstrumentClusterService;
import com.android.car.evs.CarEvsService;
import com.android.car.garagemode.GarageModeService;
-import com.android.car.hal.HalPropValue;
+import com.android.car.hal.PowerHalService;
import com.android.car.hal.VehicleHal;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.ICarServiceHelper;
import com.android.car.internal.ICarSystemServerClient;
+import com.android.car.internal.StaticBinderInterface;
+import com.android.car.internal.SystemStaticBinder;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.car.occupantconnection.CarOccupantConnectionService;
import com.android.car.occupantconnection.CarRemoteDeviceService;
@@ -94,7 +95,6 @@
import com.android.car.user.CarUserNoticeService;
import com.android.car.user.CarUserService;
import com.android.car.user.ExperimentalCarUserService;
-import com.android.car.util.LimitedTimingsTraceLog;
import com.android.car.vms.VmsBrokerService;
import com.android.car.watchdog.CarWatchdogService;
import com.android.car.wifi.CarWifiService;
@@ -121,8 +121,6 @@
@VisibleForTesting
static final String TAG = CarLog.tagFor(ICarImpl.class);
- private static final int INITIAL_VHAL_GET_RETRY = 2;
-
private final Context mContext;
private final Context mCarServiceBuiltinPackageContext;
private final VehicleHal mHal;
@@ -133,6 +131,8 @@
private final SystemInterface mSystemInterface;
+ private final FeatureFlags mFeatureFlags;
+
private final CarOemProxyService mCarOemService;
private final SystemActivityMonitoringService mSystemActivityMonitoringService;
private final CarPowerManagementService mCarPowerManagementService;
@@ -177,10 +177,8 @@
private final CarOccupantConnectionService mCarOccupantConnectionService;
private final CarRemoteDeviceService mCarRemoteDeviceService;
private final CarWifiService mCarWifiService;
-
- // Only modified at setCarRemoteAccessService for testing.
@Nullable
- private CarRemoteAccessService mCarRemoteAccessService;
+ private final CarRemoteAccessService mCarRemoteAccessService;
// Storing all the car services in the order of their init.
private final CarSystemService[] mAllServicesInInitOrder;
@@ -207,12 +205,19 @@
(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args) ->
newCarShellCommand().exec(ICarImpl.this, in, out, err, args);
+ // A static Binder class implementation. Faked during unit tests.
+ private final StaticBinderInterface mStaticBinder;
+
private ICarImpl(Builder builder) {
- LimitedTimingsTraceLog t = new LimitedTimingsTraceLog(
+ TimingsTraceLog t = new TimingsTraceLog(
CAR_SERVICE_INIT_TIMING_TAG, TraceHelper.TRACE_TAG_CAR_SERVICE,
CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS);
t.traceBegin("ICarImpl.constructor");
+ mStaticBinder = Objects.requireNonNullElseGet(builder.mStaticBinder,
+ () -> new SystemStaticBinder());
+ mFeatureFlags = Objects.requireNonNullElseGet(builder.mFeatureFlags,
+ () -> new FeatureFlagsImpl());
mDoPriorityInitInConstruction = builder.mDoPriorityInitInConstruction;
mContext = builder.mContext;
@@ -235,30 +240,16 @@
mHal = constructWithTrace(t, VehicleHal.class,
() -> new VehicleHal(mContext, builder.mVehicle), allServices);
- HalPropValue disabledOptionalFeatureValue = mHal.getIfSupportedOrFailForEarlyStage(
- VehicleProperty.DISABLED_OPTIONAL_FEATURES, INITIAL_VHAL_GET_RETRY);
-
- String[] disabledFeaturesFromVhal = null;
- if (disabledOptionalFeatureValue != null) {
- String disabledFeatures = disabledOptionalFeatureValue.getStringValue();
- if (disabledFeatures != null && !disabledFeatures.isEmpty()) {
- disabledFeaturesFromVhal = disabledFeatures.split(",");
- }
- }
- if (disabledFeaturesFromVhal == null) {
- disabledFeaturesFromVhal = new String[0];
- }
- Resources res = mContext.getResources();
- String[] defaultEnabledFeatures = res.getStringArray(
- R.array.config_allowed_optional_car_features);
- final String[] disabledFromVhal = disabledFeaturesFromVhal;
mFeatureController = constructWithTrace(t, CarFeatureController.class,
- () -> new CarFeatureController(mContext, defaultEnabledFeatures,
- disabledFromVhal, mSystemInterface.getSystemCarDir()), allServices);
+ () -> new CarFeatureController(
+ mContext, mSystemInterface.getSystemCarDir(), mHal), allServices);
mVehicleInterfaceName = builder.mVehicleInterfaceName;
mCarPropertyService = constructWithTrace(
t, CarPropertyService.class,
- () -> new CarPropertyService(mContext, mHal.getPropertyHal()), allServices);
+ () -> new CarPropertyService.Builder()
+ .setContext(mContext)
+ .setPropertyHalService(mHal.getPropertyHal())
+ .build(), allServices);
mCarDrivingStateService = constructWithTrace(
t, CarDrivingStateService.class,
() -> new CarDrivingStateService(mContext, mCarPropertyService), allServices);
@@ -273,18 +264,15 @@
() -> new CarPackageManagerService(mContext, mCarUXRestrictionsService,
mCarActivityService, mCarOccupantZoneService), allServices);
UserManager userManager = mContext.getSystemService(UserManager.class);
- if (builder.mCarUserService != null) {
- mCarUserService = builder.mCarUserService;
- CarLocalServices.addService(CarUserService.class, mCarUserService);
- allServices.add(mCarUserService);
- } else {
- int maxRunningUsers = UserManagerHelper.getMaxRunningUsers(mContext);
- mCarUserService = constructWithTrace(t, CarUserService.class,
- () -> new CarUserService(mContext, mHal.getUserHal(), userManager,
- maxRunningUsers, mCarUXRestrictionsService, mCarPackageManagerService,
- mCarOccupantZoneService),
- allServices);
- }
+ mCarUserService = getFromBuilderOrConstruct(t, CarUserService.class,
+ builder.mCarUserService,
+ () -> {
+ int maxRunningUsers = UserManagerHelper.getMaxRunningUsers(mContext);
+ return new CarUserService(mContext, mHal.getUserHal(), userManager,
+ maxRunningUsers, mCarUXRestrictionsService, mCarPackageManagerService,
+ mCarOccupantZoneService);
+ },
+ allServices);
if (mDoPriorityInitInConstruction) {
Slogf.i(TAG, "VHAL Priority Init Enabled");
Slogf.i(TAG, "Car User Service Priority Init Enabled");
@@ -309,10 +297,18 @@
mSystemActivityMonitoringService = constructWithTrace(
t, SystemActivityMonitoringService.class,
() -> new SystemActivityMonitoringService(mContext), allServices);
+
mCarPowerManagementService = constructWithTrace(
t, CarPowerManagementService.class,
- () -> new CarPowerManagementService(mContext, mHal.getPowerHal(), mSystemInterface,
- mCarUserService, builder.mPowerPolicyDaemon), allServices);
+ () -> new CarPowerManagementService.Builder()
+ .setContext(mContext)
+ .setPowerHalService(mHal.getPowerHal())
+ .setSystemInterface(mSystemInterface)
+ .setCarUserService(mCarUserService)
+ .setPowerPolicyDaemon(builder.mPowerPolicyDaemon)
+ .setFeatureFlags(mFeatureFlags)
+ .build(),
+ allServices);
if (mFeatureController.isFeatureEnabled(CarFeatures.FEATURE_CAR_USER_NOTICE_SERVICE)) {
mCarUserNoticeService = constructWithTrace(
t, CarUserNoticeService.class, () -> new CarUserNoticeService(mContext),
@@ -339,14 +335,11 @@
mCarProjectionService = constructWithTrace(t, CarProjectionService.class,
() -> new CarProjectionService(mContext, null /* handler */, mCarInputService,
mCarBluetoothService), allServices);
- if (builder.mGarageModeService == null) {
- mGarageModeService = constructWithTrace(t, GarageModeService.class,
- () -> new GarageModeService(mContext), allServices);
- } else {
- mGarageModeService = builder.mGarageModeService;
- allServices.add(mGarageModeService);
- }
- mAppFocusService = constructWithTrace(t, AppFocusService.class,
+ mGarageModeService = getFromBuilderOrConstruct(t, GarageModeService.class,
+ builder.mGarageModeService, () -> new GarageModeService(mContext),
+ allServices);
+ mAppFocusService = getFromBuilderOrConstruct(t, AppFocusService.class,
+ builder.mAppFocusService,
() -> new AppFocusService(mContext, mSystemActivityMonitoringService),
allServices);
mCarAudioService = constructWithTrace(t, CarAudioService.class,
@@ -396,23 +389,13 @@
allServices);
mCarBugreportManagerService = constructWithTrace(t, CarBugreportManagerService.class,
() -> new CarBugreportManagerService(mContext), allServices);
- if (builder.mCarWatchdogService == null) {
- mCarWatchdogService = constructWithTrace(t, CarWatchdogService.class,
- () -> new CarWatchdogService(mContext, mCarServiceBuiltinPackageContext),
- allServices);
- } else {
- mCarWatchdogService = builder.mCarWatchdogService;
- allServices.add(mCarWatchdogService);
- CarLocalServices.addService(CarWatchdogService.class, mCarWatchdogService);
- }
- if (builder.mCarPerformanceService == null) {
- mCarPerformanceService = constructWithTrace(t, CarPerformanceService.class,
- () -> new CarPerformanceService(mContext), allServices);
- } else {
- mCarPerformanceService = builder.mCarPerformanceService;
- allServices.add(mCarPerformanceService);
- CarLocalServices.addService(CarPerformanceService.class, mCarPerformanceService);
- }
+ mCarWatchdogService = getFromBuilderOrConstruct(t, CarWatchdogService.class,
+ builder.mCarWatchdogService,
+ () -> new CarWatchdogService(mContext, mCarServiceBuiltinPackageContext),
+ allServices);
+ mCarPerformanceService = getFromBuilderOrConstruct(t, CarPerformanceService.class,
+ builder.mCarPerformanceService, () -> new CarPerformanceService(mContext),
+ allServices);
mCarDevicePolicyService = constructWithTrace(
t, CarDevicePolicyService.class, () -> new CarDevicePolicyService(mContext,
mCarServiceBuiltinPackageContext, mCarUserService), allServices);
@@ -440,26 +423,23 @@
}
if (mFeatureController.isFeatureEnabled(Car.CAR_TELEMETRY_SERVICE)) {
- if (builder.mCarTelemetryService == null) {
- mCarTelemetryService = constructWithTrace(t, CarTelemetryService.class,
- () -> new CarTelemetryService(mContext, mCarPowerManagementService,
- mCarPropertyService), allServices);
- } else {
- mCarTelemetryService = builder.mCarTelemetryService;
- allServices.add(mCarTelemetryService);
- }
+ mCarTelemetryService = getFromBuilderOrConstruct(t, CarTelemetryService.class,
+ builder.mCarTelemetryService,
+ () -> new CarTelemetryService(mContext, mCarPowerManagementService,
+ mCarPropertyService),
+ allServices);
} else {
mCarTelemetryService = null;
}
if (mFeatureController.isFeatureEnabled((Car.CAR_REMOTE_ACCESS_SERVICE))) {
- if (builder.mCarRemoteAccessService == null) {
+ if (builder.mCarRemoteAccessServiceConstructor == null) {
mCarRemoteAccessService = constructWithTrace(t, CarRemoteAccessService.class,
() -> new CarRemoteAccessService(
mContext, mSystemInterface, mHal.getPowerHal()), allServices);
} else {
- mCarRemoteAccessService = builder.mCarRemoteAccessService;
- mCarRemoteAccessService.setPowerHal(mHal.getPowerHal());
+ mCarRemoteAccessService = builder.mCarRemoteAccessServiceConstructor.construct(
+ mContext, mSystemInterface, mHal.getPowerHal());
allServices.add(mCarRemoteAccessService);
}
} else {
@@ -467,8 +447,7 @@
}
mCarWifiService = constructWithTrace(t, CarWifiService.class,
- () -> new CarWifiService(mContext, mCarPowerManagementService,
- mCarUserService), allServices);
+ () -> new CarWifiService(mContext), allServices);
// Always put mCarExperimentalFeatureServiceController in last.
if (!BuildHelper.isUserBuild()) {
@@ -506,7 +485,7 @@
@MainThread
void init() {
- LimitedTimingsTraceLog t = new LimitedTimingsTraceLog(CAR_SERVICE_INIT_TIMING_TAG,
+ TimingsTraceLog t = new TimingsTraceLog(CAR_SERVICE_INIT_TIMING_TAG,
TraceHelper.TRACE_TAG_CAR_SERVICE, CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS);
t.traceBegin("ICarImpl.init");
@@ -551,7 +530,7 @@
ICarResultReceiver resultReceiver) {
Bundle bundle;
try {
- EventLogHelper.writeCarServiceSetCarServiceHelper(Binder.getCallingPid());
+ EventLogHelper.writeCarServiceSetCarServiceHelper(mStaticBinder.getCallingPid());
assertCallingFromSystemProcess();
mCarServiceHelperWrapper.setCarServiceHelper(carServiceHelper);
@@ -615,8 +594,9 @@
return mCarExperimentalFeatureServiceController.getCarManagerClassForFeature(featureName);
}
- static void assertCallingFromSystemProcess() {
- int uid = Binder.getCallingUid();
+
+ private void assertCallingFromSystemProcess() {
+ int uid = mStaticBinder.getCallingUid();
if (uid != Process.SYSTEM_UID) {
throw new SecurityException("Only allowed from system");
}
@@ -718,12 +698,12 @@
default:
// CarDisplayCompatManager does not need a new service but the Car class
// doesn't allow a new Manager class without a service.
- if (Flags.displayCompatibility()) {
+ if (mFeatureFlags.displayCompatibility()) {
if (serviceName.equals(CAR_DISPLAY_COMPAT_SERVICE)) {
return mCarActivityService;
}
}
- if (Flags.persistApSettings()) {
+ if (mFeatureFlags.persistApSettings()) {
if (serviceName.equals(Car.CAR_WIFI_SERVICE)) {
return mCarWifiService;
}
@@ -752,7 +732,7 @@
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
writer.println("Permission Denial: can't dump CarService from from pid="
- + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + mStaticBinder.getCallingPid() + ", uid=" + mStaticBinder.getCallingUid()
+ " without permission " + android.Manifest.permission.DUMP);
return;
}
@@ -798,7 +778,7 @@
String[] services = new String[length];
System.arraycopy(args, 1, services, 0, length);
if (dumpToProto) {
- if (!Flags.carDumpToProto()) {
+ if (!mFeatureFlags.carDumpToProto()) {
writer.println("Cannot dump " + services[0]
+ " to proto since FLAG_CAR_DUMP_TO_PROTO is disabled");
return;
@@ -911,8 +891,8 @@
writer.println("CarBuiltin Platform minor: " + CarBuiltin.PLATFORM_VERSION_MINOR_INT);
writer.println("Legacy versions (might differ from above as they can't be emulated)");
writer.increaseIndent();
- writer.println("Car API major: " + Car.API_VERSION_MAJOR_INT);
- writer.println("Car API minor: " + Car.API_VERSION_MINOR_INT);
+ writer.println("Car API major: " + Car.getCarVersion().getMajorVersion());
+ writer.println("Car API minor: " + Car.getCarVersion().getMinorVersion());
writer.println("Car Platform minor: " + Car.PLATFORM_VERSION_MINOR_INT);
writer.println("VHAL and Car User Service Priority Init: " + mDoPriorityInitInConstruction);
writer.decreaseIndent();
@@ -1054,9 +1034,12 @@
@Nullable
private CarSystemService getCarServiceBySubstring(String className) {
- return Arrays.asList(mAllServicesInInitOrder).stream()
- .filter(s -> s.getClass().getSimpleName().equals(className))
- .findFirst().orElse(null);
+ for (int i = 0; i < mAllServicesInInitOrder.length; i++) {
+ if (Objects.equals(mAllServicesInInitOrder[i].getClass().getSimpleName(), className)) {
+ return mAllServicesInInitOrder[i];
+ }
+ }
+ return null;
}
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
@@ -1073,7 +1056,18 @@
newCarShellCommand().exec(args, writer);
}
- private <T extends CarSystemService> T constructWithTrace(LimitedTimingsTraceLog t,
+ private static <T extends CarSystemService> T getFromBuilderOrConstruct(TimingsTraceLog t,
+ Class<T> cls, T serviceFromBuilder, Callable<T> callable,
+ List<CarSystemService> allServices) {
+ if (serviceFromBuilder != null) {
+ allServices.add(serviceFromBuilder);
+ CarLocalServices.addService(cls, serviceFromBuilder);
+ return serviceFromBuilder;
+ }
+ return constructWithTrace(t, cls, callable, allServices);
+ }
+
+ private static <T extends CarSystemService> T constructWithTrace(TimingsTraceLog t,
Class<T> cls, Callable<T> callable, List<CarSystemService> allServices) {
t.traceBegin(cls.getSimpleName());
T constructed;
@@ -1105,21 +1099,6 @@
}
@Override
- public void initBootUser() throws RemoteException {
- // TODO(b/277271542). Remove this code path.
- }
-
- // TODO(235524989): Remove this method as on user removed will now go through
- // onUserLifecycleEvent due to changes in CarServiceProxy and CarUserService.
- @Override
- public void onUserRemoved(UserHandle user) throws RemoteException {
- assertCallingFromSystemProcess();
- EventLogHelper.writeCarServiceOnUserRemoved(user.getIdentifier());
- if (DBG) Slogf.d(TAG, "onUserRemoved(): " + user);
- mCarUserService.onUserRemoved(user);
- }
-
- @Override
public void onFactoryReset(ICarResultReceiver callback) {
assertCallingFromSystemProcess();
@@ -1130,8 +1109,15 @@
@Override
public void setInitialUser(UserHandle user) {
+ assertCallingFromSystemProcess();
mCarUserService.setInitialUserFromSystemServer(user);
}
+
+ @Override
+ public void notifyFocusChanged(int pid, int uid) {
+ assertCallingFromSystemProcess();
+ mSystemActivityMonitoringService.handleFocusChanged(pid, uid);
+ }
}
/* package */ void dumpVhal(ParcelFileDescriptor fd, List<String> options)
@@ -1158,10 +1144,13 @@
CarWatchdogService mCarWatchdogService;
CarPerformanceService mCarPerformanceService;
GarageModeService mGarageModeService;
+ AppFocusService mAppFocusService;
IInterface mPowerPolicyDaemon;
CarTelemetryService mCarTelemetryService;
- CarRemoteAccessService mCarRemoteAccessService;
+ CarRemoteAccessServiceConstructor mCarRemoteAccessServiceConstructor;
boolean mDoPriorityInitInConstruction;
+ StaticBinderInterface mStaticBinder;
+ FeatureFlags mFeatureFlags;
/**
* Builds the ICarImpl object represented by this builder object
@@ -1262,6 +1251,16 @@
}
/**
+ * Sets ICarImpl builder app focus service
+ * @param appFocusService The app focus service
+ * @return Current builder object
+ */
+ public Builder setAppFocusService(AppFocusService appFocusService) {
+ mAppFocusService = appFocusService;
+ return this;
+ }
+
+ /**
* Sets ICarImpl power policy daemon
* @param powerPolicyDaemon The power policy daemon interface
* @return Current builder object
@@ -1282,12 +1281,28 @@
}
/**
- * Set ICarImpl car remote access service
- * @param carRemoteAccessService The car remote access service
+ * The constructor interface to create a CarRemoteAccessService.
+ *
+ * Used for creating a fake CarRemoteAccessService during car service test.
+ */
+ @VisibleForTesting
+ public interface CarRemoteAccessServiceConstructor {
+ /**
+ * Creates the {@link CarRemoteAccessService} object.
+ */
+ CarRemoteAccessService construct(Context context, SystemInterface systemInterface,
+ PowerHalService powerHalService);
+ }
+
+ /**
+ * Set a fake car remote access service constructor to be used for ICarImpl.
+ * @param constructor The car remote access service constructor.
* @return Current builder object
*/
- public Builder setCarRemoteAccessService(CarRemoteAccessService carRemoteAccessService) {
- mCarRemoteAccessService = carRemoteAccessService;
+ @VisibleForTesting
+ public Builder setCarRemoteAccessServiceConstructor(
+ CarRemoteAccessServiceConstructor constructor) {
+ mCarRemoteAccessServiceConstructor = constructor;
return this;
}
@@ -1302,5 +1317,22 @@
mDoPriorityInitInConstruction = doPriorityInitInConstruction;
return this;
}
+
+ /**
+ * Sets the calling Uid, only used for testing.
+ */
+ @VisibleForTesting
+ public Builder setTestStaticBinder(StaticBinderInterface testStaticBinder) {
+ mStaticBinder = testStaticBinder;
+ return this;
+ }
+
+ /**
+ * Sets the feature flags.
+ */
+ public Builder setFeatureFlags(FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
+ return this;
+ }
}
}
diff --git a/service/src/com/android/car/InputCaptureClientController.java b/service/src/com/android/car/InputCaptureClientController.java
index 685274e..49f99ea 100644
--- a/service/src/com/android/car/InputCaptureClientController.java
+++ b/service/src/com/android/car/InputCaptureClientController.java
@@ -185,8 +185,7 @@
if (client.mGrantedTypes.isEmpty()) {
inputTypesToDispatch = EMPTY_INPUT_TYPES;
} else {
- inputTypesToDispatch = client.mGrantedTypes.stream().mapToInt(
- Integer::intValue).toArray();
+ inputTypesToDispatch = CarServiceUtils.toIntArray(client.mGrantedTypes);
}
mClientsToDispatch.put(client.mCallback, inputTypesToDispatch);
}
@@ -212,13 +211,13 @@
private final SparseArray<SparseArray<LinkedList<ClientInfoForDisplay>>>
mPerInputTypeCapturers = new SparseArray<>(2);
- @GuardedBy("mLock")
/** key: display type -> client binder */
+ @GuardedBy("mLock")
private final SparseArray<HashMap<IBinder, ClientInfoForDisplay>> mAllClients =
new SparseArray<>(1);
- @GuardedBy("mLock")
/** Keeps events to dispatch together. FIFO, last one added to last */
+ @GuardedBy("mLock")
private final LinkedList<ClientsToDispatch> mClientDispatchQueue =
new LinkedList<>();
diff --git a/service/src/com/android/car/LocationManagerProxy.java b/service/src/com/android/car/LocationManagerProxy.java
index f05f2d4..13f92cd 100644
--- a/service/src/com/android/car/LocationManagerProxy.java
+++ b/service/src/com/android/car/LocationManagerProxy.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
+import android.util.Log;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.util.IndentingPrintWriter;
@@ -32,7 +33,7 @@
public class LocationManagerProxy extends ILocationManagerProxy.Stub {
private static final String TAG = CarLog.tagFor(LocationManagerProxy.class);
- private static final boolean DBG = false;
+ private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
private final LocationManager mLocationManager;
diff --git a/service/src/com/android/car/OccupantAwarenessService.java b/service/src/com/android/car/OccupantAwarenessService.java
index 0200559..db859f8 100644
--- a/service/src/com/android/car/OccupantAwarenessService.java
+++ b/service/src/com/android/car/OccupantAwarenessService.java
@@ -32,6 +32,7 @@
import android.hardware.automotive.occupant_awareness.IOccupantAwarenessClientCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
@@ -65,7 +66,7 @@
extends android.car.occupantawareness.IOccupantAwarenessManager.Stub
implements CarServiceBase {
private static final String TAG = CarLog.tagFor(OccupantAwarenessService.class);
- private static final boolean DBG = false;
+ private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
// HAL service identifier name.
@VisibleForTesting
@@ -80,7 +81,8 @@
private final ChangeListenerToHalService mHalListener = new ChangeListenerToHalService(this);
- private class ChangeCallbackList extends RemoteCallbackList<IOccupantAwarenessEventCallback> {
+ private static class ChangeCallbackList
+ extends RemoteCallbackList<IOccupantAwarenessEventCallback> {
private final WeakReference<OccupantAwarenessService> mOasService;
ChangeCallbackList(OccupantAwarenessService oasService) {
diff --git a/service/src/com/android/car/SystemActivityMonitoringService.java b/service/src/com/android/car/SystemActivityMonitoringService.java
index f3e1e3d7..e710c58 100644
--- a/service/src/com/android/car/SystemActivityMonitoringService.java
+++ b/service/src/com/android/car/SystemActivityMonitoringService.java
@@ -29,6 +29,7 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -59,9 +60,10 @@
long getPassengerActivitySetProcessGroupRetryTimeoutMs();
}
+ private static final boolean DBG = Slogf.isLoggable(CarLog.TAG_AM, Log.DEBUG);
// Passenger Activity might not be in top-app group in the 1st try. In that case, try
// again after this time. Retry will happen only once.
- private static final long PASSENGER_ACTIVITY_SET_PROCESS_GROUP_RETRY_MS = 2_000;
+ private static final long PASSENGER_ACTIVITY_SET_PROCESS_GROUP_RETRY_MS = 200;
private final ProcessObserverCallback mProcessObserver = new ProcessObserver();
@@ -85,6 +87,9 @@
@GuardedBy("mLock")
private boolean mAssignPassengerActivityToFgGroup;
+ @GuardedBy("mLock")
+ private int mDriverTopAppPid = Process.INVALID_PID;
+
public SystemActivityMonitoringService(Context context) {
this(context, new DefaultInjector());
}
@@ -221,18 +226,22 @@
* Updates the process group for given PID if it is passenger app and returns true if it should
* be retried.
*/
- private boolean doHandleProcessGroupForFgApp(int pid, int uid) {
+ private boolean updateProcessGroupForFgApp(int pid, int uid) {
+ int driverTopAppPid;
synchronized (mLock) {
if (!mAssignPassengerActivityToFgGroup) {
return false;
}
- }
- int userId = UserManagerHelper.getUserId(uid);
- // Current user will be driver. So do not touch it.
- // User 0 will be either current user or common system UI which should run with higher
- // priority.
- if (userId == ActivityManager.getCurrentUser() || userId == UserManagerHelper.USER_SYSTEM) {
- return false;
+ int userId = UserManagerHelper.getUserId(uid);
+ // Current user will be driver. So do not touch it.
+ // User 0 will be either current user or common system UI which should run with higher
+ // priority.
+ if (userId == ActivityManager.getCurrentUser()
+ || userId == UserManagerHelper.USER_SYSTEM) {
+ mDriverTopAppPid = pid;
+ return false;
+ }
+ driverTopAppPid = mDriverTopAppPid;
}
// TODO(b/261783537) ignore profile of the current user
@@ -240,13 +249,26 @@
boolean shouldRetry = false;
try {
int processGroup = helper.getProcessGroup(pid);
+ if (DBG) {
+ Slogf.d(CarLog.TAG_AM, "doHandleProcessGroupForFgApp: pid=%d pGroup=%d",
+ pid, processGroup);
+ }
switch (processGroup) {
case ProcessHelper.THREAD_GROUP_FOREGROUND:
- // already in FG group, ignore
+ // SetProcessGroup happens in OomAdjuster#mProcessGroupHandler in System
+ // Server, but which is the different thread with the main thread of
+ // OomAdjuster, and the focus change event is propagated to CarService
+ // through Binder, so there is race-condition between setting ProcessGroup in
+ // System Server and here.
+ // So, there are chances that to set Top App is not executed yet.
+ shouldRetry = true;
break;
case ProcessHelper.THREAD_GROUP_TOP_APP:
// Changing to FOREGROUND requires setting it to DEFAULT
helper.setProcessGroup(pid, ProcessHelper.THREAD_GROUP_DEFAULT);
+ if (driverTopAppPid != Process.INVALID_PID) {
+ helper.setProcessGroup(driverTopAppPid, ProcessHelper.THREAD_GROUP_TOP_APP);
+ }
break;
default:
// not in top-app yet, should retry
@@ -262,12 +284,33 @@
}
private void handleProcessGroupForFgApp(int pid, int uid) {
- if (doHandleProcessGroupForFgApp(pid, uid)) {
- mHandler.postDelayed(() -> doHandleProcessGroupForFgApp(pid, uid),
+ if (updateProcessGroupForFgApp(pid, uid)) {
+ if (DBG) {
+ Slogf.d(CarLog.TAG_AM, "Will retry handleProcessGroupForFgApp: pid=%d, uid=%d",
+ pid, uid);
+ }
+ mHandler.postDelayed(() -> updateProcessGroupForFgApp(pid, uid),
mInjector.getPassengerActivitySetProcessGroupRetryTimeoutMs());
}
}
+ private void handleProcessGroupForDiedApp(int pid) {
+ synchronized (mLock) {
+ if (!mAssignPassengerActivityToFgGroup) {
+ return;
+ }
+ if (pid == mDriverTopAppPid) {
+ mDriverTopAppPid = Process.INVALID_PID;
+ }
+ }
+ }
+
+ /** Handles focusChanged event. */
+ public void handleFocusChanged(int pid, int uid) {
+ if (DBG) Slogf.d(CarLog.TAG_AM, "notifyFocusChanged: pid=%d uid=%d", pid, uid);
+ handleProcessGroupForFgApp(pid, uid);
+ }
+
private class ProcessObserver extends ProcessObserverCallback {
@Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
@@ -276,14 +319,12 @@
String.format("onForegroundActivitiesChanged uid %d pid %d fg %b",
uid, pid, foregroundActivities));
}
- if (foregroundActivities) {
- handleProcessGroupForFgApp(pid, uid);
- }
mHandler.requestForegroundActivitiesChanged(pid, uid, foregroundActivities);
}
@Override
public void onProcessDied(int pid, int uid) {
+ handleProcessGroupForDiedApp(pid);
mHandler.requestProcessDied(pid, uid);
}
}
diff --git a/service/src/com/android/car/am/CarActivityService.java b/service/src/com/android/car/am/CarActivityService.java
index 3434f8d..b036d4e 100644
--- a/service/src/com/android/car/am/CarActivityService.java
+++ b/service/src/com/android/car/am/CarActivityService.java
@@ -88,7 +88,6 @@
private static final String TAG = CarLog.TAG_AM;
private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
- private static final int MAX_RUNNING_TASKS_TO_GET = 100;
private static final long MIRRORING_TOKEN_TIMEOUT_MS = 10 * 60 * 1000; // 10 mins
private final Context mContext;
@@ -120,16 +119,27 @@
private IBinder mCurrentMonitor;
- public interface ActivityLaunchListener {
+ /**
+ * Listener for activity callbacks.
+ */
+ public interface ActivityListener {
/**
- * Notify launch of activity.
+ * Notify coming of an activity on the top of the stack.
*
* @param topTask Task information for what is currently launched.
*/
- void onActivityLaunch(TaskInfo topTask);
+ void onActivityCameOnTop(TaskInfo topTask);
+
+ /**
+ * Notify change or vanish of an activity in the backstack.
+ *
+ * @param taskInfo task information for what is currently changed or vanished.
+ */
+ void onActivityChangedInBackstack(TaskInfo taskInfo);
}
+
@GuardedBy("mLock")
- private final ArrayList<ActivityLaunchListener> mActivityLaunchListeners = new ArrayList<>();
+ private final ArrayList<ActivityListener> mActivityListeners = new ArrayList<>();
private final HandlerThread mMonitorHandlerThread = CarServiceUtils.getHandlerThread(
SystemActivityMonitoringService.class.getSimpleName());
@@ -152,7 +162,7 @@
@Override
public void release() {
synchronized (mLock) {
- mActivityLaunchListeners.clear();
+ mActivityListeners.clear();
}
}
@@ -182,15 +192,25 @@
return UserManagerHelper.getUserId(Binder.getCallingUid());
}
- public void registerActivityLaunchListener(@NonNull ActivityLaunchListener listener) {
+ /**
+ * Register an {@link ActivityListener}
+ *
+ * @param listener listener to register.
+ */
+ public void registerActivityListener(@NonNull ActivityListener listener) {
synchronized (mLock) {
- mActivityLaunchListeners.add(listener);
+ mActivityListeners.add(listener);
}
}
- public void unregisterActivityLaunchListener(@NonNull ActivityLaunchListener listener) {
+ /**
+ * Unregister an {@link ActivityListener}.
+ *
+ * @param listener listener to unregister.
+ */
+ public void unregisterActivityListener(@NonNull ActivityListener listener) {
synchronized (mLock) {
- mActivityLaunchListeners.remove(listener);
+ mActivityListeners.remove(listener);
}
}
@@ -221,6 +241,7 @@
}
}
+ /** Ensure permission is granted. */
private void ensurePermission(String permission) {
if (mContext.checkCallingOrSelfPermission(permission)
!= PackageManager.PERMISSION_GRANTED) {
@@ -261,14 +282,22 @@
}
}
if (TaskInfoHelper.isVisible(taskInfo)) {
- mHandler.post(() -> notifyActivityLaunch(taskInfo));
+ mHandler.post(() -> notifyActivityCameOnTop(taskInfo));
}
}
- private void notifyActivityLaunch(TaskInfo taskInfo) {
+ private void notifyActivityCameOnTop(TaskInfo taskInfo) {
synchronized (mLock) {
- for (int i = 0, size = mActivityLaunchListeners.size(); i < size; ++i) {
- mActivityLaunchListeners.get(i).onActivityLaunch(taskInfo);
+ for (int i = 0, size = mActivityListeners.size(); i < size; ++i) {
+ mActivityListeners.get(i).onActivityCameOnTop(taskInfo);
+ }
+ }
+ }
+
+ private void notifyActivityChangedInBackStack(TaskInfo taskInfo) {
+ synchronized (mLock) {
+ for (int i = 0, size = mActivityListeners.size(); i < size; ++i) {
+ mActivityListeners.get(i).onActivityChangedInBackstack(taskInfo);
}
}
}
@@ -296,8 +325,13 @@
if (!isAllowedToUpdateLocked(token)) {
return;
}
+ // Do not remove the taskInfo from the mLastKnownDisplayIdForTask array since when
+ // the task vanishes, the display ID becomes -1. We want to preserve this information
+ // to finish the blocking ui for that display ID. mTasks and
+ // mLastKnownDisplayIdForTask come in sync when the blocking ui is finished.
mTasks.remove(taskInfo.taskId);
mTaskToSurfaceMap.remove(taskInfo.taskId);
+ mHandler.post(() -> notifyActivityChangedInBackStack(taskInfo));
}
}
@@ -318,7 +352,9 @@
if ((oldTaskInfo == null || !TaskInfoHelper.isVisible(oldTaskInfo)
|| !Objects.equals(oldTaskInfo.topActivity, taskInfo.topActivity))
&& TaskInfoHelper.isVisible(taskInfo)) {
- mHandler.post(() -> notifyActivityLaunch(taskInfo));
+ mHandler.post(() -> notifyActivityCameOnTop(taskInfo));
+ } else {
+ mHandler.post(() -> notifyActivityChangedInBackStack(taskInfo));
}
}
}
@@ -698,7 +734,7 @@
writer.println(" " + TaskInfoHelper.toString(taskInfo));
}
writer.println(" Surfaces: " + mTaskToSurfaceMap.toString());
- writer.println(" ActivityLaunchListeners: " + mActivityLaunchListeners.toString());
+ writer.println(" ActivityListeners: " + mActivityListeners.toString());
}
}
diff --git a/service/src/com/android/car/audio/AudioManagerWrapper.java b/service/src/com/android/car/audio/AudioManagerWrapper.java
new file mode 100644
index 0000000..43b14a1
--- /dev/null
+++ b/service/src/com/android/car/audio/AudioManagerWrapper.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.audio;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.car.builtin.media.AudioManagerHelper;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFocusInfo;
+import android.media.AudioFocusRequest;
+import android.media.AudioManager;
+import android.media.AudioManager.AudioPlaybackCallback;
+import android.media.AudioManager.AudioServerStateCallback;
+import android.media.AudioPlaybackConfiguration;
+import android.media.FadeManagerConfiguration;
+import android.media.audiopolicy.AudioPolicy;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.media.audiopolicy.AudioVolumeGroup;
+import android.os.Handler;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Class to wrap audio manager. This makes it easier to call to audio manager without the need
+ * for actually using an audio manager.
+ */
+public final class AudioManagerWrapper {
+
+ private final AudioManager mAudioManager;
+
+ AudioManagerWrapper(AudioManager audioManager) {
+ mAudioManager = Objects.requireNonNull(audioManager, "Audio manager can not be null");
+ }
+
+ int getMinVolumeIndexForAttributes(AudioAttributes audioAttributes) {
+ return mAudioManager.getMinVolumeIndexForAttributes(audioAttributes);
+ }
+
+ int getMaxVolumeIndexForAttributes(AudioAttributes audioAttributes) {
+ return mAudioManager.getMaxVolumeIndexForAttributes(audioAttributes);
+ }
+
+ int getVolumeIndexForAttributes(AudioAttributes audioAttributes) {
+ return mAudioManager.getVolumeIndexForAttributes(audioAttributes);
+ }
+
+ boolean isVolumeGroupMuted(int groupId) {
+ return AudioManagerHelper.isVolumeGroupMuted(mAudioManager, groupId);
+ }
+
+ int getLastAudibleVolumeForVolumeGroup(int groupId) {
+ return AudioManagerHelper.getLastAudibleVolumeGroupVolume(mAudioManager, groupId);
+ }
+
+ void setVolumeGroupVolumeIndex(int groupId, int gainIndex, int flags) {
+ mAudioManager.setVolumeGroupVolumeIndex(groupId, gainIndex, flags);
+ }
+
+ void adjustVolumeGroupVolume(int groupId, int adjustment, int flags) {
+ AudioManagerHelper.adjustVolumeGroupVolume(mAudioManager, groupId, adjustment, flags);
+ }
+
+ void setPreferredDeviceForStrategy(AudioProductStrategy strategy,
+ AudioDeviceAttributes audioDeviceAttributes) {
+ mAudioManager.setPreferredDeviceForStrategy(strategy, audioDeviceAttributes);
+ }
+
+ boolean setAudioDeviceGain(String address, int gain, boolean isOutput) {
+ return AudioManagerHelper.setAudioDeviceGain(mAudioManager, address, gain, isOutput);
+ }
+
+ /**
+ * {@link AudioManager#abandonAudioFocusRequest(AudioFocusRequest)}
+ */
+ public int abandonAudioFocusRequest(AudioFocusRequest audioFocusRequest) {
+ return mAudioManager.abandonAudioFocusRequest(audioFocusRequest);
+ }
+
+ /**
+ * {@link AudioManager#requestAudioFocus(AudioFocusRequest)}
+ */
+ public int requestAudioFocus(AudioFocusRequest audioFocusRequest) {
+ return mAudioManager.requestAudioFocus(audioFocusRequest);
+ }
+
+ boolean isMasterMuted() {
+ return AudioManagerHelper.isMasterMute(mAudioManager);
+ }
+
+ void setMasterMute(boolean mute, int flags) {
+ AudioManagerHelper.setMasterMute(mAudioManager, mute, flags);
+ }
+
+ int dispatchAudioFocusChange(AudioFocusInfo info, int focusChange, AudioPolicy policy) {
+ return mAudioManager.dispatchAudioFocusChange(info, focusChange, policy);
+ }
+
+ int dispatchAudioFocusChangeWithFade(AudioFocusInfo info, int changeType,
+ AudioPolicy policy, List<AudioFocusInfo> activeAfis,
+ FadeManagerConfiguration fadeConfig) {
+ return mAudioManager.dispatchAudioFocusChangeWithFade(info, changeType, policy, activeAfis,
+ fadeConfig);
+ }
+
+ void setFocusRequestResult(AudioFocusInfo info, int response, AudioPolicy policy) {
+ mAudioManager.setFocusRequestResult(info, response, policy);
+ }
+
+ void registerVolumeGroupCallback(Executor executor,
+ AudioManager.VolumeGroupCallback coreAudioVolumeGroupCallback) {
+ mAudioManager.registerVolumeGroupCallback(executor, coreAudioVolumeGroupCallback);
+ }
+
+ void unregisterVolumeGroupCallback(
+ AudioManager.VolumeGroupCallback coreAudioVolumeGroupCallback) {
+ mAudioManager.unregisterVolumeGroupCallback(coreAudioVolumeGroupCallback);
+ }
+
+ boolean isAudioServerRunning() {
+ return mAudioManager.isAudioServerRunning();
+ }
+
+ void setAudioServerStateCallback(Executor executor, AudioServerStateCallback callback) {
+ mAudioManager.setAudioServerStateCallback(executor, callback);
+ }
+
+ @SuppressLint("WrongConstant")
+ void setSupportedSystemUsages(int[] systemUsages) {
+ mAudioManager.setSupportedSystemUsages(systemUsages);
+ }
+
+ void registerAudioDeviceCallback(AudioDeviceCallback callback, Handler handler) {
+ mAudioManager.registerAudioDeviceCallback(callback, handler);
+ }
+
+ void unregisterAudioDeviceCallback(AudioDeviceCallback callback) {
+ mAudioManager.unregisterAudioDeviceCallback(callback);
+ }
+
+ void clearAudioServerStateCallback() {
+ mAudioManager.clearAudioServerStateCallback();
+ }
+
+ void unregisterAudioPolicy(AudioPolicy policy) {
+ mAudioManager.unregisterAudioPolicy(policy);
+ }
+
+ void unregisterAudioPolicyAsync(AudioPolicy policy) {
+ mAudioManager.unregisterAudioPolicyAsync(policy);
+ }
+
+ int registerAudioPolicy(AudioPolicy policy) {
+ return mAudioManager.registerAudioPolicy(policy);
+ }
+
+ void setStreamVolume(int stream, int index, int flags) {
+ mAudioManager.setStreamVolume(stream, index, flags);
+ }
+
+ int getStreamMaxVolume(int stream) {
+ return mAudioManager.getStreamMaxVolume(stream);
+ }
+
+ int getStreamMinVolume(int stream) {
+ return mAudioManager.getStreamMinVolume(stream);
+ }
+
+ int getStreamVolume(int stream) {
+ return mAudioManager.getStreamVolume(stream);
+ }
+
+ void setParameters(String parameters) {
+ mAudioManager.setParameters(parameters);
+ }
+
+ AudioDeviceInfo[] getDevices(int flags) {
+ return mAudioManager.getDevices(flags);
+ }
+
+ void registerAudioPlaybackCallback(AudioPlaybackCallback callback, @Nullable Handler handler) {
+ mAudioManager.registerAudioPlaybackCallback(callback, handler);
+ }
+
+ void unregisterAudioPlaybackCallback(AudioPlaybackCallback callback) {
+ mAudioManager.unregisterAudioPlaybackCallback(callback);
+ }
+
+ boolean releaseAudioPatch(AudioManagerHelper.AudioPatchInfo audioPatchInfo) {
+ return AudioManagerHelper.releaseAudioPatch(mAudioManager, audioPatchInfo);
+ }
+
+ List<AudioPlaybackConfiguration> getActivePlaybackConfigurations() {
+ return mAudioManager.getActivePlaybackConfigurations();
+ }
+
+ static List<AudioProductStrategy> getAudioProductStrategies() {
+ return AudioManager.getAudioProductStrategies();
+ }
+
+ static List<AudioVolumeGroup> getAudioVolumeGroups() {
+ return AudioManager.getAudioVolumeGroups();
+ }
+}
diff --git a/service/src/com/android/car/audio/CarActivationVolumeConfig.java b/service/src/com/android/car/audio/CarActivationVolumeConfig.java
new file mode 100644
index 0000000..64eb846
--- /dev/null
+++ b/service/src/com/android/car/audio/CarActivationVolumeConfig.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.audio;
+
+import android.annotation.IntDef;
+
+import com.android.internal.util.Preconditions;
+
+final class CarActivationVolumeConfig {
+
+ /**
+ * Activation volume type invoked for the first time after boot or switching
+ * {@link CarAudioZoneConfig} or user.
+ */
+ static final int ACTIVATION_VOLUME_ON_BOOT = 1;
+ /**
+ * Activation volume type invoked when the source (represented bu uid) of the newly active
+ * {@link android.media.AudioPlaybackConfiguration} for each {@link CarVolumeGroup} is changed
+ */
+ static final int ACTIVATION_VOLUME_ON_SOURCE_CHANGED = 1 << 1;
+ /**
+ * Activation volume type invoked for every newly active
+ * {@link android.media.AudioPlaybackConfiguration} for each {@link CarVolumeGroup}
+ */
+ static final int ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED = 1 << 2;
+
+ private final int mInvocationType;
+ private final int mMinActivationVolumePercentage;
+ private final int mMaxActivationVolumePercentage;
+
+ @IntDef(flag = true, value = {
+ ACTIVATION_VOLUME_ON_BOOT,
+ ACTIVATION_VOLUME_ON_SOURCE_CHANGED,
+ ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED
+ })
+ @interface ActivationVolumeInvocationType {}
+
+ CarActivationVolumeConfig(int invocationType, int minActivationVolumePercentage,
+ int maxActivationVolumePercentage) {
+ Preconditions.checkArgument(minActivationVolumePercentage < maxActivationVolumePercentage,
+ "Min activation volume percentage can not be higher than max");
+ mInvocationType = invocationType;
+ mMinActivationVolumePercentage = minActivationVolumePercentage;
+ mMaxActivationVolumePercentage = maxActivationVolumePercentage;
+ }
+
+ int getInvocationType() {
+ return mInvocationType;
+ }
+
+ int getMinActivationVolumePercentage() {
+ return mMinActivationVolumePercentage;
+ }
+
+ int getMaxActivationVolumePercentage() {
+ return mMaxActivationVolumePercentage;
+ }
+}
diff --git a/service/src/com/android/car/audio/CarAudioDeviceInfo.java b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
index a13a48d..138bbb0 100644
--- a/service/src/com/android/car/audio/CarAudioDeviceInfo.java
+++ b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
@@ -32,7 +32,6 @@
import android.car.builtin.util.Slogf;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
import android.util.proto.ProtoOutputStream;
import com.android.car.CarLog;
@@ -64,7 +63,7 @@
* per {@link ENCODING_PCM_16BIT}'s documentation.
*/
private static final int DEFAULT_ENCODING_FORMAT = ENCODING_PCM_16BIT;
- private final AudioManager mAudioManager;
+ private final AudioManagerWrapper mAudioManager;
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -96,7 +95,8 @@
@GuardedBy("mLock")
private boolean mIsActive;
- CarAudioDeviceInfo(AudioManager audioManager, AudioDeviceAttributes audioDeviceAttributes) {
+ CarAudioDeviceInfo(AudioManagerWrapper audioManager,
+ AudioDeviceAttributes audioDeviceAttributes) {
mAudioManager = audioManager;
mAudioDeviceAttributes = audioDeviceAttributes;
// Device specific information will be initialized once an actual audio device info is set
@@ -280,8 +280,7 @@
}
}
- if (AudioManagerHelper.setAudioDeviceGain(mAudioManager,
- getAddress(), gain, true)) {
+ if (mAudioManager.setAudioDeviceGain(getAddress(), gain, true)) {
// Since we can't query for the gain on a device port later,
// we have to remember what we asked for
synchronized (mLock) {
diff --git a/service/src/com/android/car/audio/CarAudioDynamicRouting.java b/service/src/com/android/car/audio/CarAudioDynamicRouting.java
index 125d8a5..7642c35 100644
--- a/service/src/com/android/car/audio/CarAudioDynamicRouting.java
+++ b/service/src/com/android/car/audio/CarAudioDynamicRouting.java
@@ -17,6 +17,7 @@
import static android.media.AudioAttributes.USAGE_MEDIA;
+import static com.android.car.audio.CarAudioUtils.getAudioDeviceInfo;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR;
import android.car.builtin.util.Slogf;
@@ -53,8 +54,9 @@
AudioAttributes.USAGE_NOTIFICATION_RINGTONE
};
- static void setupAudioDynamicRouting(CarAudioContext carAudioContext, AudioManager audioManager,
- AudioPolicy.Builder builder, SparseArray<CarAudioZone> carAudioZones) {
+ static void setupAudioDynamicRouting(CarAudioContext carAudioContext,
+ AudioManagerWrapper audioManager, AudioPolicy.Builder builder,
+ SparseArray<CarAudioZone> carAudioZones) {
for (int i = 0; i < carAudioZones.size(); i++) {
CarAudioZone zone = carAudioZones.valueAt(i);
List<CarAudioZoneConfig> zoneConfigs = zone.getAllCarAudioZoneConfigs();
@@ -94,7 +96,7 @@
private static void setupAudioDynamicRoutingForZoneConfig(AudioPolicy.Builder builder,
CarAudioZoneConfig zoneConfig, CarAudioContext carAudioContext,
- AudioManager audioManager) {
+ AudioManagerWrapper audioManager) {
CarVolumeGroup[] volumeGroups = zoneConfig.getVolumeGroups();
for (int index = 0; index < volumeGroups.length; index++) {
setupAudioDynamicRoutingForGroup(builder, volumeGroups[index], carAudioContext,
@@ -111,7 +113,8 @@
* @param audioManager audio manager to find audio configuration for the passed in info
*/
private static void setupAudioDynamicRoutingForGroup(AudioPolicy.Builder builder,
- CarVolumeGroup group, CarAudioContext carAudioContext, AudioManager audioManager) {
+ CarVolumeGroup group, CarAudioContext carAudioContext,
+ AudioManagerWrapper audioManager) {
// Note that one can not register audio mix for same bus more than once.
List<String> addresses = group.getAddresses();
for (int index = 0; index < addresses.size(); index++) {
@@ -148,7 +151,7 @@
}
if (hasContext) {
AudioDeviceInfo audioDeviceInfo =
- CarAudioUtils.getAudioDeviceInfo(info.getAudioDevice(), audioManager);
+ getAudioDeviceInfo(info.getAudioDevice(), audioManager);
// It's a valid case that an audio output address is defined in
// audio_policy_configuration and no context is assigned to it.
// In such case, do not build a policy mix with zero rules.
@@ -164,13 +167,13 @@
public static void setupAudioDynamicRoutingForMirrorDevice(
AudioPolicy.Builder mirrorPolicyBuilder, List<CarAudioDeviceInfo> audioDeviceInfos,
- AudioManager audioManager) {
+ AudioManagerWrapper audioManager) {
for (int index = 0; index < audioDeviceInfos.size(); index++) {
AudioFormat mixFormat = createMixFormatFromDevice(audioDeviceInfos.get(index));
AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
mixingRuleBuilder.addRule(CarAudioContext.getAudioAttributeFromUsage(USAGE_MEDIA),
AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
- AudioDeviceInfo info = CarAudioUtils.getAudioDeviceInfo(
+ AudioDeviceInfo info = getAudioDeviceInfo(
audioDeviceInfos.get(index).getAudioDevice(), audioManager);
addMix(mirrorPolicyBuilder, info, mixFormat, mixingRuleBuilder);
diff --git a/service/src/com/android/car/audio/CarAudioFocus.java b/service/src/com/android/car/audio/CarAudioFocus.java
index 63d39d8..aa2f687 100644
--- a/service/src/com/android/car/audio/CarAudioFocus.java
+++ b/service/src/com/android/car/audio/CarAudioFocus.java
@@ -78,7 +78,7 @@
private static final int FOCUS_EVENT_LOGGER_QUEUE_SIZE = 25;
- private final AudioManager mAudioManager;
+ private final AudioManagerWrapper mAudioManager;
private final PackageManager mPackageManager;
private final CarVolumeInfoWrapper mCarVolumeInfoWrapper;
@Nullable
@@ -117,7 +117,7 @@
@GuardedBy("mLock")
private boolean mIsFocusRestricted;
- CarAudioFocus(AudioManager audioManager, PackageManager packageManager,
+ CarAudioFocus(AudioManagerWrapper audioManager, PackageManager packageManager,
FocusInteraction focusInteraction, CarAudioZone carAudioZone,
CarVolumeInfoWrapper volumeInfoWrapper, @Nullable CarAudioFeaturesInfo features) {
mAudioManager = Objects.requireNonNull(audioManager, "Audio manager can not be null");
@@ -392,9 +392,16 @@
// Now that we're sure we'll accept this request, update any requests which we would
// block but are already out of focus but waiting to come back
List<AudioFocusEntry> blocked = evaluationResults.getNewlyBlockedAudioFocusEntries();
- Map<AudioAttributes, CarAudioFadeConfiguration> transientCarAudioFadeConfigs = null;
+ CarAudioFadeConfiguration transientCarAudioFadeConfigFromXml = null;
+ CarAudioFadeConfiguration defaultCarAudioFadeConfigFromXml = null;
+ Map<AudioAttributes, CarAudioFadeConfiguration> transientCarAudioFadeConfigsFromOemService =
+ null;
if (isFadeManagerSupported()) {
- transientCarAudioFadeConfigs =
+ defaultCarAudioFadeConfigFromXml = mCarAudioZone.getCurrentCarAudioZoneConfig()
+ .getDefaultCarAudioFadeConfiguration();
+ transientCarAudioFadeConfigFromXml = mCarAudioZone.getCurrentCarAudioZoneConfig()
+ .getCarAudioFadeConfigurationForAudioAttributes(newEntryAfi.getAttributes());
+ transientCarAudioFadeConfigsFromOemService =
evaluationResults.getAudioAttributesToCarAudioFadeConfigurationMap();
}
for (int index = 0; index < blocked.size(); index++) {
@@ -405,7 +412,10 @@
if (permanent) {
FadeManagerConfiguration transientFadeManagerConfig = getTransientFadeManagerConfig(
- entry.getAudioFocusInfo().getAttributes(), transientCarAudioFadeConfigs);
+ defaultCarAudioFadeConfigFromXml, getOptimalUsageBasedTransientFadeConfig(
+ entry.getAudioFocusInfo().getAttributes(),
+ transientCarAudioFadeConfigFromXml,
+ transientCarAudioFadeConfigsFromOemService));
// This entry has now lost focus forever
sendFocusLossLocked(entry.getAudioFocusInfo(), AUDIOFOCUS_LOSS, newEntryAfi,
!entry.isDucked(), transientFadeManagerConfig);
@@ -440,7 +450,10 @@
if (permanent) {
FadeManagerConfiguration transientFadeManagerConfig = getTransientFadeManagerConfig(
- entry.getAudioFocusInfo().getAttributes(), transientCarAudioFadeConfigs);
+ defaultCarAudioFadeConfigFromXml, getOptimalUsageBasedTransientFadeConfig(
+ entry.getAudioFocusInfo().getAttributes(),
+ transientCarAudioFadeConfigFromXml,
+ transientCarAudioFadeConfigsFromOemService));
sendFocusLossLocked(entry.getAudioFocusInfo(), AUDIOFOCUS_LOSS, newEntryAfi,
!entry.isDucked(), transientFadeManagerConfig);
permanentlyLost.add(entry);
@@ -537,19 +550,12 @@
mCarAudioContext.getContextForAudioAttribute(
audioFocusInfo.getAttributes()),
getVolumeGroupForAttribute(audioFocusInfo.getAttributes()),
- AudioManager.AUDIOFOCUS_GAIN).build();
+ AUDIOFOCUS_GAIN).build();
- OemCarAudioFocusResult.Builder builder = new OemCarAudioFocusResult.Builder(
+ OemCarAudioFocusResult focusResult = new OemCarAudioFocusResult.Builder(
convertAudioFocusEntries(holdersEvaluation.mChangedEntries),
convertAudioFocusEntries(losersEvaluation.mChangedEntries),
- results).setAudioFocusEntry(focusEntry);
- Map<AudioAttributes, CarAudioFadeConfiguration> audioAttributesToCarAudioFadeConfig =
- getAllTransientCarAudioFadeConfigurations();
- if (audioAttributesToCarAudioFadeConfig != null) {
- builder.setAudioAttributesToCarAudioFadeConfigurationMap(
- audioAttributesToCarAudioFadeConfig);
- }
- OemCarAudioFocusResult focusResult = builder.build();
+ results).setAudioFocusEntry(focusEntry).build();
t.traceEnd();
return focusResult;
}
@@ -1184,30 +1190,37 @@
}
}
- private FadeManagerConfiguration getTransientFadeManagerConfig(AudioAttributes attr,
- Map<AudioAttributes, CarAudioFadeConfiguration> attrToCarAudioFadeConfigurationsMap) {
+ private FadeManagerConfiguration getTransientFadeManagerConfig(
+ CarAudioFadeConfiguration defaultCarAudioFadeConfigFromXml,
+ CarAudioFadeConfiguration transientCarAudioFadeConfig) {
if (!isFadeManagerSupported()) {
return null;
}
- if (attrToCarAudioFadeConfigurationsMap != null
- && attrToCarAudioFadeConfigurationsMap.containsKey(attr)) {
- return attrToCarAudioFadeConfigurationsMap.get(attr).getFadeManagerConfiguration();
+ if (transientCarAudioFadeConfig != null) {
+ return transientCarAudioFadeConfig.getFadeManagerConfiguration();
}
- CarAudioFadeConfiguration defaultCarAudioFadeConfig =
- mCarAudioZone.getCurrentCarAudioZoneConfig().getDefaultCarAudioFadeConfiguration();
-
// Default configuration for primary zone is already set with core audio framework.
// Therefore, no need to set default fade config as transient. When not primary, use default
// fade configuration for transient if none is set.
- return (defaultCarAudioFadeConfig == null || mCarAudioZone.isPrimaryZone()) ? null :
- defaultCarAudioFadeConfig.getFadeManagerConfiguration();
+ return (defaultCarAudioFadeConfigFromXml == null || mCarAudioZone.isPrimaryZone())
+ ? null : defaultCarAudioFadeConfigFromXml.getFadeManagerConfiguration();
}
- private Map<AudioAttributes,
- CarAudioFadeConfiguration> getAllTransientCarAudioFadeConfigurations() {
- return isFadeManagerSupported() ? mCarAudioZone.getCurrentCarAudioZoneConfig()
- .getAllTransientCarAudioFadeConfigurations() : null;
+ // priority: transient from oem service > transient from xml
+ private CarAudioFadeConfiguration getOptimalUsageBasedTransientFadeConfig(AudioAttributes attr,
+ CarAudioFadeConfiguration transientCarAudioFadeConfigFromXml,
+ Map<AudioAttributes,
+ CarAudioFadeConfiguration> attrToCarAudioFadeConfigsFromOemService) {
+ if (!isFadeManagerSupported()) {
+ return null;
+ }
+
+ if (attrToCarAudioFadeConfigsFromOemService != null
+ && !attrToCarAudioFadeConfigsFromOemService.isEmpty()) {
+ return attrToCarAudioFadeConfigsFromOemService.get(attr);
+ }
+ return transientCarAudioFadeConfigFromXml;
}
}
diff --git a/service/src/com/android/car/audio/CarAudioParserUtils.java b/service/src/com/android/car/audio/CarAudioParserUtils.java
index a9da8d2..c9cbe30 100644
--- a/service/src/com/android/car/audio/CarAudioParserUtils.java
+++ b/service/src/com/android/car/audio/CarAudioParserUtils.java
@@ -17,9 +17,13 @@
import static android.media.AudioAttributes.USAGE_NOTIFICATION_EVENT;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR;
+
import android.car.builtin.media.AudioManagerHelper;
import android.media.AudioAttributes;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -45,6 +49,11 @@
public static final String ATTR_USAGE = "usage";
public static final String ATTR_TAGS = "tags";
+ @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR)
+ private CarAudioParserUtils() {
+ throw new UnsupportedOperationException(
+ "CarAudioParserUtils class is non-instantiable, contains static members only");
+ }
/* package */ static List<AudioAttributes> parseAudioAttributes(XmlPullParser parser,
String sectionName) throws XmlPullParserException, IOException {
diff --git a/service/src/com/android/car/audio/CarAudioPlaybackMonitor.java b/service/src/com/android/car/audio/CarAudioPlaybackMonitor.java
index cb10a8e..e7cf5bb 100644
--- a/service/src/com/android/car/audio/CarAudioPlaybackMonitor.java
+++ b/service/src/com/android/car/audio/CarAudioPlaybackMonitor.java
@@ -16,34 +16,209 @@
package com.android.car.audio;
-import android.media.AudioAttributes;
+import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
+import static com.android.car.audio.CarActivationVolumeConfig.ActivationVolumeInvocationType;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
+import android.annotation.NonNull;
+import android.car.builtin.util.Slogf;
+import android.car.media.CarAudioManager;
+import android.media.AudioAttributes;
+import android.os.Binder;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.car.CarLog;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
public final class CarAudioPlaybackMonitor {
- private final CarAudioService mCarAudioService;
+ private static final int INVALID_UID = -1;
- CarAudioPlaybackMonitor(CarAudioService carAudioService) {
+ private final Object mLock = new Object();
+ private final CarAudioService mCarAudioService;
+ private final SparseArray<CarAudioZone> mCarAudioZones;
+ private final TelephonyManager mTelephonyManager;
+ private final Executor mExecutor;
+ private final CarCallStateListener mCallStateCallback;
+ @GuardedBy("mLock")
+ private final SparseArray<SparseIntArray> mZoneIdGroupIdToUidMap;
+
+ CarAudioPlaybackMonitor(@NonNull CarAudioService carAudioService,
+ @NonNull SparseArray<CarAudioZone> carAudioZones,
+ @NonNull TelephonyManager telephonyManager) {
mCarAudioService = Objects.requireNonNull(carAudioService,
"Car audio service can not be null");
+ mCarAudioZones = Objects.requireNonNull(carAudioZones,
+ "Car audio zones can not be null");
+ mZoneIdGroupIdToUidMap = new SparseArray<>();
+ mTelephonyManager = Objects.requireNonNull(telephonyManager,
+ "Telephony manager can not be null");
+ mExecutor = Executors.newSingleThreadExecutor();
+ mCallStateCallback = new CarCallStateListener();
+ mTelephonyManager.registerTelephonyCallback(mExecutor, mCallStateCallback);
+ }
+
+ /**
+ * Reset car audio playback monitor
+ *
+ * <p>Once reset, car audio playback monitor cannot be reused since the listener to
+ * {@link TelephonyManager} has been unregistered.
+ */
+ void reset() {
+ mTelephonyManager.unregisterTelephonyCallback(mCallStateCallback);
+ }
+
+ void resetActivationTypesForZone(int zoneId) {
+ synchronized (mLock) {
+ mZoneIdGroupIdToUidMap.remove(zoneId);
+ }
}
/**
* Informs {@link CarAudioService} that newly active playbacks are received and min/max
* activation volume should be applied if needed.
*
- * @param newActivePlaybackAttributes List of {@link AudioAttributes} of the newly active
- * {@link android.media.AudioPlaybackConfiguration}s
+ * @param newActivePlaybackAttributesWithUid List of pairs of {@link AudioAttributes} and
+ * client Uid of the newly active
+ * {@link android.media.AudioPlaybackConfiguration}s
* @param zoneId Zone Id of thr newly active playbacks
*/
public void onActiveAudioPlaybackAttributesAdded(
- List<AudioAttributes> newActivePlaybackAttributes, int zoneId) {
- if (newActivePlaybackAttributes == null || newActivePlaybackAttributes.isEmpty()) {
+ List<Pair<AudioAttributes, Integer>> newActivePlaybackAttributesWithUid, int zoneId) {
+ if (newActivePlaybackAttributesWithUid == null
+ || newActivePlaybackAttributesWithUid.isEmpty()) {
return;
}
- mCarAudioService.handleActivationVolumeWithAudioAttributes(newActivePlaybackAttributes,
- zoneId);
+ CarAudioZoneConfig currentZoneConfig = mCarAudioZones.get(zoneId)
+ .getCurrentCarAudioZoneConfig();
+ List<ActivationInfo> newActivationVolumeGroupsWithType =
+ new ArrayList<>();
+ for (int index = 0; index < newActivePlaybackAttributesWithUid.size(); index++) {
+ Pair<AudioAttributes, Integer> newActivePlaybackAttributesWithUidEntry =
+ newActivePlaybackAttributesWithUid.get(index);
+ ActivationInfo activationVolumeGroupWithInvocationType =
+ getActivationInfo(
+ newActivePlaybackAttributesWithUidEntry, currentZoneConfig);
+ if (activationVolumeGroupWithInvocationType == null) {
+ continue;
+ }
+ newActivationVolumeGroupsWithType.add(activationVolumeGroupWithInvocationType);
+ }
+ if (newActivationVolumeGroupsWithType.isEmpty()) {
+ return;
+ }
+ mCarAudioService.handleActivationVolumeWithActivationInfos(
+ newActivationVolumeGroupsWithType, zoneId, currentZoneConfig.getZoneConfigId());
+ }
+
+ private ActivationInfo getActivationInfo(
+ Pair<AudioAttributes, Integer> attributesIntegerPair,
+ CarAudioZoneConfig currentZoneConfig) {
+ int zoneId = currentZoneConfig.getZoneId();
+ CarVolumeGroup volumeGroup = currentZoneConfig.getVolumeGroupForAudioAttributes(
+ attributesIntegerPair.first);
+ if (volumeGroup == null) {
+ Slogf.w(CarLog.TAG_AUDIO, "Audio attributes %s is not found in zone %d config %d",
+ attributesIntegerPair.first, zoneId, currentZoneConfig.getZoneConfigId());
+ return null;
+ }
+ int groupId = volumeGroup.getId();
+ int uid = attributesIntegerPair.second;
+ int activationVolumeInvocationType;
+ synchronized (mLock) {
+ // For a playback with a uid that does not exist for a given zone id and volume group
+ // id, it is the playback after boot or zone configuration change for activation volume.
+ if (!mZoneIdGroupIdToUidMap.contains(zoneId)) {
+ mZoneIdGroupIdToUidMap.put(zoneId, new SparseIntArray());
+ activationVolumeInvocationType =
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT;
+ } else if (mZoneIdGroupIdToUidMap.get(zoneId).get(groupId, INVALID_UID)
+ == INVALID_UID) {
+ activationVolumeInvocationType =
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT;
+ } else {
+ // For a playback with a uid existing for the given zone id and volume group id,
+ // whether it changes the source from the previous playback is determines by
+ // whether it has the same uid.
+ int prevUid = mZoneIdGroupIdToUidMap.get(zoneId).get(groupId);
+ activationVolumeInvocationType = uid == prevUid
+ ? CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED
+ : CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_SOURCE_CHANGED;
+ }
+ mZoneIdGroupIdToUidMap.get(zoneId).put(groupId, uid);
+ }
+ return new ActivationInfo(volumeGroup.getId(),
+ activationVolumeInvocationType);
+ }
+
+ static final class ActivationInfo {
+ final int mGroupId;
+ @ActivationVolumeInvocationType
+ final int mInvocationType;
+
+ ActivationInfo(int groupId, @ActivationVolumeInvocationType int type) {
+ mGroupId = groupId;
+ mInvocationType = type;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ActivationInfo)) {
+ return false;
+ }
+ ActivationInfo other = (ActivationInfo) o;
+ return other.mGroupId == mGroupId && other.mInvocationType == mInvocationType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mGroupId, mInvocationType);
+ }
+
+ @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
+ @Override
+ public String toString() {
+ return "ActivationInfo { volume group id: " + mGroupId + ", invocation type = "
+ + mInvocationType + "}";
+ }
+ }
+
+ private class CarCallStateListener extends TelephonyCallback
+ implements TelephonyCallback.CallStateListener {
+ @Override
+ public void onCallStateChanged(int state) {
+ int usage;
+ switch (state) {
+ case TelephonyManager.CALL_STATE_RINGING:
+ usage = USAGE_NOTIFICATION_RINGTONE;
+ break;
+ case TelephonyManager.CALL_STATE_OFFHOOK:
+ usage = USAGE_VOICE_COMMUNICATION;
+ break;
+ default:
+ return;
+ }
+ AudioAttributes audioAttributes = CarAudioContext.getAudioAttributeFromUsage(usage);
+ // TODO(331680279): get actual Uid from telephony or define a fake Uid for telephony
+ // playback only.
+ onActiveAudioPlaybackAttributesAdded(List.of(new Pair<>(audioAttributes,
+ Binder.getCallingUid())), CarAudioManager.PRIMARY_AUDIO_ZONE);
+ }
}
}
diff --git a/service/src/com/android/car/audio/CarAudioPolicyVolumeCallback.java b/service/src/com/android/car/audio/CarAudioPolicyVolumeCallback.java
index ee521e1..1b05f11 100644
--- a/service/src/com/android/car/audio/CarAudioPolicyVolumeCallback.java
+++ b/service/src/com/android/car/audio/CarAudioPolicyVolumeCallback.java
@@ -17,7 +17,7 @@
package com.android.car.audio;
import static android.car.builtin.media.AudioManagerHelper.adjustToString;
-import static android.car.builtin.media.AudioManagerHelper.isMasterMute;
+import static android.car.media.CarAudioManager.INVALID_VOLUME_GROUP_ID;
import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
import static android.media.AudioManager.ADJUST_LOWER;
import static android.media.AudioManager.ADJUST_MUTE;
@@ -36,7 +36,6 @@
import android.car.oem.OemCarAudioVolumeRequest;
import android.car.oem.OemCarVolumeChangeInfo;
import android.media.AudioAttributes;
-import android.media.AudioManager;
import android.media.audiopolicy.AudioPolicy;
import android.util.Log;
@@ -50,7 +49,7 @@
public static final boolean DEBUG = Slogf.isLoggable(TAG_AUDIO, Log.DEBUG);
- private final AudioManager mAudioManager;
+ private final AudioManagerWrapper mAudioManager;
private final boolean mUseCarVolumeGroupMuting;
private final AudioPolicyVolumeCallbackInternal mVolumeCallback;
private final CarVolumeInfoWrapper mCarVolumeInfo;
@@ -65,7 +64,7 @@
}
CarAudioPolicyVolumeCallback(AudioPolicyVolumeCallbackInternal volumeCallback,
- AudioManager audioManager, CarVolumeInfoWrapper carVolumeInfo,
+ AudioManagerWrapper audioManager, CarVolumeInfoWrapper carVolumeInfo,
boolean useCarVolumeGroupMuting) {
mVolumeCallback = Objects.requireNonNull(volumeCallback, "Volume Callback cannot be null");
mAudioManager = Objects.requireNonNull(audioManager, "AudioManager cannot be null");
@@ -146,6 +145,13 @@
private void evaluateVolumeAdjustmentInternal(int adjustment, int zoneId) {
int groupId = mCarVolumeInfo.getVolumeGroupIdForAudioZone(zoneId);
+ if (groupId == INVALID_VOLUME_GROUP_ID) {
+ // This can happen if all volume groups are configured with dynamic devices and the
+ // configuration to allow volume key events to dynamic devices is disabled.
+ Slogf.w(TAG_AUDIO, "onVolumeAdjustment: %s but no suitable volume group id was found.",
+ adjustToString(adjustment));
+ return;
+ }
boolean isMuted = isMuted(zoneId, groupId);
if (Slogf.isLoggable(TAG_AUDIO, VERBOSE)) {
@@ -196,7 +202,7 @@
if (mUseCarVolumeGroupMuting) {
return mCarVolumeInfo.isVolumeGroupMuted(zoneId, groupId);
}
- return isMasterMute(mAudioManager);
+ return mAudioManager.isMasterMuted();
}
private boolean isOemDuckingServiceAvailable() {
diff --git a/service/src/com/android/car/audio/CarAudioServerStateCallback.java b/service/src/com/android/car/audio/CarAudioServerStateCallback.java
index ce38a0e..a0f3dac 100644
--- a/service/src/com/android/car/audio/CarAudioServerStateCallback.java
+++ b/service/src/com/android/car/audio/CarAudioServerStateCallback.java
@@ -36,7 +36,7 @@
@Override
public void onAudioServerDown() {
Slogf.w(TAG, "Audio server died, setting audio as disabled");
- mCarAudioService.setAudioEnabled(false);
+ mCarAudioService.releaseAudioCallbacks(/* isAudioServerDown= */ true);
}
@Override
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 07347dd..cf5876f 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -16,7 +16,6 @@
package com.android.car.audio;
import static android.car.builtin.media.AudioManagerHelper.UNDEFINED_STREAM_TYPE;
-import static android.car.builtin.media.AudioManagerHelper.isMasterMute;
import static android.car.feature.Flags.carAudioFadeManagerConfiguration;
import static android.car.media.CarAudioManager.AUDIO_FEATURE_AUDIO_MIRRORING;
import static android.car.media.CarAudioManager.AUDIO_FEATURE_DYNAMIC_ROUTING;
@@ -112,6 +111,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -161,6 +161,7 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -181,8 +182,6 @@
private static final String MIRROR_COMMAND_DESTINATION = "mirroring_dst=";
private static final String DISABLE_AUDIO_MIRRORING = "mirroring=off";
- private static final String CAR_AUDIO_SERVICE_THREAD_NAME = "CarAudioService";
-
static final AudioAttributes CAR_DEFAULT_AUDIO_ATTRIBUTE =
CarAudioContext.getAudioAttributeFromUsage(USAGE_MEDIA);
@@ -210,14 +209,14 @@
private static final int EVENT_LOGGER_QUEUE_SIZE = 50;
private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
- CAR_AUDIO_SERVICE_THREAD_NAME);
+ CarAudioService.class.getSimpleName());
private final Handler mHandler = new Handler(mHandlerThread.getLooper());
private final Object mImplLock = new Object();
private final Context mContext;
private final TelephonyManager mTelephonyManager;
- private final AudioManager mAudioManager;
+ private final AudioManagerWrapper mAudioManagerWrapper;
private final boolean mUseDynamicRouting;
private final boolean mUseCoreAudioVolume;
private final boolean mUseCoreAudioRouting;
@@ -226,6 +225,7 @@
private final boolean mUseHalDuckingSignals;
private final boolean mUseMinMaxActivationVolume;
private final boolean mUseIsolatedFocusForDynamicDevices;
+ private final boolean mUseKeyEventsForDynamicDevices;
private final @CarVolume.CarVolumeListVersion int mAudioVolumeAdjustmentContextsVersion;
private final boolean mPersistMasterMuteState;
private final boolean mUseFadeManagerConfiguration;
@@ -237,21 +237,28 @@
private final CarVolumeEventHandler mCarVolumeEventHandler = new CarVolumeEventHandler();
private final AudioServerStateCallback mAudioServerStateCallback;
- private final CarAudioDeviceCallback mAudioDeviceInfoCallback;
-
private final LocalLog mServiceEventLogger = new LocalLog(EVENT_LOGGER_QUEUE_SIZE);
- private AudioControlWrapper mAudioControlWrapper;
+ @GuardedBy("mImplLock")
+ private @Nullable AudioControlWrapper mAudioControlWrapper;
private CarDucking mCarDucking;
private CarVolumeGroupMuting mCarVolumeGroupMuting;
- private HalAudioFocus mHalAudioFocus;
+ @GuardedBy("mImplLock")
+ private @Nullable HalAudioFocus mHalAudioFocus;
+
private @Nullable CarAudioGainMonitor mCarAudioGainMonitor;
@GuardedBy("mImplLock")
private @Nullable CoreAudioVolumeGroupCallback mCoreAudioVolumeGroupCallback;
+ @GuardedBy("mImplLock")
+ private CarAudioDeviceCallback mAudioDeviceInfoCallback;
- private CarOccupantZoneService mOccupantZoneService;
+ @GuardedBy("mImplLock")
private CarAudioModuleChangeMonitor mCarAudioModuleChangeMonitor;
+ @GuardedBy("mImplLock")
private @Nullable CarAudioPlaybackMonitor mCarAudioPlaybackMonitor;
+ @GuardedBy("mImplLock")
+ private boolean mIsAudioServerDown;
+
/**
* Simulates {@link ICarVolumeCallback} when it's running in legacy mode.
@@ -287,8 +294,9 @@
if (event.getAction() != ACTION_DOWN) {
return;
}
- int audioZoneId = mOccupantZoneService.getAudioZoneIdForOccupant(
- mOccupantZoneService.getOccupantZoneIdForSeat(seat));
+ CarOccupantZoneService carOccupantZoneService = getCarOccupantZoneService();
+ int audioZoneId = carOccupantZoneService.getAudioZoneIdForOccupant(
+ carOccupantZoneService.getOccupantZoneIdForSeat(seat));
if (!isAudioZoneIdValid(audioZoneId)) {
Slogf.e(TAG, "Audio zone is invalid for event %s, displayType %d, and seat %d",
event, displayType, seat);
@@ -310,19 +318,22 @@
break;
}
synchronized (mImplLock) {
+ if (mCarAudioPolicyVolumeCallback == null) {
+ return;
+ }
mCarAudioPolicyVolumeCallback.onVolumeAdjustment(adjustment, audioZoneId);
}
}
};
@GuardedBy("mImplLock")
- private AudioPolicy mVolumeControlAudioPolicy;
+ @Nullable private AudioPolicy mVolumeControlAudioPolicy;
@GuardedBy("mImplLock")
- private AudioPolicy mFocusControlAudioPolicy;
+ @Nullable private AudioPolicy mFocusControlAudioPolicy;
@GuardedBy("mImplLock")
- private AudioPolicy mRoutingAudioPolicy;
+ @Nullable private AudioPolicy mRoutingAudioPolicy;
@GuardedBy("mImplLock")
- private AudioPolicy mFadeManagerConfigAudioPolicy;
+ @Nullable private AudioPolicy mFadeManagerConfigAudioPolicy;
private CarZonesAudioFocus mFocusHandler;
private String mCarAudioConfigurationPath;
private String mCarAudioFadeConfigurationPath;
@@ -378,7 +389,7 @@
}
};
@GuardedBy("mImplLock")
- private CarAudioPolicyVolumeCallback mCarAudioPolicyVolumeCallback;
+ private @Nullable CarAudioPolicyVolumeCallback mCarAudioPolicyVolumeCallback;
private final HalAudioModuleChangeCallback mHalAudioModuleChangeCallback =
new HalAudioModuleChangeCallback() {
@@ -391,21 +402,23 @@
};
public CarAudioService(Context context) {
- this(context, getAudioConfigurationPath(), new CarVolumeCallbackHandler(),
- getAudioFadeConfigurationPath());
+ this(context, /* audioManagerWrapper = */ null, getAudioConfigurationPath(),
+ new CarVolumeCallbackHandler(), getAudioFadeConfigurationPath());
}
@VisibleForTesting
- CarAudioService(Context context, @Nullable String audioConfigurationPath,
+ CarAudioService(Context context, @Nullable AudioManagerWrapper audioManagerWrapper,
+ @Nullable String audioConfigurationPath,
CarVolumeCallbackHandler carVolumeCallbackHandler,
@Nullable String audioFadeConfigurationPath) {
mContext = Objects.requireNonNull(context,
"Context to create car audio service can not be null");
mCarAudioConfigurationPath = audioConfigurationPath;
mCarAudioFadeConfigurationPath = audioFadeConfigurationPath;
- mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ mAudioManagerWrapper = audioManagerWrapper == null
+ ? new AudioManagerWrapper(mContext.getSystemService(AudioManager.class))
+ : audioManagerWrapper;
mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
mUseCoreAudioVolume = mContext.getResources().getBoolean(R.bool.audioUseCoreVolume);
mUseCoreAudioRouting = mContext.getResources().getBoolean(R.bool.audioUseCoreRouting);
@@ -435,9 +448,11 @@
mUseIsolatedFocusForDynamicDevices = Flags.carAudioDynamicDevices() && !runInLegacyMode()
&& mContext.getResources().getBoolean(
R.bool.audioUseIsolatedAudioFocusForDynamicDevices);
+ mUseKeyEventsForDynamicDevices = Flags.carAudioDynamicDevices() && !runInLegacyMode()
+ && mContext.getResources().getBoolean(
+ R.bool.audioEnableVolumeKeyEventsToDynamicDevices);
validateFeatureFlagSettings();
mAudioServerStateCallback = new CarAudioServerStateCallback(this);
- mAudioDeviceInfoCallback = new CarAudioDeviceCallback(this);
}
private void validateFeatureFlagSettings() {
@@ -451,13 +466,21 @@
*/
@Override
public void init() {
+ boolean isAudioServerDown = !mAudioManagerWrapper.isAudioServerRunning();
+ mAudioManagerWrapper.setAudioServerStateCallback(mContext.getMainExecutor(),
+ mAudioServerStateCallback);
synchronized (mImplLock) {
- mOccupantZoneService = CarLocalServices.getService(CarOccupantZoneService.class);
mCarInputService = CarLocalServices.getService(CarInputService.class);
- if (!runInLegacyMode()) {
+ mIsAudioServerDown = isAudioServerDown;
+ if (mIsAudioServerDown) {
+ mServiceEventLogger.log("Audio server is down at init");
+ Slogf.e(TAG, "Audio server is down at init, will wait for server state callback"
+ + " to initialize");
+ return;
+ } else if (!runInLegacyMode()) {
// Must be called before setting up policies or audio control hal
loadAndInitCarAudioZonesLocked();
- setupCarAudioPlaybackMonitor();
+ setupCarAudioPlaybackMonitorLocked();
setupAudioControlDuckingAndVolumeControlLocked();
setupControlAndRoutingAudioPoliciesLocked();
setupFadeManagerConfigAudioPolicyLocked();
@@ -468,32 +491,37 @@
setupPowerPolicyListener();
mCarInputService.registerKeyEventListener(mCarKeyEventListener,
KEYCODES_OF_INTEREST);
- setupAudioDeviceInfoCallback();
+ setupAudioDeviceInfoCallbackLocked();
} else {
Slogf.i(TAG, "Audio dynamic routing not enabled, run in legacy mode");
setupLegacyVolumeChangedListener();
}
-
- mAudioManager.setSupportedSystemUsages(CarAudioContext.getSystemUsages());
- mAudioManager.setAudioServerStateCallback(mContext.getMainExecutor(),
- mAudioServerStateCallback);
}
-
+ setSupportedUsages();
restoreMasterMuteState();
+
}
- private void setupAudioDeviceInfoCallback() {
+ private void setSupportedUsages() {
+ mAudioManagerWrapper.setSupportedSystemUsages(CarAudioContext.getSystemUsages());
+ }
+
+ @GuardedBy("mImplLock")
+ private void setupAudioDeviceInfoCallbackLocked() {
if (!Flags.carAudioDynamicDevices()) {
return;
}
- mAudioManager.registerAudioDeviceCallback(mAudioDeviceInfoCallback, mHandler);
+ mAudioDeviceInfoCallback = new CarAudioDeviceCallback(this);
+ mAudioManagerWrapper.registerAudioDeviceCallback(mAudioDeviceInfoCallback, mHandler);
}
- private void releaseAudioDeviceInfoCallback() {
+ @GuardedBy("mImplLock")
+ private void releaseAudioDeviceInfoCallbackLocked() {
if (!Flags.carAudioDynamicDevices()) {
return;
}
- mAudioManager.unregisterAudioDeviceCallback(mAudioDeviceInfoCallback);
+ mAudioManagerWrapper.unregisterAudioDeviceCallback(mAudioDeviceInfoCallback);
+ mAudioDeviceInfoCallback = null;
}
private void setupPowerPolicyListener() {
@@ -514,46 +542,113 @@
@Override
public void release() {
+ mAudioManagerWrapper.clearAudioServerStateCallback();
+ releaseAudioCallbacks(/* isAudioServerDown= */ false);
synchronized (mImplLock) {
- if (!runInLegacyMode()) {
- releaseAudioPoliciesLocked();
- releaseAudioDeviceInfoCallback();
- } else {
- AudioManagerHelper.unregisterVolumeAndMuteReceiver(mContext,
- mLegacyVolumeChangedHelper);
- }
- if (mCoreAudioVolumeGroupCallback != null) {
- mCoreAudioVolumeGroupCallback.release();
- }
mCarVolumeCallbackHandler.release();
- mOccupantZoneService.unregisterCallback(mOccupantZoneCallback);
+ }
+ }
- if (mHalAudioFocus != null) {
- mHalAudioFocus.unregisterFocusListener();
- }
-
- if (mAudioControlWrapper != null) {
- try {
- mAudioControlWrapper.clearModuleChangeCallback();
- } catch (Exception e) {
- Slogf.w(TAG, "Failed to clear audio control wrapper module change callback",
- e);
- }
- mAudioControlWrapper.unlinkToDeath();
- mAudioControlWrapper = null;
- }
-
- if (mCarAudioPowerListener != null) {
- mCarAudioPowerListener.stopListeningForPolicyChanges();
- }
- mAudioManager.clearAudioServerStateCallback();
+ void releaseAudioCallbacks(boolean isAudioServerDown) {
+ synchronized (mImplLock) {
+ mIsAudioServerDown = isAudioServerDown;
+ releaseLegacyVolumeAndMuteReceiverLocked();
+ // If the audio server is down prevent from unregistering the audio policy
+ // otherwise car audio service may run into a lock contention with the audio server
+ // until it fully recovers
+ releaseAudioPoliciesLocked(!isAudioServerDown);
+ releaseAudioPlaybackCallbackLocked();
+ // There is an inherent dependency from HAL audio focus (AFH)
+ // to audio control HAL (ACH), since AFH holds a reference to ACH
+ releaseHalAudioFocusLocked();
+ releaseCoreVolumeGroupCallbackLocked();
+ releaseAudioPlaybackMonitorLocked();
+ releasePowerListenerLocked();
+ releaseAudioDeviceInfoCallbackLocked();
+ releaseHalAudioModuleChangeCallbackLocked();
+ CarOccupantZoneService occupantZoneService = getCarOccupantZoneService();
+ occupantZoneService.unregisterCallback(mOccupantZoneCallback);
mCarInputService.unregisterKeyEventListener(mCarKeyEventListener);
+ // Audio control may be running in the same process as audio server.
+ // Thus we can not release the audio control wrapper for now
+ if (mIsAudioServerDown) {
+ return;
+ }
+ // Audio control wrapper must be released last
+ releaseAudioControlWrapperLocked();
+ }
+ }
+
+ private CarOccupantZoneService getCarOccupantZoneService() {
+ return CarLocalServices.getService(CarOccupantZoneService.class);
+ }
+
+ @GuardedBy("mImplLock")
+ private void releaseLegacyVolumeAndMuteReceiverLocked() {
+ if (!runInLegacyMode()) {
+ return;
+ }
+ AudioManagerHelper.unregisterVolumeAndMuteReceiver(mContext, mLegacyVolumeChangedHelper);
+ }
+
+ @GuardedBy("mImplLock")
+ private void releasePowerListenerLocked() {
+ if (mCarAudioPowerListener == null) {
+ return;
+ }
+ mCarAudioPowerListener.stopListeningForPolicyChanges();
+ mCarAudioPowerListener = null;
+ }
+
+ @GuardedBy("mImplLock")
+ private void releaseAudioPlaybackMonitorLocked() {
+ if (mCarAudioPlaybackMonitor == null) {
+ return;
+ }
+ mCarAudioPlaybackMonitor.reset();
+ mCarAudioPlaybackMonitor = null;
+ }
+
+ @GuardedBy("mImplLock")
+ private void releaseCoreVolumeGroupCallbackLocked() {
+ if (mCoreAudioVolumeGroupCallback == null) {
+ return;
+ }
+ mCoreAudioVolumeGroupCallback.release();
+ mCoreAudioVolumeGroupCallback = null;
+ }
+
+ @GuardedBy("mImplLock")
+ private void releaseAudioControlWrapperLocked() {
+ if (mAudioControlWrapper != null) {
+ mAudioControlWrapper.unlinkToDeath();
+ mAudioControlWrapper = null;
}
}
@GuardedBy("mImplLock")
- private void releaseAudioPoliciesLocked() {
- releaseAudioRoutingPolicyLocked();
+ private void releaseHalAudioFocusLocked() {
+ if (mHalAudioFocus == null) {
+ return;
+ }
+ mHalAudioFocus.unregisterFocusListener();
+ mHalAudioFocus = null;
+ }
+
+ @GuardedBy("mImplLock")
+ private void releaseAudioPlaybackCallbackLocked() {
+ if (mCarAudioPlaybackCallback == null) {
+ return;
+ }
+ mAudioManagerWrapper.unregisterAudioPlaybackCallback(mCarAudioPlaybackCallback);
+ mCarAudioPlaybackCallback = null;
+ }
+
+ @GuardedBy("mImplLock")
+ private void releaseAudioPoliciesLocked(boolean unregisterRoutingPolicy) {
+ if (unregisterRoutingPolicy) {
+ releaseAudioRoutingPolicyLocked();
+ }
releaseVolumeControlAudioPolicyLocked();
releaseFocusControlAudioPolicyLocked();
releaseFadeManagerConfigAudioPolicyLocked();
@@ -564,7 +659,7 @@
if (mVolumeControlAudioPolicy == null) {
return;
}
- mAudioManager.unregisterAudioPolicyAsync(mVolumeControlAudioPolicy);
+ mAudioManagerWrapper.unregisterAudioPolicy(mVolumeControlAudioPolicy);
mVolumeControlAudioPolicy = null;
mCarAudioPolicyVolumeCallback = null;
}
@@ -574,7 +669,7 @@
if (mFocusControlAudioPolicy == null) {
return;
}
- mAudioManager.unregisterAudioPolicyAsync(mFocusControlAudioPolicy);
+ mAudioManagerWrapper.unregisterAudioPolicy(mFocusControlAudioPolicy);
mFocusControlAudioPolicy = null;
mFocusHandler.setOwningPolicy(null, null);
mFocusHandler = null;
@@ -585,7 +680,7 @@
if (mRoutingAudioPolicy == null) {
return;
}
- mAudioManager.unregisterAudioPolicyAsync(mRoutingAudioPolicy);
+ mAudioManagerWrapper.unregisterAudioPolicyAsync(mRoutingAudioPolicy);
mRoutingAudioPolicy = null;
}
@@ -595,7 +690,7 @@
return;
}
- mAudioManager.unregisterAudioPolicyAsync(mFadeManagerConfigAudioPolicy);
+ mAudioManagerWrapper.unregisterAudioPolicy(mFadeManagerConfigAudioPolicy);
mFadeManagerConfigAudioPolicy = null;
}
@@ -623,7 +718,7 @@
writer.println("Current State:");
writer.increaseIndent();
- writer.printf("Master muted? %b\n", isMasterMute(mAudioManager));
+ writer.printf("Master muted? %b\n", mAudioManagerWrapper.isMasterMuted());
if (mCarAudioPowerListener != null) {
writer.printf("Audio enabled? %b\n", mCarAudioPowerListener.isAudioEnabled());
}
@@ -637,6 +732,8 @@
writer.printf("Use min/max activation volume? %b\n", mUseMinMaxActivationVolume);
writer.printf("Use isolated focus for dynamic devices? %b\n",
mUseIsolatedFocusForDynamicDevices);
+ writer.printf("Allow key events to dynamic devices? %b\n",
+ mUseKeyEventsForDynamicDevices);
writer.println();
mCarVolume.dump(writer);
writer.println();
@@ -724,7 +821,7 @@
public void dumpProto(ProtoOutputStream proto) {
synchronized (mImplLock) {
long currentStateToken = proto.start(CarAudioDumpProto.CURRENT_STATE);
- proto.write(CarAudioState.MASTER_MUTED, isMasterMute(mAudioManager));
+ proto.write(CarAudioState.MASTER_MUTED, mAudioManagerWrapper.isMasterMuted());
if (mCarAudioPowerListener != null) {
proto.write(CarAudioState.AUDIO_ENABLED, mCarAudioPowerListener.isAudioEnabled());
}
@@ -855,7 +952,7 @@
// For legacy stream type based volume control
boolean wasMute;
if (runInLegacyMode()) {
- mAudioManager.setStreamVolume(
+ mAudioManagerWrapper.setStreamVolume(
CarAudioDynamicRouting.STREAM_TYPES[groupId], index, flags);
return;
}
@@ -876,20 +973,24 @@
getVolumeGroupInfo(zoneId, groupId), callbackFlags, eventTypes)));
}
- void handleActivationVolumeWithAudioAttributes(List<AudioAttributes> audioAttributesList,
- int zoneId) {
+ void handleActivationVolumeWithActivationInfos(
+ List<CarAudioPlaybackMonitor.ActivationInfo> activationInfoList, int zoneId,
+ int zoneConfigId) {
ArrayList<Integer> groupIdList = new ArrayList<>();
synchronized (mImplLock) {
- for (int i = 0; i < audioAttributesList.size(); i++) {
- AudioAttributes audioAttributes = audioAttributesList.get(i);
+ if (mCarAudioZones.get(zoneId).getCurrentCarAudioZoneConfig().getZoneConfigId()
+ != zoneConfigId) {
+ Slogf.w(CarLog.TAG_AUDIO, "Zone configuration for zone %d is changed, no "
+ + "activation volume is invoked", zoneId);
+ return;
+ }
+ for (int i = 0; i < activationInfoList.size(); i++) {
+ int volumeGroupId = activationInfoList.get(i)
+ .mGroupId;
CarVolumeGroup volumeGroup = mCarAudioZones.get(zoneId)
- .getVolumeGroupForAudioAttributes(audioAttributes);
- if (volumeGroup == null) {
- Slogf.w(CarLog.TAG_AUDIO, "Audio attributes %s is not found in zone %d",
- audioAttributes, zoneId);
- continue;
- }
- if (!volumeGroup.handleActivationVolume()) {
+ .getCurrentVolumeGroup(volumeGroupId);
+ if (!volumeGroup.handleActivationVolume(
+ activationInfoList.get(i).mInvocationType)) {
continue;
}
groupIdList.add(volumeGroup.getId());
@@ -913,6 +1014,14 @@
EXTRA_INFO_SHOW_UI))));
}
+ @GuardedBy("mImplLock")
+ private void resetActivationTypeLocked(int zoneId) {
+ if (mCarAudioPlaybackMonitor == null) {
+ return;
+ }
+ mCarAudioPlaybackMonitor.resetActivationTypesForZone(zoneId);
+ }
+
private void handleMuteChanged(int zoneId, int groupId, int flags) {
if (!mUseCarVolumeGroupMuting) {
return;
@@ -934,7 +1043,7 @@
}
void setMasterMute(boolean mute, int flags) {
- AudioManagerHelper.setMasterMute(mAudioManager, mute, flags);
+ mAudioManagerWrapper.setMasterMute(mute, flags);
// Master Mute only applies to primary zone
callbackMasterMuteChange(PRIMARY_AUDIO_ZONE, flags);
@@ -945,7 +1054,7 @@
// Persists master mute state if applicable
if (mPersistMasterMuteState) {
- mCarAudioSettings.storeMasterMute(isMasterMute(mAudioManager));
+ mCarAudioSettings.storeMasterMute(mAudioManagerWrapper.isMasterMuted());
}
}
@@ -965,7 +1074,7 @@
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
if (runInLegacyMode()) {
- return mAudioManager.getStreamMaxVolume(
+ return mAudioManagerWrapper.getStreamMaxVolume(
CarAudioDynamicRouting.STREAM_TYPES[groupId]);
}
@@ -983,7 +1092,7 @@
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
if (runInLegacyMode()) {
- return mAudioManager.getStreamMinVolume(
+ return mAudioManagerWrapper.getStreamMinVolume(
CarAudioDynamicRouting.STREAM_TYPES[groupId]);
}
@@ -1002,7 +1111,7 @@
// For legacy stream type based volume control
if (runInLegacyMode()) {
- return mAudioManager.getStreamVolume(
+ return mAudioManagerWrapper.getStreamVolume(
CarAudioDynamicRouting.STREAM_TYPES[groupId]);
}
@@ -1054,7 +1163,7 @@
Objects.requireNonNull(callback, "Media audio request callback can not be null");
Objects.requireNonNull(info, "Occupant zone info can not be null");
- int audioZoneId = mOccupantZoneService.getAudioZoneIdForOccupant(info.zoneId);
+ int audioZoneId = getCarOccupantZoneService().getAudioZoneIdForOccupant(info.zoneId);
if (audioZoneId == PRIMARY_AUDIO_ZONE) {
throw new IllegalArgumentException("Occupant " + info
+ " already owns the primary audio zone");
@@ -1078,7 +1187,7 @@
if (mCarAudioMirrorRequestHandler.isMirrorEnabledForZone(audioZoneId)) {
long mirrorId = mCarAudioMirrorRequestHandler.getRequestIdForAudioZone(audioZoneId);
CarOccupantZoneManager.OccupantZoneInfo info =
- mOccupantZoneService.getOccupantForAudioZoneId(audioZoneId);
+ getCarOccupantZoneService().getOccupantForAudioZoneId(audioZoneId);
if (runIfFailed != null) {
runIfFailed.run();
}
@@ -1116,13 +1225,14 @@
return false;
}
- int audioZoneId = mOccupantZoneService.getAudioZoneIdForOccupant(info.zoneId);
+ CarOccupantZoneService carOccupantZoneService = getCarOccupantZoneService();
+ int audioZoneId = carOccupantZoneService.getAudioZoneIdForOccupant(info.zoneId);
verifyMirrorNotEnabledForZone(() -> mMediaRequestHandler
.rejectMediaAudioRequest(requestId), "allow", audioZoneId);
+ int userId = carOccupantZoneService.getUserForOccupant(info.zoneId);
synchronized (mImplLock) {
- int userId = mOccupantZoneService.getUserForOccupant(info.zoneId);
return handleAssignAudioFromUserIdToPrimaryAudioZoneLocked(token,
userId, audioZoneId, requestId);
}
@@ -1374,7 +1484,7 @@
"Audio zone must have an active user to allow mirroring");
}
- CarOccupantZoneManager.OccupantZoneInfo info = mOccupantZoneService
+ CarOccupantZoneManager.OccupantZoneInfo info = getCarOccupantZoneService()
.getOccupantForAudioZoneId(zoneId);
if (mMediaRequestHandler.isMediaAudioAllowedInPrimaryZone(info)) {
@@ -1442,7 +1552,7 @@
return;
}
- CarOccupantZoneManager.OccupantZoneInfo info = mOccupantZoneService
+ CarOccupantZoneManager.OccupantZoneInfo info = getCarOccupantZoneService()
.getOccupantForAudioZoneId(audioZoneId);
if (mMediaRequestHandler.isMediaAudioAllowedInPrimaryZone(info)) {
@@ -1511,7 +1621,7 @@
builder.append(MIRROR_COMMAND_SEPARATOR);
Slogf.i(TAG, "Sending mirror command to audio HAL: %s", builder);
- mAudioManager.setParameters(builder.toString());
+ mAudioManagerWrapper.setParameters(builder.toString());
}
private String getAudioMirroringOffCommand(String mirrorSource) {
@@ -1598,7 +1708,8 @@
if (newConfig.length == 0) {
Slogf.i(TAG, "Sending mirror off command to audio HAL for address %s",
mirrorDevice.getAddress());
- mAudioManager.setParameters(getAudioMirroringOffCommand(mirrorDevice.getAddress()));
+ mAudioManagerWrapper.setParameters(
+ getAudioMirroringOffCommand(mirrorDevice.getAddress()));
}
//Send the signal to current listeners at the end
@@ -1699,7 +1810,7 @@
}
private List<CarAudioDeviceInfo> generateCarAudioDeviceInfos() {
- AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
+ AudioDeviceInfo[] deviceInfos = mAudioManagerWrapper.getDevices(
AudioManager.GET_DEVICES_OUTPUTS);
List<CarAudioDeviceInfo> carInfos = new ArrayList<>();
@@ -1711,7 +1822,7 @@
AudioDeviceInfo info = deviceInfos[index];
AudioDeviceAttributes attributes = new AudioDeviceAttributes(info);
- CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
// TODO(b/305301155): Move set audio device info closer to where it is used.
// On dynamic configuration change for example
carInfo.setAudioDeviceInfo(info);
@@ -1722,7 +1833,7 @@
}
private AudioDeviceInfo[] getAllInputDevices() {
- return mAudioManager.getDevices(
+ return mAudioManagerWrapper.getDevices(
AudioManager.GET_DEVICES_INPUTS);
}
@@ -1732,7 +1843,7 @@
try (InputStream fileStream = new FileInputStream(mCarAudioConfigurationPath);
InputStream inputStream = new BufferedInputStream(fileStream)) {
- CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mAudioManager,
+ CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, inputStream, carAudioDeviceInfos, inputDevices,
mServiceEventLogger, mUseCarVolumeGroupMuting, mUseCoreAudioVolume,
mUseCoreAudioRouting, mUseFadeManagerConfiguration,
@@ -1813,10 +1924,14 @@
}
@GuardedBy("mImplLock")
- private void setupCarAudioPlaybackMonitor() {
- if (mUseMinMaxActivationVolume) {
- mCarAudioPlaybackMonitor = new CarAudioPlaybackMonitor(this);
+ private void setupCarAudioPlaybackMonitorLocked() {
+ if (!mUseMinMaxActivationVolume) {
+ return;
}
+ int telephonyDefaultDataSubscriptionId = SubscriptionManager
+ .getDefaultDataSubscriptionId();
+ mCarAudioPlaybackMonitor = new CarAudioPlaybackMonitor(this, mCarAudioZones,
+ mTelephonyManager.createForSubscriptionId(telephonyDefaultDataSubscriptionId));
}
@GuardedBy("mImplLock")
@@ -1849,7 +1964,7 @@
return;
}
mCoreAudioVolumeGroupCallback = new CoreAudioVolumeGroupCallback(
- new CarVolumeInfoWrapper(this), mAudioManager);
+ new CarVolumeInfoWrapper(this), mAudioManagerWrapper);
mCoreAudioVolumeGroupCallback.init(mContext.getMainExecutor());
}
@@ -1867,13 +1982,13 @@
// Mirror policy has to be set before general audio policy
log.traceBegin("routing-policy-setup");
setupMirrorDevicePolicyLocked(builder);
- CarAudioDynamicRouting.setupAudioDynamicRouting(mCarAudioContext, mAudioManager, builder,
- mCarAudioZones);
+ CarAudioDynamicRouting.setupAudioDynamicRouting(mCarAudioContext, mAudioManagerWrapper,
+ builder, mCarAudioZones);
log.traceEnd();
AudioPolicy routingAudioPolicy = builder.build();
log.traceBegin("routing-policy-register");
- int r = mAudioManager.registerAudioPolicy(routingAudioPolicy);
+ int r = mAudioManagerWrapper.registerAudioPolicy(routingAudioPolicy);
log.traceEnd();
log.traceEnd();
@@ -1910,14 +2025,14 @@
};
mCarAudioPolicyVolumeCallback = new CarAudioPolicyVolumeCallback(volumeCallbackInternal,
- mAudioManager, new CarVolumeInfoWrapper(this), mUseCarVolumeGroupMuting);
+ mAudioManagerWrapper, new CarVolumeInfoWrapper(this), mUseCarVolumeGroupMuting);
// Attach the {@link AudioPolicyVolumeCallback}
CarAudioPolicyVolumeCallback.addVolumeCallbackToPolicy(volumeControlPolicyBuilder,
mCarAudioPolicyVolumeCallback);
mVolumeControlAudioPolicy = volumeControlPolicyBuilder.build();
- int status = mAudioManager.registerAudioPolicy(mVolumeControlAudioPolicy);
+ int status = mAudioManagerWrapper.registerAudioPolicy(mVolumeControlAudioPolicy);
if (status != AudioManager.SUCCESS) {
throw new IllegalStateException("Could not register the car audio service's volume"
+ " control audio policy, error: " + status);
@@ -1929,7 +2044,7 @@
// Used to configure our audio policy to handle focus events.
// This gives us the ability to decide which audio focus requests to accept and bypasses
// the framework ducking logic.
- mFocusHandler = CarZonesAudioFocus.createCarZonesAudioFocus(mAudioManager,
+ mFocusHandler = CarZonesAudioFocus.createCarZonesAudioFocus(mAudioManagerWrapper,
mContext.getPackageManager(), mCarAudioZones, mCarAudioSettings, mCarDucking,
new CarVolumeInfoWrapper(this), getAudioFeaturesInfo());
@@ -1942,7 +2057,7 @@
mFocusControlAudioPolicy = focusControlPolicyBuilder.build();
mFocusHandler.setOwningPolicy(this, mFocusControlAudioPolicy);
- int status = mAudioManager.registerAudioPolicy(mFocusControlAudioPolicy);
+ int status = mAudioManagerWrapper.registerAudioPolicy(mFocusControlAudioPolicy);
if (status != AudioManager.SUCCESS) {
throw new IllegalStateException("Could not register the car audio service's focus"
+ " control audio policy, error: " + status);
@@ -1972,7 +2087,7 @@
}
mFadeManagerConfigAudioPolicy = new AudioPolicy.Builder(mContext).build();
- int status = mAudioManager.registerAudioPolicy(mFadeManagerConfigAudioPolicy);
+ int status = mAudioManagerWrapper.registerAudioPolicy(mFadeManagerConfigAudioPolicy);
if (status != AudioManager.SUCCESS) {
throw new IllegalStateException("Could not register the car audio service's fade"
+ " configuration audio policy, error: " + status);
@@ -2036,14 +2151,14 @@
}
CarAudioDynamicRouting.setupAudioDynamicRoutingForMirrorDevice(mirrorPolicyBuilder,
- mCarAudioMirrorRequestHandler.getMirroringDeviceInfos(), mAudioManager);
+ mCarAudioMirrorRequestHandler.getMirroringDeviceInfos(), mAudioManagerWrapper);
}
@GuardedBy("mImplLock")
private void setupAudioConfigurationCallbackLocked() {
mCarAudioPlaybackCallback = new CarAudioPlaybackCallback(mCarAudioZones,
mCarAudioPlaybackMonitor, mClock, mKeyEventTimeoutMs);
- mAudioManager.registerAudioPlaybackCallback(mCarAudioPlaybackCallback, null);
+ mAudioManagerWrapper.registerAudioPlaybackCallback(mCarAudioPlaybackCallback, null);
}
@GuardedBy("mImplLock")
@@ -2051,9 +2166,35 @@
CarOccupantZoneService occupantZoneService;
SparseIntArray audioZoneIdToOccupantZoneMapping;
audioZoneIdToOccupantZoneMapping = mAudioZoneIdToOccupantZoneIdMapping;
- occupantZoneService = mOccupantZoneService;
+ occupantZoneService = getCarOccupantZoneService();
occupantZoneService.setAudioZoneIdsForOccupantZoneIds(audioZoneIdToOccupantZoneMapping);
occupantZoneService.registerCallback(mOccupantZoneCallback);
+ callOccupantConfigForSelfIfNeeded(occupantZoneService);
+ }
+
+ private void callOccupantConfigForSelfIfNeeded(CarOccupantZoneService occupantZoneService) {
+ int driverId = occupantZoneService.getDriverUserId();
+ boolean isSystemUser = UserHandle.SYSTEM.getIdentifier() == driverId;
+ // If the current driver is the system, then we need to wait for the user to be started.
+ // This will be triggered by the occupant zone service.
+ if (isSystemUser) {
+ return;
+ }
+ CarOccupantZoneManager.OccupantZoneInfo driverInfo =
+ occupantZoneService.getOccupantZoneForUser(UserHandle.of(driverId));
+ // If the driver is not configured then need to wait for the driver to be configured.
+ // This will be triggered by the occupant zone service.
+ if (driverInfo == null) {
+ return;
+ }
+ // Driver is already configured, need to handle the change given that we will not receive
+ // the user change callback. This must be handled in separate thread to prevent blocking the
+ // car service initialization. This may happen if audio server crash and car audio service
+ // is re-initializing or if the car audio service took too long to initialized and user
+ // driver occupant is already configured.
+ mServiceEventLogger.log("User already initialized during car audio service init,"
+ + " handling occupant zone config internally");
+ mHandler.post(this::handleOccupantZoneUserChanged);
}
@GuardedBy("mImplLock")
@@ -2064,7 +2205,7 @@
return;
}
- mHalAudioFocus = new HalAudioFocus(mAudioManager, mAudioControlWrapper,
+ mHalAudioFocus = new HalAudioFocus(mAudioManagerWrapper, mAudioControlWrapper,
mCarAudioPlaybackMonitor, mCarAudioContext, getAudioZoneIds());
mHalAudioFocus.registerFocusListener();
}
@@ -2076,9 +2217,11 @@
Slogf.d(CarLog.TAG_AUDIO, "HalAudioGainCallback is not supported on this device");
return;
}
- mCarAudioGainMonitor = new CarAudioGainMonitor(mAudioControlWrapper,
- new CarVolumeInfoWrapper(this), mCarAudioZones);
- mCarAudioGainMonitor.registerAudioGainListener(mHalAudioGainCallback);
+ synchronized (mImplLock) {
+ mCarAudioGainMonitor = new CarAudioGainMonitor(mAudioControlWrapper,
+ new CarVolumeInfoWrapper(this), mCarAudioZones);
+ mCarAudioGainMonitor.registerAudioGainListener(mHalAudioGainCallback);
+ }
}
@GuardedBy("mImplLock")
@@ -2093,6 +2236,19 @@
mCarAudioModuleChangeMonitor.setModuleChangeCallback(mHalAudioModuleChangeCallback);
}
+ @GuardedBy("mImplLock")
+ private void releaseHalAudioModuleChangeCallbackLocked() {
+ if (mCarAudioModuleChangeMonitor == null) {
+ return;
+ }
+ try {
+ mCarAudioModuleChangeMonitor.clearModuleChangeCallback();
+ } catch (Exception e) {
+ Slogf.w(TAG, "Failed to clear audio control wrapper module change callback", e);
+ }
+ mCarAudioModuleChangeMonitor = null;
+ }
+
/*
* Currently only BUS and BUILT_SPEAKER devices are valid static devices.
*/
@@ -2151,7 +2307,8 @@
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
List<String> sourceAddresses = new ArrayList<>();
- AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+ AudioDeviceInfo[] devices =
+ mAudioManagerWrapper.getDevices(AudioManager.GET_DEVICES_INPUTS);
if (devices.length == 0) {
Slogf.w(TAG, "getExternalSources, no input devices found");
}
@@ -2225,7 +2382,8 @@
@AttributeUsage int usage, int gainInMillibels) {
// Find the named source port
AudioDeviceInfo sourcePortInfo = null;
- AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+ AudioDeviceInfo[] deviceInfos =
+ mAudioManagerWrapper.getDevices(AudioManager.GET_DEVICES_INPUTS);
for (AudioDeviceInfo info : deviceInfos) {
if (sourceAddress.equals(info.getAddress())) {
// This is the one for which we're looking
@@ -2257,7 +2415,7 @@
private void releaseAudioPatchLocked(CarAudioPatchHandle carPatch) {
Objects.requireNonNull(carPatch);
- if (AudioManagerHelper.releaseAudioPatch(mAudioManager, getAudioPatchInfo(carPatch))) {
+ if (mAudioManagerWrapper.releaseAudioPatch(getAudioPatchInfo(carPatch))) {
Slogf.d(TAG, "releaseAudioPatch %s successfully", carPatch);
}
// If we didn't find a match, then something went awry, but it's probably not fatal...
@@ -2418,7 +2576,7 @@
int getCallStateForZone(int zoneId) {
synchronized (mImplLock) {
// Only driver can use telephony stack
- if (getUserIdForZoneLocked(zoneId) == mOccupantZoneService.getDriverUserId()) {
+ if (getUserIdForZoneLocked(zoneId) == getCarOccupantZoneService().getDriverUserId()) {
return mTelephonyManager.getCallState();
}
}
@@ -2427,7 +2585,7 @@
private List<AudioAttributes> getActiveAttributesFromPlaybackConfigurations(int zoneId) {
return getCarAudioZone(zoneId)
- .findActiveAudioAttributesFromPlaybackConfigurations(mAudioManager
+ .findActiveAudioAttributesFromPlaybackConfigurations(mAudioManagerWrapper
.getActivePlaybackConfigurations());
}
@@ -2490,12 +2648,13 @@
@GuardedBy("mImplLock")
private int getZoneIdForUserLocked(UserHandle handle) {
+ CarOccupantZoneService carOccupantZoneService = getCarOccupantZoneService();
CarOccupantZoneManager.OccupantZoneInfo info =
- mOccupantZoneService.getOccupantZoneForUser(handle);
+ carOccupantZoneService.getOccupantZoneForUser(handle);
int audioZoneId = CarAudioManager.INVALID_AUDIO_ZONE;
if (info != null) {
- audioZoneId = mOccupantZoneService.getAudioZoneIdForOccupant(info.zoneId);
+ audioZoneId = carOccupantZoneService.getAudioZoneIdForOccupant(info.zoneId);
}
return audioZoneId == CarAudioManager.INVALID_AUDIO_ZONE ? PRIMARY_AUDIO_ZONE : audioZoneId;
@@ -2615,7 +2774,7 @@
private AudioDeviceInfo getAudioDeviceInfoOrThrowIfNotFound(
AudioDeviceAttributes audioDeviceAttributes) {
AudioDeviceInfo info = CarAudioUtils.getAudioDeviceInfo(audioDeviceAttributes,
- mAudioManager);
+ mAudioManagerWrapper);
if (info != null) {
return info;
}
@@ -2639,6 +2798,9 @@
@GuardedBy("mImplLock")
private boolean setUserIdDeviceAffinityLocked(List<AudioDeviceInfo> devices,
int userId, int zoneId) {
+ if (mIsAudioServerDown || mRoutingAudioPolicy == null) {
+ return false;
+ }
boolean results = mRoutingAudioPolicy.setUserIdDeviceAffinity(userId, devices);
if (!results) {
Slogf.w(TAG, "setUserIdDeviceAffinityLocked for userId %d in zone %d Failed,"
@@ -2660,8 +2822,9 @@
Slogf.w(TAG, "Occupant %s is not mapped to any audio zone", info);
return false;
}
- int userId = mOccupantZoneService.getUserForOccupant(info.zoneId);
- int audioZoneId = mOccupantZoneService.getAudioZoneIdForOccupant(info.zoneId);
+ CarOccupantZoneService carOccupantZoneService = getCarOccupantZoneService();
+ int userId = carOccupantZoneService.getUserForOccupant(info.zoneId);
+ int audioZoneId = carOccupantZoneService.getAudioZoneIdForOccupant(info.zoneId);
synchronized (mImplLock) {
CarAudioZone audioZone = getCarAudioZoneLocked(audioZoneId);
@@ -2763,6 +2926,12 @@
*/
@GuardedBy("mImplLock")
private boolean setZoneIdForUidNoCheckLocked(int zoneId, int uid) {
+ if (mIsAudioServerDown || mRoutingAudioPolicy == null) {
+ Slogf.w(TAG, "setZoneIdForUidNoCheck Failed set device affinity"
+ + " for uid %d in zone %d, routing policy not available.",
+ uid, zoneId);
+ return false;
+ }
Slogf.d(TAG, "setZoneIdForUidNoCheck Calling uid %d mapped to %d", uid, zoneId);
//Request to add uid device affinity
List<AudioDeviceInfo> deviceInfos =
@@ -2785,6 +2954,12 @@
*/
@GuardedBy("mImplLock")
private boolean checkAndRemoveUidLocked(int uid) {
+ if (mIsAudioServerDown || mRoutingAudioPolicy == null) {
+ Slogf.w(TAG, "checkAndRemoveUid Failed remove device affinity for uid %d"
+ + ", routing policy not available.",
+ uid);
+ return false;
+ }
Integer zoneId = mUidToZoneMap.get(uid);
if (zoneId != null) {
Slogf.i(TAG, "checkAndRemoveUid removing Calling uid %d from zone %d", uid, zoneId);
@@ -2990,7 +3165,7 @@
}
CarOccupantZoneManager.OccupantZoneInfo info =
- mOccupantZoneService.getOccupantForAudioZoneId(zoneId);
+ getCarOccupantZoneService().getOccupantForAudioZoneId(zoneId);
if (mMediaRequestHandler.isMediaAudioAllowedInPrimaryZone(info)) {
throw new IllegalStateException(
@@ -3021,7 +3196,7 @@
}
CarOccupantZoneManager.OccupantZoneInfo info =
- mOccupantZoneService.getOccupantForAudioZoneId(zoneId);
+ getCarOccupantZoneService().getOccupantForAudioZoneId(zoneId);
if (mMediaRequestHandler.isMediaAudioAllowedInPrimaryZone(info)) {
Slogf.w(TAG, "handleSwitchZoneConfig failed, occupant %s in audio zone %d is "
+ "currently sharing to primary zone, undo audio sharing in primary "
@@ -3058,22 +3233,20 @@
try {
log.traceBegin("switch-config-set-" + zoneConfig.getConfigId());
zone.setCurrentCarZoneConfig(zoneConfig);
- CarAudioZoneConfig newZoneConfig = zone.getCurrentCarAudioZoneConfig();
- // Default config always exists in the policy, so should be able to switch to
- // default without creating a new policy
- newAudioPolicy = newZoneConfig.isDefault()
- ? mRoutingAudioPolicy : setupRoutingAudioPolicyLocked();
+ newAudioPolicy = setupRoutingAudioPolicyLocked();
setAllUserIdDeviceAffinitiesToNewPolicyLocked(newAudioPolicy);
+ swapRoutingAudioPolicyLocked(newAudioPolicy);
zone.updateVolumeGroupsSettingsForUser(userId);
carVolumeGroupInfoList = getVolumeGroupInfosForZoneLocked(zoneId);
updateFadeManagerConfigurationLocked(zone.isPrimaryZone());
+ resetActivationTypeLocked(zoneConfig.getZoneId());
} catch (Exception e) {
Slogf.e(TAG, "Failed to switch configuration id " + zoneConfig.getConfigId());
zone.setCurrentCarZoneConfig(prevZoneConfig.getCarAudioZoneConfigInfo());
succeeded = false;
// No need to unset the user id device affinities, since the policy is removed
if (newAudioPolicy != null && newAudioPolicy != mRoutingAudioPolicy) {
- mAudioManager.unregisterAudioPolicyAsync(newAudioPolicy);
+ mAudioManagerWrapper.unregisterAudioPolicyAsync(newAudioPolicy);
}
} finally {
log.traceEnd();
@@ -3081,9 +3254,6 @@
log.traceBegin("switch-config-focus" + zoneConfig.getConfigId());
mFocusHandler.reevaluateAndRegainAudioFocusList(pendingFocusInfos);
log.traceEnd();
- if (succeeded) {
- swapRoutingAudioPolicyLocked(newAudioPolicy);
- }
}
if (!succeeded) {
log.traceEnd();
@@ -3105,7 +3275,7 @@
return;
}
List<AudioDeviceInfo> dynamicDevicesInConfig =
- getDynamicDevicesInConfig(zoneConfig, mAudioManager);
+ getDynamicDevicesInConfig(zoneConfig, mAudioManagerWrapper);
// If the devices were already removed just move on, device removal will manage the rest
if (dynamicDevicesInConfig.isEmpty()) {
return;
@@ -3122,7 +3292,7 @@
return;
}
List<AudioDeviceInfo> dynamicDevicesInConfig =
- getDynamicDevicesInConfig(zoneConfig, mAudioManager);
+ getDynamicDevicesInConfig(zoneConfig, mAudioManagerWrapper);
// If the devices were already removed just move on, device removal will manage the rest
if (dynamicDevicesInConfig.isEmpty()) {
return;
@@ -3133,15 +3303,23 @@
@GuardedBy("mImplLock")
private void swapRoutingAudioPolicyLocked(AudioPolicy newAudioPolicy) {
+ TimingsTraceLog log = new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE);
+ log.traceBegin("swap-policy");
if (newAudioPolicy == mRoutingAudioPolicy) {
+ log.traceEnd();
return;
}
AudioPolicy previousRoutingPolicy = mRoutingAudioPolicy;
mRoutingAudioPolicy = newAudioPolicy;
if (previousRoutingPolicy == null) {
+ log.traceEnd();
return;
}
- mAudioManager.unregisterAudioPolicyAsync(previousRoutingPolicy);
+ try {
+ mAudioManagerWrapper.unregisterAudioPolicy(previousRoutingPolicy);
+ } finally {
+ log.traceEnd();
+ }
}
@GuardedBy("mImplLock")
@@ -3151,7 +3329,7 @@
for (int c = 0; c < mAudioZoneIdToOccupantZoneIdMapping.size(); c++) {
int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(c);
int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId);
- int userId = mOccupantZoneService.getUserForOccupant(occupantZoneId);
+ int userId = getCarOccupantZoneService().getUserForOccupant(occupantZoneId);
if (userId == UserManagerHelper.USER_NULL) {
continue;
}
@@ -3168,7 +3346,7 @@
CarAudioZone zone) {
List<AudioDeviceInfo> devices = getAudioDeviceInfos(zone);
CarOccupantZoneManager.OccupantZoneInfo info =
- mOccupantZoneService.getOccupantForAudioZoneId(zone.getId());
+ getCarOccupantZoneService().getOccupantForAudioZoneId(zone.getId());
if (mMediaRequestHandler.isMediaAudioAllowedInPrimaryZone(info)) {
devices.add(getMediaDeviceForPrimaryZoneLocked());
} else if (mCarAudioMirrorRequestHandler.isMirrorEnabledForZone(zone.getId())) {
@@ -3263,14 +3441,31 @@
}
return carVolume.getSuggestedAudioContextAndSaveIfFound(
getAllActiveAttributesForZone(zoneId), getCallStateForZone(zoneId),
- getActiveHalAudioAttributesForZone(zoneId));
+ getActiveHalAudioAttributesForZone(zoneId),
+ getInactiveAudioAttributesForZone(zoneId));
+ }
+
+ private List<AudioAttributes> getInactiveAudioAttributesForZone(int zoneId) {
+ if (mUseKeyEventsForDynamicDevices) {
+ return Collections.emptyList();
+ }
+
+ CarAudioZoneConfigInfo info;
+ synchronized (mImplLock) {
+ info = getCarAudioZoneLocked(zoneId).getCurrentCarAudioZoneConfig()
+ .getCarAudioZoneConfigInfo();
+ }
+
+ return CarAudioUtils.getAudioAttributesForDynamicDevices(info);
}
private List<AudioAttributes> getActiveHalAudioAttributesForZone(int zoneId) {
- if (mHalAudioFocus == null) {
- return new ArrayList<>(0);
+ synchronized (mImplLock) {
+ if (mHalAudioFocus == null) {
+ return new ArrayList<>(0);
+ }
+ return mHalAudioFocus.getActiveAudioAttributesForZone(zoneId);
}
- return mHalAudioFocus.getActiveAudioAttributesForZone(zoneId);
}
/**
@@ -3290,7 +3485,8 @@
}
private void handleOccupantZoneUserChanged() {
- int driverUserId = mOccupantZoneService.getDriverUserId();
+ int driverUserId = getCarOccupantZoneService().getDriverUserId();
+ Slogf.i(TAG, "handleOccupantZoneUserChanged current driver %s", driverUserId);
synchronized (mImplLock) {
if (!isOccupantZoneMappingAvailableLocked()) {
adjustZonesToUserIdLocked(driverUserId);
@@ -3350,6 +3546,7 @@
zone.updateVolumeGroupsSettingsForUser(userId);
mFocusHandler.updateUserForZoneId(zone.getId(), userId);
setUserIdForAudioZoneLocked(userId, zone.getId());
+ resetActivationTypeLocked(zone.getId());
}
@GuardedBy("mImplLock")
@@ -3361,7 +3558,7 @@
private void updateUserForOccupantZoneLocked(int occupantZoneId, int audioZoneId,
@UserIdInt int driverUserId, int occupantZoneForDriver) {
CarAudioZone audioZone = getCarAudioZoneLocked(audioZoneId);
- int userId = mOccupantZoneService.getUserForOccupant(occupantZoneId);
+ int userId = getCarOccupantZoneService().getUserForOccupant(occupantZoneId);
int prevUserId = getUserIdForZoneLocked(audioZoneId);
if (userId == prevUserId) {
@@ -3398,6 +3595,7 @@
audioZone.updateVolumeGroupsSettingsForUser(userId);
mFocusHandler.updateUserForZoneId(audioZoneId, userId);
setUserIdForAudioZoneLocked(userId, audioZoneId);
+ resetActivationTypeLocked(audioZoneId);
}
private void removeAudioMirrorForZoneId(int audioZoneId) {
@@ -3425,7 +3623,7 @@
private int getOccupantZoneIdForDriver() {
List<CarOccupantZoneManager.OccupantZoneInfo> occupantZoneInfos =
- mOccupantZoneService.getAllOccupantZones();
+ getCarOccupantZoneService().getAllOccupantZones();
for (CarOccupantZoneManager.OccupantZoneInfo info: occupantZoneInfos) {
if (info.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
return info.zoneId;
@@ -3437,6 +3635,10 @@
@GuardedBy("mImplLock")
private void setUserIdDeviceAffinitiesLocked(CarAudioZone zone, @UserIdInt int userId,
int audioZoneId) {
+ if (mIsAudioServerDown || mRoutingAudioPolicy == null) {
+ Slogf.w(TAG, "setUserIdDeviceAffinitiesLocked failed, audio policy not in bad state");
+ return;
+ }
List<AudioDeviceInfo> infos = getAudioDeviceInfos(zone);
if (!infos.isEmpty() && !mRoutingAudioPolicy.setUserIdDeviceAffinity(userId, infos)) {
throw new IllegalStateException(String.format(
@@ -3463,6 +3665,9 @@
private void resetZoneToDefaultUser(CarAudioZone zone, @UserIdInt int driverUserId) {
resetCarZonesAudioFocus(zone.getId(), driverUserId);
zone.updateVolumeGroupsSettingsForUser(driverUserId);
+ synchronized (mImplLock) {
+ resetActivationTypeLocked(zone.getId());
+ }
}
private void resetCarZonesAudioFocus(int audioZoneId, @UserIdInt int driverUserId) {
@@ -3475,9 +3680,12 @@
if (userId == UserManagerHelper.USER_NULL) {
return;
}
+ if (mIsAudioServerDown || mRoutingAudioPolicy == null) {
+ Slogf.e(TAG, "removeUserIdDeviceAffinities(%d) routing policy unavailable", userId);
+ return;
+ }
if (!mRoutingAudioPolicy.removeUserIdDeviceAffinity(userId)) {
Slogf.e(TAG, "removeUserIdDeviceAffinities(%d) Failed", userId);
- return;
}
}
@@ -3507,39 +3715,72 @@
return mAudioControlWrapper;
}
- private void resetHalAudioFocus() {
- if (mHalAudioFocus != null) {
- mHalAudioFocus.reset();
- mHalAudioFocus.registerFocusListener();
+ @GuardedBy("mImplLock")
+ private void resetHalAudioFocusLocked() {
+ if (mHalAudioFocus == null) {
+ return;
}
+ mHalAudioFocus.reset();
+ mHalAudioFocus.registerFocusListener();
}
- private void resetHalAudioGain() {
- if (mCarAudioGainMonitor != null) {
+ @GuardedBy("mImplLock")
+ private void resetHalAudioGainLocked() {
+ synchronized (mImplLock) {
+ if (mCarAudioGainMonitor == null) {
+ return;
+ }
mCarAudioGainMonitor.reset();
mCarAudioGainMonitor.registerAudioGainListener(mHalAudioGainCallback);
}
}
- private void resetHalAudioModuleChange() {
- if (mCarAudioModuleChangeMonitor != null) {
- mCarAudioModuleChangeMonitor.setModuleChangeCallback(mHalAudioModuleChangeCallback);
+ @GuardedBy("mImplLock")
+ private void resetHalAudioModuleChangeLocked() {
+ if (mCarAudioModuleChangeMonitor == null) {
+ return;
}
+ mCarAudioModuleChangeMonitor.setModuleChangeCallback(mHalAudioModuleChangeCallback);
}
+ @GuardedBy("mImplLock")
private void handleAudioDeviceGainsChangedLocked(
List<Integer> halReasons, List<CarAudioGainConfigInfo> gains) {
+ if (mCarAudioGainMonitor == null) {
+ return;
+ }
mCarAudioGainMonitor.handleAudioDeviceGainsChanged(halReasons, gains);
}
+ @GuardedBy("mImplLock")
private void handleAudioPortsChangedLocked(List<HalAudioDeviceInfo> deviceInfos) {
+ if (mCarAudioModuleChangeMonitor == null) {
+ return;
+ }
mCarAudioModuleChangeMonitor.handleAudioPortsChanged(deviceInfos);
}
private void audioControlDied() {
- resetHalAudioFocus();
- resetHalAudioGain();
- resetHalAudioModuleChange();
+ // If audio server is down, do not attempt to recover since it may lead to contention.
+ // Once the audio server is back up the audio control HAL will be re-initialized.
+ if (!mAudioManagerWrapper.isAudioServerRunning()) {
+ String message = "Audio control died while audio server is not running";
+ Slogf.w(TAG, message);
+ mServiceEventLogger.log(message);
+ return;
+ }
+ synchronized (mImplLock) {
+ // Verify the server has not gone down to prevent releasing audio control HAL
+ if (mIsAudioServerDown) {
+ String message = "Audio control died while audio server is down";
+ Slogf.w(TAG, message);
+ mServiceEventLogger.log(message);
+ return;
+ }
+ resetHalAudioFocusLocked();
+ resetHalAudioGainLocked();
+ resetHalAudioModuleChangeLocked();
+ }
}
boolean isAudioZoneIdValid(int zoneId) {
@@ -3628,7 +3869,7 @@
focusInfo.getAttributes());
return isMedia && mMediaRequestHandler
- .isMediaAudioAllowedInPrimaryZone(mOccupantZoneService
+ .isMediaAudioAllowedInPrimaryZone(getCarOccupantZoneService()
.getOccupantZoneForUser(UserHandle
.getUserHandleForUid(focusInfo.getClientUid())));
}
@@ -3686,6 +3927,11 @@
}
void audioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+ synchronized (mImplLock) {
+ if (mIsAudioServerDown) {
+ return;
+ }
+ }
Slogf.d(TAG, "Added audio devices " + Arrays.toString(addedDevices));
List<AudioDeviceInfo> devices = filterBusDevices(addedDevices);
@@ -3717,6 +3963,11 @@
}
void audioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+ synchronized (mImplLock) {
+ if (mIsAudioServerDown) {
+ return;
+ }
+ }
Slogf.d(TAG, "Removed audio devices " + Arrays.toString(removedDevices));
List<AudioDeviceInfo> devices = filterBusDevices(removedDevices);
diff --git a/service/src/com/android/car/audio/CarAudioUtils.java b/service/src/com/android/car/audio/CarAudioUtils.java
index 3c0697a..5594a50 100644
--- a/service/src/com/android/car/audio/CarAudioUtils.java
+++ b/service/src/com/android/car/audio/CarAudioUtils.java
@@ -32,6 +32,8 @@
import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET;
import static android.media.AudioManager.GET_DEVICES_OUTPUTS;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR;
+
import static java.util.Collections.EMPTY_LIST;
import android.annotation.Nullable;
@@ -39,15 +41,18 @@
import android.car.media.CarAudioZoneConfigInfo;
import android.car.media.CarVolumeGroupEvent;
import android.car.media.CarVolumeGroupInfo;
+import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
+
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import java.util.ArrayList;
import java.util.List;
final class CarAudioUtils {
+ @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR)
private CarAudioUtils() {
throw new UnsupportedOperationException();
}
@@ -75,7 +80,7 @@
@Nullable
static AudioDeviceInfo getAudioDeviceInfo(AudioDeviceAttributes audioDeviceAttributes,
- AudioManager audioManager) {
+ AudioManagerWrapper audioManager) {
AudioDeviceInfo[] infos = audioManager.getDevices(GET_DEVICES_OUTPUTS);
for (int c = 0; c < infos.length; c++) {
if (!infos[c].getAddress().equals(audioDeviceAttributes.getAddress())) {
@@ -108,7 +113,7 @@
}
static List<AudioDeviceInfo> getDynamicDevicesInConfig(CarAudioZoneConfigInfo zoneConfig,
- AudioManager manager) {
+ AudioManagerWrapper manager) {
return Flags.carAudioDynamicDevices()
? getDynamicAudioDevices(zoneConfig.getConfigVolumeGroups(), manager) : EMPTY_LIST;
}
@@ -137,8 +142,24 @@
return true;
}
+ static List<AudioAttributes> getAudioAttributesForDynamicDevices(CarAudioZoneConfigInfo info) {
+ List<AudioAttributes> audioAttributes = new ArrayList<>();
+ if (!Flags.carAudioDynamicDevices()) {
+ return audioAttributes;
+ }
+ List<CarVolumeGroupInfo> groups = info.getConfigVolumeGroups();
+ for (int c = 0; c < groups.size(); c++) {
+ CarVolumeGroupInfo groupInfo = groups.get(c);
+ if (excludesDynamicDevices(groupInfo.getAudioDeviceAttributes())) {
+ continue;
+ }
+ audioAttributes.addAll(groupInfo.getAudioAttributes());
+ }
+ return audioAttributes;
+ }
+
private static List<AudioDeviceInfo> getDynamicAudioDevices(
- List<CarVolumeGroupInfo> volumeGroups, AudioManager manager) {
+ List<CarVolumeGroupInfo> volumeGroups, AudioManagerWrapper manager) {
List<AudioDeviceInfo> dynamicDevices = new ArrayList<>();
for (int c = 0; c < volumeGroups.size(); c++) {
dynamicDevices.addAll(getDynamicDevices(volumeGroups.get(c), manager));
@@ -147,7 +168,7 @@
}
private static List<AudioDeviceInfo> getDynamicDevices(CarVolumeGroupInfo carVolumeGroupInfo,
- AudioManager manager) {
+ AudioManagerWrapper manager) {
List<AudioDeviceInfo> dynamicDevices = new ArrayList<>();
List<AudioDeviceAttributes> devices = carVolumeGroupInfo.getAudioDeviceAttributes();
for (int c = 0; c < devices.size(); c++) {
diff --git a/service/src/com/android/car/audio/CarAudioVolumeGroup.java b/service/src/com/android/car/audio/CarAudioVolumeGroup.java
index c24d58f..491e778 100644
--- a/service/src/com/android/car/audio/CarAudioVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarAudioVolumeGroup.java
@@ -51,11 +51,9 @@
CarAudioSettings settingsManager,
SparseArray<CarAudioDeviceInfo> contextToDeviceInfo, int zoneId, int configId,
int volumeGroupId, String name, int stepSize, int defaultGain, int minGain, int maxGain,
- boolean useCarVolumeGroupMute, int maxActivationVolumePercentage,
- int minActivationVolumePercentage) {
+ boolean useCarVolumeGroupMute, CarActivationVolumeConfig carActivationVolumeConfig) {
super(carAudioContext, settingsManager, contextToDeviceInfo, zoneId, configId,
- volumeGroupId, name, useCarVolumeGroupMute, maxActivationVolumePercentage,
- minActivationVolumePercentage);
+ volumeGroupId, name, useCarVolumeGroupMute, carActivationVolumeConfig);
Preconditions.checkArgument(stepSize != 0, "Step Size must not be zero");
mStepSize = stepSize;
mDefaultGain = defaultGain;
diff --git a/service/src/com/android/car/audio/CarAudioZone.java b/service/src/com/android/car/audio/CarAudioZone.java
index c1b068e..381a8a8 100644
--- a/service/src/com/android/car/audio/CarAudioZone.java
+++ b/service/src/com/android/car/audio/CarAudioZone.java
@@ -172,12 +172,6 @@
Slogf.w(CarLog.TAG_AUDIO, "No zone configurations for zone %d", mId);
return false;
}
- int currentConfigId = getCurrentConfigId();
- if (!mCarAudioZoneConfigs.contains(currentConfigId)) {
- Slogf.w(CarLog.TAG_AUDIO, "Current zone configuration %d for zone %d does not exist",
- currentConfigId, mId);
- return false;
- }
boolean isDefaultConfigFound = false;
for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) {
CarAudioZoneConfig zoneConfig = mCarAudioZoneConfigs.valueAt(index);
@@ -196,8 +190,7 @@
}
isDefaultConfigFound = true;
}
- if (!zoneConfig.validateVolumeGroups(mCarAudioContext,
- useCoreAudioRouting)) {
+ if (!zoneConfig.validateVolumeGroups(mCarAudioContext, useCoreAudioRouting)) {
return false;
}
}
@@ -325,7 +318,12 @@
*/
public void updateVolumeGroupsSettingsForUser(int userId) {
for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) {
- mCarAudioZoneConfigs.valueAt(index).updateVolumeGroupsSettingsForUser(userId);
+ CarAudioZoneConfig config = mCarAudioZoneConfigs.valueAt(index);
+ if (!config.isSelected()) {
+ continue;
+ }
+ config.updateVolumeGroupsSettingsForUser(userId);
+ break;
}
}
diff --git a/service/src/com/android/car/audio/CarAudioZoneConfig.java b/service/src/com/android/car/audio/CarAudioZoneConfig.java
index 6baaff3..7efed70 100644
--- a/service/src/com/android/car/audio/CarAudioZoneConfig.java
+++ b/service/src/com/android/car/audio/CarAudioZoneConfig.java
@@ -354,8 +354,9 @@
isFadeManagerConfigurationEnabled());
if (isFadeManagerConfigurationEnabled()) {
writer.printf("Default car audio fade manager config name: %s\n",
- mDefaultCarAudioFadeConfiguration.getName());
- writer.printf("Transient car audio fade manager configurations: %d\n",
+ mDefaultCarAudioFadeConfiguration == null ? "none"
+ : mDefaultCarAudioFadeConfiguration.getName());
+ writer.printf("Transient car audio fade manager configurations#: %d\n",
mAudioAttributesToCarAudioFadeConfiguration.size());
writer.increaseIndent();
for (Map.Entry<AudioAttributes, CarAudioFadeConfiguration> entry :
@@ -390,6 +391,7 @@
proto.end(zoneConfigToken);
}
+ @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
private void dumpAttributeToCarAudioFadeConfigProto(ProtoOutputStream proto) {
for (Map.Entry<AudioAttributes, CarAudioFadeConfiguration> entry :
mAudioAttributesToCarAudioFadeConfiguration.entrySet()) {
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
index bf77561..dc09f03 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -43,7 +43,6 @@
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
import android.media.audiopolicy.AudioProductStrategy;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -69,7 +68,6 @@
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.Set;
-import java.util.stream.Collectors;
/**
* A helper class loads all audio zones from the configuration XML file.
@@ -91,6 +89,7 @@
private static final String TAG_VOLUME_GROUP = "group";
private static final String TAG_AUDIO_DEVICE = "device";
private static final String TAG_CONTEXT = "context";
+ private static final String ATTR_ACTIVATION_VOLUME_CONFIG = "activationConfig";
private static final String ATTR_VERSION = "version";
private static final String ATTR_IS_PRIMARY = "isPrimary";
private static final String ATTR_IS_CONFIG_DEFAULT = "isDefault";
@@ -105,10 +104,20 @@
private static final String TAG_INPUT_DEVICE = "inputDevice";
private static final String TAG_MIRRORING_DEVICES = "mirroringDevices";
private static final String TAG_MIRRORING_DEVICE = "mirroringDevice";
+ private static final String TAG_ACTIVATION_VOLUME_CONFIGS = "activationVolumeConfigs";
+ private static final String TAG_ACTIVATION_VOLUME_CONFIG = "activationVolumeConfig";
+ private static final String TAG_ACTIVATION_VOLUME_CONFIG_ENTRY = "activationVolumeConfigEntry";
+ private static final String ATTR_ACTIVATION_VOLUME_CONFIG_NAME = "name";
+ private static final String ATTR_ACTIVATION_VOLUME_INVOCATION_TYPE = "invocationType";
private static final String ATTR_MAX_ACTIVATION_VOLUME_PERCENTAGE =
"maxActivationVolumePercentage";
private static final String ATTR_MIN_ACTIVATION_VOLUME_PERCENTAGE =
"minActivationVolumePercentage";
+ private static final String ACTIVATION_VOLUME_INVOCATION_TYPE_ON_BOOT = "onBoot";
+ private static final String ACTIVATION_VOLUME_INVOCATION_TYPE_ON_SOURCE_CHANGED =
+ "onSourceChanged";
+ private static final String ACTIVATION_VOLUME_INVOCATION_TYPE_ON_PLAYBACK_CHANGED =
+ "onPlaybackChanged";
private static final String TAG_APPLY_FADE_CONFIGS = "applyFadeConfigs";
private static final String FADE_CONFIG = "fadeConfig";
private static final String FADE_CONFIG_NAME = "name";
@@ -130,8 +139,12 @@
private static final int ACTIVATION_VOLUME_PERCENTAGE_MIN = 0;
private static final int ACTIVATION_VOLUME_PERCENTAGE_MAX = 100;
+ private static final int ACTIVATION_VOLUME_INVOCATION_TYPE =
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT
+ | CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_SOURCE_CHANGED
+ | CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED;
- private final AudioManager mAudioManager;
+ private final AudioManagerWrapper mAudioManager;
private final CarAudioSettings mCarAudioSettings;
private final List<CarAudioContextInfo> mCarAudioContextInfos = new ArrayList<>();
private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo;
@@ -147,6 +160,8 @@
private final boolean mUseFadeManagerConfiguration;
private final CarAudioFadeConfigurationHelper mCarAudioFadeConfigurationHelper;
private final List<CarAudioDeviceInfo> mMirroringDevices = new ArrayList<>();
+ private final Map<String, CarActivationVolumeConfig> mConfigNameToActivationVolumeConfig =
+ new ArrayMap<>();
private final ArrayMap<String, Integer> mContextNameToId = new ArrayMap<>();
private final LocalLog mCarServiceLocalLog;
@@ -158,7 +173,7 @@
* <p><b>Note: <b/> CarAudioZonesHelper is expected to be used from a single thread. This
* should be the same thread that originally called new CarAudioZonesHelper.
*/
- CarAudioZonesHelper(AudioManager audioManager, CarAudioSettings carAudioSettings,
+ CarAudioZonesHelper(AudioManagerWrapper audioManager, CarAudioSettings carAudioSettings,
InputStream inputStream, List<CarAudioDeviceInfo> carAudioDeviceInfos,
AudioDeviceInfo[] inputDeviceInfo, LocalLog serviceLog, boolean useCarVolumeGroupMute,
boolean useCoreAudioVolume, boolean useCoreAudioRouting,
@@ -198,9 +213,14 @@
private static Map<String, CarAudioDeviceInfo> generateAddressToInfoMap(
List<CarAudioDeviceInfo> carAudioDeviceInfos) {
- return carAudioDeviceInfos.stream()
- .filter(info -> !TextUtils.isEmpty(info.getAddress()))
- .collect(Collectors.toMap(CarAudioDeviceInfo::getAddress, info -> info));
+ Map<String, CarAudioDeviceInfo> addressToInfoMap = new ArrayMap<>();
+ for (int i = 0; i < carAudioDeviceInfos.size(); i++) {
+ CarAudioDeviceInfo info = carAudioDeviceInfos.get(i);
+ if (!TextUtils.isEmpty(info.getAddress())) {
+ addressToInfoMap.put(info.getAddress(), info);
+ }
+ }
+ return addressToInfoMap;
}
private static Map<String, AudioDeviceInfo> generateAddressToInputAudioDeviceInfoMap(
@@ -243,6 +263,8 @@
parseCarAudioContexts(parser);
} else if (Objects.equals(parser.getName(), TAG_MIRRORING_DEVICES)) {
parseMirroringDevices(parser);
+ } else if (Objects.equals(parser.getName(), TAG_ACTIVATION_VOLUME_CONFIGS)) {
+ parseActivationVolumeConfigs(parser);
} else if (Objects.equals(parser.getName(), TAG_AUDIO_ZONES)) {
loadCarAudioContexts();
return parseAudioZones(parser);
@@ -367,7 +389,6 @@
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
if (Objects.equals(parser.getName(), TAG_AUDIO_ZONE)) {
CarAudioZone zone = parseAudioZone(parser);
- verifyOnlyOnePrimaryZone(zone, carAudioZones);
carAudioZones.put(zone.getId(), zone);
} else {
CarAudioParserUtils.skip(parser);
@@ -389,13 +410,6 @@
}
}
- private void verifyOnlyOnePrimaryZone(CarAudioZone newZone, SparseArray<CarAudioZone> zones) {
- if (newZone.getId() == PRIMARY_AUDIO_ZONE && zones.contains(PRIMARY_AUDIO_ZONE)) {
- throw new RuntimeException("More than one zone parsed with primary audio zone ID: "
- + PRIMARY_AUDIO_ZONE);
- }
- }
-
private void verifyPrimaryZonePresent(SparseArray<CarAudioZone> zones) {
if (!zones.contains(PRIMARY_AUDIO_ZONE)) {
throw new RuntimeException("Primary audio zone is required");
@@ -626,18 +640,14 @@
.append(zoneConfigBuilder.getZoneConfigId()).append(" group ")
.append(groupId).toString();
}
- int maxActivationVolumePercentage = parseVolumeGroupActivationVolume(parser,
- /* isMax= */ true);
- int minActivationVolumePercentage = parseVolumeGroupActivationVolume(parser,
- /* isMax= */ false);
- validateMinMaxActivationVolume(maxActivationVolumePercentage,
- minActivationVolumePercentage);
+
+ CarActivationVolumeConfig activationVolumeConfig =
+ getActivationVolumeConfig(parser);
CarVolumeGroupFactory factory = new CarVolumeGroupFactory(mAudioManager,
mCarAudioSettings, mCarAudioContext, zoneConfigBuilder.getZoneId(),
zoneConfigBuilder.getZoneConfigId(), groupId, groupName,
- mUseCarVolumeGroupMute, maxActivationVolumePercentage,
- minActivationVolumePercentage);
+ mUseCarVolumeGroupMute, activationVolumeConfig);
if (!parseVolumeGroup(parser, factory)) {
valid = false;
@@ -654,7 +664,94 @@
return valid;
}
- private int parseVolumeGroupActivationVolume(XmlPullParser parser, boolean isMax) {
+ private CarActivationVolumeConfig getActivationVolumeConfig(XmlPullParser parser) {
+ String activationVolumeConfigName = parser.getAttributeValue(NAMESPACE,
+ ATTR_ACTIVATION_VOLUME_CONFIG);
+ if (activationVolumeConfigName != null) {
+ if (isVersionLessThanFour()) {
+ throw new IllegalArgumentException(TAG_VOLUME_GROUP + " "
+ + ATTR_ACTIVATION_VOLUME_CONFIG
+ + " attribute not supported for versions less than "
+ + SUPPORTED_VERSION_4 + ", but current version is "
+ + mCurrentVersion);
+ }
+ if (!mConfigNameToActivationVolumeConfig
+ .containsKey(activationVolumeConfigName)) {
+ throw new IllegalArgumentException(TAG_ACTIVATION_VOLUME_CONFIG + " with "
+ + ATTR_ACTIVATION_VOLUME_CONFIG_NAME + " attribute of "
+ + activationVolumeConfigName + " does not exist");
+ }
+ if (Flags.carAudioMinMaxActivationVolume()) {
+ return mConfigNameToActivationVolumeConfig.get(activationVolumeConfigName);
+ }
+ }
+ if (!Flags.carAudioMinMaxActivationVolume()) {
+ mCarServiceLocalLog.log("Found " + TAG_VOLUME_GROUP + " "
+ + ATTR_ACTIVATION_VOLUME_CONFIG
+ + " attribute while min/max activation volume is disabled");
+ }
+ return new CarActivationVolumeConfig(ACTIVATION_VOLUME_INVOCATION_TYPE,
+ ACTIVATION_VOLUME_PERCENTAGE_MIN, ACTIVATION_VOLUME_PERCENTAGE_MAX);
+ }
+
+ private void parseActivationVolumeConfigs(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ if (Objects.equals(parser.getName(), TAG_ACTIVATION_VOLUME_CONFIG)) {
+ parseActivationVolumeConfig(parser);
+ } else {
+ CarAudioParserUtils.skip(parser);
+ }
+ }
+ }
+
+ private void parseActivationVolumeConfig(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String activationConfigName = parser.getAttributeValue(NAMESPACE,
+ ATTR_ACTIVATION_VOLUME_CONFIG_NAME);
+ Objects.requireNonNull(activationConfigName, TAG_ACTIVATION_VOLUME_CONFIG + " "
+ + ATTR_ACTIVATION_VOLUME_CONFIG_NAME + " attribute must be present.");
+ if (mConfigNameToActivationVolumeConfig.containsKey(activationConfigName)) {
+ throw new IllegalArgumentException(ATTR_ACTIVATION_VOLUME_CONFIG_NAME + " "
+ + activationConfigName + " repeats, " + ATTR_ACTIVATION_VOLUME_CONFIG_NAME
+ + " can not repeat.");
+ }
+ int configEntryCount = 0;
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ if (Objects.equals(parser.getName(), TAG_ACTIVATION_VOLUME_CONFIG_ENTRY)) {
+ if (configEntryCount == 0) {
+ CarActivationVolumeConfig config = parseActivationVolumeConfigEntry(parser);
+ mConfigNameToActivationVolumeConfig.put(activationConfigName, config);
+ configEntryCount++;
+ } else {
+ throw new IllegalArgumentException("Multiple "
+ + TAG_ACTIVATION_VOLUME_CONFIG_ENTRY + "s in one "
+ + TAG_ACTIVATION_VOLUME_CONFIG + " is not supported");
+ }
+ }
+ CarAudioParserUtils.skip(parser);
+ }
+ }
+
+ private CarActivationVolumeConfig parseActivationVolumeConfigEntry(XmlPullParser parser) {
+ int activationVolumeInvocationTypes = parseVolumeGroupActivationVolumeTypes(parser);
+ int minActivationVolumePercentage = parseVolumeGroupActivationVolumePercentage(parser,
+ /* isMax= */ false);
+ int maxActivationVolumePercentage = parseVolumeGroupActivationVolumePercentage(parser,
+ /* isMax= */ true);
+ validateMinMaxActivationVolume(maxActivationVolumePercentage,
+ minActivationVolumePercentage);
+ return new CarActivationVolumeConfig(activationVolumeInvocationTypes,
+ minActivationVolumePercentage, maxActivationVolumePercentage);
+ }
+
+ private int parseVolumeGroupActivationVolumePercentage(XmlPullParser parser, boolean isMax) {
int defaultValue = isMax ? ACTIVATION_VOLUME_PERCENTAGE_MAX
: ACTIVATION_VOLUME_PERCENTAGE_MIN;
String attr = isMax ? ATTR_MAX_ACTIVATION_VOLUME_PERCENTAGE
@@ -663,17 +760,31 @@
if (activationPercentageString == null) {
return defaultValue;
}
- if (isVersionLessThanFour()) {
- throw new IllegalArgumentException(TAG_VOLUME_GROUP + " " + attr
- + " attribute not supported for versions less than " + SUPPORTED_VERSION_4
- + ", but current version is " + mCurrentVersion);
+ return CarAudioParserUtils.parsePositiveIntAttribute(attr, activationPercentageString);
+ }
+
+ private int parseVolumeGroupActivationVolumeTypes(XmlPullParser parser) {
+ String activationPercentageString = parser.getAttributeValue(NAMESPACE,
+ ATTR_ACTIVATION_VOLUME_INVOCATION_TYPE);
+ if (activationPercentageString == null) {
+ return ACTIVATION_VOLUME_INVOCATION_TYPE;
}
- if (Flags.carAudioMinMaxActivationVolume()) {
- return CarAudioParserUtils.parsePositiveIntAttribute(attr, activationPercentageString);
+ if (Objects.equals(activationPercentageString, ACTIVATION_VOLUME_INVOCATION_TYPE_ON_BOOT)) {
+ return CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT;
+ } else if (Objects.equals(activationPercentageString,
+ ACTIVATION_VOLUME_INVOCATION_TYPE_ON_SOURCE_CHANGED)) {
+ return CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT
+ | CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_SOURCE_CHANGED;
+ } else if (Objects.equals(activationPercentageString,
+ ACTIVATION_VOLUME_INVOCATION_TYPE_ON_PLAYBACK_CHANGED)) {
+ return CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT
+ | CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_SOURCE_CHANGED
+ | CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED;
+ } else {
+ throw new IllegalArgumentException(" Value " + activationPercentageString
+ + " is invalid for " + TAG_VOLUME_GROUP + " "
+ + ATTR_ACTIVATION_VOLUME_INVOCATION_TYPE);
}
- mCarServiceLocalLog.log("Found " + TAG_VOLUME_GROUP + " " + attr
- + " attribute while min/max activation volume is disabled");
- return defaultValue;
}
private boolean parseVolumeGroup(XmlPullParser parser, CarVolumeGroupFactory groupFactory)
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
index 73780eb..633bba6 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
@@ -64,6 +64,8 @@
private static final int MIN_ACTIVATION_VOLUME_PERCENTAGE = 0;
private static final int MAX_ACTIVATION_VOLUME_PERCENTAGE = 100;
+ private static final int ACTIVATION_VOLUME_INVOCATION_TYPE =
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT;
private final Context mContext;
private final @XmlRes int mXmlConfiguration;
@@ -211,8 +213,9 @@
new CarVolumeGroupFactory(/* audioManager= */ null, mCarAudioSettings,
mCarAudioContext, PRIMARY_AUDIO_ZONE, ZONE_CONFIG_ID, id,
String.valueOf(id), /* useCarVolumeGroupMute= */ false,
- /* maxActivationVolumePercentage= */ MAX_ACTIVATION_VOLUME_PERCENTAGE,
- /* minActivationVolumePercentage= */ MIN_ACTIVATION_VOLUME_PERCENTAGE);
+ new CarActivationVolumeConfig(ACTIVATION_VOLUME_INVOCATION_TYPE,
+ MIN_ACTIVATION_VOLUME_PERCENTAGE, MAX_ACTIVATION_VOLUME_PERCENTAGE)
+ );
List<Integer> audioContexts = parseAudioContexts(parser, attrs);
diff --git a/service/src/com/android/car/audio/CarAudioZonesValidator.java b/service/src/com/android/car/audio/CarAudioZonesValidator.java
index d7c6bc5..e43778f 100644
--- a/service/src/com/android/car/audio/CarAudioZonesValidator.java
+++ b/service/src/com/android/car/audio/CarAudioZonesValidator.java
@@ -15,14 +15,16 @@
*/
package com.android.car.audio;
-
import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR;
+
import android.media.AudioDeviceAttributes;
import android.util.ArraySet;
import android.util.SparseArray;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.internal.util.Preconditions;
import java.util.List;
@@ -33,6 +35,7 @@
*/
final class CarAudioZonesValidator {
+ @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR)
private CarAudioZonesValidator() {
throw new UnsupportedOperationException(
"CarAudioZonesValidator class is non-instantiable, contains static members only");
diff --git a/service/src/com/android/car/audio/CarVolume.java b/service/src/com/android/car/audio/CarVolume.java
index 85bef27..a05d3e0 100644
--- a/service/src/com/android/car/audio/CarVolume.java
+++ b/service/src/com/android/car/audio/CarVolume.java
@@ -166,6 +166,16 @@
return CarServiceUtils.toIntArray(contextByPriority);
}
+ private ArraySet<Integer> convertAttributesToContextsSet(List<AudioAttributes>
+ audioAttributesPriorities) {
+ ArraySet<Integer> contexts = new ArraySet<>();
+ for (int index = 0; index < audioAttributesPriorities.size(); index++) {
+ contexts.add(mCarAudioContext.getContextForAudioAttribute(
+ audioAttributesPriorities.get(index)));
+ }
+ return contexts;
+ }
+
/**
* @see CarAudioService#resetSelectedVolumeContext()
*/
@@ -184,7 +194,8 @@
*/
@AudioContext int getSuggestedAudioContextAndSaveIfFound(
List<AudioAttributes> activePlaybackAttributes, int callState,
- List<AudioAttributes> activeHalAttributes) {
+ List<AudioAttributes> activeHalAttributes,
+ List<AudioAttributes> inactiveAudioAttributes) {
int activeContext = getAudioContextStillActive();
if (!CarAudioContext.isInvalidContextId(activeContext)) {
@@ -196,7 +207,7 @@
getActiveAttributes(activePlaybackAttributes, callState, activeHalAttributes);
@AudioContext int context = findActiveContextWithHighestPriority(activeAttributes,
- mVolumePriorityByAudioContext);
+ mVolumePriorityByAudioContext, inactiveAudioAttributes);
setAudioContextStillActive(context);
@@ -204,14 +215,22 @@
}
private @AudioContext int findActiveContextWithHighestPriority(
- ArraySet<AudioAttributes> activeAttributes, SparseIntArray contextPriorities) {
+ ArraySet<AudioAttributes> activeAttributes, SparseIntArray contextPriorities,
+ List<AudioAttributes> inactiveAudioAttributes) {
int currentContext = mCarAudioContext.getContextForAttributes(
CAR_DEFAULT_AUDIO_ATTRIBUTE);
int currentPriority = mLowestPriority;
+ ArraySet<Integer> inactiveContexts =
+ convertAttributesToContextsSet(inactiveAudioAttributes);
+
for (int index = 0; index < activeAttributes.size(); index++) {
@AudioContext int context = mCarAudioContext.getContextForAudioAttribute(
activeAttributes.valueAt(index));
+
+ if (inactiveContexts.contains(context)) {
+ continue;
+ }
int priority = contextPriorities.get(context, CONTEXT_NOT_PRIORITIZED);
if (priority == CONTEXT_NOT_PRIORITIZED) {
continue;
@@ -227,7 +246,20 @@
}
}
- return currentContext;
+ return !inactiveContexts.contains(currentContext) ? currentContext :
+ getNextBestDefaultContext(inactiveContexts);
+ }
+
+ private int getNextBestDefaultContext(ArraySet<Integer> inactiveContexts) {
+ int[] contextVolumePriority = getContextPriorityList(mAudioVolumeAdjustmentContextsVersion);
+ for (int c = 0; c < contextVolumePriority.length; c++) {
+ int context = contextVolumePriority[c];
+ if (inactiveContexts.contains(context)) {
+ continue;
+ }
+ return context;
+ }
+ return CarAudioContext.getInvalidContext();
}
private void setAudioContextStillActive(@AudioContext int context) {
diff --git a/service/src/com/android/car/audio/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
index 4db0952..6ae9676 100644
--- a/service/src/com/android/car/audio/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -36,6 +36,7 @@
import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET;
+import static com.android.car.audio.CarActivationVolumeConfig.ActivationVolumeInvocationType;
import static com.android.car.audio.hal.HalAudioGainCallback.reasonToString;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
@@ -107,8 +108,7 @@
protected final Object mLock = new Object();
private final CarAudioContext mCarAudioContext;
- private final int mMinActivationVolumePercentage;
- private final int mMaxActivationVolumePercentage;
+ private final CarActivationVolumeConfig mCarActivationVolumeConfig;
@GuardedBy("mLock")
protected final SparseArray<String> mContextToAddress;
@@ -174,7 +174,7 @@
protected CarVolumeGroup(CarAudioContext carAudioContext, CarAudioSettings settingsManager,
SparseArray<CarAudioDeviceInfo> contextToDevices, int zoneId, int configId,
int volumeGroupId, String name, boolean useCarVolumeGroupMute,
- int maxActivationVolumePercentage, int minActivationVolumePercentage) {
+ CarActivationVolumeConfig carActivationVolumeConfig) {
mSettingsManager = settingsManager;
mCarAudioContext = carAudioContext;
mContextToDevices = contextToDevices;
@@ -197,8 +197,8 @@
}
mHasCriticalAudioContexts = containsCriticalAttributes(volumeAttributes);
- mMaxActivationVolumePercentage = maxActivationVolumePercentage;
- mMinActivationVolumePercentage = minActivationVolumePercentage;
+ mCarActivationVolumeConfig = Objects.requireNonNull(carActivationVolumeConfig,
+ "Activation volume config can not be null");
}
void init() {
@@ -210,11 +210,6 @@
}
@GuardedBy("mLock")
- protected boolean hasPendingAttenuationReasonsLocked() {
- return !mReasons.isEmpty();
- }
-
- @GuardedBy("mLock")
protected void setBlockedLocked(int blockedIndex) {
mBlockedGainIndex = blockedIndex;
}
@@ -460,17 +455,23 @@
int getMaxActivationGainIndex() {
int maxGainIndex = getMaxGainIndex();
int minGainIndex = getMinGainIndex();
- return minGainIndex + (int) Math.round(mMaxActivationVolumePercentage / 100.0
+ return minGainIndex + (int) Math.round(
+ mCarActivationVolumeConfig.getMaxActivationVolumePercentage() / 100.0
* (maxGainIndex - minGainIndex));
}
int getMinActivationGainIndex() {
int maxGainIndex = getMaxGainIndex();
int minGainIndex = getMinGainIndex();
- return minGainIndex + (int) Math.round(mMinActivationVolumePercentage / 100.0
+ return minGainIndex + (int) Math.round(
+ mCarActivationVolumeConfig.getMinActivationVolumePercentage() / 100.0
* (maxGainIndex - minGainIndex));
}
+ int getActivationVolumeInvocationType() {
+ return mCarActivationVolumeConfig.getInvocationType();
+ }
+
int getCurrentGainIndex() {
synchronized (mLock) {
if (isMutedLocked()) {
@@ -539,8 +540,12 @@
storeGainIndexForUserLocked(gainIndex, mUserId);
}
- boolean handleActivationVolume() {
- if (!carAudioMinMaxActivationVolume()) {
+ boolean handleActivationVolume(
+ @ActivationVolumeInvocationType int activationVolumeInvocationType) {
+ if (!carAudioMinMaxActivationVolume()
+ || (getActivationVolumeInvocationType() & activationVolumeInvocationType) == 0) {
+ // Min/max activation volume is not invoked if the given invocation type is not allowed
+ // for the volume group.
return false;
}
boolean invokeVolumeGainIndexChanged = true;
@@ -609,8 +614,9 @@
writer.printf("Gain indexes (min / max / default / current): %d %d %d %d\n",
getMinGainIndex(), getMaxGainIndex(), getDefaultGainIndex(),
mCurrentGainIndex);
- writer.printf("Activation gain indexes (min / max): %d %d\n",
- getMinActivationGainIndex(), getMaxActivationGainIndex());
+ writer.printf("Activation gain (min index / max index / invocation type): %d %d %d\n",
+ getMinActivationGainIndex(), getMaxActivationGainIndex(),
+ getActivationVolumeInvocationType());
for (int i = 0; i < mContextToAddress.size(); i++) {
writer.printf("Context: %s -> Address: %s\n",
mCarAudioContext.toString(mContextToAddress.keyAt(i)),
@@ -671,6 +677,8 @@
getMinActivationGainIndex());
proto.write(CarAudioDumpProto.CarVolumeGain.MAX_ACTIVATION_GAIN_INDEX,
getMaxActivationGainIndex());
+ proto.write(CarAudioDumpProto.CarVolumeGain.ACTIVATION_INVOCATION_TYPE,
+ getActivationVolumeInvocationType());
proto.end(volumeGainToken);
for (int i = 0; i < mContextToAddress.size(); i++) {
@@ -976,18 +984,9 @@
boolean hasAudioAttributes(AudioAttributes audioAttributes) {
synchronized (mLock) {
- for (int index = 0; index < mContextToAddress.size(); index++) {
- int context = mContextToAddress.keyAt(index);
- AudioAttributes[] contextAttributes =
- mCarAudioContext.getAudioAttributesForContext(context);
- for (int attrIndex = 0; attrIndex < contextAttributes.length; attrIndex++) {
- if (Objects.equals(contextAttributes[attrIndex], audioAttributes)) {
- return true;
- }
- }
- }
+ return mContextToAddress.contains(mCarAudioContext.getContextForAttributes(
+ audioAttributes));
}
- return false;
}
List<AudioAttributes> getAudioAttributes() {
diff --git a/service/src/com/android/car/audio/CarVolumeGroupFactory.java b/service/src/com/android/car/audio/CarVolumeGroupFactory.java
index 02e7a07..eaac163 100644
--- a/service/src/com/android/car/audio/CarVolumeGroupFactory.java
+++ b/service/src/com/android/car/audio/CarVolumeGroupFactory.java
@@ -15,7 +15,6 @@
*/
package com.android.car.audio;
-import android.media.AudioManager;
import android.util.SparseArray;
import com.android.car.audio.CarAudioContext.AudioContext;
@@ -36,19 +35,18 @@
private final CarAudioSettings mCarAudioSettings;
private final SparseArray<CarAudioDeviceInfo> mContextToDeviceInfo = new SparseArray<>();
private final CarAudioContext mCarAudioContext;
- private final AudioManager mAudioManager;
- private final int mMinActivationVolumePercentage;
- private final int mMaxActivationVolumePercentage;
+ private final AudioManagerWrapper mAudioManager;
+ private final CarActivationVolumeConfig mCarActivationVolumeConfig;
private int mStepSize = UNSET_STEP_SIZE;
private int mDefaultGain = Integer.MIN_VALUE;
private int mMaxGain = Integer.MIN_VALUE;
private int mMinGain = Integer.MAX_VALUE;
- CarVolumeGroupFactory(AudioManager audioManager, CarAudioSettings carAudioSettings,
+ CarVolumeGroupFactory(AudioManagerWrapper audioManager, CarAudioSettings carAudioSettings,
CarAudioContext carAudioContext, int zoneId, int configId, int volumeGroupId,
- String name, boolean useCarVolumeGroupMute, int maxActivationVolumePercentage,
- int minActivationVolumePercentage) {
+ String name, boolean useCarVolumeGroupMute,
+ CarActivationVolumeConfig carActivationVolumeConfig) {
mAudioManager = audioManager;
mCarAudioSettings = Objects.requireNonNull(carAudioSettings,
"Car audio settings can not be null");
@@ -59,8 +57,8 @@
mId = volumeGroupId;
mName = Objects.requireNonNull(name, "Car Volume Group name can not be null");
mUseCarVolumeGroupMute = useCarVolumeGroupMute;
- mMaxActivationVolumePercentage = maxActivationVolumePercentage;
- mMinActivationVolumePercentage = minActivationVolumePercentage;
+ mCarActivationVolumeConfig = Objects.requireNonNull(carActivationVolumeConfig,
+ "Car activation volume config can not be null");
}
CarVolumeGroup getCarVolumeGroup(boolean useCoreAudioVolume) {
@@ -70,12 +68,11 @@
if (useCoreAudioVolume) {
group = new CoreAudioVolumeGroup(mAudioManager, mCarAudioContext, mCarAudioSettings,
mContextToDeviceInfo, mZoneId, mConfigId, mId, mName, mUseCarVolumeGroupMute,
- mMaxActivationVolumePercentage, mMinActivationVolumePercentage);
+ mCarActivationVolumeConfig);
} else {
group = new CarAudioVolumeGroup(mCarAudioContext, mCarAudioSettings,
mContextToDeviceInfo, mZoneId, mConfigId, mId, mName, mStepSize, mDefaultGain,
- mMinGain, mMaxGain, mUseCarVolumeGroupMute, mMaxActivationVolumePercentage,
- mMinActivationVolumePercentage);
+ mMinGain, mMaxGain, mUseCarVolumeGroupMute, mCarActivationVolumeConfig);
}
group.init();
return group;
diff --git a/service/src/com/android/car/audio/CarZonesAudioFocus.java b/service/src/com/android/car/audio/CarZonesAudioFocus.java
index 7ffb0b1..42c09d5 100644
--- a/service/src/com/android/car/audio/CarZonesAudioFocus.java
+++ b/service/src/com/android/car/audio/CarZonesAudioFocus.java
@@ -16,9 +16,13 @@
package com.android.car.audio;
+import static android.media.AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+
import static com.android.car.audio.FocusInteraction.AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL_URI;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+import static java.util.Collections.EMPTY_LIST;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -41,6 +45,7 @@
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.car.oem.CarOemProxyService;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
@@ -57,12 +62,15 @@
private static final String TAG = CarLog.tagFor(CarZonesAudioFocus.class);
private final CarFocusCallback mCarFocusCallback;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
private CarAudioService mCarAudioService; // Dynamically assigned just after construction
+ @GuardedBy("mLock")
private AudioPolicy mAudioPolicy; // Dynamically assigned just after construction
private final SparseArray<CarAudioFocus> mFocusZones;
- public static CarZonesAudioFocus createCarZonesAudioFocus(AudioManager audioManager,
+ public static CarZonesAudioFocus createCarZonesAudioFocus(AudioManagerWrapper audioManager,
PackageManager packageManager, SparseArray<CarAudioZone> carAudioZones,
CarAudioSettings carAudioSettings, CarFocusCallback carFocusCallback,
CarVolumeInfoWrapper carVolumeInfoWrapper, @Nullable CarAudioFeaturesInfo features) {
@@ -160,11 +168,20 @@
* @return list of results for regaining focus
*/
List<Integer> reevaluateAndRegainAudioFocusList(List<AudioFocusInfo> audioFocusInfos) {
+ CarAudioService service;
+ synchronized (mLock) {
+ service = mCarAudioService;
+ }
+ if (service == null) {
+ Slogf.e(TAG, "reevaluateAndRegainAudioFocusList failed,"
+ + " car audio service unavailable");
+ return EMPTY_LIST;
+ }
List<Integer> res = new ArrayList<>(audioFocusInfos.size());
ArraySet<Integer> zoneIds = new ArraySet<>();
for (int index = 0; index < audioFocusInfos.size(); index++) {
AudioFocusInfo afi = audioFocusInfos.get(index);
- int zoneId = getAudioZoneIdForAudioFocusInfo(afi);
+ int zoneId = getAudioZoneIdForAudioFocusInfo(afi, service);
res.add(getCarAudioFocusForZoneId(zoneId).reevaluateAndRegainAudioFocus(afi));
zoneIds.add(zoneId);
}
@@ -177,7 +194,15 @@
}
int reevaluateAndRegainAudioFocus(AudioFocusInfo afi) {
- int zoneId = getAudioZoneIdForAudioFocusInfo(afi);
+ CarAudioService service;
+ synchronized (mLock) {
+ service = mCarAudioService;
+ }
+ if (service == null) {
+ Slogf.e(TAG, "reevaluateAndRegainAudioFocus failed, car audio service unavailable");
+ return AUDIOFOCUS_REQUEST_FAILED;
+ }
+ int zoneId = getAudioZoneIdForAudioFocusInfo(afi, service);
return getCarAudioFocusForZoneId(zoneId).reevaluateAndRegainAudioFocus(afi);
}
@@ -191,11 +216,13 @@
* @param parentPolicy owning parent car audio policy
*/
void setOwningPolicy(CarAudioService carAudioService, AudioPolicy parentPolicy) {
- mAudioPolicy = parentPolicy;
- mCarAudioService = carAudioService;
+ synchronized (mLock) {
+ mAudioPolicy = parentPolicy;
+ mCarAudioService = carAudioService;
+ }
for (int i = 0; i < mFocusZones.size(); i++) {
- mFocusZones.valueAt(i).setOwningPolicy(mAudioPolicy);
+ mFocusZones.valueAt(i).setOwningPolicy(parentPolicy);
}
}
@@ -210,7 +237,15 @@
@Override
public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {
- int zoneId = getAudioZoneIdForAudioFocusInfo(afi);
+ CarAudioService service;
+ synchronized (mLock) {
+ service = mCarAudioService;
+ }
+ if (service == null) {
+ Slogf.e(TAG, "onAudioFocusRequest failed, car audio service unavailable");
+ return;
+ }
+ int zoneId = getAudioZoneIdForAudioFocusInfo(afi, service);
getCarAudioFocusForZoneId(zoneId).onAudioFocusRequest(afi, requestResult);
notifyFocusListeners(new int[]{zoneId});
}
@@ -222,7 +257,15 @@
*/
@Override
public void onAudioFocusAbandon(AudioFocusInfo afi) {
- int zoneId = getAudioZoneIdForAudioFocusInfo(afi);
+ CarAudioService service;
+ synchronized (mLock) {
+ service = mCarAudioService;
+ }
+ if (service == null) {
+ Slogf.e(TAG, "onAudioFocusAbandon failed, car audio service unavailable");
+ return;
+ }
+ int zoneId = getAudioZoneIdForAudioFocusInfo(afi, service);
getCarAudioFocusForZoneId(zoneId).onAudioFocusAbandon(afi);
notifyFocusListeners(new int[]{zoneId});
}
@@ -231,8 +274,8 @@
return mFocusZones.get(zoneId);
}
- private int getAudioZoneIdForAudioFocusInfo(AudioFocusInfo afi) {
- int zoneId = mCarAudioService.getZoneIdForAudioFocusInfo(afi);
+ private int getAudioZoneIdForAudioFocusInfo(AudioFocusInfo afi, CarAudioService service) {
+ int zoneId = service.getZoneIdForAudioFocusInfo(afi);
// If the bundle attribute for AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID has been assigned
// Use zone id from that instead.
@@ -243,7 +286,7 @@
bundle.getInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID,
-1);
// check if the zone id is within current zones bounds
- if (mCarAudioService.isAudioZoneIdValid(bundleZoneId)) {
+ if (service.isAudioZoneIdValid(bundleZoneId)) {
Slogf.d(TAG, "getFocusForAudioFocusInfo valid zoneId %d with bundle request for"
+ " client %s", bundleZoneId, afi.getClientId());
zoneId = bundleZoneId;
@@ -310,8 +353,10 @@
}
public void updateUserForZoneId(int audioZoneId, @UserIdInt int userId) {
- Preconditions.checkArgument(mCarAudioService.isAudioZoneIdValid(audioZoneId),
- "Invalid zoneId %d", audioZoneId);
+ synchronized (mLock) {
+ Preconditions.checkArgument(mCarAudioService.isAudioZoneIdValid(audioZoneId),
+ "Invalid zoneId %d", audioZoneId);
+ }
mFocusZones.get(audioZoneId).getFocusInteraction().setUserIdForSettings(userId);
}
diff --git a/service/src/com/android/car/audio/CoreAudioHelper.java b/service/src/com/android/car/audio/CoreAudioHelper.java
index 11a76a4..50305f0 100644
--- a/service/src/com/android/car/audio/CoreAudioHelper.java
+++ b/service/src/com/android/car/audio/CoreAudioHelper.java
@@ -22,7 +22,6 @@
import android.car.builtin.media.AudioManagerHelper;
import android.car.builtin.util.Slogf;
import android.media.AudioAttributes;
-import android.media.AudioManager;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
import android.util.SparseArray;
@@ -78,12 +77,12 @@
* @see AudioProductStrategy
*/
static final List<AudioProductStrategy> sAudioProductStrategies =
- AudioManager.getAudioProductStrategies();
+ AudioManagerWrapper.getAudioProductStrategies();
/**
* @see AudioVolumeGroup
*/
static final List<AudioVolumeGroup> sAudioVolumeGroups =
- AudioManager.getAudioVolumeGroups();
+ AudioManagerWrapper.getAudioVolumeGroups();
static final SparseArray<String> sGroupIdToNames = new SparseArray<>() {
{
for (int index = 0; index < sAudioVolumeGroups.size(); index++) {
diff --git a/service/src/com/android/car/audio/CoreAudioVolumeGroup.java b/service/src/com/android/car/audio/CoreAudioVolumeGroup.java
index 962292a..88f7ebd 100644
--- a/service/src/com/android/car/audio/CoreAudioVolumeGroup.java
+++ b/service/src/com/android/car/audio/CoreAudioVolumeGroup.java
@@ -18,12 +18,13 @@
import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_ATTENUATION_CHANGED;
import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_MUTE_CHANGED;
import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED;
+import static android.media.AudioManager.ADJUST_MUTE;
+import static android.media.AudioManager.ADJUST_UNMUTE;
import static com.android.car.CarLog.TAG_AUDIO;
import static com.android.car.audio.CoreAudioHelper.getProductStrategyForAudioAttributes;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
-import android.car.builtin.media.AudioManagerHelper;
import android.car.builtin.util.Slogf;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
@@ -48,11 +49,12 @@
*/
final class CoreAudioVolumeGroup extends CarVolumeGroup {
static final String TAG = TAG_AUDIO + ".CoreAudioVolumeGroup";
+ public static final int EMPTY_FLAGS = 0;
/**
* For all volume operations, attributes are required
*/
private final AudioAttributes mAudioAttributes;
- private final AudioManager mAudioManager;
+ private final AudioManagerWrapper mAudioManager;
@GuardedBy("mLock")
private int mAmCurrentGainIndex;
private final int mAmId;
@@ -62,14 +64,12 @@
private boolean mAmGroupMuted;
private int mAmLastAudibleGainIndex;
- CoreAudioVolumeGroup(AudioManager audioManager, CarAudioContext carAudioContext,
+ CoreAudioVolumeGroup(AudioManagerWrapper audioManager, CarAudioContext carAudioContext,
CarAudioSettings settingsManager, SparseArray<CarAudioDeviceInfo> contextToDevices,
int zoneId, int configId, int volumeGroupId, String name,
- boolean useCarVolumeGroupMute, int maxActivationVolumePercentage,
- int minActivationVolumePercentage) {
+ boolean useCarVolumeGroupMute, CarActivationVolumeConfig carActivationVolumeConfig) {
super(carAudioContext, settingsManager, contextToDevices, zoneId, configId, volumeGroupId,
- name, useCarVolumeGroupMute, maxActivationVolumePercentage,
- minActivationVolumePercentage);
+ name, useCarVolumeGroupMute, carActivationVolumeConfig);
mAudioManager = audioManager;
mAudioAttributes = CoreAudioHelper.selectAttributesForVolumeGroupName(name);
mAmId = CoreAudioHelper.getVolumeGroupIdForAudioAttributes(mAudioAttributes);
@@ -102,11 +102,11 @@
}
boolean isAmGroupMuted() {
- return AudioManagerHelper.isVolumeGroupMuted(mAudioManager, mAmId);
+ return mAudioManager.isVolumeGroupMuted(mAmId);
}
int getAmLastAudibleIndex() {
- return AudioManagerHelper.getLastAudibleVolumeGroupVolume(mAudioManager, mAmId);
+ return mAudioManager.getLastAudibleVolumeForVolumeGroup(mAmId);
}
@Override
@@ -153,8 +153,7 @@
}
if (isAmGroupMuted() != mute) {
if (mute) {
- AudioManagerHelper.adjustVolumeGroupVolume(mAudioManager, mAmId,
- AudioManager.ADJUST_MUTE, /* flags= */ 0);
+ mAudioManager.adjustVolumeGroupVolume(mAmId, ADJUST_MUTE, EMPTY_FLAGS);
} else if (!isBlockedLocked() && !isHalMutedLocked()) {
// Unmute shall not break any pending attenuation / limitation
int index = getRestrictedGainForIndexLocked(getCurrentGainIndexLocked());
@@ -163,8 +162,7 @@
setCurrentGainIndexLocked(index, /* canChangeMuteState= */ true);
}
// TODO(b/260298113): index 0 mutes Am Group, wait for orthogonal mute/volume in AM
- AudioManagerHelper.adjustVolumeGroupVolume(mAudioManager, mAmId,
- AudioManager.ADJUST_UNMUTE, /* flags= */ 0);
+ mAudioManager.adjustVolumeGroupVolume(mAmId, ADJUST_UNMUTE, EMPTY_FLAGS);
}
}
}
diff --git a/service/src/com/android/car/audio/CoreAudioVolumeGroupCallback.java b/service/src/com/android/car/audio/CoreAudioVolumeGroupCallback.java
index 2122886..53b5f09 100644
--- a/service/src/com/android/car/audio/CoreAudioVolumeGroupCallback.java
+++ b/service/src/com/android/car/audio/CoreAudioVolumeGroupCallback.java
@@ -31,10 +31,10 @@
static final String TAG = TAG_AUDIO + ".CoreAudioVolumeGroupCallback";
private final CarVolumeInfoWrapper mCarVolumeInfoWrapper;
- private final AudioManager mAudioManager;
+ private final AudioManagerWrapper mAudioManager;
CoreAudioVolumeGroupCallback(CarVolumeInfoWrapper carVolumeInfoWrapper,
- AudioManager audioManager) {
+ AudioManagerWrapper audioManager) {
mCarVolumeInfoWrapper = Objects.requireNonNull(carVolumeInfoWrapper,
"CarVolumeInfoWrapper cannot be null");
mAudioManager = Objects.requireNonNull(audioManager, "AudioManager cannot be null");
diff --git a/service/src/com/android/car/audio/ZoneAudioPlaybackCallback.java b/service/src/com/android/car/audio/ZoneAudioPlaybackCallback.java
index 6ccb261..534c887 100644
--- a/service/src/com/android/car/audio/ZoneAudioPlaybackCallback.java
+++ b/service/src/com/android/car/audio/ZoneAudioPlaybackCallback.java
@@ -25,6 +25,7 @@
import android.media.AudioAttributes;
import android.media.AudioPlaybackConfiguration;
import android.util.ArrayMap;
+import android.util.Pair;
import android.util.proto.ProtoOutputStream;
import com.android.car.audio.CarAudioDumpProto.CarAudioPlaybackCallbackProto;
@@ -65,13 +66,13 @@
public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configurations) {
ArrayMap<String, AudioPlaybackConfiguration> newActiveConfigs =
filterNewActiveConfiguration(configurations);
- List<AudioAttributes> newlyActiveAudioAttributes = new ArrayList<>();
+ List<Pair<AudioAttributes, Integer>> newlyActiveAudioAttributesWithUid = new ArrayList<>();
synchronized (mLock) {
List<AudioPlaybackConfiguration> newlyInactiveConfigurations =
getNewlyInactiveConfigurationsLocked(newActiveConfigs);
if (mCarAudioPlaybackMonitor != null) {
- newlyActiveAudioAttributes = getNewlyActiveAudioAttributes(newActiveConfigs);
+ newlyActiveAudioAttributesWithUid = getNewlyActiveAudioAttributes(newActiveConfigs);
}
mLastActiveConfigs.clear();
@@ -80,9 +81,9 @@
startTimersForContextThatBecameInactiveLocked(newlyInactiveConfigurations);
}
- if (mCarAudioPlaybackMonitor != null && !newlyActiveAudioAttributes.isEmpty()) {
+ if (mCarAudioPlaybackMonitor != null && !newlyActiveAudioAttributesWithUid.isEmpty()) {
mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(
- newlyActiveAudioAttributes, mCarAudioZone.getId());
+ newlyActiveAudioAttributesWithUid, mCarAudioZone.getId());
}
}
@@ -126,7 +127,7 @@
}
@GuardedBy("mLock")
- private List<AudioAttributes> getNewlyActiveAudioAttributes(
+ private List<Pair<AudioAttributes, Integer>> getNewlyActiveAudioAttributes(
ArrayMap<String, AudioPlaybackConfiguration> newActiveConfigurations) {
List<AudioPlaybackConfiguration> audioPlaybackConfigurationsWithNewAttributes =
new ArrayList<>();
@@ -137,7 +138,18 @@
audioPlaybackConfigurationsWithNewAttributes
.add(newActiveConfigurations.valueAt(index));
}
- return getAudioAttributesFromPlaybacks(audioPlaybackConfigurationsWithNewAttributes);
+ List<Pair<AudioAttributes, Integer>> attributesUidList = new ArrayList<>();
+ for (int index = 0; index < audioPlaybackConfigurationsWithNewAttributes.size(); index++) {
+ AudioPlaybackConfiguration configuration = audioPlaybackConfigurationsWithNewAttributes
+ .get(index);
+ List<AudioAttributes> attributes = getAudioAttributesFromPlaybacks(
+ List.of(configuration));
+ if (attributes.isEmpty()) {
+ continue;
+ }
+ attributesUidList.add(new Pair<>(attributes.get(0), configuration.getClientUid()));
+ }
+ return attributesUidList;
}
private ArrayMap<String, AudioPlaybackConfiguration> filterNewActiveConfiguration(
diff --git a/service/src/com/android/car/audio/hal/AudioControlWrapperAidl.java b/service/src/com/android/car/audio/hal/AudioControlWrapperAidl.java
index 63bb1fe..9ac5e58 100644
--- a/service/src/com/android/car/audio/hal/AudioControlWrapperAidl.java
+++ b/service/src/com/android/car/audio/hal/AudioControlWrapperAidl.java
@@ -65,6 +65,7 @@
private static final int AIDL_AUDIO_CONTROL_VERSION_1 = 1;
private static final int AIDL_AUDIO_CONTROL_VERSION_2 = 2;
+ private static final int AIDL_AUDIO_CONTROL_VERSION_3 = 3;
private IBinder mBinder;
private IAudioControl mAudioControl;
@@ -141,6 +142,11 @@
Objects.requireNonNull(gainCallback, "Audio Gain Callback can not be null");
IAudioGainCallback agc = new AudioGainCallbackWrapper(gainCallback);
try {
+ if (mAudioControl.getInterfaceVersion() < AIDL_AUDIO_CONTROL_VERSION_2) {
+ Slogf.w(TAG, "Registering audio gain callback is not supported"
+ + " for versions less than " + AIDL_AUDIO_CONTROL_VERSION_2);
+ return;
+ }
mAudioControl.registerGainCallback(agc);
} catch (RemoteException e) {
Slogf.e(TAG, "Failed to register gain callback");
@@ -268,6 +274,11 @@
@Override
public void run() {
try {
+ if (mAudioControl.getInterfaceVersion() < AIDL_AUDIO_CONTROL_VERSION_3) {
+ Slogf.w(TAG, "Setting module change callback is not supported"
+ + " for versions less than " + AIDL_AUDIO_CONTROL_VERSION_3);
+ return;
+ }
mAudioControl.setModuleChangeCallback(callback);
mModuleChangeCallbackRegistered = true;
} catch (RemoteException e) {
@@ -301,6 +312,11 @@
@Override
public void run() {
try {
+ if (mAudioControl.getInterfaceVersion() < AIDL_AUDIO_CONTROL_VERSION_3) {
+ Slogf.w(TAG, "Clearing module change callback is not supported"
+ + " for versions less than " + AIDL_AUDIO_CONTROL_VERSION_3);
+ return;
+ }
mAudioControl.clearModuleChangeCallback();
mModuleChangeCallbackRegistered = false;
} catch (RemoteException e) {
diff --git a/service/src/com/android/car/audio/hal/HalAudioDeviceInfo.java b/service/src/com/android/car/audio/hal/HalAudioDeviceInfo.java
index eefb608..f2e67a6 100644
--- a/service/src/com/android/car/audio/hal/HalAudioDeviceInfo.java
+++ b/service/src/com/android/car/audio/hal/HalAudioDeviceInfo.java
@@ -21,11 +21,14 @@
import static android.media.audio.common.AudioDeviceType.OUT_DEVICE;
import static android.media.audio.common.AudioGainMode.JOINT;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
import android.annotation.NonNull;
import android.media.audio.common.AudioDevice;
import android.media.audio.common.AudioGain;
import android.media.audio.common.AudioPort;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.internal.util.Preconditions;
import java.util.Objects;
@@ -103,6 +106,7 @@
return mType == IN_DEVICE;
}
+ @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
@Override
public String toString() {
return new StringBuilder()
diff --git a/service/src/com/android/car/audio/hal/HalAudioFocus.java b/service/src/com/android/car/audio/hal/HalAudioFocus.java
index 787e776..b80c0d3 100644
--- a/service/src/com/android/car/audio/hal/HalAudioFocus.java
+++ b/service/src/com/android/car/audio/hal/HalAudioFocus.java
@@ -29,23 +29,23 @@
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.car.builtin.util.Slogf;
import android.car.media.CarAudioManager;
import android.hardware.audio.common.PlaybackTrackMetadata;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
-import android.media.AudioManager;
import android.os.Binder;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import com.android.car.CarLog;
+import com.android.car.audio.AudioManagerWrapper;
import com.android.car.audio.CarAudioContext;
import com.android.car.audio.CarAudioContext.AudioAttributesWrapper;
import com.android.car.audio.CarAudioDumpProto;
@@ -67,7 +67,7 @@
public final class HalAudioFocus implements HalFocusListener {
private static final String TAG = CarLog.tagFor(HalAudioFocus.class);
- private final AudioManager mAudioManager;
+ private final AudioManagerWrapper mAudioManager;
private final AudioControlWrapper mAudioControlWrapper;
private final CarAudioContext mCarAudioContext;
@Nullable
@@ -81,10 +81,10 @@
private final SparseArray<Map<AudioAttributesWrapper, HalAudioFocusRequest>>
mHalFocusRequestsByZoneAndAttributes;
- public HalAudioFocus(@NonNull AudioManager audioManager,
- @NonNull AudioControlWrapper audioControlWrapper,
+ public HalAudioFocus(AudioManagerWrapper audioManager,
+ AudioControlWrapper audioControlWrapper,
@Nullable CarAudioPlaybackMonitor carAudioPlaybackMonitor,
- @NonNull CarAudioContext carAudioContext, @NonNull int[] audioZoneIds) {
+ CarAudioContext carAudioContext, int[] audioZoneIds) {
mAudioManager = Objects.requireNonNull(audioManager);
mAudioControlWrapper = Objects.requireNonNull(audioControlWrapper);
mCarAudioContext = Objects.requireNonNull(carAudioContext);
@@ -361,8 +361,8 @@
}
long identity = Binder.clearCallingIdentity();
try {
- mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(List.of(attributes),
- zoneId);
+ mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(List.of(
+ new Pair<>(attributes, Binder.getCallingUid())), zoneId);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/service/src/com/android/car/bluetooth/BluetoothConnectionRetryManager.java b/service/src/com/android/car/bluetooth/BluetoothConnectionRetryManager.java
index 45f9aee..7f94ab4 100644
--- a/service/src/com/android/car/bluetooth/BluetoothConnectionRetryManager.java
+++ b/service/src/com/android/car/bluetooth/BluetoothConnectionRetryManager.java
@@ -376,6 +376,7 @@
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
// TODO (201800664): Profile State Change actions are hidden. This is a work around for now
filter.addAction(BluetoothUtils.A2DP_SINK_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothUtils.A2DP_SOURCE_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothUtils.HFP_CLIENT_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothUtils.MAP_CLIENT_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothUtils.PAN_CONNECTION_STATE_CHANGED);
diff --git a/service/src/com/android/car/bluetooth/BluetoothProfileInhibitManager.java b/service/src/com/android/car/bluetooth/BluetoothProfileInhibitManager.java
index eaa535e..bb15cdd 100644
--- a/service/src/com/android/car/bluetooth/BluetoothProfileInhibitManager.java
+++ b/service/src/com/android/car/bluetooth/BluetoothProfileInhibitManager.java
@@ -34,6 +34,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import com.android.car.CarLog;
@@ -44,9 +45,10 @@
import com.android.internal.annotations.GuardedBy;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.StringJoiner;
/**
* Manages the inhibiting of Bluetooth profile connections to and from specific devices.
@@ -280,15 +282,15 @@
*/
@GuardedBy("mProfileInhibitsLock")
private void commitLocked() {
- Set<BluetoothConnection> inhibitedProfiles = new HashSet<>(mProfileInhibits.keySet());
+ ArraySet<BluetoothConnection> inhibitedProfiles = new ArraySet<>(mProfileInhibits.keySet());
// Don't write out profiles that were disabled before a request was made, since
// restoring those profiles is a no-op.
inhibitedProfiles.removeAll(mAlreadyDisabledProfiles);
- String savedDisconnects =
- inhibitedProfiles
- .stream()
- .map(BluetoothConnection::encode)
- .collect(Collectors.joining(SETTINGS_DELIMITER));
+ StringJoiner savedDisconnectsJoiner = new StringJoiner(SETTINGS_DELIMITER);
+ for (int index = 0; index < inhibitedProfiles.size(); index++) {
+ savedDisconnectsJoiner.add(inhibitedProfiles.valueAt(index).encode());
+ }
+ String savedDisconnects = savedDisconnectsJoiner.toString();
Settings.Secure.putString(mUserContext.getContentResolver(),
KEY_BLUETOOTH_PROFILES_INHIBITED, savedDisconnects);
@@ -475,11 +477,15 @@
*/
private InhibitRecord findInhibitRecord(BluetoothConnection params, IBinder token) {
synchronized (mProfileInhibitsLock) {
- return mProfileInhibits.get(params)
- .stream()
- .filter(r -> r.getToken() == token)
- .findAny()
- .orElse(null);
+ Set<InhibitRecord> profileInhibitSet = mProfileInhibits.get(params);
+ Iterator<InhibitRecord> it = profileInhibitSet.iterator();
+ while (it.hasNext()) {
+ InhibitRecord r = it.next();
+ if (r.getToken() == token) {
+ return r;
+ }
+ }
+ return null;
}
}
diff --git a/service/src/com/android/car/bluetooth/BluetoothUtils.java b/service/src/com/android/car/bluetooth/BluetoothUtils.java
index 0a8a249..f731d53 100644
--- a/service/src/com/android/car/bluetooth/BluetoothUtils.java
+++ b/service/src/com/android/car/bluetooth/BluetoothUtils.java
@@ -32,6 +32,7 @@
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/** Utils for Bluetooth */
public final class BluetoothUtils {
@@ -260,7 +261,7 @@
static int[] getManagedProfilesIds() {
int[] profileIds = new int[sProfileActions.size()];
int i = 0;
- for (HashMap.Entry record : sProfileActions.entrySet()) {
+ for (Map.Entry<String, Integer> record : sProfileActions.entrySet()) {
profileIds[i] = ((Integer) record.getValue()).intValue();
i += 1;
}
diff --git a/service/src/com/android/car/bluetooth/CarBluetoothService.java b/service/src/com/android/car/bluetooth/CarBluetoothService.java
index a2d7d18..adda55a 100644
--- a/service/src/com/android/car/bluetooth/CarBluetoothService.java
+++ b/service/src/com/android/car/bluetooth/CarBluetoothService.java
@@ -96,6 +96,10 @@
private ICarPerUserService mCarPerUserService;
@GuardedBy("mPerUserLock")
private ICarBluetoothUserService mCarBluetoothUserService;
+ // Whether this service is already released. We should not create new handler thread if service
+ // is already released.
+ @GuardedBy("mPerUserLock")
+ private boolean mReleased = false;
private final CarPerUserServiceHelper mUserServiceHelper;
private final CarPerUserServiceHelper.ServiceCallback mUserServiceCallback =
new CarPerUserServiceHelper.ServiceCallback() {
@@ -105,6 +109,13 @@
Slogf.d(TAG, "Connected to CarPerUserService");
}
synchronized (mPerUserLock) {
+ if (mReleased) {
+ // We create handlerThread in initializeUserLocked. We must make sure we do not
+ // create handler thread after release otherwise the newly created thread might
+ // not be cleaned up properly.
+ return;
+ }
+
// Explicitly clear out existing per-user objects since we can't rely on the
// onServiceDisconnected and onPreUnbind calls to always be called before this
destroyUserLocked();
@@ -164,6 +175,9 @@
if (DBG) {
Slogf.d(TAG, "init()");
}
+ synchronized (mPerUserLock) {
+ mReleased = false;
+ }
mUserServiceHelper.registerServiceCallback(mUserServiceCallback);
}
@@ -180,6 +194,7 @@
mUserServiceHelper.unregisterServiceCallback(mUserServiceCallback);
synchronized (mPerUserLock) {
destroyUserLocked();
+ mReleased = true;
}
}
@@ -548,7 +563,6 @@
mDeviceManager.setDeviceConnectionPriority(device, priority);
}
}
- return;
}
/**
diff --git a/service/src/com/android/car/bluetooth/FastPairAdvertiser.java b/service/src/com/android/car/bluetooth/FastPairAdvertiser.java
index de17b85..00bda8a 100644
--- a/service/src/com/android/car/bluetooth/FastPairAdvertiser.java
+++ b/service/src/com/android/car/bluetooth/FastPairAdvertiser.java
@@ -27,8 +27,6 @@
import android.bluetooth.le.AdvertisingSetCallback;
import android.bluetooth.le.AdvertisingSetParameters;
import android.bluetooth.le.BluetoothLeAdvertiser;
-import android.car.Car;
-import android.car.PlatformVersion;
import android.car.builtin.bluetooth.le.AdvertisingSetCallbackHelper;
import android.car.builtin.bluetooth.le.AdvertisingSetHelper;
import android.car.builtin.util.Slogf;
@@ -233,7 +231,7 @@
byte[] hashed = MessageDigest.getInstance("SHA-256").digest(v);
ByteBuffer byteBuffer = ByteBuffer.wrap(hashed);
for (int j = 0; j < 8; j++) {
- long k = Integer.toUnsignedLong(byteBuffer.getInt()) % (size * 8);
+ long k = Integer.toUnsignedLong(byteBuffer.getInt()) % (size * 8L);
filter[(int) (k / 8)] |= (byte) (1 << (k % 8));
}
} catch (Exception e) {
@@ -320,59 +318,31 @@
}
private void initializeAdvertisingSetCallback() {
- // Certain functionality of {@link AdvertisingSetCallback} were disabled in
- // {@code TIRAMISU} (major == 33, minor == 0) due to hidden API usage. These functionality
- // were later restored, but require platform version to be at least TM-QPR-1
- // (major == 33, minor == 1).
- PlatformVersion version = Car.getPlatformVersion();
- if (DBG) {
- Slogf.d(TAG, "AdvertisingSetCallback running on platform version (major=%d, minor=%d)",
- version.getMajorVersion(), version.getMinorVersion());
- }
- if (version.isAtLeast(PlatformVersion.VERSION_CODES.TIRAMISU_1)) {
- AdvertisingSetCallbackHelper.Callback proxy =
- new AdvertisingSetCallbackHelper.Callback() {
- @Override
- public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
- int status) {
- onAdvertisingSetStartedHandler(advertisingSet, txPower, status);
- if (advertisingSet != null) {
- AdvertisingSetHelper.getOwnAddress(advertisingSet);
+ AdvertisingSetCallbackHelper.Callback proxy =
+ new AdvertisingSetCallbackHelper.Callback() {
+ @Override
+ public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
+ int status) {
+ onAdvertisingSetStartedHandler(advertisingSet, txPower, status);
+ if (advertisingSet != null) {
+ AdvertisingSetHelper.getOwnAddress(advertisingSet);
+ }
}
- }
- @Override
- public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
- onAdvertisingSetStoppedHandler(advertisingSet);
- }
+ @Override
+ public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
+ onAdvertisingSetStoppedHandler(advertisingSet);
+ }
- @Override
- public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType,
- String address) {
- onOwnAddressReadHandler(addressType, address);
- }
- };
+ @Override
+ public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType,
+ String address) {
+ onOwnAddressReadHandler(addressType, address);
+ }
+ };
- mAdvertisingSetCallback =
- AdvertisingSetCallbackHelper.createRealCallbackFromProxy(proxy);
- } else {
- mAdvertisingSetCallback = new AdvertisingSetCallback() {
- @Override
- public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
- int status) {
- onAdvertisingSetStartedHandler(advertisingSet, txPower, status);
- // TODO(b/241933163): once there are formal APIs to get own address, this
- // warning can be removed.
- Slogf.w(TAG, "AdvertisingSet#getOwnAddress not called."
- + " This feature is not supported in this platform version.");
- }
-
- @Override
- public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
- onAdvertisingSetStoppedHandler(advertisingSet);
- }
- };
- }
+ mAdvertisingSetCallback =
+ AdvertisingSetCallbackHelper.createRealCallbackFromProxy(proxy);
}
// For {@link AdvertisingSetCallback#onAdvertisingSetStarted} and its proxy
diff --git a/service/src/com/android/car/bluetooth/FastPairGattServer.java b/service/src/com/android/car/bluetooth/FastPairGattServer.java
index ac36ba5..e3fb5ca 100644
--- a/service/src/com/android/car/bluetooth/FastPairGattServer.java
+++ b/service/src/com/android/car/bluetooth/FastPairGattServer.java
@@ -90,7 +90,6 @@
.fromString("00002A00-0000-1000-8000-00805f9b34fb");
private static final String TAG = CarLog.tagFor(FastPairGattServer.class);
private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
- private static final int MAX_KEY_COUNT = 10;
private static final int KEY_LIFESPAN_AWAIT_PAIRING = 60_000;
// Spec *does* say indefinitely but not having a timeout is risky. This matches the BT stack's
// internal pairing timeout
@@ -658,8 +657,9 @@
* Determine if this pairing request is based on the anti-spoof keys associated with the model
* id or stored account keys.
*
- * @param accountKey
- * @return
+ * @param pairingRequest Pairing request
+ * @return Whether pairing request is based on the anti-spoof keys associated with the model id
+ * or stored account keys.
*/
private byte[] processKeyBasedPairing(byte[] pairingRequest) {
if (mFailureCounter.hasExceededLimit()) {
@@ -819,7 +819,6 @@
// Check that the request is either a Key-based Pairing Request or an Action Request
if (decryptedRequest[0] == 0x00 || decryptedRequest[0] == 0x10) {
String localAddress = mBluetoothAdapter.getAddress();
- byte[] localAddressBytes = BluetoothUtils.getBytesFromAddress(localAddress);
// Extract the remote address bytes from the message
byte[] remoteAddressBytes = Arrays.copyOfRange(decryptedRequest, 2, 8);
BluetoothDevice localDevice = mBluetoothAdapter.getRemoteDevice(localAddress);
diff --git a/service/src/com/android/car/cluster/InstrumentClusterService.java b/service/src/com/android/car/cluster/InstrumentClusterService.java
index c243126..d20f6d2 100644
--- a/service/src/com/android/car/cluster/InstrumentClusterService.java
+++ b/service/src/com/android/car/cluster/InstrumentClusterService.java
@@ -415,7 +415,7 @@
* @deprecated CarClusterManager is being deprecated.
*/
@Deprecated
- private class ClusterManagerService extends IInstrumentClusterManagerService.Stub {
+ private static class ClusterManagerService extends IInstrumentClusterManagerService.Stub {
@Override
public void startClusterActivity(Intent intent) throws RemoteException {
// No op.
diff --git a/service/src/com/android/car/evs/CarEvsService.java b/service/src/com/android/car/evs/CarEvsService.java
index 8c48ced..1430582 100644
--- a/service/src/com/android/car/evs/CarEvsService.java
+++ b/service/src/com/android/car/evs/CarEvsService.java
@@ -16,17 +16,10 @@
package com.android.car.evs;
-import static android.car.evs.CarEvsManager.ERROR_BUSY;
import static android.car.evs.CarEvsManager.ERROR_NONE;
import static android.car.evs.CarEvsManager.ERROR_UNAVAILABLE;
-import static android.car.evs.CarEvsManager.SERVICE_STATE_ACTIVE;
-import static android.car.evs.CarEvsManager.SERVICE_STATE_INACTIVE;
-import static android.car.evs.CarEvsManager.SERVICE_STATE_REQUESTED;
-import static android.car.evs.CarEvsManager.SERVICE_STATE_UNAVAILABLE;
-import static android.car.evs.CarEvsManager.STREAM_EVENT_STREAM_STOPPED;
import static com.android.car.CarLog.TAG_EVS;
-import static com.android.car.evs.StateMachine.REQUEST_PRIORITY_LOW;
import static com.android.car.evs.StateMachine.REQUEST_PRIORITY_NORMAL;
import static com.android.car.evs.StateMachine.REQUEST_PRIORITY_HIGH;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
@@ -56,16 +49,16 @@
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.util.ArraySet;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
@@ -78,7 +71,6 @@
import com.android.car.hal.EvsHalService;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.evs.CarEvsUtils;
-import com.android.car.internal.evs.EvsHalWrapper;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -115,6 +107,10 @@
implements CarServiceBase {
private static final boolean DBG = Slogf.isLoggable(TAG_EVS, Log.DEBUG);
+ private static final String EVS_INTERFACE_NAME =
+ "android.hardware.automotive.evs.IEvsEnumerator";
+ private static final String EVS_DEFAULT_INSTANCE_NAME = "default";
+
static final class EvsHalEvent {
private long mTimestamp;
@@ -153,12 +149,14 @@
private final DisplayManager mDisplayManager; // To monitor the default display's state
private final Object mLock = new Object();
private final ArraySet<IBinder> mSessionTokens = new ArraySet<>();
+ private final boolean mIsEvsAvailable;
// This handler is to monitor the client sends a video stream request within a given time
// after a state transition to the REQUESTED state.
private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final class StatusListenerList extends RemoteCallbackList<ICarEvsStatusListener> {
+ private static final class StatusListenerList
+ extends RemoteCallbackList<ICarEvsStatusListener> {
private final WeakReference<CarEvsService> mService;
StatusListenerList(CarEvsService evsService) {
@@ -218,8 +216,10 @@
Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
if (mCurrentDisplayState == display.getState()) {
// We already handled this display state change.
- Slogf.i(TAG_EVS, "We already handled a reported display status, %d",
- display.getState());
+ if (DBG) {
+ Slogf.d(TAG_EVS, "We already handled a reported display status, %d",
+ display.getState());
+ }
return;
}
@@ -289,8 +289,32 @@
/** Creates an Extended View System service instance given a {@link Context}. */
public CarEvsService(Context context, Context builtinContext, EvsHalService halService,
CarPropertyService propertyService) {
+ this(context, builtinContext, halService, propertyService, /* checkDependencies= */ true);
+ }
+
+ @VisibleForTesting
+ CarEvsService(Context context, Context builtinContext, EvsHalService halService,
+ CarPropertyService propertyService, boolean checkDependencies) {
mContext = context;
mBuiltinContext = builtinContext;
+
+ // CarEvsService should become ineffective if the EVS service is not available. We confirm
+ // this by checking whether IEvsEnumerator/default instance is declared in VINTF. This check
+ // could be skipped only for testing purposes.
+ String instanceName = EVS_INTERFACE_NAME + "/" + EVS_DEFAULT_INSTANCE_NAME;
+ mIsEvsAvailable = !checkDependencies || ServiceManager.isDeclared(instanceName);
+ if (!mIsEvsAvailable) {
+ Slogf.e(TAG_EVS, "%s does not exist. CarEvsService won't be available.", instanceName);
+
+ // Set all final variables ineffective.
+ mPropertyService = null;
+ mEvsHalService = null;
+ mServiceInstances = new SparseArray<>();
+ mDisplayManager = null;
+
+ return;
+ }
+
mPropertyService = propertyService;
mEvsHalService = halService;
@@ -384,6 +408,11 @@
Slogf.d(TAG_EVS, "Initializing the service");
}
+ if (!mIsEvsAvailable) {
+ Slogf.e(TAG_EVS, "CarEvsService cannot be initialized due to missing dependencies.");
+ return;
+ }
+
for (int i = mServiceInstances.size() - 1; i >= 0; i--) {
StateMachine instance = mServiceInstances.valueAt(i);
if (instance.init()) {
@@ -595,7 +624,7 @@
ArraySet<Integer> types = mCallbackToServiceType.get(callback.asBinder());
if (types == null) {
mCallbackToServiceType.put(callback.asBinder(),
- new ArraySet<>(Set.of(new Integer(type))));
+ new ArraySet<>(Set.of(type)));
} else {
types.add(type);
}
@@ -685,8 +714,7 @@
Objects.requireNonNull(buffer);
// 8 MSB tells the service type of this buffer.
- @CarEvsServiceType int type = CarEvsUtils.getTag(buffer.getId());
- mServiceInstances.get(type).doneWithFrame(buffer.getId());
+ mServiceInstances.get(buffer.getType()).doneWithFrame(buffer.getId());
}
/**
@@ -778,6 +806,11 @@
CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
Objects.requireNonNull(id);
+ if (!mIsEvsAvailable) {
+ Slogf.e(TAG_EVS, "CarEvsService is not available.");
+ return false;
+ }
+
if (!BuildHelper.isDebuggableBuild()) {
// This method is not allowed in the release build.
Slogf.e(TAG_EVS, "It is not allowed to change a camera assigned to the rearview " +
@@ -830,10 +863,18 @@
*
* @return A string identifier of current rearview camera device.
*/
- @NonNull
+ @Nullable
public String getRearviewCameraIdFromCommand() {
CarServiceUtils.assertPermission(mContext, Car.PERMISSION_MONITOR_CAR_EVS_STATUS);
- return mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW).getCameraId();
+
+ StateMachine instance = mServiceInstances.get(CarEvsManager.SERVICE_TYPE_REARVIEW);
+ if (instance == null) {
+ Slogf.e(TAG_EVS, "Ignores a request to get a camera id for unavailable " +
+ "REARVIEW service.");
+ return null;
+ }
+
+ return instance.getCameraId();
}
/**
@@ -852,6 +893,8 @@
@CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(type);
StateMachine instance = mServiceInstances.get(serviceType);
if (instance == null) {
+ Slogf.e(TAG_EVS, "Ignores a request to get a camera id for unavailable service %s.",
+ type);
return null;
}
@@ -875,6 +918,12 @@
@NonNull String cameraId) {
CarServiceUtils.assertPermission(mContext, Car.PERMISSION_USE_CAR_EVS_CAMERA);
+ if (!mIsEvsAvailable) {
+ Slogf.e(TAG_EVS, "Failed to enable %s service due to missing dependencies.",
+ typeString);
+ return false;
+ }
+
@CarEvsServiceType int serviceType = CarEvsUtils.convertToServiceType(typeString);
for (int i = 0; i < mServiceInstances.size(); i++) {
int type = mServiceInstances.keyAt(i);
@@ -948,7 +997,7 @@
ArraySet<Integer> types = mCallbackToServiceType.get(callback.asBinder());
if (types == null) {
mCallbackToServiceType.put(callback.asBinder(),
- new ArraySet<>(Set.of(new Integer(type))));
+ new ArraySet<>(Set.of(type)));
} else {
types.add(type);
}
@@ -992,29 +1041,6 @@
}
}
- /** Handles client disconnections; may request to stop a video stream. */
- private void handleClientDisconnected(ICarEvsStreamCallback callback) {
- // If the last stream client is disconnected before it stops a video stream, request to stop
- // current video stream.
- ArraySet<Integer> types = mCallbackToServiceType.get(callback.asBinder());
- if (types == null) {
- Slogf.d(TAG_EVS, "Ignores an incidental loss of unknown callback %s.",
- callback.asBinder());
- return;
- }
-
- for (int i = 0; i < types.size(); i++) {
- StateMachine instance = mServiceInstances.get(types.valueAt(i));
- if (instance == null) {
- Slogf.i(TAG_EVS, "Ignores an incidental loss of a callback %s for service %d.",
- callback.asBinder(), types.valueAt(i));
- return;
- }
-
- instance.handleClientDisconnected(callback);
- }
- }
-
/** Notifies the service status gets changed */
void broadcastStateTransition(int type, int state) {
int idx = mStatusListeners.beginBroadcast();
@@ -1030,31 +1056,6 @@
mStatusListeners.finishBroadcast();
}
- /** Stops a current service */
- void stopService() {
- stopService(/* callback= */ null);
- }
-
- private void stopService(ICarEvsStreamCallback callback) {
- ArraySet<Integer> types = mCallbackToServiceType.get(callback.asBinder());
- if (types == null || types.isEmpty()) {
- Slogf.d(TAG_EVS, "Ignores a request to stop a service for unknown callback %s.",
- callback.asBinder());
- return;
- }
-
- for (int i = 0; i < types.size(); i++) {
- StateMachine instance = mServiceInstances.get(types.valueAt(i));
- if (instance == null) {
- Slogf.i(TAG_EVS, "Ignores a request to stop unsupported service %d.",
- types.valueAt(i));
- return;
- }
-
- instance.requestStopVideoStream(callback);
- }
- }
-
private void handlePropertyEvent(CarPropertyEvent event) {
if (event.getEventType() != CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE) {
// CarEvsService is interested only in the property change event.
@@ -1107,18 +1108,6 @@
}
}
- /** Notify the client of a video stream loss */
- private static void notifyStreamStopped(@NonNull ICarEvsStreamCallback callback) {
- Objects.requireNonNull(callback);
-
- try {
- callback.onStreamEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED);
- } catch (RemoteException e) {
- // Likely the binder death incident
- Slogf.w(TAG_EVS, Log.getStackTraceString(e));
- }
- }
-
/** Handles a disconnection of a status monitoring client. */
private void handleClientDisconnected(ICarEvsStatusListener listener) {
mStatusListeners.unregister(listener);
diff --git a/service/src/com/android/car/evs/StateMachine.java b/service/src/com/android/car/evs/StateMachine.java
index e4e67f4..d305a46 100644
--- a/service/src/com/android/car/evs/StateMachine.java
+++ b/service/src/com/android/car/evs/StateMachine.java
@@ -23,16 +23,13 @@
import static android.car.evs.CarEvsManager.SERVICE_STATE_INACTIVE;
import static android.car.evs.CarEvsManager.SERVICE_STATE_REQUESTED;
import static android.car.evs.CarEvsManager.SERVICE_STATE_UNAVAILABLE;
-import static android.car.evs.CarEvsManager.STREAM_EVENT_STREAM_STOPPED;
import static com.android.car.CarLog.TAG_EVS;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEBUGGING_CODE;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.car.builtin.util.Slogf;
-import android.car.feature.Flags;
import android.car.evs.CarEvsBufferDescriptor;
import android.car.evs.CarEvsManager;
import android.car.evs.CarEvsManager.CarEvsError;
@@ -41,21 +38,19 @@
import android.car.evs.CarEvsManager.CarEvsStreamEvent;
import android.car.evs.CarEvsStatus;
import android.car.evs.ICarEvsStreamCallback;
+import android.car.feature.Flags;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.hardware.HardwareBuffer;
-import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.util.ArraySet;
-import android.util.SparseIntArray;
import android.util.Log;
+import android.util.SparseIntArray;
import com.android.car.BuiltinPackageDependency;
import com.android.car.CarServiceUtils;
@@ -63,11 +58,12 @@
import com.android.car.internal.evs.CarEvsUtils;
import com.android.car.internal.evs.EvsHalWrapper;
import com.android.car.internal.util.IndentingPrintWriter;
+import com.android.car.util.TransitionLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
+import java.util.ArrayList;
import java.util.Objects;
/** CarEvsService state machine implementation to handle all state transitions. */
@@ -87,6 +83,8 @@
// Object to recognize Runnable objects.
private static final String CALLBACK_RUNNABLE_TOKEN = StateMachine.class.getSimpleName();
private static final String DEFAULT_CAMERA_ALIAS = "default";
+ // Maximum length of state transition logs.
+ private static final int MAX_TRANSITION_LOG_LENGTH = 20;
private final SparseIntArray mBufferRecords = new SparseIntArray();
private final CarEvsService mService;
@@ -123,8 +121,11 @@
}
}
+ // For the dumpsys logging.
@GuardedBy("mLock")
- private String mCameraId;
+ private final ArrayList<TransitionLog> mTransitionLogs = new ArrayList<>();
+
+ private final String mCameraId;
// Current state.
@GuardedBy("mLock")
@@ -237,8 +238,7 @@
while (idx-- > 0) {
ICarEvsStreamCallback callback = mCallbacks.getBroadcastItem(idx);
try {
- int taggedEvent = CarEvsUtils.putTag(mServiceType, event);
- callback.onStreamEvent(taggedEvent);
+ callback.onStreamEvent(mServiceType, event);
} catch (RemoteException e) {
Slogf.w(mLogTag, "Failed to forward an event to %s", callback);
}
@@ -262,12 +262,12 @@
while (idx-- > 0) {
ICarEvsStreamCallback callback = mCallbacks.getBroadcastItem(idx);
try {
- int bufferId = CarEvsUtils.putTag(mServiceType, id);
CarEvsBufferDescriptor descriptor;
if (Flags.carEvsStreamManagement()) {
- descriptor = new CarEvsBufferDescriptor(bufferId, mServiceType, buffer);
+ descriptor = new CarEvsBufferDescriptor(id, mServiceType, buffer);
} else {
- descriptor = new CarEvsBufferDescriptor(bufferId, buffer);
+ descriptor = new CarEvsBufferDescriptor(
+ CarEvsUtils.putTag(mServiceType, id), buffer);
}
callback.onNewFrame(descriptor);
refcount += 1;
@@ -469,8 +469,10 @@
/** Requests to cancel a pending activity request. */
void cancelActivityRequest() {
- if (mState != SERVICE_STATE_REQUESTED) {
- return;
+ synchronized (mLock) {
+ if (mState != SERVICE_STATE_REQUESTED) {
+ return;
+ }
}
if (execute(REQUEST_PRIORITY_HIGH, SERVICE_STATE_INACTIVE) != ERROR_NONE) {
@@ -552,9 +554,19 @@
CarEvsUtils.convertToString(mServiceType));
writer.printf("SessionToken = %s.\n",
mSessionToken == null ? "Not exist" : mSessionToken);
+ writer.printf("Camera Id = %s.\n", mCameraId);
+
+ writer.println("Current state: " + mState);
writer.increaseIndent();
- mHalCallback.dump(writer);
+ writer.println("State transition log:");
+ writer.increaseIndent();
+ for (int i = 0; i < mTransitionLogs.size(); i++) {
+ writer.println(mTransitionLogs.get(i));
+ }
writer.decreaseIndent();
+ writer.decreaseIndent();
+
+ mHalCallback.dump(writer);
writer.printf("\n");
}
}
@@ -640,6 +652,12 @@
if (previousState != newState) {
Slogf.i(mLogTag, "Transition completed: %s", stateToString(destination));
mService.broadcastStateTransition(CarEvsManager.SERVICE_TYPE_REARVIEW, newState);
+
+ // Log a successful state transition.
+ synchronized (mLock) {
+ addTransitionLogLocked(mLogTag, previousState, newState,
+ System.currentTimeMillis());
+ }
} else {
Slogf.i(mLogTag, "Stay at %s", stateToString(newState));
}
@@ -679,6 +697,7 @@
* @return true if we should launch an activity.
* false otherwise.
*/
+ @GuardedBy("mLock")
private boolean needToStartActivityLocked() {
if (mActivityName == null || mHandler.hasCallbacks(mActivityRequestTimeoutRunnable)) {
// No activity has been registered yet or it is already requested.
@@ -772,7 +791,7 @@
/**
* Try to connect to the EVS HAL service until it succeeds at a given interval.
*
- * @param internalInMillis an interval to try again if current attempt fails.
+ * @param intervalInMillis an interval to try again if current attempt fails.
*/
private void connectToHalServiceIfNecessary(long intervalInMillis) {
if (execute(REQUEST_PRIORITY_HIGH, SERVICE_STATE_INACTIVE) != ERROR_NONE) {
@@ -795,9 +814,7 @@
}
try {
- int taggedEvent = CarEvsUtils.putTag(mServiceType,
- CarEvsManager.STREAM_EVENT_STREAM_STOPPED);
- callback.onStreamEvent(taggedEvent);
+ callback.onStreamEvent(mServiceType, CarEvsManager.STREAM_EVENT_STREAM_STOPPED);
} catch (RemoteException e) {
// Likely the binder death incident
Slogf.w(TAG_EVS, Log.getStackTraceString(e));
@@ -1156,6 +1173,17 @@
}
}
+ @GuardedBy("mLock")
+ private void addTransitionLogLocked(String name, int from, int to, long timestamp) {
+ if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_LENGTH) {
+ // Remove the least recently added entry.
+ mTransitionLogs.remove(0);
+ }
+
+ mTransitionLogs.add(
+ new TransitionLog(name, stateToString(from), stateToString(to), timestamp));
+ }
+
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
@Override
public String toString() {
diff --git a/service/src/com/android/car/garagemode/GarageMode.java b/service/src/com/android/car/garagemode/GarageMode.java
index 6615933..fbd7304 100644
--- a/service/src/com/android/car/garagemode/GarageMode.java
+++ b/service/src/com/android/car/garagemode/GarageMode.java
@@ -457,9 +457,7 @@
private void cleanupGarageMode(Runnable completor) {
synchronized (mLock) {
if (!mGarageModeActive) {
- if (completor != null) {
- completor.run();
- }
+ completor.run();
Slogf.e(TAG, "Trying to cleanup garage mode when it is inactive. Request ignored");
return;
}
@@ -467,11 +465,11 @@
mGarageModeActive = false;
// Always update the completor with the latest completor.
mBackgroundUserStopCompletor = completor;
+ Slogf.i(TAG, "Stopping of background user queued. Total background users to stop: "
+ + "%d", mStartedBackgroundUsers.size());
}
stopMonitoringThread();
CarStatsLogHelper.logGarageModeStop();
- Slogf.i(TAG, "Stopping of background user queued. Total background users to stop: "
- + "%d", mStartedBackgroundUsers.size());
mHandler.removeCallbacks(mStopUserCheckRunnable);
mHandler.removeCallbacks(mBackgroundUserStopTimeout);
mHandler.postDelayed(mBackgroundUserStopTimeout, STOP_BACKGROUND_USER_TIMEOUT_MS);
diff --git a/service/src/com/android/car/hal/ClusterHalService.java b/service/src/com/android/car/hal/ClusterHalService.java
index eaf24dc..238b101 100644
--- a/service/src/com/android/car/hal/ClusterHalService.java
+++ b/service/src/com/android/car/hal/ClusterHalService.java
@@ -345,7 +345,7 @@
}
HalPropValue request = mPropValueBuilder.build(CLUSTER_NAVIGATION_STATE,
/* areaId= */ 0, SystemClock.elapsedRealtime(), VehiclePropertyStatus.AVAILABLE,
- /* value= */ navigateState);
+ /* values= */ navigateState);
send(request);
}
diff --git a/service/src/com/android/car/hal/EvsHalService.java b/service/src/com/android/car/hal/EvsHalService.java
index e5b7397..8e84bc1 100644
--- a/service/src/com/android/car/hal/EvsHalService.java
+++ b/service/src/com/android/car/hal/EvsHalService.java
@@ -18,15 +18,18 @@
import static android.car.evs.CarEvsManager.SERVICE_TYPE_REARVIEW;
import static android.car.evs.CarEvsManager.SERVICE_TYPE_SURROUNDVIEW;
+import static android.hardware.automotive.vehicle.VehicleProperty.CAMERA_SERVICE_CURRENT_STATE;
import static android.hardware.automotive.vehicle.VehicleProperty.EVS_SERVICE_REQUEST;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
import android.car.builtin.util.Slogf;
+import android.car.evs.CarEvsManager.CarEvsServiceState;
import android.car.evs.CarEvsManager.CarEvsServiceType;
import android.hardware.automotive.vehicle.EvsServiceRequestIndex;
import android.hardware.automotive.vehicle.EvsServiceState;
import android.hardware.automotive.vehicle.EvsServiceType;
+import android.os.ServiceSpecificException;
import android.util.Log;
import android.util.SparseArray;
@@ -47,6 +50,7 @@
private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
private static final int[] SUPPORTED_PROPERTIES = new int[] {
+ CAMERA_SERVICE_CURRENT_STATE,
EVS_SERVICE_REQUEST,
};
@@ -56,14 +60,17 @@
private final SparseArray<HalPropConfig> mProperties = new SparseArray();
private final VehicleHal mHal;
+ private final HalPropValueBuilder mPropValueBuilder;
@GuardedBy("mLock")
private EvsHalEventListener mListener;
private boolean mIsEvsServiceRequestSupported;
+ private boolean mIsCameraServiceCurrentStateSupported;
public EvsHalService(VehicleHal hal) {
mHal = hal;
+ mPropValueBuilder = hal.getHalPropValueBuilder();
}
/**
@@ -79,7 +86,7 @@
* Sets the event listener to receive Vehicle's EVS-related events.
*
* @param listener {@link EvsHalEventListener}
- * @throws {@link IllegalStateException} if none of required VHAL properties are not supported
+ * @throws IllegalStateException if none of required VHAL properties are not supported
* on this device.
*/
public void setListener(EvsHalEventListener listener) {
@@ -100,6 +107,11 @@
return mIsEvsServiceRequestSupported;
}
+ /** Returns whether {@code CAMERA_SERVICE_CURRENT_STATE} is supported */
+ public boolean isCameraServiceCurrentStateSupported() {
+ return mIsCameraServiceCurrentStateSupported;
+ }
+
@Override
public void init() {
synchronized (mLock) {
@@ -137,6 +149,8 @@
}
mIsEvsServiceRequestSupported = mProperties.contains(EVS_SERVICE_REQUEST);
+ mIsCameraServiceCurrentStateSupported =
+ mProperties.contains(CAMERA_SERVICE_CURRENT_STATE);
}
}
@@ -155,6 +169,23 @@
dispatchHalEvents(values, listener);
}
+ /**
+ * Reports the current state of CarEvsService.
+ *
+ * @param state {@link android.car.evs.CarEvsManager.CarEvsServiceState} value that represents
+ * the current state of {@code CarEvsService}.
+ */
+ public void reportCurrentState(@CarEvsServiceState int[] state) {
+ HalPropValue currentStates = mPropValueBuilder.build(CAMERA_SERVICE_CURRENT_STATE,
+ /* areaId= */ 0, /* values= */ state);
+
+ try {
+ mHal.set(currentStates);
+ } catch (ServiceSpecificException | IllegalArgumentException e) {
+ Slogf.e(TAG, "Failed to set a hal property: %s, err: %s", currentStates, e);
+ }
+ }
+
private void dispatchHalEvents(List<HalPropValue> values, EvsHalEventListener listener) {
for (int i = 0; i < values.size(); ++i) {
HalPropValue v = values.get(i);
@@ -181,6 +212,10 @@
listener.onEvent(type, on);
break;
+ case CAMERA_SERVICE_CURRENT_STATE:
+ // Nothing to do with this write-only property.
+ break;
+
default:
if (DBG) {
Slogf.d(TAG, "Received unknown property change: " + v);
@@ -195,5 +230,7 @@
public void dump(PrintWriter writer) {
writer.println("*EVSHALSERVICE*");
writer.printf("Use EVS_SERVICE_REQUEST: %b\n", isEvsServiceRequestSupported());
+ writer.printf("Use CAMERA_SERVICE_CURRENT_STATE: %b\n",
+ isCameraServiceCurrentStateSupported());
}
}
diff --git a/service/src/com/android/car/hal/HalPropConfig.java b/service/src/com/android/car/hal/HalPropConfig.java
index c676be8..8ee3f8d 100644
--- a/service/src/com/android/car/hal/HalPropConfig.java
+++ b/service/src/com/android/car/hal/HalPropConfig.java
@@ -16,6 +16,7 @@
package com.android.car.hal;
+import android.annotation.Nullable;
import android.car.VehicleAreaType;
import android.car.feature.Flags;
import android.car.hardware.CarPropertyConfig;
@@ -47,9 +48,6 @@
VehicleProperty.HVAC_TEMPERATURE_DISPLAY_UNITS,
VehicleProperty.VEHICLE_SPEED_DISPLAY_UNITS);
- private final PropertyHalServiceConfigs mPropertyHalServiceConfigs =
- PropertyHalServiceConfigs.getInstance();
-
/**
* Get the property ID.
*/
@@ -95,49 +93,14 @@
*/
public abstract Object toVehiclePropConfig();
- private int getCommonAccessFromHalAreaConfigs(HalAreaConfig[] halAreaConfigs) {
- boolean readOnlyPresent = false;
- boolean writeOnlyPresent = false;
- boolean readWritePresent = false;
- for (int i = 0; i < halAreaConfigs.length; i++) {
- int access = halAreaConfigs[i].getAccess();
- switch (access) {
- case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ:
- readOnlyPresent = true;
- break;
- case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE:
- writeOnlyPresent = true;
- break;
- case CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE:
- readWritePresent = true;
- break;
- default:
- // AreaId config has an invalid VehiclePropertyAccess value
- return CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_NONE;
- }
- }
-
- if (writeOnlyPresent) {
- if (!readOnlyPresent && !readWritePresent) {
- return CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE;
- }
- // Config cannot set write-only access for some areaId configs and read/read-write
- // access for other areaIds per property.
- return CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_NONE;
- }
- if (readOnlyPresent) {
- return CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ;
- }
- return CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE;
- }
-
/**
* Converts {@link HalPropConfig} to {@link CarPropertyConfig}.
*
* @param mgrPropertyId The Property ID used by Car Property Manager, different from the
* property ID used by VHAL.
*/
- public CarPropertyConfig<?> toCarPropertyConfig(int mgrPropertyId) {
+ public CarPropertyConfig<?> toCarPropertyConfig(int mgrPropertyId,
+ PropertyHalServiceConfigs propertyHalServiceConfigs) {
int propId = getPropId();
int areaType = getVehicleAreaType(propId & VehicleArea.MASK);
Class<?> clazz = CarPropertyUtils.getJavaClass(propId & VehiclePropertyType.MASK);
@@ -172,8 +135,11 @@
carPropertyConfigBuilder.setConfigArray(configArray);
HalAreaConfig[] halAreaConfigs = getAreaConfigs();
+ var allPossibleEnumValues = propertyHalServiceConfigs
+ .getAllPossibleSupportedEnumValues(getPropId());
if (halAreaConfigs.length == 0) {
- carPropertyConfigBuilder.addAreaIdConfig(generateAreaIdConfig(clazz, /* areaId= */ 0,
+ carPropertyConfigBuilder.addAreaIdConfig(generateAreaIdConfig(clazz,
+ allPossibleEnumValues, /* areaId= */ 0,
/* minInt32Value= */ 0, /* maxInt32Value= */ 0,
/* minFloatValue= */ 0, /* maxFloatValue= */ 0,
/* minInt64Value= */ 0, /* maxInt64Value= */ 0,
@@ -186,7 +152,8 @@
int areaAccess = (halAreaConfig.getAccess() == VehiclePropertyAccess.NONE)
? access : halAreaConfig.getAccess();
carPropertyConfigBuilder.addAreaIdConfig(
- generateAreaIdConfig(clazz, halAreaConfig.getAreaId(),
+ generateAreaIdConfig(clazz, allPossibleEnumValues,
+ halAreaConfig.getAreaId(),
halAreaConfig.getMinInt32Value(), halAreaConfig.getMaxInt32Value(),
halAreaConfig.getMinFloatValue(), halAreaConfig.getMaxFloatValue(),
halAreaConfig.getMinInt64Value(), halAreaConfig.getMaxInt64Value(),
@@ -197,7 +164,8 @@
return carPropertyConfigBuilder.build();
}
- private AreaIdConfig generateAreaIdConfig(Class<?> clazz, int areaId, int minInt32Value,
+ private AreaIdConfig generateAreaIdConfig(Class<?> clazz,
+ @Nullable Set<Integer> allPossibleEnumValues, int areaId, int minInt32Value,
int maxInt32Value, float minFloatValue, float maxFloatValue, long minInt64Value,
long maxInt64Value, long[] supportedEnumValues, boolean supportVariableUpdateRate,
int access) {
@@ -219,11 +187,9 @@
managerSupportedEnumValues.add((int) supportedEnumValues[i]);
}
areaIdConfigBuilder.setSupportedEnumValues(managerSupportedEnumValues);
- } else if (mPropertyHalServiceConfigs.getAllPossibleSupportedEnumValues(getPropId())
- != null) {
- areaIdConfigBuilder.setSupportedEnumValues(new ArrayList(
- mPropertyHalServiceConfigs.getAllPossibleSupportedEnumValues(
- getPropId())));
+ } else if (allPossibleEnumValues != null) {
+ areaIdConfigBuilder.setSupportedEnumValues(
+ new ArrayList(allPossibleEnumValues));
}
}
} else if (classMatched(Float.class, clazz) && (minFloatValue != 0 || maxFloatValue != 0)) {
diff --git a/service/src/com/android/car/hal/HalPropValue.java b/service/src/com/android/car/hal/HalPropValue.java
index b91acb3..b82f487 100644
--- a/service/src/com/android/car/hal/HalPropValue.java
+++ b/service/src/com/android/car/hal/HalPropValue.java
@@ -23,10 +23,10 @@
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
import android.car.VehiclePropertyIds;
+import android.car.builtin.util.Slogf;
import android.car.hardware.CarPropertyValue;
import android.hardware.automotive.vehicle.VehiclePropertyStatus;
import android.hardware.automotive.vehicle.VehiclePropertyType;
-import android.util.Log;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.property.CarPropertyHelper;
@@ -208,45 +208,45 @@
HalPropValue other = (HalPropValue) argument;
if (other.getPropId() != getPropId()) {
- Log.i(TAG, "Property ID mismatch, got " + other.getPropId() + " want "
+ Slogf.i(TAG, "Property ID mismatch, got " + other.getPropId() + " want "
+ getPropId());
return false;
}
if (other.getAreaId() != getAreaId()) {
- Log.i(TAG, "Area ID mismatch, got " + other.getAreaId() + " want " + getAreaId());
+ Slogf.i(TAG, "Area ID mismatch, got " + other.getAreaId() + " want " + getAreaId());
return false;
}
if (other.getStatus() != getStatus()) {
- Log.i(TAG, "Status mismatch, got " + other.getStatus() + " want " + getStatus());
+ Slogf.i(TAG, "Status mismatch, got " + other.getStatus() + " want " + getStatus());
return false;
}
if (other.getTimestamp() != getTimestamp()) {
- Log.i(TAG, "Timestamp mismatch, got " + other.getTimestamp() + " want "
+ Slogf.i(TAG, "Timestamp mismatch, got " + other.getTimestamp() + " want "
+ getTimestamp());
return false;
}
if (!equalInt32Values(other)) {
- Log.i(TAG, "Int32Values mismatch, got " + other.dumpInt32Values() + " want "
+ Slogf.i(TAG, "Int32Values mismatch, got " + other.dumpInt32Values() + " want "
+ dumpInt32Values());
return false;
}
if (!equalFloatValues(other)) {
- Log.i(TAG, "FloatValues mismatch, got " + other.dumpFloatValues() + " want "
+ Slogf.i(TAG, "FloatValues mismatch, got " + other.dumpFloatValues() + " want "
+ dumpFloatValues());
return false;
}
if (!equalInt64Values(other)) {
- Log.i(TAG, "Int64Values mismatch, got " + other.dumpInt64Values() + " want "
+ Slogf.i(TAG, "Int64Values mismatch, got " + other.dumpInt64Values() + " want "
+ dumpInt64Values());
return false;
}
if (!Arrays.equals(other.getByteArray(), getByteArray())) {
- Log.i(TAG, "ByteValues mismatch, got " + Arrays.toString(other.getByteArray())
+ Slogf.i(TAG, "ByteValues mismatch, got " + Arrays.toString(other.getByteArray())
+ " want " + Arrays.toString(getByteArray()));
return false;
}
if (!other.getStringValue().equals(getStringValue())) {
- Log.i(TAG, "StringValue mismatch, got " + other.getStringValue() + " want "
+ Slogf.i(TAG, "StringValue mismatch, got " + other.getStringValue() + " want "
+ getStringValue());
return false;
}
diff --git a/service/src/com/android/car/hal/HalServiceBase.java b/service/src/com/android/car/hal/HalServiceBase.java
index 57b4bf1..7ee05bd 100644
--- a/service/src/com/android/car/hal/HalServiceBase.java
+++ b/service/src/com/android/car/hal/HalServiceBase.java
@@ -86,7 +86,6 @@
*/
@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public void takeProperties(@NonNull Collection<HalPropConfig> properties) {
- return;
}
/**
@@ -94,7 +93,6 @@
*/
@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public void onHalEvents(List<HalPropValue> values) {
- return;
}
/**
diff --git a/service/src/com/android/car/hal/PowerHalService.java b/service/src/com/android/car/hal/PowerHalService.java
index 6ba5957..9cbca71 100644
--- a/service/src/com/android/car/hal/PowerHalService.java
+++ b/service/src/com/android/car/hal/PowerHalService.java
@@ -15,11 +15,12 @@
*/
package com.android.car.hal;
+import static android.hardware.automotive.vehicle.VehicleProperty.AP_POWER_BOOTUP_REASON;
import static android.hardware.automotive.vehicle.VehicleProperty.AP_POWER_STATE_REPORT;
import static android.hardware.automotive.vehicle.VehicleProperty.AP_POWER_STATE_REQ;
-import static android.hardware.automotive.vehicle.VehicleProperty.AP_POWER_BOOTUP_REASON;
import static android.hardware.automotive.vehicle.VehicleProperty.DISPLAY_BRIGHTNESS;
import static android.hardware.automotive.vehicle.VehicleProperty.PER_DISPLAY_BRIGHTNESS;
+import static android.hardware.automotive.vehicle.VehicleProperty.SHUTDOWN_REQUEST;
import static android.hardware.automotive.vehicle.VehicleProperty.VEHICLE_IN_USE;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
@@ -28,6 +29,7 @@
import android.annotation.Nullable;
import android.car.builtin.util.Slogf;
import android.car.builtin.view.DisplayHelper;
+import android.car.feature.FeatureFlags;
import android.content.Context;
import android.hardware.automotive.vehicle.VehicleApPowerBootupReason;
import android.hardware.automotive.vehicle.VehicleApPowerStateConfigFlag;
@@ -40,6 +42,7 @@
import android.hardware.display.DisplayManager;
import android.os.ServiceSpecificException;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.view.Display;
import com.android.car.CarLog;
@@ -63,6 +66,10 @@
// Set display brightness from 0-100%
public static final int MAX_BRIGHTNESS = 100;
+ // TODO(b/337307388): replace this with VehicleProperty.PER_DISPLAY_MAX_BRIGHTNESS once we use
+ // property V4.
+ private static final int PER_DISPLAY_MAX_BRIGHTNESS = 0x11410F4E;
+
private record PropertyInfo(boolean needSubscription) {}
private static SparseArray<PropertyInfo> getSupportedProperties() {
@@ -74,6 +81,7 @@
propertyInfo.put(PER_DISPLAY_BRIGHTNESS, new PropertyInfo(/*needSubscription=*/ true));
propertyInfo.put(VEHICLE_IN_USE, new PropertyInfo(/*needSubscription=*/ false));
propertyInfo.put(AP_POWER_BOOTUP_REASON, new PropertyInfo(/*needSubscription=*/ false));
+ propertyInfo.put(PER_DISPLAY_MAX_BRIGHTNESS, new PropertyInfo(/*needSubscription=*/ false));
return propertyInfo;
}
@@ -314,6 +322,7 @@
private final SparseArray<HalPropConfig> mProperties = new SparseArray<>();
private final Context mContext;
private final VehicleHal mHal;
+ private final FeatureFlags mFeatureFlags;
@Nullable
@GuardedBy("mLock")
private ArrayList<HalPropValue> mQueuedEvents;
@@ -322,10 +331,13 @@
@GuardedBy("mLock")
private int mMaxDisplayBrightness;
@GuardedBy("mLock")
+ private SparseIntArray mMaxPerDisplayBrightness = new SparseIntArray();
+ @GuardedBy("mLock")
private boolean mPerDisplayBrightnessSupported;
- public PowerHalService(Context context, VehicleHal hal) {
+ public PowerHalService(Context context, FeatureFlags featureFlags, VehicleHal hal) {
mContext = context;
+ mFeatureFlags = featureFlags;
mHal = hal;
}
@@ -519,7 +531,7 @@
}
try {
- mHal.set(VehicleProperty.SHUTDOWN_REQUEST, /* areaId= */ 0).to(shutdownParam);
+ mHal.set(SHUTDOWN_REQUEST, /* areaId= */ 0).to(shutdownParam);
} catch (ServiceSpecificException | IllegalArgumentException e) {
Slogf.e(CarLog.TAG_POWER, "cannot send SHUTDOWN_REQUEST to VHAL", e);
}
@@ -584,6 +596,32 @@
}
/**
+ * Returns whether {@code VEHICLE_IN_USE} is supported and getting it returns a valid value.
+ */
+ public boolean isVehicleInUseSupported() {
+ try {
+ HalPropValue value = mHal.get(VEHICLE_IN_USE);
+ if (value.getStatus() != VehiclePropertyStatus.AVAILABLE) {
+ Slogf.w(CarLog.TAG_POWER,
+ "VEHICLE_IN_USE is supported in config but getting it returns a property "
+ + "value: " + value + " which does not contain AVAILABLE status");
+ return false;
+ }
+ return true;
+ } catch (ServiceSpecificException | IllegalArgumentException e) {
+ Slogf.w(CarLog.TAG_POWER, "VEHICLE_IN_USE is not supported", e);
+ return false;
+ }
+ }
+
+ /**
+ * Returns whether {@code SHUTDOWN_REQUEST} is supported
+ */
+ public boolean isShutdownRequestSupported() {
+ return mHal.getPropConfig(SHUTDOWN_REQUEST) != null;
+ }
+
+ /**
* Gets the head unit's bootup reason.
*
* This reason is only set once during bootup and will not change if, say user enters the
@@ -650,7 +688,8 @@
synchronized (mLock) {
for (int i = 0; i < mProperties.size(); i++) {
int propId = mProperties.valueAt(i).getPropId();
- if (SUPPORTED_PROPERTIES.get(propId).needSubscription) {
+ if (mProperties.contains(propId)
+ && SUPPORTED_PROPERTIES.get(propId).needSubscription) {
mHal.subscribeProperty(this, propId);
}
}
@@ -668,10 +707,40 @@
+ mMaxDisplayBrightness);
mMaxDisplayBrightness = 1;
}
+
+ if (mFeatureFlags.perDisplayMaxBrightness()) {
+ getMaxPerDisplayBrightnessFromVhalLocked();
+ }
}
}
}
+ @GuardedBy("mLock")
+ private void getMaxPerDisplayBrightnessFromVhalLocked() {
+ if (!mPerDisplayBrightnessSupported
+ || !mProperties.contains(PER_DISPLAY_MAX_BRIGHTNESS)) {
+ return;
+ }
+
+ try {
+ HalPropValue value = mHal.get(PER_DISPLAY_MAX_BRIGHTNESS);
+ for (int i = 0; i + 1 < value.getInt32ValuesSize(); i += 2) {
+ int displayPort = value.getInt32Value(i);
+ int maxDisplayBrightness = value.getInt32Value(i + 1);
+ if (maxDisplayBrightness <= 0) {
+ Slogf.w(CarLog.TAG_POWER,
+ "Max display brightness from vehicle HAL for display port: %d is "
+ + "invalid: %d", displayPort, maxDisplayBrightness);
+ maxDisplayBrightness = 1;
+ }
+ mMaxPerDisplayBrightness.put(displayPort, maxDisplayBrightness);
+ }
+ } catch (ServiceSpecificException e) {
+ Slogf.e(CarLog.TAG_POWER, "Cannot get PER_DISPLAY_MAX_BRIGHTNESS", e);
+ }
+
+ }
+
@Override
public void release() {
synchronized (mLock) {
@@ -778,20 +847,27 @@
}
case PER_DISPLAY_BRIGHTNESS:
{
- int maxBrightness;
- synchronized (mLock) {
- maxBrightness = mMaxDisplayBrightness;
- }
int displayPort;
int brightness;
try {
displayPort = v.getInt32Value(0);
- brightness = v.getInt32Value(1) * MAX_BRIGHTNESS / maxBrightness;
+ brightness = v.getInt32Value(1);
} catch (IndexOutOfBoundsException e) {
Slogf.e(CarLog.TAG_POWER, "Received invalid event, ignore, int32Values: "
+ v.dumpInt32Values(), e);
break;
}
+ int maxBrightness;
+ synchronized (mLock) {
+ if (!mFeatureFlags.perDisplayMaxBrightness()
+ || mMaxPerDisplayBrightness.size() == 0) {
+ maxBrightness = mMaxDisplayBrightness;
+ } else {
+ maxBrightness = mMaxPerDisplayBrightness.get(displayPort,
+ /* valueIfKeyNotFound= */ 1);
+ }
+ }
+ brightness = brightness * MAX_BRIGHTNESS / maxBrightness;
brightness = adjustBrightness(brightness, /* minBrightness= */ 0,
MAX_BRIGHTNESS);
Slogf.i(CarLog.TAG_POWER, "Received PER_DISPLAY_BRIGHTNESS=" + brightness
diff --git a/service/src/com/android/car/hal/PropertyHalService.java b/service/src/com/android/car/hal/PropertyHalService.java
index 1814883..e995f37 100644
--- a/service/src/com/android/car/hal/PropertyHalService.java
+++ b/service/src/com/android/car/hal/PropertyHalService.java
@@ -65,7 +65,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-import android.util.Pair;
import android.util.SparseArray;
import com.android.car.CarLog;
@@ -127,7 +126,6 @@
// it should be considered a success. If we get the initial value successfully and the
// initial value is the same as the target value, we treat the async set as success.
private static final int GET_INITIAL_VALUE_FOR_SET = 2;
- private static final float UPDATE_RATE_ERROR = -1f;
// A fake pending request ID for car property service.
private static final int CAR_PROP_SVC_REQUEST_ID = -1;
@@ -140,12 +138,12 @@
private final PairSparseArray<CarPropertyValue> mStaticPropertyIdAreaIdCache =
new PairSparseArray<>();
- private static final Histogram sGetAsyncEndToEndLatencyHistogram = new Histogram(
+ private final Histogram mGetAsyncEndToEndLatencyHistogram = new Histogram(
"automotive_os.value_get_async_end_to_end_latency",
new Histogram.ScaledRangeOptions(/* binCount= */ 20, /* minValue= */ 0,
/* firstBinWidth= */ 2, /* scaleFactor= */ 1.5f));
- private static final Histogram sSetAsyncEndToEndLatencyHistogram = new Histogram(
+ private final Histogram mSetAsyncEndToEndLatencyHistogram = new Histogram(
"automotive_os.value_set_async_end_to_end_latency",
new Histogram.ScaledRangeOptions(/* binCount= */ 20, /* minValue= */ 0,
/* firstBinWidth= */ 2, /* scaleFactor= */ 1.5f));
@@ -155,6 +153,17 @@
@Retention(RetentionPolicy.SOURCE)
private @interface AsyncRequestType {}
+ public record ClientType(Integer requestId) {
+ @Override
+ public String toString() {
+ if (requestId == CAR_PROP_SVC_REQUEST_ID) {
+ return "PropertyHalService.subscribeProperty";
+ }
+ return "PropertyHalService.setCarPropertyValuesAsync(requestId="
+ + requestId.toString() + ")";
+ }
+ }
+
private static final class GetSetValueResultWrapper {
private GetSetValueResult mGetSetValueResult;
private long mAsyncRequestStartTime;
@@ -384,8 +393,6 @@
@GuardedBy("mLock")
private final SparseArray<HalPropConfig> mHalPropIdToPropConfig =
new SparseArray<>();
- @GuardedBy("mLock")
- private final SparseArray<Pair<String, String>> mMgrPropIdToPermissions = new SparseArray<>();
// A pending request pool to store all pending async get/set property request info.
// Service request ID is int, not long, but we only have one version of PendingRequestPool.
@GuardedBy("mLock")
@@ -412,7 +419,7 @@
// client requests at 10hz, then we should do nothing, however, if we internally unsubscribe,
// then the [propA, areaA] should be subscribed at 10hz.
@GuardedBy("mLock")
- private final SubscriptionManager<Integer> mSubManager = new SubscriptionManager<>();
+ private final SubscriptionManager<ClientType> mSubManager = new SubscriptionManager<>();
private class AsyncRequestTimeoutCallback implements TimeoutCallback {
@Override
@@ -488,7 +495,7 @@
return;
}
List<GetSetValueResult> getSetValueResults = logAndReturnResults(
- sGetAsyncEndToEndLatencyHistogram, results, GET);
+ mGetAsyncEndToEndLatencyHistogram, results, GET);
try {
mAsyncPropertyResultCallback.onGetValueResults(
new GetSetValueResultList(getSetValueResults));
@@ -502,7 +509,7 @@
return;
}
List<GetSetValueResult> getSetValueResults = logAndReturnResults(
- sSetAsyncEndToEndLatencyHistogram, results, SET);
+ mSetAsyncEndToEndLatencyHistogram, results, SET);
try {
mAsyncPropertyResultCallback.onSetValueResults(
new GetSetValueResultList(getSetValueResults));
@@ -687,7 +694,7 @@
if (carPropertyValue != null) {
int propertyId = carPropertyValue.getPropertyId();
int areaId = carPropertyValue.getAreaId();
- if (isStaticAndSystemProperty(propertyId)) {
+ if (isStaticAndSystemPropertyLocked(propertyId)) {
mStaticPropertyIdAreaIdCache.put(propertyId, areaId,
carPropertyValue);
}
@@ -777,7 +784,7 @@
clientRequestInfo.getAsyncRequestStartTime(),
clientRequestInfo.getRetryCount()));
removePendingAsyncPropRequestInfoLocked(clientRequestInfo);
- sSetAsyncEndToEndLatencyHistogram
+ mSetAsyncEndToEndLatencyHistogram
.logSample((float) System.currentTimeMillis()
- clientRequestInfo.getAsyncRequestStartTime());
continue;
@@ -1008,7 +1015,7 @@
mHalPropIdToWaitingUpdateRequestInfo.remove(halPropId);
}
// We no longer need to subscribe to the property.
- mSubManager.stageUnregister(pendingRequest.getServiceRequestId(),
+ mSubManager.stageUnregister(new ClientType(pendingRequest.getServiceRequestId()),
new ArraySet<Integer>(Set.of(halPropId)));
}
@@ -1068,7 +1075,7 @@
HalPropConfig halPropConfig = mHalPropIdToPropConfig.valueAt(i);
int mgrPropId = halToManagerPropId(halPropConfig.getPropId());
CarPropertyConfig<?> carPropertyConfig = halPropConfig.toCarPropertyConfig(
- mgrPropId);
+ mgrPropId, mPropertyHalServiceConfigs);
mgrPropIdToCarPropConfig.put(mgrPropId, carPropertyConfig);
}
return mgrPropIdToCarPropConfig;
@@ -1091,7 +1098,7 @@
HalPropConfig halPropConfig;
synchronized (mLock) {
halPropConfig = mHalPropIdToPropConfig.get(halPropId);
- if (isStaticAndSystemProperty(mgrPropId)) {
+ if (isStaticAndSystemPropertyLocked(mgrPropId)) {
CarPropertyValue carPropertyValue = mStaticPropertyIdAreaIdCache.get(mgrPropId,
areaId);
if (carPropertyValue != null) {
@@ -1106,10 +1113,10 @@
halPropValue = mVehicleHal.get(halPropId, areaId);
try {
CarPropertyValue result = halPropValue.toCarPropertyValue(mgrPropId, halPropConfig);
- if (!isStaticAndSystemProperty(mgrPropId)) {
- return result;
- }
synchronized (mLock) {
+ if (!isStaticAndSystemPropertyLocked(mgrPropId)) {
+ return result;
+ }
mStaticPropertyIdAreaIdCache.put(mgrPropId, areaId, result);
return result;
}
@@ -1193,7 +1200,6 @@
public void subscribeProperty(List<CarSubscription> carSubscriptions)
throws ServiceSpecificException {
synchronized (mLock) {
- Set<Integer> halPropIdsToSubscribe = new ArraySet<>();
// Even though this involves binder call, this must be done inside the lock so that
// the state in {@code mSubManager} is consistent with the state in VHAL.
for (int i = 0; i < carSubscriptions.size(); i++) {
@@ -1207,9 +1213,9 @@
}
int halPropId = managerToHalPropId(mgrPropId);
// Note that we use halPropId instead of mgrPropId in mSubManager.
- mSubManager.stageNewOptions(CAR_PROP_SVC_REQUEST_ID, List.of(newCarSubscription(
- halPropId, areaIds, updateRateHz, carSubscription.enableVariableUpdateRate,
- carSubscription.resolution)));
+ mSubManager.stageNewOptions(new ClientType(CAR_PROP_SVC_REQUEST_ID),
+ List.of(newCarSubscription(halPropId, areaIds, updateRateHz,
+ carSubscription.enableVariableUpdateRate, carSubscription.resolution)));
}
try {
updateSubscriptionRateLocked();
@@ -1234,8 +1240,8 @@
synchronized (mLock) {
// Even though this involves binder call, this must be done inside the lock so that
// the state in {@code mSubManager} is consistent with the state in VHAL.
- mSubManager.stageUnregister(CAR_PROP_SVC_REQUEST_ID, new ArraySet<Integer>(Set.of(
- halPropId)));
+ mSubManager.stageUnregister(new ClientType(CAR_PROP_SVC_REQUEST_ID),
+ new ArraySet<Integer>(Set.of(halPropId)));
try {
updateSubscriptionRateLocked();
} catch (ServiceSpecificException e) {
@@ -1246,21 +1252,6 @@
}
}
- @GuardedBy("mLock")
- private int[] getAllAreaIdsLocked(int mgrPropId) {
- int[] areaIds = EMPTY_INT_ARRAY;
- HalPropConfig halPropConfig = mHalPropIdToPropConfig.get(managerToHalPropId(mgrPropId));
- if (halPropConfig == null) {
- if (DBG) {
- Slogf.d(TAG, "Unable to get any areaIds from %s",
- VehiclePropertyIds.toString(mgrPropId));
- }
- return areaIds;
- }
- areaIds = halPropConfig.toCarPropertyConfig(mgrPropId).getAreaIds();
- return areaIds;
- }
-
@Override
public void init() {
if (DBG) {
@@ -1491,10 +1482,9 @@
// Check payload if it is an userdebug build.
if (BuildHelper.isDebuggableBuild()
&& !mPropertyHalServiceConfigs.checkPayload(halPropValue)) {
- Slogf.w(TAG,
+ Slogf.wtf(TAG,
"Drop event for property: %s because it is failed "
+ "in payload checking.", halPropValue);
- continue;
}
int mgrPropId = halToManagerPropId(halPropId);
if (DBG && halPropValue.getStatus() != VehiclePropertyStatus.AVAILABLE) {
@@ -1786,7 +1776,7 @@
HalPropConfig halPropConfig = mHalPropIdToPropConfig.get(halPropId);
setRequestInfo.parseClientUpdateRateHz(halPropConfig.toCarPropertyConfig(
- setRequestInfo.getPropertyId()));
+ setRequestInfo.getPropertyId(), mPropertyHalServiceConfigs));
if (mHalPropIdToWaitingUpdateRequestInfo.get(halPropId) == null) {
mHalPropIdToWaitingUpdateRequestInfo.put(halPropId, new ArrayList<>());
@@ -1797,7 +1787,7 @@
// Enable VUR for continuous since we only want to know when the value is updated.
boolean enableVur = (halPropConfig.getChangeMode()
== CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS);
- mSubManager.stageNewOptions(setRequestInfo.getServiceRequestId(),
+ mSubManager.stageNewOptions(new ClientType(setRequestInfo.getServiceRequestId()),
// Note that we use halPropId instead of mgrPropId in mSubManager.
List.of(newCarSubscription(halPropId,
new int[]{setRequestInfo.getAreaId()},
@@ -1952,7 +1942,7 @@
}
@GuardedBy("mLock")
- private boolean isStaticAndSystemProperty(int propertyId) {
+ private boolean isStaticAndSystemPropertyLocked(int propertyId) {
return mHalPropIdToPropConfig.get(managerToHalPropId(propertyId))
.getChangeMode() == VEHICLE_PROPERTY_CHANGE_MODE_STATIC
&& isSystemProperty(propertyId);
diff --git a/service/src/com/android/car/hal/UserHalHelper.java b/service/src/com/android/car/hal/UserHalHelper.java
index df5add5..6792e2f 100644
--- a/service/src/com/android/car/hal/UserHalHelper.java
+++ b/service/src/com/android/car/hal/UserHalHelper.java
@@ -23,6 +23,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.car.builtin.os.UserManagerHelper;
+import android.car.builtin.util.Slogf;
import android.hardware.automotive.vehicle.CreateUserRequest;
import android.hardware.automotive.vehicle.InitialUserInfoRequestType;
import android.hardware.automotive.vehicle.InitialUserInfoResponse;
@@ -44,7 +45,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
-import android.util.Log;
import com.android.car.hal.HalCallback.HalCallbackStatus;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
@@ -347,7 +347,7 @@
* @throws IllegalArgumentException if the HAL property doesn't have the proper format.
*/
public static InitialUserInfoResponse toInitialUserInfoResponse(HalPropValue prop) {
- if (DEBUG) Log.d(TAG, "toInitialUserInfoResponse(): " + prop);
+ if (DEBUG) Slogf.d(TAG, "toInitialUserInfoResponse(): %s", prop);
Objects.requireNonNull(prop, "prop cannot be null");
checkArgument(prop.getPropId() == INITIAL_USER_INFO_PROPERTY, "invalid prop on %s", prop);
@@ -368,7 +368,7 @@
if (!TextUtils.isEmpty(prop.getStringValue())) {
stringValues = TextUtils.split(prop.getStringValue(), STRING_SEPARATOR);
if (DEBUG) {
- Log.d(TAG, "toInitialUserInfoResponse(): values=" + Arrays.toString(stringValues)
+ Slogf.d(TAG, "toInitialUserInfoResponse(): values=" + Arrays.toString(stringValues)
+ " length: " + stringValues.length);
}
}
@@ -398,7 +398,7 @@
+ " on " + prop);
}
- if (DEBUG) Log.d(TAG, "returning : " + response);
+ if (DEBUG) Slogf.d(TAG, "returning : " + response);
return response;
}
@@ -595,7 +595,7 @@
List<UserHandle> users = UserManagerHelper.getUserHandles(um, /* excludeDying= */ false);
if (users == null || users.isEmpty()) {
- Log.w(TAG, "newUsersInfo(): no users");
+ Slogf.w(TAG, "newUsersInfo(): no users");
return emptyUsersInfo();
}
@@ -616,7 +616,7 @@
halUsers.add(halUser);
} catch (Exception e) {
// Most likely the user was removed
- Log.w(TAG, "newUsersInfo(): ignoring user " + user + " due to exception", e);
+ Slogf.w(TAG, "newUsersInfo(): ignoring user " + user + " due to exception", e);
}
}
int existingUsersSize = halUsers.size();
@@ -627,7 +627,7 @@
usersInfo.currentUser.flags = convertFlags(userHandleHelper, currentUser);
} else {
// This should not happen.
- Log.wtf(TAG, "Current user is not part of existing users. usersInfo: " + usersInfo);
+ Slogf.wtf(TAG, "Current user is not part of existing users. usersInfo: " + usersInfo);
}
return usersInfo;
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 0a1d198..ec0b30e 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -77,7 +77,6 @@
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
/**
* Abstraction for vehicle HAL. This class handles interface with native HAL and does basic parsing
@@ -271,7 +270,7 @@
mPropValueBuilder = vehicle.getHalPropValueBuilder();
mHandlerThread = handlerThread;
mHandler = new Handler(mHandlerThread.getLooper());
- mPowerHal = powerHal != null ? powerHal : new PowerHalService(context, this);
+ mPowerHal = powerHal != null ? powerHal : new PowerHalService(context, mFeatureFlags, this);
mPropertyHal = propertyHal != null ? propertyHal : new PropertyHalService(this);
mInputHal = inputHal != null ? inputHal : new InputHalService(this);
mVmsHal = vmsHal != null ? vmsHal : new VmsHalService(context, this);
@@ -1266,8 +1265,11 @@
*/
public void dumpSpecificHals(PrintWriter writer, String... halNames) {
synchronized (mLock) {
- Map<String, HalServiceBase> byName = mAllServices.stream()
- .collect(Collectors.toMap(s -> s.getClass().getSimpleName(), s -> s));
+ ArrayMap<String, HalServiceBase> byName = new ArrayMap<>();
+ for (int index = 0; index < mAllServices.size(); index++) {
+ HalServiceBase halService = mAllServices.get(index);
+ byName.put(halService.getClass().getSimpleName(), halService);
+ }
for (String halName : halNames) {
HalServiceBase service = byName.get(halName);
if (service == null) {
diff --git a/service/src/com/android/car/hal/fakevhal/FakeVhalConfigParser.java b/service/src/com/android/car/hal/fakevhal/FakeVhalConfigParser.java
index 3812972..1acc965 100644
--- a/service/src/com/android/car/hal/fakevhal/FakeVhalConfigParser.java
+++ b/service/src/com/android/car/hal/fakevhal/FakeVhalConfigParser.java
@@ -35,20 +35,21 @@
import android.hardware.automotive.vehicle.VehicleLightSwitch;
import android.hardware.automotive.vehicle.VehiclePropConfig;
import android.hardware.automotive.vehicle.VehicleProperty;
-import android.hardware.automotive.vehicle.VehiclePropertyGroup;
+import android.util.JsonReader;
+import android.util.JsonToken;
+import android.util.MalformedJsonException;
import android.util.Pair;
import android.util.SparseArray;
import com.android.car.CarLog;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
+import com.android.car.internal.util.IntArray;
+import com.android.car.internal.util.LongArray;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
@@ -61,6 +62,10 @@
public final class FakeVhalConfigParser {
private static final String TAG = CarLog.tagFor(FakeVhalConfigParser.class);
+
+ // Defined VehiclePropertyAccess is always 0+
+ private static final int ACCESS_NOT_SET = -1;
+
private static final String ENUM_CLASS_DIRECTORY = "android.hardware.automotive.vehicle.";
private static final String JSON_FIELD_NAME_ROOT = "properties";
private static final String JSON_FIELD_NAME_PROPERTY_ID = "property";
@@ -112,6 +117,8 @@
private static final int FUEL_DOOR_REAR_LEFT = PortLocationType.REAR_LEFT;
// TODO(b/241984846) Keep SEAT_2_CENTER from HVAC_LEFT here. May have a new design to handle
// HVAC zone ids.
+ private static final int HVAC_FRONT_ROW = SEAT_1_LEFT | SEAT_1_RIGHT;
+ private static final int HVAC_REAR_ROW = SEAT_2_LEFT | SEAT_2_CENTER | SEAT_2_RIGHT;
private static final int HVAC_LEFT = SEAT_1_LEFT | SEAT_2_LEFT | SEAT_2_CENTER;
private static final int HVAC_RIGHT = SEAT_1_RIGHT | SEAT_2_RIGHT;
private static final int HVAC_ALL = HVAC_LEFT | HVAC_RIGHT;
@@ -135,6 +142,8 @@
Map.entry("HVAC_ALL", HVAC_ALL),
Map.entry("HVAC_LEFT", HVAC_LEFT),
Map.entry("HVAC_RIGHT", HVAC_RIGHT),
+ Map.entry("HVAC_FRONT_ROW", HVAC_FRONT_ROW),
+ Map.entry("HVAC_REAR_ROW", HVAC_REAR_ROW),
Map.entry("VENDOR_EXTENSION_INT_PROPERTY",
TestVendorProperty.VENDOR_EXTENSION_INT_PROPERTY),
Map.entry("VENDOR_EXTENSION_BOOLEAN_PROPERTY",
@@ -208,7 +217,7 @@
* @param customConfigFile The custom config JSON file to parse from.
* @return a list of {@link ConfigDeclaration} storing configs and values for each property.
* @throws IOException if unable to read the config file.
- * @throws IllegalArgumentException if file is invalid JSON or when a JSONException is caught.
+ * @throws IllegalArgumentException if the config file content is not in expected format.
*/
public SparseArray<ConfigDeclaration> parseJsonConfig(File customConfigFile) throws
IOException, IllegalArgumentException {
@@ -232,47 +241,55 @@
* @param configInputStream The {@link InputStream} to parse from.
* @return a list of {@link ConfigDeclaration} storing configs and values for each property.
* @throws IOException if unable to read the config file.
- * @throws IllegalArgumentException if file is invalid JSON or when a JSONException is caught.
+ * @throws IllegalArgumentException if file is not a valid expected JSON file.
*/
public SparseArray<ConfigDeclaration> parseJsonConfig(InputStream configInputStream)
throws IOException {
- String configString = new String(configInputStream.readAllBytes());
-
- // Parse JSON root object.
- JSONObject configJsonObject;
- JSONArray configJsonArray;
- try {
- configJsonObject = new JSONObject(configString);
- } catch (JSONException e) {
- throw new IllegalArgumentException("This file does not contain a valid JSONObject.", e);
- }
- try {
- configJsonArray = configJsonObject.getJSONArray(JSON_FIELD_NAME_ROOT);
- } catch (JSONException e) {
- throw new IllegalArgumentException(JSON_FIELD_NAME_ROOT + " field value is not a valid "
- + "JSONArray.", e);
- }
-
SparseArray<ConfigDeclaration> allPropConfigs = new SparseArray<>();
List<String> errors = new ArrayList<>();
- // Parse each property.
- for (int i = 0; i < configJsonArray.length(); i++) {
- JSONObject propertyObject = configJsonArray.optJSONObject(i);
- if (propertyObject == null) {
- errors.add(JSON_FIELD_NAME_ROOT + " array has an invalid JSON element at index "
- + i);
- continue;
- }
- ConfigDeclaration propConfig = parseEachProperty(propertyObject, errors);
- if (propConfig == null) {
- errors.add("Unable to parse JSON object: " + propertyObject + " at index " + i);
- if (allPropConfigs.size() != 0) {
- errors.add("Last successfully parsed property Id: "
- + allPropConfigs.valueAt(allPropConfigs.size() - 1).getConfig().prop);
+ // A wrapper to be effective final. We need the wrapper to be passed into lambda. Only one
+ // element is expected.
+ List<Boolean> rootFound = new ArrayList<>();
+
+ try (var reader = new JsonReader(new InputStreamReader(configInputStream, "UTF-8"))) {
+ reader.setLenient(true);
+ int parsed = parseObjectEntry(reader, (String fieldName) -> {
+ if (!fieldName.equals(JSON_FIELD_NAME_ROOT)) {
+ reader.skipValue();
+ return;
}
- continue;
+
+ rootFound.add(true);
+ int propertyParsed = parseArrayEntry(reader, (int index) -> {
+ ConfigDeclaration propConfig = parseEachProperty(reader, index, errors);
+ if (propConfig == null) {
+ errors.add("Unable to parse property config at index " + index);
+ if (allPropConfigs.size() != 0) {
+ errors.add("Last successfully parsed property Id: "
+ + allPropConfigs.valueAt(allPropConfigs.size() - 1)
+ .getConfig().prop);
+ }
+ return;
+ }
+ allPropConfigs.put(propConfig.getConfig().prop, propConfig);
+ });
+
+ if (propertyParsed < 0) {
+ throw new IllegalArgumentException(JSON_FIELD_NAME_ROOT
+ + " field value is not a valid JSONArray.");
+ }
+ });
+ if (parsed < 0) {
+ throw new IllegalArgumentException(
+ "This file does not contain a valid JSONObject.");
}
- allPropConfigs.put(propConfig.getConfig().prop, propConfig);
+ } catch (IllegalStateException | MalformedJsonException e) {
+ // Captures all unexpected json parsing error.
+ throw new IllegalArgumentException("Invalid json syntax", e);
+ }
+
+ if (rootFound.size() == 0) {
+ throw new IllegalArgumentException("Missing root field: " + JSON_FIELD_NAME_ROOT);
}
if (!errors.isEmpty()) {
throw new IllegalArgumentException(String.join("\n", errors));
@@ -283,131 +300,121 @@
/**
* Parses each property for its configs and values.
*
- * @param propertyObject A JSONObject which stores all configs and values of a property.
+ * @param reader The reader to parse.
+ * @param index The property index.
* @param errors A list to keep all errors.
* @return a {@link ConfigDeclaration} instance, null if failed to parse.
*/
@Nullable
- private ConfigDeclaration parseEachProperty(JSONObject propertyObject, List<String> errors) {
+ private ConfigDeclaration parseEachProperty(JsonReader reader, int index, List<String> errors)
+ throws IOException {
int initialErrorCount = errors.size();
- List<String> fieldNames = getFieldNames(propertyObject);
-
- if (fieldNames == null) {
- errors.add("The JSONObject " + propertyObject + " is empty.");
- return null;
- }
VehiclePropConfig vehiclePropConfig = new VehiclePropConfig();
vehiclePropConfig.prop = VehicleProperty.INVALID;
- boolean isAccessSet = false;
- boolean isChangeModeSet = false;
- boolean isAreasSet = false;
- RawPropValues rawPropValues = null;
- JSONArray areas = null;
- for (int i = 0; i < fieldNames.size(); i++) {
- String fieldName = fieldNames.get(i);
- switch (fieldName) {
- case JSON_FIELD_NAME_PROPERTY_ID:
- vehiclePropConfig.prop = parseIntValue(propertyObject, fieldName, errors);
- break;
- case JSON_FIELD_NAME_CONFIG_STRING:
- vehiclePropConfig.configString = parseStringValue(propertyObject, fieldName,
- errors);
- break;
- case JSON_FIELD_NAME_MIN_SAMPLE_RATE:
- vehiclePropConfig.minSampleRate = parseFloatValue(propertyObject, fieldName,
- errors);
- break;
- case JSON_FIELD_NAME_MAX_SAMPLE_RATE:
- vehiclePropConfig.maxSampleRate = parseFloatValue(propertyObject, fieldName,
- errors);
- break;
- case JSON_FIELD_NAME_ACCESS:
- vehiclePropConfig.access = parseIntValue(propertyObject, fieldName, errors);
- isAccessSet = true;
- break;
- case JSON_FIELD_NAME_CHANGE_MODE:
- vehiclePropConfig.changeMode = parseIntValue(propertyObject, fieldName, errors);
- isChangeModeSet = true;
- break;
- case JSON_FIELD_NAME_CONFIG_ARRAY:
- JSONArray configArray = propertyObject.optJSONArray(fieldName);
- if (configArray == null) {
- errors.add(fieldName + " doesn't have a mapped JSONArray value.");
- continue;
- }
- vehiclePropConfig.configArray = parseIntArrayValue(configArray, errors);
- break;
- case JSON_FIELD_NAME_DEFAULT_VALUE:
- JSONObject defaultValueObject = propertyObject.optJSONObject(fieldName);
- if (defaultValueObject == null) {
- Slogf.w(TAG, "%s doesn't have a mapped value.", fieldName);
- continue;
- }
- rawPropValues = parseDefaultValue(defaultValueObject, errors);
- break;
- case JSON_FIELD_NAME_AREAS:
- areas = propertyObject.optJSONArray(fieldName);
- isAreasSet = true;
- break;
- case JSON_FIELD_NAME_COMMENT:
- // The "comment" field is used for comment in the config files and is ignored
- // by the parser.
- break;
- default:
- Slogf.i(TAG, "%s is an unknown field name. It didn't get parsed.", fieldName);
- }
+ class Wrapper {
+ boolean mIsAccessSet;
+ boolean mIsChangeModeSet;
+ RawPropValues mRawPropValues;
}
- if (vehiclePropConfig.prop == VehicleProperty.INVALID) {
- errors.add(propertyObject + " doesn't have propId. PropId is required.");
+ var wrapper = new Wrapper();
+ SparseArray<RawPropValues> defaultValuesByAreaId = new SparseArray<>();
+
+ try {
+ int parsed = parseObjectEntry(reader, (String fieldName) -> {
+ switch (fieldName) {
+ case JSON_FIELD_NAME_PROPERTY_ID:
+ vehiclePropConfig.prop = parseIntValue(reader, fieldName, errors);
+ break;
+ case JSON_FIELD_NAME_CONFIG_STRING:
+ vehiclePropConfig.configString = parseStringValue(reader, fieldName,
+ errors);
+ break;
+ case JSON_FIELD_NAME_MIN_SAMPLE_RATE:
+ vehiclePropConfig.minSampleRate = parseFloatValue(reader, fieldName,
+ errors);
+ break;
+ case JSON_FIELD_NAME_MAX_SAMPLE_RATE:
+ vehiclePropConfig.maxSampleRate = parseFloatValue(reader, fieldName,
+ errors);
+ break;
+ case JSON_FIELD_NAME_ACCESS:
+ vehiclePropConfig.access = parseIntValue(reader, fieldName, errors);
+ wrapper.mIsAccessSet = true;
+ break;
+ case JSON_FIELD_NAME_CHANGE_MODE:
+ vehiclePropConfig.changeMode = parseIntValue(reader, fieldName, errors);
+ wrapper.mIsChangeModeSet = true;
+ break;
+ case JSON_FIELD_NAME_CONFIG_ARRAY:
+ vehiclePropConfig.configArray = parseIntArrayValue(reader, fieldName,
+ errors);
+ break;
+ case JSON_FIELD_NAME_DEFAULT_VALUE:
+ wrapper.mRawPropValues = parseDefaultValue(reader, errors);
+ break;
+ case JSON_FIELD_NAME_AREAS:
+ vehiclePropConfig.areaConfigs = parseAreaConfigs(reader,
+ defaultValuesByAreaId, errors);
+ break;
+ case JSON_FIELD_NAME_COMMENT:
+ // The "comment" field is used for comment in the config files and is
+ // ignored by the parser.
+ reader.skipValue();
+ break;
+ default:
+ Slogf.w(TAG, "%s is an unknown field name. It didn't get parsed.",
+ fieldName);
+ reader.skipValue();
+ }
+ });
+ if (parsed < 0) {
+ errors.add(JSON_FIELD_NAME_ROOT
+ + " array has an invalid JSON element at index " + index);
+ return null;
+ }
+ if (parsed == 0) {
+ errors.add("The property object at index" + index + " is empty.");
+ return null;
+ }
+ } catch (IllegalArgumentException e) {
+ errors.add("Faild to parse property field, error: " + e.getMessage());
return null;
}
- if (!isAccessSet) {
- if (AccessForVehicleProperty.values.containsKey(vehiclePropConfig.prop)) {
+ if (vehiclePropConfig.prop == VehicleProperty.INVALID) {
+ errors.add("PropId is required for any property.");
+ return null;
+ }
+
+ int propertyId = vehiclePropConfig.prop;
+
+ if (!wrapper.mIsAccessSet) {
+ if (AccessForVehicleProperty.values.containsKey(propertyId)) {
vehiclePropConfig.access = AccessForVehicleProperty.values
- .get(vehiclePropConfig.prop);
+ .get(propertyId);
} else {
- errors.add("Access field is not set for this property: " + propertyObject);
+ errors.add("Access field is not set for this property: " + propertyId);
}
}
- if (!isChangeModeSet) {
+ if (!wrapper.mIsChangeModeSet) {
if (ChangeModeForVehicleProperty.values.containsKey(vehiclePropConfig.prop)) {
vehiclePropConfig.changeMode = ChangeModeForVehicleProperty.values
- .get(vehiclePropConfig.prop);
+ .get(propertyId);
} else {
- errors.add("ChangeMode field is not set for this property: " + propertyObject);
+ errors.add("ChangeMode field is not set for this property: " + propertyId);
}
}
- List<VehicleAreaConfig> areaConfigs = new ArrayList<>();
- SparseArray<RawPropValues> defaultValuesByAreaId = new SparseArray<>();
-
- if (isAreasSet) {
- if (areas == null) {
- errors.add(JSON_FIELD_NAME_AREAS + " doesn't have a mapped array value.");
- } else {
- for (int j = 0; j < areas.length(); j++) {
- JSONObject areaObject = areas.optJSONObject(j);
- if (areaObject == null) {
- errors.add("Unable to get a JSONObject element for "
- + JSON_FIELD_NAME_AREAS + " at index " + j);
- continue;
- }
- Pair<VehicleAreaConfig, RawPropValues> result =
- parseAreaConfig(areaObject, vehiclePropConfig.access, errors);
- if (result != null) {
- areaConfigs.add(result.first);
- if (result.second != null) {
- defaultValuesByAreaId.put(result.first.areaId, result.second);
- }
- }
+ // If area access is not set, set it to the global access.
+ if (vehiclePropConfig.areaConfigs != null) {
+ for (int i = 0; i < vehiclePropConfig.areaConfigs.length; i++) {
+ if (vehiclePropConfig.areaConfigs[i].access == ACCESS_NOT_SET) {
+ vehiclePropConfig.areaConfigs[i].access = vehiclePropConfig.access;
}
- vehiclePropConfig.areaConfigs = areaConfigs.toArray(
- new VehicleAreaConfig[areaConfigs.size()]);
}
}
@@ -423,65 +430,123 @@
return null;
}
- return new ConfigDeclaration(vehiclePropConfig, rawPropValues, defaultValuesByAreaId);
+ return new ConfigDeclaration(vehiclePropConfig, wrapper.mRawPropValues,
+ defaultValuesByAreaId);
+ }
+
+ /**
+ * Parses area configs array.
+ *
+ * @param reader The reader to parse.
+ * @param defaultValuesByAreaId The output default property value by specific area ID.
+ * @param errors The list to store all errors.
+ * @return a pair of configs and values for one area, null if failed to parse.
+ */
+ @Nullable
+ private VehicleAreaConfig[] parseAreaConfigs(JsonReader reader,
+ SparseArray<RawPropValues> defaultValuesByAreaId, List<String> errors)
+ throws IOException {
+ int initialErrorCount = errors.size();
+ List<VehicleAreaConfig> areaConfigs = new ArrayList<>();
+ int parsed = 0;
+
+ try {
+ parsed = parseArrayEntry(reader, (int index) -> {
+ Pair<VehicleAreaConfig, RawPropValues> result = parseAreaConfig(reader, index,
+ errors);
+ if (result != null) {
+ areaConfigs.add(result.first);
+ if (result.second != null) {
+ defaultValuesByAreaId.put(result.first.areaId, result.second);
+ }
+ }
+ });
+ } catch (IllegalArgumentException e) {
+ errors.add("Failed to parse " + JSON_FIELD_NAME_AREAS + ", error: " + e.getMessage());
+ return null;
+ }
+
+ if (parsed < 0) {
+ errors.add(JSON_FIELD_NAME_AREAS + " doesn't have a valid JSONArray value.");
+ return null;
+ }
+ if (errors.size() > initialErrorCount) {
+ return null;
+ }
+ return areaConfigs.toArray(new VehicleAreaConfig[areaConfigs.size()]);
}
/**
* Parses area JSON config object.
*
- * @param areaObject A JSONObject of field name "areas".
+ * @param reader The reader to parse.
* @param errors The list to store all errors.
* @return a pair of configs and values for one area, null if failed to parse.
*/
@Nullable
- private Pair<VehicleAreaConfig, RawPropValues> parseAreaConfig(JSONObject areaObject,
- int defaultAccessMode, List<String> errors) {
+ private Pair<VehicleAreaConfig, RawPropValues> parseAreaConfig(JsonReader reader,
+ int index, List<String> errors) throws IOException {
int initialErrorCount = errors.size();
- List<String> fieldNames = getFieldNames(areaObject);
+ VehicleAreaConfig areaConfig = new VehicleAreaConfig();
- if (fieldNames == null) {
- errors.add("The JSONObject " + areaObject + " is empty.");
+ class Wrapper {
+ RawPropValues mDefaultValue;
+ boolean mHasAreaId;
+ boolean mIsAccessSet;
+ }
+ var wrapper = new Wrapper();
+ int parsed = 0;
+
+ try {
+ parsed = parseObjectEntry(reader, (String fieldName) -> {
+ switch (fieldName) {
+ case JSON_FIELD_NAME_ACCESS:
+ areaConfig.access = parseIntValue(reader, fieldName, errors);
+ wrapper.mIsAccessSet = true;
+ break;
+ case JSON_FIELD_NAME_AREA_ID:
+ areaConfig.areaId = parseIntValue(reader, fieldName, errors);
+ wrapper.mHasAreaId = true;
+ break;
+ case JSON_FIELD_NAME_MIN_INT32_VALUE:
+ areaConfig.minInt32Value = parseIntValue(reader, fieldName, errors);
+ break;
+ case JSON_FIELD_NAME_MAX_INT32_VALUE:
+ areaConfig.maxInt32Value = parseIntValue(reader, fieldName, errors);
+ break;
+ case JSON_FIELD_NAME_MIN_FLOAT_VALUE:
+ areaConfig.minFloatValue = parseFloatValue(reader, fieldName, errors);
+ break;
+ case JSON_FIELD_NAME_MAX_FLOAT_VALUE:
+ areaConfig.maxFloatValue = parseFloatValue(reader, fieldName, errors);
+ break;
+ case JSON_FIELD_NAME_DEFAULT_VALUE:
+ wrapper.mDefaultValue = parseDefaultValue(reader, errors);
+ break;
+ default:
+ Slogf.i(TAG, "%s is an unknown field name. It didn't get parsed.",
+ fieldName);
+ reader.skipValue();
+ }
+ });
+ } catch (IllegalArgumentException e) {
+ errors.add("Failed to parse areaConfig, error: " + e.getMessage());
return null;
}
- VehicleAreaConfig areaConfig = new VehicleAreaConfig();
- RawPropValues defaultValue = null;
- boolean hasAreaId = false;
- boolean isAccessSet = false;
-
- for (int i = 0; i < fieldNames.size(); i++) {
- String fieldName = fieldNames.get(i);
- switch (fieldName) {
- case JSON_FIELD_NAME_ACCESS:
- areaConfig.access = parseIntValue(areaObject, fieldName, errors);
- isAccessSet = true;
- break;
- case JSON_FIELD_NAME_AREA_ID:
- areaConfig.areaId = parseIntValue(areaObject, fieldName, errors);
- hasAreaId = true;
- break;
- case JSON_FIELD_NAME_MIN_INT32_VALUE:
- areaConfig.minInt32Value = parseIntValue(areaObject, fieldName, errors);
- break;
- case JSON_FIELD_NAME_MAX_INT32_VALUE:
- areaConfig.maxInt32Value = parseIntValue(areaObject, fieldName, errors);
- break;
- case JSON_FIELD_NAME_MIN_FLOAT_VALUE:
- areaConfig.minFloatValue = parseFloatValue(areaObject, fieldName, errors);
- break;
- case JSON_FIELD_NAME_MAX_FLOAT_VALUE:
- areaConfig.maxFloatValue = parseFloatValue(areaObject, fieldName, errors);
- break;
- case JSON_FIELD_NAME_DEFAULT_VALUE:
- defaultValue = parseDefaultValue(areaObject.optJSONObject(fieldName), errors);
- break;
- default:
- Slogf.i(TAG, "%s is an unknown field name. It didn't get parsed.", fieldName);
- }
+ if (parsed < 0) {
+ errors.add("Unable to get a JSONObject element for " + JSON_FIELD_NAME_AREAS
+ + " at index " + index);
+ return null;
+ }
+ if (parsed == 0) {
+ errors.add("The JSONObject element for " + JSON_FIELD_NAME_AREAS + " at index "
+ + index + " is empty.");
+ return null;
}
- if (!hasAreaId) {
- errors.add(areaObject + " doesn't have areaId. AreaId is required.");
+ if (!wrapper.mHasAreaId) {
+ errors.add(areaConfig + " doesn't have areaId. AreaId is required.");
return null;
}
@@ -489,74 +554,66 @@
return null;
}
- if (!isAccessSet) {
- areaConfig.access = defaultAccessMode;
+ if (!wrapper.mIsAccessSet) {
+ areaConfig.access = ACCESS_NOT_SET;
}
- return Pair.create(areaConfig, defaultValue);
+ return Pair.create(areaConfig, wrapper.mDefaultValue);
}
/**
* Parses the "defaultValue" field of a property object and area property object.
*
- * @param defaultValue The defaultValue JSONObject to be parsed.
+ * @param reader The reader to parse.
* @param errors The list to store all errors.
* @return a {@link RawPropValues} object which stores defaultValue, null if failed to parse.
*/
@Nullable
- private RawPropValues parseDefaultValue(JSONObject defaultValue, List<String> errors) {
+ private RawPropValues parseDefaultValue(JsonReader reader, List<String> errors)
+ throws IOException {
int initialErrorCount = errors.size();
- List<String> fieldNames = getFieldNames(defaultValue);
+ RawPropValues rawPropValues = new RawPropValues();
+ int parsed = 0;
- if (fieldNames == null) {
- Slogf.w(TAG, "The JSONObject %s is empty.", defaultValue.toString());
+ try {
+ parsed = parseObjectEntry(reader, (String fieldName) -> {
+ switch (fieldName) {
+ case JSON_FIELD_NAME_INT32_VALUES: {
+ rawPropValues.int32Values = parseIntArrayValue(reader, fieldName, errors);
+ break;
+ }
+ case JSON_FIELD_NAME_INT64_VALUES: {
+ rawPropValues.int64Values = parseLongArrayValue(reader, fieldName, errors);
+ break;
+ }
+ case JSON_FIELD_NAME_FLOAT_VALUES: {
+ rawPropValues.floatValues = parseFloatArrayValue(reader, fieldName, errors);
+ break;
+ }
+ case JSON_FIELD_NAME_STRING_VALUE: {
+ rawPropValues.stringValue = parseStringValue(reader, fieldName, errors);
+ break;
+ }
+ default:
+ Slogf.i(TAG, "%s is an unknown field name. It didn't get parsed.",
+ fieldName);
+ reader.skipValue();
+ }
+ });
+ } catch (IllegalArgumentException e) {
+ errors.add("Failed to parse " + JSON_FIELD_NAME_DEFAULT_VALUE + ", error: "
+ + e.getMessage());
return null;
}
- RawPropValues rawPropValues = new RawPropValues();
-
- for (int i = 0; i < fieldNames.size(); i++) {
- String fieldName = fieldNames.get(i);
- switch (fieldName) {
- case JSON_FIELD_NAME_INT32_VALUES: {
- JSONArray int32Values = defaultValue.optJSONArray(fieldName);
- if (int32Values == null) {
- errors.add("Failed to parse the field name: " + fieldName + " for "
- + "defaultValueObject: " + defaultValue);
- continue;
- }
- rawPropValues.int32Values = parseIntArrayValue(int32Values, errors);
- break;
- }
- case JSON_FIELD_NAME_INT64_VALUES: {
- JSONArray int64Values = defaultValue.optJSONArray(fieldName);
- if (int64Values == null) {
- errors.add("Failed to parse the field name: " + fieldName + " for "
- + "defaultValueObject: " + defaultValue);
- continue;
- }
- rawPropValues.int64Values = parseLongArrayValue(int64Values, errors);
- break;
- }
- case JSON_FIELD_NAME_FLOAT_VALUES: {
- JSONArray floatValues = defaultValue.optJSONArray(fieldName);
- if (floatValues == null) {
- errors.add("Failed to parse the field name: " + fieldName + " for "
- + "defaultValueObject: " + defaultValue);
- continue;
- }
- rawPropValues.floatValues = parseFloatArrayValue(floatValues, errors);
- break;
- }
- case JSON_FIELD_NAME_STRING_VALUE: {
- rawPropValues.stringValue = parseStringValue(defaultValue, fieldName, errors);
- break;
- }
- default:
- Slogf.i(TAG, "%s is an unknown field name. It didn't get parsed.", fieldName);
- }
+ if (parsed < 0) {
+ errors.add(JSON_FIELD_NAME_DEFAULT_VALUE + " doesn't have a valid JSONObject value");
+ return null;
}
-
+ if (parsed == 0) {
+ Slogf.w(TAG, JSON_FIELD_NAME_DEFAULT_VALUE + " is empty, assuming no overwrite");
+ return null;
+ }
if (errors.size() > initialErrorCount) {
return null;
}
@@ -566,76 +623,67 @@
/**
* Parses String Json value.
*
- * @param parentObject The JSONObject will be parsed.
+ * @param reader The reader to parse.
* @param fieldName Field name of JSON object name/value mapping.
* @param errors The list to store all errors.
* @return a string of parsed value, null if failed to parse.
*/
@Nullable
- private String parseStringValue(JSONObject parentObject, String fieldName,
- List<String> errors) {
- String value = parentObject.optString(fieldName);
- if (Objects.equals(value, "")) {
- errors.add(fieldName + " doesn't have a mapped value.");
+ private String parseStringValue(JsonReader reader, String fieldName,
+ List<String> errors) throws IOException {
+ try {
+ String result = nextStringAdvance(reader);
+ if (result.equals("")) {
+ errors.add(fieldName + " doesn't have a valid string value.");
+ return null;
+ }
+ return result;
+ } catch (Exception e) {
+ errors.add(fieldName + " doesn't have a valid string value.");
return null;
}
- return value;
}
/**
* Parses int Json value.
*
- * @param parentObject The JSONObject will be parsed.
+ * @param reader The reader to parse.
* @param fieldName Field name of JSON object name/value mapping.
* @param errors The list to store all errors.
* @return a value as int, 0 if failed to parse.
*/
- private int parseIntValue(JSONObject parentObject, String fieldName, List<String> errors) {
- if (isString(parentObject, fieldName)) {
- String constantValue;
- try {
- constantValue = parentObject.getString(fieldName);
- return parseConstantValue(constantValue, errors);
- } catch (JSONException e) {
- errors.add(fieldName + " doesn't have a mapped string value. " + e.getMessage());
- return 0;
- }
+ private int parseIntValue(JsonReader reader, String fieldName, List<String> errors)
+ throws IOException {
+ if (isString(reader)) {
+ String constantValue = nextStringAdvance(reader);
+ return parseConstantValue(constantValue, errors);
}
- Object value = parentObject.opt(fieldName);
- if (value != JSONObject.NULL) {
- if (value.getClass() == Integer.class) {
- return parentObject.optInt(fieldName);
- }
- errors.add(fieldName + " doesn't have a mapped int value.");
+ try {
+ return nextIntAdvance(reader);
+ } catch (Exception e) {
+ errors.add(fieldName + " doesn't have a valid int value.");
return 0;
}
- errors.add(fieldName + " doesn't have a mapped value.");
- return 0;
}
/**
* Parses float Json value.
*
- * @param parentObject The JSONObject will be parsed.
+ * @param reader The reader to parse.
* @param fieldName Field name of JSON object name/value mapping.
* @param errors The list to store all errors.
* @return the parsed value as float, {@code 0f} if failed to parse.
*/
- private float parseFloatValue(JSONObject parentObject, String fieldName, List<String> errors) {
- if (isString(parentObject, fieldName)) {
- String constantValue;
- try {
- constantValue = parentObject.getString(fieldName);
- } catch (JSONException e) {
- errors.add(fieldName + " doesn't have a mapped string value. " + e.getMessage());
- return 0f;
- }
+ private float parseFloatValue(JsonReader reader, String fieldName, List<String> errors)
+ throws IOException {
+ if (isString(reader)) {
+ String constantValue = nextStringAdvance(reader);
return (float) parseConstantValue(constantValue, errors);
}
try {
- return (float) parentObject.getDouble(fieldName);
- } catch (JSONException e) {
- errors.add(fieldName + " doesn't have a mapped float value. " + e.getMessage());
+ return (float) nextDoubleAdvance(reader);
+ } catch (Exception e) {
+ errors.add(fieldName + " doesn't have a valid float value. " + e.getMessage());
return 0f;
}
}
@@ -688,149 +736,145 @@
}
/**
- * Parses int values in a {@link JSONArray}.
+ * Parses a intger JSON array.
*
- * @param values The JSON array to be parsed.
+ * @param reader The reader to parse.
+ * @param fieldName Field name of JSON object name/value mapping.
* @param errors The list to store all errors.
* @return an int array of default values, null if failed to parse.
*/
@Nullable
- private int[] parseIntArrayValue(JSONArray values, List<String> errors) {
+ private int[] parseIntArrayValue(JsonReader reader, String fieldName, List<String> errors)
+ throws IOException {
int initialErrorCount = errors.size();
- int[] valueArray = new int[values.length()];
+ var values = new IntArray();
- for (int i = 0; i < values.length(); i++) {
- if (isString(values, i)) {
- String stringValue = values.optString(i);
- valueArray[i] = parseConstantValue(stringValue, errors);
- } else {
+ try {
+ int parsed = parseArrayEntry(reader, (int index) -> {
+ if (isString(reader)) {
+ values.add(parseConstantValue(nextStringAdvance(reader), errors));
+ return;
+ }
try {
- valueArray[i] = values.getInt(i);
- } catch (JSONException e) {
- errors.add(values + " doesn't have a mapped int value at index " + i + " "
+ values.add(nextIntAdvance(reader));
+ } catch (Exception e) {
+ errors.add(fieldName + " doesn't have a valid int value at index " + index + " "
+ e.getMessage());
}
+ });
+ if (parsed < 0) {
+ errors.add(fieldName + " doesn't have a valid JSONArray value.");
+ return null;
}
+ } catch (IllegalArgumentException e) {
+ errors.add("Failed to parse field: " + fieldName + ", error: " + e.getMessage());
+ return null;
}
if (errors.size() > initialErrorCount) {
return null;
}
- return valueArray;
+ return values.toArray();
}
/**
- * Parses long values in a {@link JSONArray}.
+ * Parses a long JSON array.
*
- * @param values The JSON array to be parsed.
+ * @param reader The reader to parse.
+ * @param fieldName Field name of JSON object name/value mapping.
* @param errors The list to store all errors.
* @return a long array of default values, null if failed to parse.
*/
@Nullable
- private long[] parseLongArrayValue(JSONArray values, List<String> errors) {
+ private long[] parseLongArrayValue(JsonReader reader, String fieldName, List<String> errors)
+ throws IOException {
int initialErrorCount = errors.size();
- long[] valueArray = new long[values.length()];
+ var values = new LongArray();
- for (int i = 0; i < values.length(); i++) {
- if (isString(values, i)) {
- String stringValue = values.optString(i);
- valueArray[i] = parseConstantValue(stringValue, errors);
- } else {
- try {
- valueArray[i] = values.getLong(i);
- } catch (JSONException e) {
- errors.add(values + " doesn't have a mapped long value at index " + i + " "
- + e.getMessage());
+ try {
+ int parsed = parseArrayEntry(reader, (int index) -> {
+ if (isString(reader)) {
+ values.add(parseConstantValue(nextStringAdvance(reader), errors));
+ return;
}
+ try {
+ values.add(nextLongAdvance(reader));
+ } catch (Exception e) {
+ errors.add(fieldName + " doesn't have a valid long value at index " + index
+ + " " + e.getMessage());
+ }
+ });
+ if (parsed < 0) {
+ errors.add(fieldName + " doesn't have a valid JSONArray value.");
+ return null;
}
+ } catch (IllegalArgumentException e) {
+ errors.add("Failed to parse field: " + fieldName + ", error: " + e.getMessage());
+ return null;
}
if (errors.size() > initialErrorCount) {
return null;
}
- return valueArray;
+ return values.toArray();
}
/**
- * Parses float values in a {@link JSONArray}.
+ * Parses a float JSON array.
*
- * @param values The JSON array to be parsed.
+ * @param reader The reader to parse.
+ * @param fieldName Field name of JSON object name/value mapping.
* @param errors The list to store all errors.
* @return a float array of default value, null if failed to parse.
*/
@Nullable
- private float[] parseFloatArrayValue(JSONArray values, List<String> errors) {
+ private float[] parseFloatArrayValue(JsonReader reader, String fieldName, List<String> errors)
+ throws IOException {
int initialErrorCount = errors.size();
- float[] valueArray = new float[values.length()];
+ List<Float> values = new ArrayList<>();
- for (int i = 0; i < values.length(); i++) {
- if (isString(values, i)) {
- String stringValue = values.optString(i);
- valueArray[i] = (float) parseConstantValue(stringValue, errors);
- } else {
- try {
- valueArray[i] = (float) values.getDouble(i);
- } catch (JSONException e) {
- errors.add(values + " doesn't have a mapped float value at index " + i + " "
- + e.getMessage());
+ try {
+ int parsed = parseArrayEntry(reader, (int index) -> {
+ if (isString(reader)) {
+ values.add((float) parseConstantValue(nextStringAdvance(reader), errors));
+ return;
}
+ try {
+ values.add((float) nextDoubleAdvance(reader));
+ } catch (Exception e) {
+ errors.add(fieldName + " doesn't have a valid float value at index " + index
+ + " " + e.getMessage());
+ }
+ });
+ if (parsed < 0) {
+ errors.add(fieldName + " doesn't have a valid JSONArray value.");
+ return null;
}
+ } catch (IllegalArgumentException e) {
+ errors.add("Failed to parse field: " + fieldName + ", error: " + e.getMessage());
+ return null;
}
if (errors.size() > initialErrorCount) {
return null;
}
+ var valueArray = new float[values.size()];
+ for (int i = 0; i < values.size(); i++) {
+ valueArray[i] = values.get(i);
+ }
return valueArray;
}
/**
- * Checks if parentObject contains field and the field is a String.
+ * Checks if the next token is a string.
*
- * @param parentObject The JSONObject containing the field.
- * @param fieldName The name for the JSON field.
- * @return {@code true} if parent object contains this field and the value is string.
+ * @param reader The reader.
+ * @return {@code true} if the next token is a string.
*/
- private boolean isString(JSONObject parentObject, String fieldName) {
- return parentObject.opt(fieldName) != JSONObject.NULL && parentObject.opt(fieldName)
- .getClass() == String.class;
- }
-
- /**
- * Checks if the JSON array contains the index and the element at the index is a String.
- *
- * @param jsonArray The JSON array to be checked.
- * @param index The index of the JSON array element which will be checked.
- * @return {@code true} if the JSON array has value at index and the value is string.
- */
- private boolean isString(JSONArray jsonArray, int index) {
- return jsonArray.opt(index) != JSONObject.NULL && jsonArray.opt(index).getClass()
- == String.class;
- }
-
- /**
- * Gets all field names of a {@link JSONObject}.
- *
- * @param jsonObject The JSON object to read field names from.
- * @return a list of all the field names of an JSONObject, null if the object is empty.
- */
- @Nullable
- private List<String> getFieldNames(JSONObject jsonObject) {
- JSONArray names = jsonObject.names();
-
- if (names == null) {
- return null;
- }
-
- List<String> fieldNames = new ArrayList<>();
- for (int i = 0; i < names.length(); i++) {
- String fieldName = names.optString(i);
- if (fieldName != null) {
- fieldNames.add(fieldName);
- }
- }
- return fieldNames;
+ private boolean isString(JsonReader reader) throws IOException {
+ return reader.peek() == JsonToken.STRING;
}
/**
@@ -843,13 +887,96 @@
return configFile.exists() && configFile.isFile();
}
+ private interface RunanbleWithException {
+ void run(String fieldName) throws IOException;
+ }
+
/**
- * Converts system property to vendor property.
+ * Iterates through entries in a JSONObject.
*
- * @param property The property going to be converted.
- * @return an int represents vendor property.
+ * If reader is not currently parsing an object, the cursor will still be advanced.
+ *
+ * @param reader The reader.
+ * @param forEachEntry The invokable for each entry.
+ * @return How many entries are iterated or -1 if the reader is not currently parsing an object.
*/
- private static int toVendorProperty(int property) {
- return (property & VehiclePropertyGroup.MASK) | VehiclePropertyGroup.VENDOR;
+ private static int parseObjectEntry(JsonReader reader, RunanbleWithException forEachEntry)
+ throws IOException {
+ if (reader.peek() != JsonToken.BEGIN_OBJECT) {
+ reader.skipValue();
+ return -1;
+ }
+ int i = 0;
+ reader.beginObject();
+ while (reader.hasNext()) {
+ forEachEntry.run(reader.nextName());
+ i++;
+ }
+ reader.endObject();
+ return i;
+ }
+
+ private interface RunanbleIndexWithException {
+ void run(int index) throws IOException;
+ }
+
+ /**
+ * Iterates through entries in a JSONArray.
+ *
+ * If reader is not currently parsing an array, the cursor will still be advanced.
+ *
+ * @param reader The reader.
+ * @param forEachEntry The invokable for each entry.
+ * @return How many entries are iterated or -1 if the reader is not currently parsing an array.
+ */
+ private static int parseArrayEntry(JsonReader reader, RunanbleIndexWithException forEachEntry)
+ throws IOException {
+ if (reader.peek() != JsonToken.BEGIN_ARRAY) {
+ reader.skipValue();
+ return -1;
+ }
+ reader.beginArray();
+ int i = 0;
+ while (reader.hasNext()) {
+ forEachEntry.run(i++);
+ }
+ reader.endArray();
+ return i;
+ }
+
+ private static int nextIntAdvance(JsonReader reader) throws IOException {
+ try {
+ return reader.nextInt();
+ } catch (RuntimeException | IOException e) {
+ reader.skipValue();
+ throw e;
+ }
+ }
+
+ private static long nextLongAdvance(JsonReader reader) throws IOException {
+ try {
+ return reader.nextLong();
+ } catch (RuntimeException | IOException e) {
+ reader.skipValue();
+ throw e;
+ }
+ }
+
+ private static double nextDoubleAdvance(JsonReader reader) throws IOException {
+ try {
+ return reader.nextDouble();
+ } catch (RuntimeException | IOException e) {
+ reader.skipValue();
+ throw e;
+ }
+ }
+
+ private static String nextStringAdvance(JsonReader reader) throws IOException {
+ try {
+ return reader.nextString();
+ } catch (RuntimeException | IOException e) {
+ reader.skipValue();
+ throw e;
+ }
}
}
diff --git a/service/src/com/android/car/hal/property/PropertyHalServiceConfigs.java b/service/src/com/android/car/hal/property/PropertyHalServiceConfigs.java
index d0eab40..37d4aa5 100644
--- a/service/src/com/android/car/hal/property/PropertyHalServiceConfigs.java
+++ b/service/src/com/android/car/hal/property/PropertyHalServiceConfigs.java
@@ -29,8 +29,8 @@
import android.hardware.automotive.vehicle.VehiclePropertyType;
import android.os.Trace;
import android.util.ArraySet;
+import android.util.JsonReader;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import com.android.car.CarLog;
import com.android.car.hal.BidirectionalSparseIntArray;
@@ -44,13 +44,9 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
import java.io.IOException;
import java.io.InputStream;
-import java.lang.reflect.Field;
+import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -141,13 +137,6 @@
private final FeatureFlags mFeatureFlags;
- /**
- * Index key is an AIDL HAL property ID, and the value is readPermission, writePermission.
- * If the property can not be written (or read), set value as NULL.
- * Throw an IllegalArgumentException when try to write READ_ONLY properties or read WRITE_ONLY
- * properties.
- */
- private final SparseIntArray mHalPropIdToValidBitFlag = new SparseIntArray();
private static final String TAG = CarLog.tagFor(PropertyHalServiceConfigs.class);
private final SparseArray<Set<Integer>> mHalPropIdToEnumSet = new SparseArray<>();
@@ -175,7 +164,8 @@
mHalPropIdToCarSvcConfig = parseJsonConfig(defaultConfigInputStream,
"defaultResource");
} catch (IOException e) {
- String errorMsg = "failed to close resource input stream for: " + CONFIG_RESOURCE_NAME;
+ String errorMsg = "failed to open/close resource input stream for: "
+ + CONFIG_RESOURCE_NAME;
Slogf.e(TAG, errorMsg, e);
throw new IllegalStateException(errorMsg, e);
}
@@ -489,113 +479,219 @@
@VisibleForTesting
/* package */ SparseArray<CarSvcPropertyConfig> parseJsonConfig(InputStream configFile,
String path) {
- String configString;
try {
- configString = new String(configFile.readAllBytes());
- } catch (IOException e) {
- throw new IllegalArgumentException("Cannot read from config file: " + path, e);
- }
- JSONObject configJsonObject;
- try {
- configJsonObject = new JSONObject(configString);
- } catch (JSONException e) {
- throw new IllegalArgumentException("Config file: " + path
- + " does not contain a valid JSONObject.", e);
- }
- JSONObject properties;
- SparseArray<CarSvcPropertyConfig> configs = new SparseArray<>();
- try {
- properties = configJsonObject.getJSONObject(JSON_FIELD_NAME_PROPERTIES);
- for (String propertyName : properties.keySet()) {
- JSONObject propertyObj = properties.getJSONObject(propertyName);
- String featureFlag = propertyObj.optString("featureFlag");
- if (!featureFlag.isEmpty()) {
- if (featureFlag.equals(VIC_FLAG_NAME)) {
- if (!mFeatureFlags.androidVicVehicleProperties()) {
- Slogf.w(TAG, "The required feature flag for property: "
- + propertyName + " is not enabled, so its config is ignored");
- continue;
+ SparseArray<CarSvcPropertyConfig> configs = new SparseArray<>();
+ try (var reader = new JsonReader(new InputStreamReader(configFile, "UTF-8"))) {
+ reader.setLenient(true);
+ parseObjectEntry(reader, () -> {
+ if (!reader.nextName().equals(JSON_FIELD_NAME_PROPERTIES)) {
+ reader.skipValue();
+ return;
+ }
+ parseObjectEntry(reader, () -> {
+ String propertyName = reader.nextName();
+ CarSvcPropertyConfig config;
+ try {
+ config = readPropertyObject(propertyName, reader);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Invalid json config for property: "
+ + propertyName + ", error: " + e);
}
- } else {
- throw new IllegalArgumentException("Unknown feature flag: "
- + featureFlag + " for property: " + propertyName);
- }
- }
-
- CarSvcPropertyConfig config = new CarSvcPropertyConfig();
- if (propertyObj.optBoolean("deprecated")) {
- continue;
- }
- config.propertyId = propertyObj.getInt("propertyId");
- int halPropId = config.propertyId;
- if (propertyObj.has("vhalPropertyId")) {
- halPropId = propertyObj.getInt("vhalPropertyId");
- }
- config.halPropId = halPropId;
- config.propertyName = propertyName;
- config.description = propertyObj.getString("description");
- JSONArray enumJsonArray = propertyObj.optJSONArray("dataEnums");
- if (enumJsonArray != null) {
- config.dataEnums = new ArraySet<Integer>();
- for (int i = 0; i < enumJsonArray.length(); i++) {
- config.dataEnums.add(enumJsonArray.getInt(i));
- }
- }
- JSONArray flagJsonArray = propertyObj.optJSONArray("dataFlags");
- if (flagJsonArray != null) {
- List<Integer> dataFlags = new ArrayList<>();
- for (int i = 0; i < flagJsonArray.length(); i++) {
- dataFlags.add(flagJsonArray.getInt(i));
- }
- config.validBitFlag = generateAllCombination(dataFlags);
- }
- config.permissions = parsePermission(propertyName, propertyObj);
- configs.put(config.halPropId, config);
+ if (config == null) {
+ return;
+ }
+ configs.put(config.halPropId, config);
+ });
+ });
}
return configs;
- } catch (JSONException e) {
+ } catch (IllegalStateException | IOException e) {
throw new IllegalArgumentException("Config file: " + path
- + " has invalid JSON format.", e);
+ + " does not contain a valid JSON object.", e);
}
}
- private static PropertyPermissions parsePermission(String propertyName, JSONObject propertyObj)
- throws JSONException {
- PropertyPermissions.Builder builder = new PropertyPermissions.Builder();
- JSONObject jsonReadPermission = propertyObj.optJSONObject("readPermission");
- if (jsonReadPermission != null) {
- builder.setReadPermission(parsePermissionCondition(jsonReadPermission));
+ private @Nullable CarSvcPropertyConfig readPropertyObject(
+ String propertyName, JsonReader reader) throws IOException {
+ String featureFlag = null;
+ boolean deprecated = false;
+ int propertyId = 0;
+ int vhalPropertyId = 0;
+ String description = null;
+ ArraySet<Integer> dataEnums = new ArraySet<Integer>();
+ List<Integer> dataFlags = new ArrayList<>();
+ PermissionCondition readPermission = null;
+ PermissionCondition writePermission = null;
+ // Starts parsing each field.
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ switch (name) {
+ case "featureFlag":
+ featureFlag = reader.nextString();
+ break;
+ case "deprecated":
+ deprecated = reader.nextBoolean();
+ break;
+ case "propertyId":
+ propertyId = reader.nextInt();
+ break;
+ case "vhalPropertyId":
+ vhalPropertyId = reader.nextInt();
+ break;
+ case "description":
+ description = reader.nextString();
+ break;
+ case "dataEnums":
+ reader.beginArray();
+ while (reader.hasNext()) {
+ dataEnums.add(reader.nextInt());
+ }
+ reader.endArray();
+ break;
+ case "dataFlags":
+ reader.beginArray();
+ while (reader.hasNext()) {
+ dataFlags.add(reader.nextInt());
+ }
+ reader.endArray();
+ break;
+ case "readPermission":
+ try {
+ readPermission = parsePermissionCondition(reader);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException(
+ "Failed to parse read permissions for property: " + propertyName
+ + ", error: " + e);
+ }
+ break;
+ case "writePermission":
+ try {
+ writePermission = parsePermissionCondition(reader);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException(
+ "Failed to parse write permissions for property: " + propertyName
+ + ", error: " + e);
+ }
+ break;
+ default:
+ reader.skipValue();
+ }
}
- JSONObject jsonWritePermission = propertyObj.optJSONObject("writePermission");
- if (jsonWritePermission != null) {
- builder.setWritePermission(parsePermissionCondition(jsonWritePermission));
+ reader.endObject();
+
+ // Finished parsing each field, now check whether the required fields are present and
+ // assign them to config.
+ if (deprecated) {
+ return null;
}
- if (jsonReadPermission == null && jsonWritePermission == null) {
+ if (featureFlag != null) {
+ if (featureFlag.equals(VIC_FLAG_NAME)) {
+ if (!mFeatureFlags.androidVicVehicleProperties()) {
+ Slogf.w(TAG, "The required feature flag for property: "
+ + propertyName + " is not enabled, so its config is ignored");
+ return null;
+ }
+ } else {
+ throw new IllegalArgumentException("Unknown feature flag: "
+ + featureFlag + " for property: " + propertyName);
+ }
+ }
+ CarSvcPropertyConfig config = new CarSvcPropertyConfig();
+ config.propertyName = propertyName;
+ if (description == null) {
+ throw new IllegalArgumentException("Missing required description field for property: "
+ + propertyName);
+ }
+ config.description = description;
+ if (propertyId == 0) {
+ throw new IllegalArgumentException("Missing required propertyId field for property: "
+ + propertyName);
+ }
+ config.propertyId = propertyId;
+ if (vhalPropertyId != 0) {
+ config.halPropId = vhalPropertyId;
+ } else {
+ config.halPropId = propertyId;
+ }
+ if (!dataEnums.isEmpty()) {
+ config.dataEnums = dataEnums;
+ }
+ if (!dataFlags.isEmpty()) {
+ config.validBitFlag = generateAllCombination(dataFlags);
+ }
+ if (readPermission == null && writePermission == null) {
throw new IllegalArgumentException(
"No read or write permission specified for: " + propertyName);
}
- return builder.build();
+ var builder = new PropertyPermissions.Builder();
+ if (readPermission != null) {
+ builder.setReadPermission(readPermission);
+ }
+ if (writePermission != null) {
+ builder.setWritePermission(writePermission);
+ }
+ config.permissions = builder.build();
+ return config;
}
- private static PermissionCondition parsePermissionCondition(JSONObject permissionObj)
- throws JSONException {
- String type = permissionObj.getString("type");
+ private interface RunanbleWithException {
+ void run() throws IOException;
+ }
+
+ private static void parseObjectEntry(JsonReader reader, RunanbleWithException forEachEntry)
+ throws IOException {
+ reader.beginObject();
+ while (reader.hasNext()) {
+ forEachEntry.run();
+ }
+ reader.endObject();
+ }
+
+ private static PermissionCondition parsePermissionCondition(JsonReader reader)
+ throws IOException {
+ // we only have one type, use a list to be effective-final.
+ List<String> types = new ArrayList<>();
+ List<PermissionCondition> permissions = new ArrayList<>();
+ parseObjectEntry(reader, () -> {
+ String name = reader.nextName();
+ switch (name) {
+ case "type":
+ types.add(reader.nextString());
+ break;
+ case "value":
+ try {
+ permissions.add(new SinglePermission(reader.nextString()));
+ } catch (IllegalStateException e) {
+ // The value field is not a string, then it must be an array.
+ reader.beginArray();
+ while (reader.hasNext()) {
+ permissions.add(parsePermissionCondition(reader));
+ }
+ reader.endArray();
+ }
+ break;
+ default:
+ reader.skipValue();
+ }
+ });
+ if (types.size() == 0) {
+ throw new IllegalArgumentException("Missing type field for permission");
+ }
+ String type = types.get(0);
+ if (permissions.size() < 1) {
+ throw new IllegalArgumentException("Missing valid value field for permission");
+ }
if (type.equals("single")) {
- return new SinglePermission(permissionObj.getString("value"));
- }
- if (!type.equals("anyOf") && !type.equals("allOf")) {
- throw new IllegalArgumentException("Unknown permission type: " + type
- + ", only support single, anyOf or allOf");
- }
- JSONArray jsonSubPermissions = permissionObj.getJSONArray("value");
- PermissionCondition[] subPermissions = new PermissionCondition[jsonSubPermissions.length()];
- for (int i = 0; i < jsonSubPermissions.length(); i++) {
- subPermissions[i] = parsePermissionCondition(jsonSubPermissions.getJSONObject(i));
+ return permissions.get(0);
}
if (type.equals("anyOf")) {
- return new AnyOfPermissions(subPermissions);
+ return new AnyOfPermissions(permissions.toArray(new PermissionCondition[0]));
}
- return new AllOfPermissions(subPermissions);
+ if (type.equals("allOf")) {
+ return new AllOfPermissions(permissions.toArray(new PermissionCondition[0]));
+ }
+ throw new IllegalArgumentException("Invalid permission type: " + type);
}
private static boolean checkFormatForAllProperties(HalPropValue propValue) {
@@ -647,29 +743,6 @@
return true;
}
- private static List<Integer> getIntegersFromDataEnums(Class... clazz) {
- List<Integer> integerList = new ArrayList<>(5);
- for (Class c: clazz) {
- Field[] fields = c.getDeclaredFields();
- for (Field f : fields) {
- if (f.getType() == int.class) {
- try {
- integerList.add(f.getInt(c));
- } catch (IllegalAccessException | RuntimeException e) {
- Slogf.w(TAG, "Failed to get value");
- }
- }
- }
- }
- return integerList;
- }
-
- // Generate all combinations at once
- private static int generateAllCombination(Class clazz) {
- List<Integer> bitFlags = getIntegersFromDataEnums(clazz);
- return generateAllCombination(bitFlags);
- }
-
private static int generateAllCombination(List<Integer> bitFlags) {
int combination = bitFlags.get(0);
for (int i = 1; i < bitFlags.size(); i++) {
diff --git a/service/src/com/android/car/hal/property/PropertyPermissionInfo.java b/service/src/com/android/car/hal/property/PropertyPermissionInfo.java
index 48c7415..8965150 100644
--- a/service/src/com/android/car/hal/property/PropertyPermissionInfo.java
+++ b/service/src/com/android/car/hal/property/PropertyPermissionInfo.java
@@ -16,7 +16,6 @@
package com.android.car.hal.property;
-import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.hardware.automotive.vehicle.VehicleVendorPermission.PERMISSION_DEFAULT;
import static android.hardware.automotive.vehicle.VehicleVendorPermission.PERMISSION_GET_VENDOR_CATEGORY_1;
import static android.hardware.automotive.vehicle.VehicleVendorPermission.PERMISSION_GET_VENDOR_CATEGORY_10;
@@ -72,96 +71,6 @@
* This utility class provides helper method to deal with property permission.
*/
public class PropertyPermissionInfo {
- // Create SinglePermission objects for each permission
- private static final SinglePermission PERMISSION_CONTROL_CAR_DOORS =
- new SinglePermission(Car.PERMISSION_CONTROL_CAR_DOORS);
- private static final SinglePermission PERMISSION_CONTROL_CAR_MIRRORS =
- new SinglePermission(Car.PERMISSION_CONTROL_CAR_MIRRORS);
- private static final SinglePermission PERMISSION_CONTROL_CAR_SEATS =
- new SinglePermission(Car.PERMISSION_CONTROL_CAR_SEATS);
- private static final SinglePermission PERMISSION_CONTROL_CAR_WINDOWS =
- new SinglePermission(Car.PERMISSION_CONTROL_CAR_WINDOWS);
- private static final SinglePermission PERMISSION_CONTROL_GLOVE_BOX =
- new SinglePermission(Car.PERMISSION_CONTROL_GLOVE_BOX);
- private static final SinglePermission PERMISSION_READ_INTERIOR_LIGHTS =
- new SinglePermission(Car.PERMISSION_READ_INTERIOR_LIGHTS);
- private static final SinglePermission PERMISSION_CONTROL_INTERIOR_LIGHTS =
- new SinglePermission(Car.PERMISSION_CONTROL_INTERIOR_LIGHTS);
- private static final SinglePermission PERMISSION_EXTERIOR_LIGHTS =
- new SinglePermission(Car.PERMISSION_EXTERIOR_LIGHTS);
- private static final SinglePermission PERMISSION_CONTROL_EXTERIOR_LIGHTS =
- new SinglePermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS);
- private static final SinglePermission PERMISSION_CONTROL_CAR_AIRBAGS =
- new SinglePermission(Car.PERMISSION_CONTROL_CAR_AIRBAGS);
- private static final SinglePermission PERMISSION_READ_WINDSHIELD_WIPERS =
- new SinglePermission(Car.PERMISSION_READ_WINDSHIELD_WIPERS);
- private static final SinglePermission PERMISSION_CONTROL_WINDSHIELD_WIPERS =
- new SinglePermission(Car.PERMISSION_CONTROL_WINDSHIELD_WIPERS);
- private static final SinglePermission PERMISSION_READ_STEERING_STATE =
- new SinglePermission(Car.PERMISSION_READ_STEERING_STATE);
- private static final SinglePermission PERMISSION_CONTROL_STEERING_WHEEL =
- new SinglePermission(Car.PERMISSION_CONTROL_STEERING_WHEEL);
- private static final SinglePermission PERMISSION_CONTROL_CAR_CLIMATE =
- new SinglePermission(Car.PERMISSION_CONTROL_CAR_CLIMATE);
- private static final SinglePermission PERMISSION_READ_DISPLAY_UNITS =
- new SinglePermission(Car.PERMISSION_READ_DISPLAY_UNITS);
- private static final SinglePermission PERMISSION_CONTROL_DISPLAY_UNITS =
- new SinglePermission(Car.PERMISSION_CONTROL_DISPLAY_UNITS);
- private static final SinglePermission PERMISSION_IDENTIFICATION =
- new SinglePermission(Car.PERMISSION_IDENTIFICATION);
- private static final SinglePermission PERMISSION_CAR_INFO =
- new SinglePermission(Car.PERMISSION_CAR_INFO);
- private static final SinglePermission PERMISSION_PRIVILEGED_CAR_INFO =
- new SinglePermission(Car.PERMISSION_PRIVILEGED_CAR_INFO);
- private static final SinglePermission PERMISSION_READ_ADAS_SETTINGS =
- new SinglePermission(Car.PERMISSION_READ_ADAS_SETTINGS);
- private static final SinglePermission PERMISSION_CONTROL_ADAS_SETTINGS =
- new SinglePermission(Car.PERMISSION_CONTROL_ADAS_SETTINGS);
- private static final SinglePermission PERMISSION_READ_ADAS_STATES =
- new SinglePermission(Car.PERMISSION_READ_ADAS_STATES);
- private static final SinglePermission PERMISSION_CONTROL_ADAS_STATES =
- new SinglePermission(Car.PERMISSION_CONTROL_ADAS_STATES);
- private static final SinglePermission PERMISSION_READ_DRIVER_MONITORING_SETTINGS =
- new SinglePermission(Car.PERMISSION_READ_DRIVER_MONITORING_SETTINGS);
- private static final SinglePermission PERMISSION_CONTROL_DRIVER_MONITORING_SETTINGS =
- new SinglePermission(Car.PERMISSION_CONTROL_DRIVER_MONITORING_SETTINGS);
- private static final SinglePermission PERMISSION_READ_DRIVER_MONITORING_STATES =
- new SinglePermission(Car.PERMISSION_READ_DRIVER_MONITORING_STATES);
- private static final SinglePermission PERMISSION_CAR_ENGINE_DETAILED =
- new SinglePermission(Car.PERMISSION_CAR_ENGINE_DETAILED);
- private static final SinglePermission PERMISSION_MILEAGE =
- new SinglePermission(Car.PERMISSION_MILEAGE);
- private static final SinglePermission PERMISSION_SPEED =
- new SinglePermission(Car.PERMISSION_SPEED);
- private static final SinglePermission PERMISSION_ENERGY =
- new SinglePermission(Car.PERMISSION_ENERGY);
- private static final SinglePermission PERMISSION_CONTROL_CAR_ENERGY =
- new SinglePermission(Car.PERMISSION_CONTROL_CAR_ENERGY);
- private static final SinglePermission PERMISSION_ENERGY_PORTS =
- new SinglePermission(Car.PERMISSION_ENERGY_PORTS);
- private static final SinglePermission PERMISSION_CONTROL_ENERGY_PORTS =
- new SinglePermission(Car.PERMISSION_CONTROL_ENERGY_PORTS);
- private static final SinglePermission PERMISSION_ADJUST_RANGE_REMAINING =
- new SinglePermission(Car.PERMISSION_ADJUST_RANGE_REMAINING);
- private static final SinglePermission PERMISSION_TIRES =
- new SinglePermission(Car.PERMISSION_TIRES);
- private static final SinglePermission PERMISSION_POWERTRAIN =
- new SinglePermission(Car.PERMISSION_POWERTRAIN);
- private static final SinglePermission PERMISSION_CONTROL_POWERTRAIN =
- new SinglePermission(Car.PERMISSION_CONTROL_POWERTRAIN);
- private static final SinglePermission PERMISSION_EXTERIOR_ENVIRONMENT =
- new SinglePermission(Car.PERMISSION_EXTERIOR_ENVIRONMENT);
- private static final SinglePermission PERMISSION_CAR_DYNAMICS_STATE =
- new SinglePermission(Car.PERMISSION_CAR_DYNAMICS_STATE);
- private static final SinglePermission PERMISSION_CAR_EPOCH_TIME =
- new SinglePermission(Car.PERMISSION_CAR_EPOCH_TIME);
- private static final SinglePermission PERMISSION_ACCESS_FINE_LOCATION =
- new SinglePermission(ACCESS_FINE_LOCATION);
- private static final SinglePermission PERMISSION_VENDOR_EXTENSION =
- new SinglePermission(Car.PERMISSION_VENDOR_EXTENSION);
- private static final AnyOfPermissions PERMISSION_ENERGY_OR_CONTROL_CAR_ENERGY =
- new AnyOfPermissions(PERMISSION_ENERGY, PERMISSION_CONTROL_CAR_ENERGY);
-
/**
* Class to hold {@code readPermission} and {@code writePermission} in a single object.
*/
diff --git a/service/src/com/android/car/logging/HistogramFactoryInterface.java b/service/src/com/android/car/logging/HistogramFactoryInterface.java
new file mode 100644
index 0000000..bc5c916
--- /dev/null
+++ b/service/src/com/android/car/logging/HistogramFactoryInterface.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.logging;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+
+import com.android.modules.expresslog.Histogram;
+
+/**
+ * Interface for creating a {@link com.android.modules.expresslog.Histogram}.
+ *
+ * This interface allows faking the implementation in unit tests.
+ */
+public interface HistogramFactoryInterface {
+ /**
+ * Creates a new histogram using uniform (linear) sized bins.
+ *
+ * @param metricId to log, logging will be no-op if metricId is not defined in the TeX
+ * catalog
+ * @param binCount amount of histogram bins. 2 bin indexes will be calculated
+ * automatically to represent underflow & overflow bins
+ * @param minValue is included in the first bin, values less than minValue
+ * go to underflow bin
+ * @param exclusiveMaxValue is included in the overflow bucket. For accurate
+ * measure up to kMax, then exclusiveMaxValue
+ * should be set to kMax + 1
+ */
+ Histogram newUniformHistogram(String metricId, @IntRange(from = 1) int binCount,
+ float minValue, float exclusiveMaxValue);
+
+ /**
+ * Creates a new histogram using scaled range bins.
+ *
+ * @param metricId to log, logging will be no-op if metricId is not defined in the TeX
+ * catalog
+ * @param binCount amount of histogram bins. 2 bin indexes will be calculated
+ * automatically to represent underflow & overflow bins
+ * @param minValue is included in the first bin, values less than minValue
+ * go to underflow bin
+ * @param firstBinWidth used to represent first bin width and as a reference to calculate
+ * width for consecutive bins
+ * @param scaleFactor used to calculate width for consecutive bins
+ */
+ Histogram newScaledRangeHistogram(String metricId, @IntRange(from = 1) int binCount,
+ int minValue, @FloatRange(from = 1.f) float firstBinWidth,
+ @FloatRange(from = 1.f) float scaleFactor);
+}
diff --git a/service/src/com/android/car/logging/SystemHistogramFactory.java b/service/src/com/android/car/logging/SystemHistogramFactory.java
new file mode 100644
index 0000000..b3903d1
--- /dev/null
+++ b/service/src/com/android/car/logging/SystemHistogramFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.logging;
+
+import com.android.modules.expresslog.Histogram;
+
+/**
+ * A real implementation for {@link com.android.car.internal.logging.HistogramFactoryInterface}.
+ */
+public class SystemHistogramFactory implements HistogramFactoryInterface {
+ @Override
+ public Histogram newUniformHistogram(String metricId, int binCount,
+ float minValue, float exclusiveMaxValue) {
+ return new Histogram(metricId, new Histogram.UniformOptions(
+ binCount, minValue, exclusiveMaxValue));
+ }
+
+ @Override
+ public Histogram newScaledRangeHistogram(String metricId, int binCount,
+ int minValue, float firstBinWidth, float scaleFactor) {
+ return new Histogram(metricId, new Histogram.ScaledRangeOptions(
+ binCount, minValue, firstBinWidth, scaleFactor));
+ }
+}
diff --git a/service/src/com/android/car/occupantconnection/CarOccupantConnectionService.java b/service/src/com/android/car/occupantconnection/CarOccupantConnectionService.java
index 0ede3bf..cb0882f 100644
--- a/service/src/com/android/car/occupantconnection/CarOccupantConnectionService.java
+++ b/service/src/com/android/car/occupantconnection/CarOccupantConnectionService.java
@@ -336,7 +336,7 @@
/* registeredReceiverEndpointMap= */ new BinderKeyValueContainer<>(),
/* pendingConnectionRequestMap= */ new BinderKeyValueContainer<>(),
/* acceptedConnectionRequestMap= */ new BinderKeyValueContainer<>(),
- /* establishConnections= */ new ArraySet<>());
+ /* establishedConnections= */ new ArraySet<>());
}
@VisibleForTesting
@@ -382,9 +382,9 @@
// TODO(b/257117236): implement this method.
}
+ /** Run `adb shell dumpsys car_service --services CarOccupantConnectionService` to dump. */
@Override
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
- /** Run `adb shell dumpsys car_service --services CarOccupantConnectionService` to dump. */
public void dump(IndentingPrintWriter writer) {
writer.println("*CarOccupantConnectionService*");
synchronized (mLock) {
diff --git a/service/src/com/android/car/occupantconnection/CarRemoteDeviceService.java b/service/src/com/android/car/occupantconnection/CarRemoteDeviceService.java
index 739439d..3a6cb82 100644
--- a/service/src/com/android/car/occupantconnection/CarRemoteDeviceService.java
+++ b/service/src/com/android/car/occupantconnection/CarRemoteDeviceService.java
@@ -29,7 +29,9 @@
import static android.car.builtin.display.DisplayManagerHelper.EVENT_FLAG_DISPLAY_CHANGED;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_INVISIBLE;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
+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.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_VISIBLE;
import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
import static com.android.car.CarServiceUtils.assertPermission;
@@ -435,11 +437,15 @@
// It listens to user STARTING event because it needs to initialize PerUserInfo for
// the new user as early as possible (b/300676850).
// It listens to user UNLOCKED and INVISIBLE events because it needs to update the
- // OccupantZoneState. UNLOCKED event indicates the connection becomes ready,
- // while INVISIBLE event indicates the connection changes to not ready.
+ // OccupantZoneState. UNLOCKED or VISIBLE event indicates the connection becomes
+ // ready, while INVISIBLE event indicates the connection changes to not ready.
+ // It listens to user SWITCHING event because it needs to update PerUserInfo through
+ // CarOccupantZoneService (b/331780823).
.addEventType(USER_LIFECYCLE_EVENT_TYPE_STARTING)
.addEventType(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+ .addEventType(USER_LIFECYCLE_EVENT_TYPE_VISIBLE)
.addEventType(USER_LIFECYCLE_EVENT_TYPE_INVISIBLE)
+ .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
.build();
userService.addUserLifecycleListener(userEventFilter, mUserLifecycleListener);
}
diff --git a/service/src/com/android/car/oem/CarOemProxyServiceHelper.java b/service/src/com/android/car/oem/CarOemProxyServiceHelper.java
index 9ae54fc..2365824 100644
--- a/service/src/com/android/car/oem/CarOemProxyServiceHelper.java
+++ b/service/src/com/android/car/oem/CarOemProxyServiceHelper.java
@@ -64,14 +64,14 @@
private static final int MIN_THREAD_POOL_SIZE = 8;
@VisibleForTesting
- /**
+ /*
* Max number of circular calls per caller. A circular is call between CarService and OEM
* Service where CarService calls OEM service and OEM services calls back to CarService.
*/
static final int MAX_CIRCULAR_CALLS_PER_CALLER = 5;
@VisibleForTesting
- /**
+ /*
* Max number of total circular calls. A circular is call between CarService and OEM
* Service where CarService calls OEM service and OEM services calls back to CarService.
*
diff --git a/service/src/com/android/car/pm/BlockingUiCommandListenerMediator.java b/service/src/com/android/car/pm/BlockingUiCommandListenerMediator.java
new file mode 100644
index 0000000..20b3ffe
--- /dev/null
+++ b/service/src/com/android/car/pm/BlockingUiCommandListenerMediator.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pm;
+
+import android.app.TaskInfo;
+import android.car.builtin.app.TaskInfoHelper;
+import android.car.builtin.util.Slogf;
+import android.car.content.pm.ICarBlockingUiCommandListener;
+import android.content.Intent;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+
+import com.android.car.CarLog;
+import com.android.car.internal.util.IndentingPrintWriter;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class for controlling the {@link ICarBlockingUiCommandListener}.
+ *
+ * @hide
+ */
+public final class BlockingUiCommandListenerMediator {
+ private static final String TAG = CarLog.tagFor(BlockingUiCommandListenerMediator.class);
+ private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
+ private static final int NO_LISTENERS_FOR_DISPLAY = 0;
+ private static final int MAX_FINISHING_BLOCKING_UI_LOG_SIZE = 5;
+
+ private final Object mLock = new Object();
+ /**
+ * Mapping between the display IDs and the BlockingUICommandListeners.
+ */
+ @GuardedBy("mLock")
+ private final SparseArray<RemoteCallbackList<ICarBlockingUiCommandListener>>
+ mDisplayToCarBlockingUiCommandListener = new SparseArray<>();
+ // For dumpsys logging
+ @GuardedBy("mLock")
+ private final List<FinishLogs> mFinishingBlockingUiFinishLogs = new ArrayList<>(
+ MAX_FINISHING_BLOCKING_UI_LOG_SIZE);
+
+ /**
+ * Registers the {@link ICarBlockingUiCommandListener} listening for the commands to control the
+ * BlockingUI.
+ *
+ * @param listener listener to register.
+ * @param displayId display Id with which the listener is associated.
+ */
+ public void registerBlockingUiCommandListener(ICarBlockingUiCommandListener listener,
+ int displayId) {
+ synchronized (mLock) {
+ RemoteCallbackList<ICarBlockingUiCommandListener> carBlockingUIRemoteCallbacks =
+ mDisplayToCarBlockingUiCommandListener.get(displayId);
+ if (carBlockingUIRemoteCallbacks == null) {
+ carBlockingUIRemoteCallbacks = new RemoteCallbackList<>();
+ mDisplayToCarBlockingUiCommandListener.put(displayId, carBlockingUIRemoteCallbacks);
+ }
+ carBlockingUIRemoteCallbacks.register(listener);
+ }
+ }
+
+ /**
+ * Unregisters the {@link ICarBlockingUiCommandListener}.
+ *
+ * @param listener listener to unregister.
+ */
+ public void unregisterBlockingUiCommandListener(ICarBlockingUiCommandListener listener) {
+ synchronized (mLock) {
+ for (int i = 0; i < mDisplayToCarBlockingUiCommandListener.size(); i++) {
+ int displayId = mDisplayToCarBlockingUiCommandListener.keyAt(i);
+ RemoteCallbackList<ICarBlockingUiCommandListener> carBlockingUIRemoteCallbacks =
+ mDisplayToCarBlockingUiCommandListener.get(displayId);
+ boolean unregistered = carBlockingUIRemoteCallbacks.unregister(listener);
+ if (unregistered) {
+ if (carBlockingUIRemoteCallbacks.getRegisteredCallbackCount() == 0) {
+ mDisplayToCarBlockingUiCommandListener.remove(displayId);
+ }
+ return;
+ }
+ }
+ Slogf.e(TAG, "BlockingUIListener already unregistered");
+ }
+ }
+
+ /**
+ * Broadcast the finish command to listeners.
+ *
+ * @param taskInfo the {@link TaskInfo} due to which finish is broadcast to the
+ * listeners.
+ * @param lastKnownDisplayId the last known display id where the task changed or vanished.
+ */
+ public void finishBlockingUi(TaskInfo taskInfo, int lastKnownDisplayId) {
+ synchronized (mLock) {
+ int displayId = getDisplayId(taskInfo, lastKnownDisplayId);
+ if (!mDisplayToCarBlockingUiCommandListener.contains(displayId)) {
+ Slogf.e(TAG, "No BlockingUI listeners for display Id %d", displayId);
+ return;
+ }
+ addFinishBlockingUiTransitionLogLocked(taskInfo, displayId);
+ RemoteCallbackList<ICarBlockingUiCommandListener> carBlockingUIRemoteCallbacks =
+ mDisplayToCarBlockingUiCommandListener.get(displayId);
+ int numCallbacks = carBlockingUIRemoteCallbacks.beginBroadcast();
+ if (DBG) {
+ Slogf.d(TAG, "Broadcasting finishBlockingUi to %d callbacks for %d display ID",
+ numCallbacks, displayId);
+ }
+ for (int i = 0; i < numCallbacks; i++) {
+ try {
+ carBlockingUIRemoteCallbacks.getBroadcastItem(i).finishBlockingUi();
+ } catch (RemoteException remoteException) {
+ Slogf.e(TAG, "Error dispatching finishBlockingUi", remoteException);
+ }
+ }
+ carBlockingUIRemoteCallbacks.finishBroadcast();
+ }
+ }
+
+ private static int getDisplayId(TaskInfo taskInfo, int lastKnownDisplayId) {
+ int displayId = TaskInfoHelper.getDisplayId(taskInfo);
+ // Display ID can often be invalid when the task has vanished.
+ if (displayId == Display.INVALID_DISPLAY) {
+ displayId = lastKnownDisplayId;
+ }
+ return displayId;
+ }
+
+ /**
+ * Returns the number of registered callbacks for the display ID.
+ *
+ * @param displayId display Id with which the listener is associated.
+ * @return number of registered callbacks for the given {@code displayId}.
+ */
+ @VisibleForTesting
+ public int getCarBlockingUiCommandListenerRegisteredCallbacksForDisplay(int displayId) {
+ synchronized (mLock) {
+ if (mDisplayToCarBlockingUiCommandListener.get(displayId) != null) {
+ return mDisplayToCarBlockingUiCommandListener
+ .get(displayId).getRegisteredCallbackCount();
+ }
+ return NO_LISTENERS_FOR_DISPLAY;
+ }
+ }
+
+ /** Log information why the blocking ui was finished. */
+ @GuardedBy("mLock")
+ private void addFinishBlockingUiTransitionLogLocked(TaskInfo taskInfo, int displayId) {
+ if (mFinishingBlockingUiFinishLogs.size() >= MAX_FINISHING_BLOCKING_UI_LOG_SIZE) {
+ mFinishingBlockingUiFinishLogs.removeFirst();
+ }
+ FinishLogs log = new FinishLogs(taskInfo.taskId, taskInfo.baseIntent, displayId,
+ System.currentTimeMillis());
+ mFinishingBlockingUiFinishLogs.add(log);
+ }
+
+ /** Dump the {@code mFinishingBlockingUiFinishLogs}. */
+ public void dump(IndentingPrintWriter writer) {
+ synchronized (mLock) {
+ writer.println("*BlockingUiCommandListenerMediator*");
+ writer.println("Finishing BlockingUi logs:");
+ for (int i = 0; i < mFinishingBlockingUiFinishLogs.size(); i++) {
+ writer.println(mFinishingBlockingUiFinishLogs.get(i).toString());
+ }
+ }
+ }
+
+ /**
+ * An utility class to dump blocking ui finishing logs.
+ */
+ private static final class FinishLogs {
+ private final int mTaskId;
+ private final Intent mBaseIntent;
+ private final int mDisplayId;
+ private final long mTimestampMs;
+
+ FinishLogs(int taskId, Intent baseIntent, int displayId, long timestampMs) {
+ mTaskId = taskId;
+ mBaseIntent = baseIntent;
+ mDisplayId = displayId;
+ mTimestampMs = timestampMs;
+ }
+
+ private CharSequence timeToLog(long timestamp) {
+ return android.text.format.DateFormat.format("MM-dd HH:mm:ss", timestamp);
+ }
+
+ @Override
+ public String toString() {
+ return "Finish BlockingUI, reason: taskId=" + mTaskId + ", baseIntent=" + mBaseIntent
+ + ", displayId=" + mDisplayId + " at timestamp=" + timeToLog(mTimestampMs);
+ }
+ }
+}
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index 17cd669..854229a 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -17,7 +17,7 @@
package com.android.car.pm;
import static android.Manifest.permission.QUERY_ALL_PACKAGES;
-import static android.car.Car.PERMISSION_QUERY_DISPLAY_COMPATIBILITY;
+import static android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY;
import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME;
import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID;
import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_DISPLAY_ID;
@@ -50,6 +50,7 @@
import android.car.content.pm.CarAppBlockingPolicy;
import android.car.content.pm.CarAppBlockingPolicyService;
import android.car.content.pm.CarPackageManager;
+import android.car.content.pm.ICarBlockingUiCommandListener;
import android.car.content.pm.ICarPackageManager;
import android.car.drivingstate.CarUxRestrictions;
import android.car.drivingstate.ICarUxRestrictionsChangeListener;
@@ -91,6 +92,7 @@
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -163,6 +165,7 @@
// For dumpsys logging.
private final LocalLog mBlockedActivityLogs = new LocalLog(LOG_SIZE);
+ private final BlockingUiCommandListenerMediator mBlockingUiCommandListenerMediator;
// Store the allowlist and blocklist strings from the resource file.
private String mConfiguredAllowlist;
@@ -212,13 +215,17 @@
private final boolean mPreventTemplatedAppsFromShowingDialog;
private final String mTemplateActivityClassName;
- private final ActivityLaunchListener mActivityLaunchListener = new ActivityLaunchListener();
+ private final ActivityListener mActivityListener = new ActivityListener();
// K: (logical) display id of a physical display, V: UXR change listener of this display.
// For multi-display, monitor UXR change on each display.
@GuardedBy("mLock")
private final SparseArray<UxRestrictionsListener> mUxRestrictionsListeners =
new SparseArray<>();
+ // K: (logical) display id of a display, V: Task info of the blocking ui of this display.
+ // For multi-display, monitor blocking ui task information for each display.
+ @GuardedBy("mLock")
+ private final SparseArray<TaskInfo> mBlockingUiTaskInfoPerDisplay = new SparseArray<>();
private final VendorServiceController mVendorServiceController;
// Information related to when the installed packages should be parsed for building a allow and
@@ -231,6 +238,11 @@
private final PackageParsingEventReceiver mPackageParsingEventReceiver =
new PackageParsingEventReceiver();
+ /**
+ * Mapping between the task ID and the last known display ID.
+ */
+ @GuardedBy("mLock")
+ private final SparseIntArray mLastKnownDisplayIdForTask = new SparseIntArray();
private final UserLifecycleListener mUserLifecycleListener = event -> {
if (!isEventOfType(TAG, event, USER_LIFECYCLE_EVENT_TYPE_SWITCHING)) {
@@ -279,6 +291,7 @@
mPreventTemplatedAppsFromShowingDialog =
res.getBoolean(R.bool.config_preventTemplatedAppsFromShowingDialog);
mTemplateActivityClassName = res.getString(R.string.config_template_activity_class_name);
+ mBlockingUiCommandListenerMediator = new BlockingUiCommandListenerMediator();
}
@@ -663,6 +676,7 @@
mActivityAllowlistMap.clear();
mActivityDenylistPackages.clear();
mClientPolicies.clear();
+ mBlockingUiTaskInfoPerDisplay.clear();
if (mProxies != null) {
for (AppBlockingPolicyProxy proxy : mProxies) {
proxy.disconnect();
@@ -674,7 +688,7 @@
mLock.notifyAll();
}
mContext.unregisterReceiver(mPackageParsingEventReceiver);
- mActivityService.unregisterActivityLaunchListener(mActivityLaunchListener);
+ mActivityService.unregisterActivityListener(mActivityListener);
synchronized (mLock) {
for (int i = 0; i < mUxRestrictionsListeners.size(); i++) {
UxRestrictionsListener listener = mUxRestrictionsListeners.valueAt(i);
@@ -741,7 +755,7 @@
mCarOccupantZoneService.registerCallback(mOccupantZoneCallback);
mVendorServiceController.init();
- mActivityService.registerActivityLaunchListener(mActivityLaunchListener);
+ mActivityService.registerActivityListener(mActivityListener);
}
private final ICarOccupantZoneCallback mOccupantZoneCallback =
@@ -1295,6 +1309,7 @@
PackageManagerHelper.PROPERTY_CAR_SERVICE_OVERLAY_PACKAGES,
/* default= */ null));
mVendorServiceController.dump(writer);
+ mBlockingUiCommandListenerMediator.dump(writer);
}
}
@@ -1744,9 +1759,9 @@
throw new SecurityException("requires permission " + QUERY_ALL_PACKAGES);
}
int callingUid = Binder.getCallingUid();
- if (!hasPermissionGranted(PERMISSION_QUERY_DISPLAY_COMPATIBILITY, callingUid)) {
+ if (!hasPermissionGranted(PERMISSION_MANAGE_DISPLAY_COMPATIBILITY, callingUid)) {
throw new SecurityException("requires permission "
- + PERMISSION_QUERY_DISPLAY_COMPATIBILITY);
+ + PERMISSION_MANAGE_DISPLAY_COMPATIBILITY);
}
return CarServiceHelperWrapper.getInstance().requiresDisplayCompat(
Objects.requireNonNull(packageName, "packageName cannot be Null"));
@@ -1916,15 +1931,161 @@
}
}
- private class ActivityLaunchListener implements CarActivityService.ActivityLaunchListener {
+ private class ActivityListener implements CarActivityService.ActivityListener {
@Override
- public void onActivityLaunch(TaskInfo topTask) {
+ public void onActivityCameOnTop(TaskInfo topTask) {
if (topTask == null) {
Slogf.e(TAG, "Received callback with null top task.");
return;
}
+ boolean isBlockingActivity = Objects.equals(mActivityBlockingActivity,
+ topTask.topActivity);
+ synchronized (mLock) {
+ if (isBlockingActivity) {
+ // This is to keep track of the blocking ui taskInfo.
+ mBlockingUiTaskInfoPerDisplay.put(TaskInfoHelper.getDisplayId(topTask),
+ topTask);
+ }
+ mLastKnownDisplayIdForTask.put(topTask.taskId,
+ TaskInfoHelper.getDisplayId(topTask));
+ }
blockTopActivityIfNecessary(topTask);
}
+
+ @Override
+ public void onActivityChangedInBackstack(TaskInfo taskInfo) {
+ if (taskInfo == null) {
+ Slogf.e(TAG, "Received callback with null task info.");
+ return;
+ }
+ int lastKnownDisplayId;
+ synchronized (mLock) {
+ // Only update the array if display Id for the task is valid since display Id can
+ // often be invalid when the task has vanished.
+ if (TaskInfoHelper.getDisplayId(taskInfo) != Display.INVALID_DISPLAY) {
+ mLastKnownDisplayIdForTask.put(taskInfo.taskId,
+ TaskInfoHelper.getDisplayId(taskInfo));
+ }
+ lastKnownDisplayId = mLastKnownDisplayIdForTask.get(taskInfo.taskId);
+ }
+ if (DBG) {
+ Slogf.i(TAG, "Callback for task %s on display id %d.", taskInfo.taskId,
+ lastKnownDisplayId);
+ }
+ // Only finish the blocking ui if it is visible and there is some change in the
+ // backstack for the activity that is being blocked. This is because the blocked
+ // activity could have crashed due to which there is a need for blocking ui to finish.
+ if (isBlockingUiVisible(lastKnownDisplayId) && isBlockedActivityTarget(
+ lastKnownDisplayId, taskInfo)) {
+ mHandler.post(() -> finishBlockingUi(taskInfo));
+ synchronized (mLock) {
+ mBlockingUiTaskInfoPerDisplay.delete(lastKnownDisplayId);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if blocking ui is visible on that {@code displayId}.
+ *
+ * @param displayId the display id of the {@link TaskInfo}.
+ * @return {@code true} if the blocking ui is visible, {@code false} otherwise.
+ */
+ private boolean isBlockingUiVisible(int displayId) {
+ synchronized (mLock) {
+ return mBlockingUiTaskInfoPerDisplay.get(displayId) != null && TaskInfoHelper.isVisible(
+ mBlockingUiTaskInfoPerDisplay.get(displayId));
+ }
+ }
+
+ /**
+ * Check if this stack change came from the blocked {@code taskInfo} in
+ * {@code mBlockingActivityTargets} for {@code displayId}. Ignore other activities since they
+ * cannot can cause a visibility change for the blocked activity target.
+ *
+ * @param displayId the display id of the {@link TaskInfo}.
+ * @param taskInfo {@link TaskInfo} due to which the task stack changed.
+ * @return {@code true} if this stack change came from blocked {@code taskInfo} for
+ * {@code displayId}, {@code false} otherwise.
+ */
+ private boolean isBlockedActivityTarget(int displayId, TaskInfo taskInfo) {
+ return mBlockingActivityTargets.get(displayId) == taskInfo.topActivity;
+ }
+
+ /**
+ * Registers the {@link ICarBlockingUiCommandListener} listening for the commands to control the
+ * blocking ui.
+ *
+ * @param listener listener to register.
+ * @param displayId display Id with which the listener is associated.
+ */
+ public void registerBlockingUiCommandListener(ICarBlockingUiCommandListener listener,
+ int displayId) {
+ ensurePermission();
+ mBlockingUiCommandListenerMediator.registerBlockingUiCommandListener(listener, displayId);
+ }
+
+ /**
+ * Unregisters the {@link ICarBlockingUiCommandListener}.
+ *
+ * @param listener listener to unregister.
+ */
+ public void unregisterBlockingUiCommandListener(ICarBlockingUiCommandListener listener) {
+ ensurePermission();
+ mBlockingUiCommandListenerMediator.unregisterBlockingUiCommandListener(listener);
+ }
+
+ /**
+ * Broadcast the finish command to listeners.
+ *
+ * @param taskInfo the {@link TaskInfo} due to which finish is broadcast to the listeners.
+ */
+ public void finishBlockingUi(TaskInfo taskInfo) {
+ ensurePermission();
+ int displayId = getLastKnownDisplayIdForTask(taskInfo.taskId);
+ mBlockingUiCommandListenerMediator.finishBlockingUi(taskInfo, displayId);
+ cleanUpLastKnownDisplayIdForTask(taskInfo);
+ }
+
+ /**
+ * Returns the last known display Id for the given {@link TaskInfo}.
+ */
+ private int getLastKnownDisplayIdForTask(int taskId) {
+ synchronized (mLock) {
+ return mLastKnownDisplayIdForTask.get(taskId);
+ }
+ }
+
+ /**
+ * Removes the task from {@code mLastKnownDisplayIdForTask}.
+ */
+ private void cleanUpLastKnownDisplayIdForTask(TaskInfo taskInfo) {
+ synchronized (mLock) {
+ // This can happen when the task has not been removed from mLastKnownDisplayIdForTask
+ // when the task vanishes in onTaskVanished.
+ mLastKnownDisplayIdForTask.delete(taskInfo.taskId);
+ }
+ }
+
+ /**
+ * Get number of registered callbacks for the display ID.
+ *
+ * @param displayId display Id with which the listener is associated.
+ * @return number of registered callbacks for the given {@code displayId}.
+ */
+ @VisibleForTesting
+ public int getCarBlockingUiCommandListenerRegisteredCallbacksForDisplay(int displayId) {
+ return mBlockingUiCommandListenerMediator
+ .getCarBlockingUiCommandListenerRegisteredCallbacksForDisplay(displayId);
+ }
+
+ /** Ensure permission is granted. */
+ private void ensurePermission() {
+ if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "requires permission " + Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY);
+ }
}
/**
diff --git a/service/src/com/android/car/pm/VendorServiceController.java b/service/src/com/android/car/pm/VendorServiceController.java
index e721b6e..de851e6 100644
--- a/service/src/com/android/car/pm/VendorServiceController.java
+++ b/service/src/com/android/car/pm/VendorServiceController.java
@@ -30,7 +30,6 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
-import android.app.ActivityManager;
import android.car.builtin.util.Slogf;
import android.car.hardware.power.CarPowerManager;
import android.car.hardware.power.ICarPowerStateListener;
@@ -60,7 +59,9 @@
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.car.power.CarPowerManagementService;
+import com.android.car.user.ActivityManagerCurrentUserFetcher;
import com.android.car.user.CarUserService;
+import com.android.car.user.CurrentUserFetcher;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
@@ -97,6 +98,7 @@
private final Handler mHandler;
private CarUserService mCarUserService;
private CarPowerManagementService mPowerManagementService;
+ private CurrentUserFetcher mCurrentUserFetcher;
private final BroadcastReceiver mPackageChangeReceiver = new BroadcastReceiver() {
@Override
@@ -152,9 +154,16 @@
};
VendorServiceController(Context context, Looper looper) {
+ this(context, looper, new ActivityManagerCurrentUserFetcher());
+ }
+
+ @VisibleForTesting
+ VendorServiceController(Context context, Looper looper,
+ CurrentUserFetcher currentUserFetcher) {
mContext = context;
mUserManager = context.getSystemService(UserManager.class);
mHandler = new Handler(looper);
+ mCurrentUserFetcher = currentUserFetcher;
}
void init() {
@@ -289,7 +298,7 @@
packageName, userId);
}
- int currentUserId = ActivityManager.getCurrentUser();
+ int currentUserId = mCurrentUserFetcher.getCurrentUser();
int size = mVendorServiceInfos.size();
for (int i = 0; i < size; i++) {
VendorServiceInfo serviceInfo = mVendorServiceInfos.get(i);
@@ -317,7 +326,7 @@
private void handleOnUserSwitching(@UserIdInt int userId) {
// The user switch notification is obsolete if userId is different from the current
// foreground user. Ignore it.
- int currentUserId = ActivityManager.getCurrentUser();
+ int currentUserId = mCurrentUserFetcher.getCurrentUser();
if (currentUserId != userId) {
Slogf.w(TAG, "Received userSwitch event for user " + userId
+ " while current foreground user is " + currentUserId + "."
@@ -375,7 +384,7 @@
private void startOrBindServicesForUser(UserHandle user, @Nullable Boolean forPostUnlock) {
boolean unlocked = mUserManager.isUserUnlockingOrUnlocked(user);
- int currentUserId = ActivityManager.getCurrentUser();
+ int currentUserId = mCurrentUserFetcher.getCurrentUser();
int userId = user.getIdentifier();
for (VendorServiceInfo service: mVendorServiceInfos) {
if (forPostUnlock != null
@@ -443,7 +452,7 @@
VendorServiceConnection connection = mConnections.get(key);
if (connection == null) {
connection = new VendorServiceConnection(mContext, mHandler, key.mVendorServiceInfo,
- key.mUserHandle);
+ key.mUserHandle, mCurrentUserFetcher);
mConnections.put(key, connection);
}
@@ -489,14 +498,17 @@
private final Context mUserContext;
private final Handler mHandler;
private final Handler mFailureHandler;
+ private final CurrentUserFetcher mCurrentUserFetcher;
VendorServiceConnection(Context context, Handler handler,
- VendorServiceInfo vendorServiceInfo, UserHandle user) {
+ VendorServiceInfo vendorServiceInfo, UserHandle user,
+ CurrentUserFetcher currentUserFetcher) {
mHandler = handler;
mVendorServiceInfo = vendorServiceInfo;
mUser = user;
mUserContext = context.createContextAsUser(mUser, /* flags= */ 0);
mCarUserService = CarLocalServices.getService(CarUserService.class);
+ mCurrentUserFetcher = currentUserFetcher;
mFailureHandler = new Handler(handler.getLooper()) {
@Override
@@ -628,7 +640,7 @@
return;
}
- int currentUserId = ActivityManager.getCurrentUser();
+ int currentUserId = mCurrentUserFetcher.getCurrentUser();
if (isUserInScope(mUser.getIdentifier(), mVendorServiceInfo, mCarUserService,
currentUserId)) {
// Double the delay after each failure.
diff --git a/service/src/com/android/car/power/CarPowerManagementService.java b/service/src/com/android/car/power/CarPowerManagementService.java
index 6d762ab..23a43f8 100644
--- a/service/src/com/android/car/power/CarPowerManagementService.java
+++ b/service/src/com/android/car/power/CarPowerManagementService.java
@@ -35,11 +35,12 @@
import android.car.Car;
import android.car.CarOccupantZoneManager;
import android.car.ICarResultReceiver;
-import android.car.PlatformVersion;
import android.car.builtin.app.ActivityManagerHelper;
+import android.car.builtin.content.pm.PackageManagerHelper;
import android.car.builtin.os.BuildHelper;
import android.car.builtin.os.HandlerHelper;
import android.car.builtin.os.ServiceManagerHelper;
+import android.car.builtin.os.UserManagerHelper;
import android.car.builtin.util.EventLogHelper;
import android.car.builtin.util.Slogf;
import android.car.feature.FeatureFlags;
@@ -92,6 +93,7 @@
import com.android.car.CarServiceBase;
import com.android.car.CarServiceUtils;
import com.android.car.CarStatsLogHelper;
+import com.android.car.ICarImpl;
import com.android.car.R;
import com.android.car.hal.PowerHalService;
import com.android.car.hal.PowerHalService.BootupReason;
@@ -170,6 +172,7 @@
private static final long CAR_POWER_POLICY_DAEMON_FIND_MARGINAL_TIME_MS = 300;
private static final long CAR_POWER_POLICY_DAEMON_BIND_RETRY_INTERVAL_MS = 500;
private static final int CAR_POWER_POLICY_DAEMON_BIND_MAX_RETRY = 3;
+ private static final long CAR_POWER_POLICY_DEFINITION_TIMEOUT_MS = 500;
// TODO(b/286303350): remove once power policy refactor complete, replace w/refactored version
private static final String CAR_POWER_POLICY_DAEMON_INTERFACE =
"android.frameworks.automotive.powerpolicy.internal.ICarPowerPolicySystemNotification/"
@@ -257,6 +260,8 @@
@GuardedBy("mSimulationWaitObject")
private int mResumeDelayFromSimulatedSuspendSec = NO_WAKEUP_BY_TIMER;
@GuardedBy("mSimulationWaitObject")
+ private int mCancelDelayFromSimulatedSuspendSec = NO_WAKEUP_BY_TIMER;
+ @GuardedBy("mSimulationWaitObject")
private boolean mFreeMemoryBeforeSuspend;
@GuardedBy("mLock")
@@ -293,6 +298,7 @@
// After ICarPowerPolicyDelegateCallback is set, mReadyForCallback is set to true;
private AtomicBoolean mReadyForCallback = new AtomicBoolean(false);
private BinderHandler mBinderHandler;
+ private boolean mPowerPoliciesInitialized;
// TODO(b/286303350): remove after policy refactor, since daemon will be source of truth
@GuardedBy("mLock")
private String mCurrentPowerPolicyId;
@@ -306,6 +312,7 @@
// TODO(b/286303350): remove after policy refactor, since daemon will control power policy
@GuardedBy("mLock")
private boolean mHasControlOverDaemon;
+ private final CountDownLatch mPowerPolicyInitializationLatch = new CountDownLatch(1);
@GuardedBy("mLock")
private CarPowerPolicy mCurrentAccumulatedPowerPolicy = getInitialAccumulatedPowerPolicy();
private AtomicBoolean mIsListenerWaitingCancelled = new AtomicBoolean(false);
@@ -334,6 +341,8 @@
// Allows for injecting feature flag values during testing
private FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
+ @GuardedBy("mSimulationWaitObject")
+ private boolean mBlockFromSimulatedCancelEvent;
@VisibleForTesting
void readPowerPolicyFromXml(InputStream inputStream)
@@ -349,7 +358,7 @@
void take(T listener);
}
- private final class PowerManagerCallbackList<T extends IInterface> extends
+ private static final class PowerManagerCallbackList<T extends IInterface> extends
RemoteCallbackList<T> {
private ActionOnDeath<T> mActionOnDeath;
@@ -368,27 +377,165 @@
}
}
- public CarPowerManagementService(Context context, PowerHalService powerHal,
- SystemInterface systemInterface, CarUserService carUserService,
- IInterface powerPolicyDaemon) {
- this(context, context.getResources(), powerHal, systemInterface,
- context.getSystemService(UserManager.class), carUserService, powerPolicyDaemon,
- new PowerComponentHandler(context, systemInterface), /* featureFlags= */ null,
- /* screenOffHandler= */ null, /* silentModeHwStatePath= */ null,
- /* silentModeKernelStatePath= */ null, /* bootReason= */ null);
+ /**
+ * Builder for {@link android.car.power.CarPowerManagementService}.
+ */
+ public static final class Builder {
+ private Context mContext;
+ private PowerHalService mPowerHalService;
+ private SystemInterface mSystemInterface;
+ private UserManager mUserManager;
+ private CarUserService mCarUserService;
+ private PowerComponentHandler mPowerComponentHandler;
+ private @Nullable IInterface mPowerPolicyDaemon;
+ private @Nullable FeatureFlags mFeatureFlags;
+ private @Nullable ScreenOffHandler mScreenOffHandler;
+ private @Nullable String mSilentModeHwStatePath;
+ private @Nullable String mSilentModeKernelStatePath;
+ private @Nullable String mBootReason;
+ private Resources mResources;
+ private boolean mBuilt;
+
+ /**
+ * Sets the {@link Context}.
+ */
+ public Builder setContext(Context context) {
+ mContext = context;
+ return this;
+ }
+
+ /**
+ * Sets the {@link PowerHalService}.
+ */
+ public Builder setPowerHalService(PowerHalService powerHalService) {
+ mPowerHalService = powerHalService;
+ return this;
+ }
+
+ /**
+ * Sets the {@link SystemInterface}.
+ */
+ public Builder setSystemInterface(SystemInterface systemInterface) {
+ mSystemInterface = systemInterface;
+ return this;
+ }
+
+ /**
+ * Sets the {@link CarUserService}.
+ */
+ public Builder setCarUserService(CarUserService carUserService) {
+ mCarUserService = carUserService;
+ return this;
+ }
+
+ /**
+ * Sets the {@link IInterface} for power policy daemon.
+ */
+ public Builder setPowerPolicyDaemon(@Nullable IInterface powerPolicyDaemon) {
+ mPowerPolicyDaemon = powerPolicyDaemon;
+ return this;
+ }
+
+ /**
+ * Builds the object.
+ */
+ public CarPowerManagementService build() {
+ if (mBuilt) {
+ throw new IllegalStateException("Only allowed to be built once");
+ }
+ mBuilt = true;
+ return new CarPowerManagementService(this);
+ }
+
+ /**
+ * Sets the {@link PowerComponentHandler}.
+ */
+ @VisibleForTesting
+ public Builder setPowerComponentHandler(PowerComponentHandler powerComponentHandler) {
+ mPowerComponentHandler = powerComponentHandler;
+ return this;
+ }
+
+ /**
+ * Sets the {@link UserManager}.
+ */
+ @VisibleForTesting
+ public Builder setUserManager(UserManager userManager) {
+ mUserManager = userManager;
+ return this;
+ }
+
+ /**
+ * Sets the {@link FeatureFlags}.
+ */
+ @VisibleForTesting
+ public Builder setFeatureFlags(FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
+ return this;
+ }
+
+ /**
+ * Sets the {@link ScreenOffHandler}.
+ */
+ @VisibleForTesting
+ public Builder setScreenOffHandler(ScreenOffHandler screenOffHandler) {
+ mScreenOffHandler = screenOffHandler;
+ return this;
+ }
+
+ /**
+ * Sets the silent mode hardware state path.
+ */
+ @VisibleForTesting
+ public Builder setSilentModeHwStatePath(String silentModeHwStatePath) {
+ mSilentModeHwStatePath = silentModeHwStatePath;
+ return this;
+ }
+
+ /**
+ * Sets the silent mode kernel state path.
+ */
+ @VisibleForTesting
+ public Builder setSilentModeKernelStatePath(String silentModeKernelStatePath) {
+ mSilentModeKernelStatePath = silentModeKernelStatePath;
+ return this;
+ }
+
+ /**
+ * Sets the boot reason.
+ */
+ @VisibleForTesting
+ public Builder setBootReason(String bootReason) {
+ mBootReason = bootReason;
+ return this;
+ }
+
+ /**
+ * Sets the {@link Resources}.
+ */
+ @VisibleForTesting
+ public Builder setResources(Resources resources) {
+ mResources = resources;
+ return this;
+ }
}
- @VisibleForTesting
- public CarPowerManagementService(Context context, Resources resources, PowerHalService powerHal,
- SystemInterface systemInterface, UserManager userManager, CarUserService carUserService,
- IInterface powerPolicyDaemon, PowerComponentHandler powerComponentHandler,
- @Nullable FeatureFlags featureFlags, @Nullable ScreenOffHandler screenOffHandler,
- @Nullable String silentModeHwStatePath, @Nullable String silentModeKernelStatePath,
- @Nullable String bootReason) {
- mContext = context;
- mHal = powerHal;
- mSystemInterface = systemInterface;
- mUserManager = userManager;
+ public CarPowerManagementService(Context context, PowerHalService powerHalService,
+ SystemInterface systemInterface, CarUserService carUserService,
+ IInterface powerPolicyDaemon) {
+ this(new Builder().setContext(context).setPowerHalService(powerHalService)
+ .setSystemInterface(systemInterface).setCarUserService(carUserService)
+ .setPowerPolicyDaemon(powerPolicyDaemon));
+ }
+
+ private CarPowerManagementService(Builder builder) {
+ mContext = Objects.requireNonNull(builder.mContext);
+ mHal = Objects.requireNonNull(builder.mPowerHalService);
+ mSystemInterface = Objects.requireNonNull(builder.mSystemInterface);
+ mUserManager = Objects.requireNonNullElseGet(builder.mUserManager,
+ () -> builder.mContext.getSystemService(UserManager.class));
+ Resources resources = Objects.requireNonNullElseGet(builder.mResources,
+ () -> builder.mContext.getResources());
mShutdownPrepareTimeMs = resources.getInteger(
R.integer.maxGarageModeRunningDurationInSecs) * 1000;
mSwitchGuestUserBeforeSleep = resources.getBoolean(
@@ -400,12 +547,15 @@
mShutdownPrepareTimeMs, MIN_MAX_GARAGE_MODE_DURATION_MS);
mShutdownPrepareTimeMs = MIN_MAX_GARAGE_MODE_DURATION_MS;
}
- mUserService = carUserService;
- if (featureFlags != null) {
- mFeatureFlags = featureFlags;
+ mUserService = Objects.requireNonNull(builder.mCarUserService);
+ if (builder.mFeatureFlags != null) {
+ mFeatureFlags = builder.mFeatureFlags;
}
+ // In a real situation, this should be null.
+ IInterface powerPolicyDaemon = builder.mPowerPolicyDaemon;
if (mFeatureFlags.carPowerPolicyRefactoring()) {
mRefactoredCarPowerPolicyDaemon = (ICarPowerPolicyDelegate) powerPolicyDaemon;
+ mPowerPoliciesInitialized = false;
} else {
mCarPowerPolicyDaemon = (ICarPowerPolicySystemNotification) powerPolicyDaemon;
if (powerPolicyDaemon != null) {
@@ -413,19 +563,21 @@
mHasControlOverDaemon = true;
}
}
- mWifiManager = context.getSystemService(WifiManager.class);
+ mWifiManager = mContext.getSystemService(WifiManager.class);
mTetheringManager = mContext.getSystemService(TetheringManager.class);
mWifiStateFile = new AtomicFile(
new File(mSystemInterface.getSystemCarDir(), WIFI_STATE_FILENAME));
mTetheringStateFile = new AtomicFile(
new File(mSystemInterface.getSystemCarDir(), TETHERING_STATE_FILENAME));
mWifiAdjustmentForSuspend = isWifiAdjustmentForSuspendConfig();
- mPowerComponentHandler = powerComponentHandler;
- mSilentModeHandler = new SilentModeHandler(this, mFeatureFlags, silentModeHwStatePath,
- silentModeKernelStatePath, bootReason);
+ mPowerComponentHandler = Objects.requireNonNullElseGet(builder.mPowerComponentHandler,
+ () -> new PowerComponentHandler(mContext, mSystemInterface));
+ mSilentModeHandler = new SilentModeHandler(this, mFeatureFlags,
+ builder.mSilentModeHwStatePath, builder.mSilentModeKernelStatePath,
+ builder.mBootReason);
mMaxSuspendWaitDurationMs = Math.max(MIN_SUSPEND_WAIT_DURATION_MS,
Math.min(getMaxSuspendWaitDurationConfig(), MAX_SUSPEND_WAIT_DURATION_MS));
- mScreenOffHandler = Objects.requireNonNullElseGet(screenOffHandler, () ->
+ mScreenOffHandler = Objects.requireNonNullElseGet(builder.mScreenOffHandler, () ->
new ScreenOffHandler(mContext, mSystemInterface, mHandler.getLooper()));
}
@@ -802,6 +954,10 @@
powerStateName, request.getFailureReason());
return;
}
+ if (request.isDeferred()) {
+ Slogf.i(TAG, "Applying power policy for power state(%s) is deferred", powerStateName);
+ return;
+ }
CarPowerPolicy accumulatedPolicy = request.getAccumulatedPolicy();
updateCurrentPowerPolicy(accumulatedPolicy);
notifyPowerPolicyChange(accumulatedPolicy);
@@ -1124,12 +1280,42 @@
waitForShutdownPrepareListenersToComplete(timeoutMs, intervalMs);
}
+ private void forceSimulatedCancel() {
+ synchronized (mLock) {
+ mPendingPowerStates.addFirst(new CpmsState(CpmsState.WAIT_FOR_VHAL,
+ CarPowerManager.STATE_SHUTDOWN_CANCELLED,
+ /* canPostpone= */ false));
+ }
+ mHandler.handlePowerStateChange();
+ synchronized (mSimulationWaitObject) {
+ mBlockFromSimulatedCancelEvent = true;
+ mSimulationWaitObject.notifyAll();
+ }
+ }
+
private void handleWaitForFinish(CpmsState state) {
int timeoutMs = getShutdownEnterTimeoutConfig();
sendPowerManagerEvent(state.mCarPowerStateListenerState, timeoutMs);
Runnable taskAtCompletion = () -> {
Slogf.i(TAG, "All listeners completed for %s",
powerStateToString(state.mCarPowerStateListenerState));
+ if (mFeatureFlags.carPowerCancelShellCommand()) {
+ synchronized (mSimulationWaitObject) {
+ if (mInSimulatedDeepSleepMode && mCancelDelayFromSimulatedSuspendSec >= 0) {
+ mHandler.postDelayed(() -> forceSimulatedCancel(),
+ mCancelDelayFromSimulatedSuspendSec * 1000L);
+ while (!mBlockFromSimulatedCancelEvent) {
+ try {
+ mSimulationWaitObject.wait();
+ } catch (InterruptedException ignored) {
+ Thread.currentThread().interrupt(); // Restore interrupted status
+ }
+ }
+ mInSimulatedDeepSleepMode = false;
+ return;
+ }
+ }
+ }
int wakeupSec;
synchronized (mLock) {
// If we're shutting down immediately, don't schedule a wakeup time.
@@ -1460,9 +1646,11 @@
// Otherwise, if the first listener calls finish() synchronously, we will
// see the list go empty and we will think that we are done.
PowerManagerCallbackList<ICarPowerStateListener> completingInternalListeners =
- new PowerManagerCallbackList(l -> { });
+ new PowerManagerCallbackList(l -> {
+ });
PowerManagerCallbackList<ICarPowerStateListener> completingBinderListeners =
- new PowerManagerCallbackList(l -> { });
+ new PowerManagerCallbackList(l -> {
+ });
synchronized (mLock) {
if (isCompletionAllowed(newState)) {
if (timeoutMs < 0) {
@@ -1559,10 +1747,8 @@
synchronized (mLock) {
mLastSleepEntryTime = SystemClock.elapsedRealtime();
}
- @CarPowerManager.CarPowerState int nextListenerState;
if (simulatedMode) {
simulateSleepByWaiting();
- nextListenerState = CarPowerManager.STATE_SHUTDOWN_CANCELLED;
} else {
boolean sleepSucceeded = suspendWithRetries();
if (!sleepSucceeded) {
@@ -1570,14 +1756,12 @@
// We either won't get here at all or we will power off very soon.
return;
}
- synchronized (mLock) {
- // We suspended and have now resumed
- nextListenerState = (mActionOnFinish == ACTION_ON_FINISH_DEEP_SLEEP)
- ? CarPowerManager.STATE_SUSPEND_EXIT
- : CarPowerManager.STATE_HIBERNATION_EXIT;
- }
}
+ @CarPowerManager.CarPowerState int nextListenerState;
synchronized (mLock) {
+ nextListenerState = (mActionOnFinish == ACTION_ON_FINISH_DEEP_SLEEP)
+ ? CarPowerManager.STATE_SUSPEND_EXIT
+ : CarPowerManager.STATE_HIBERNATION_EXIT;
// Any wakeup time from before is no longer valid.
mNextWakeupSec = 0;
}
@@ -1871,6 +2055,7 @@
*/
@Override
public void applyPowerPolicy(String policyId) {
+ Slogf.i(TAG, "applyPowerPolicy(%s) from binder", policyId);
CarServiceUtils.assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_POWER_POLICY);
Preconditions.checkArgument(policyId != null, "policyId cannot be null");
Preconditions.checkArgument(!policyId.startsWith(PolicyReader.SYSTEM_POWER_POLICY_PREFIX),
@@ -1889,6 +2074,7 @@
*/
@Override
public void setPowerPolicyGroup(String policyGroupId) throws RemoteException {
+ Slogf.i(TAG, "setPowerPolicyGroup(%s)", policyGroupId);
CarServiceUtils.assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_POWER_POLICY);
Preconditions.checkArgument(policyGroupId != null, "policyGroupId cannot be null");
if (mFeatureFlags.carPowerPolicyRefactoring()) {
@@ -2061,9 +2247,10 @@
@Override
public void onApplyPowerPolicySucceeded(int requestId,
- android.frameworks.automotive.powerpolicy.CarPowerPolicy accumulatedPolicy) {
- Slogf.i(TAG, "onApplyPowerPolicySucceeded: requestId = %d, policyId = %s", requestId,
- accumulatedPolicy.policyId);
+ android.frameworks.automotive.powerpolicy.CarPowerPolicy accumulatedPolicy,
+ boolean deferred) {
+ Slogf.i(TAG, "onApplyPowerPolicySucceeded: requestId = %d, policyId = %s, "
+ + "deferred = %b", requestId, accumulatedPolicy.policyId, deferred);
AsyncPolicyRequest policyRequest;
synchronized (mLock) {
policyRequest = mRequestIdToPolicyRequest.get(requestId);
@@ -2073,7 +2260,7 @@
return;
}
CarPowerPolicy accumulatedPowerPolicy = convertPowerPolicyFromDaemon(accumulatedPolicy);
- policyRequest.onPolicyRequestSucceeded(accumulatedPowerPolicy);
+ policyRequest.onPolicyRequestSucceeded(accumulatedPowerPolicy, deferred);
}
@Override
@@ -2137,6 +2324,8 @@
enabledComponents.toArray(String[]::new),
disabledComponents.toArray(String[]::new));
}
+ mPowerPoliciesInitialized = true;
+ mPowerPolicyInitializationLatch.countDown();
}
@VisibleForTesting
@@ -2175,7 +2364,15 @@
mPowerComponentHandler.applyPowerPolicy(currentPowerPolicy);
notifyPowerPolicyChange(currentPowerPolicy);
// To cover the case where power state changed before connecting to CPPD.
- notifyPowerStateChangeToDaemon(daemon, getPowerState());
+ int currentPowerState = getPowerState();
+ if (currentPowerState != CarPowerManager.STATE_WAIT_FOR_VHAL
+ && currentPowerState != CarPowerManager.STATE_ON) {
+ Slogf.w(TAG, "Current power state is %s, doesn't correspond to wait for VHAL "
+ + "or on state, skipping notification of power state to daemon.",
+ powerStateToString(currentPowerState));
+ } else {
+ notifyPowerStateChangeToDaemon(daemon, currentPowerState);
+ }
} else {
Slogf.i(TAG, "CPMS is taking control from carpowerpolicyd");
ICarPowerPolicySystemNotification daemon;
@@ -2254,10 +2451,13 @@
@GuardedBy("mLock")
private boolean mPolicyRequestSucceeded;
@GuardedBy("mLock")
+ @Nullable
private CarPowerPolicy mAccumulatedPolicy;
@GuardedBy("mLock")
@PowerPolicyFailureReason
private int mFailureReason;
+ @GuardedBy("mLock")
+ private boolean mIsDeferred;
AsyncPolicyRequest(int requestId, long timeoutMs) {
mRequestId = requestId;
@@ -2274,6 +2474,7 @@
}
}
+ @Nullable
public CarPowerPolicy getAccumulatedPolicy() {
synchronized (mLock) {
return mAccumulatedPolicy;
@@ -2287,14 +2488,23 @@
}
}
+ public boolean isDeferred() {
+ synchronized (mLock) {
+ return mIsDeferred;
+ }
+ }
+
public boolean await() throws InterruptedException {
return mPolicyRequestLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS);
}
- public void onPolicyRequestSucceeded(CarPowerPolicy accumulatedPolicy) {
+ public void onPolicyRequestSucceeded(CarPowerPolicy accumulatedPolicy, boolean deferred) {
synchronized (mLock) {
mPolicyRequestSucceeded = true;
- mAccumulatedPolicy = accumulatedPolicy;
+ if (!deferred) {
+ mAccumulatedPolicy = accumulatedPolicy;
+ }
+ mIsDeferred = deferred;
}
mPolicyRequestLatch.countDown();
}
@@ -2394,6 +2604,10 @@
request.getFailureReason());
return getPolicyRequestError(requestId, request.getFailureReason());
}
+ if (request.isDeferred()) {
+ Slogf.i(TAG, "Applying power policy(%s) is deferred", policyId);
+ return PolicyOperationStatus.OK;
+ }
CarPowerPolicy accumulatedPolicy = request.getAccumulatedPolicy();
updateCurrentPowerPolicy(accumulatedPolicy);
if (delayNotification) {
@@ -2454,6 +2668,7 @@
}
private void cancelPreemptivePowerPolicy() {
+ Slogf.i(TAG, "Canceling preemptive power policy");
String policyId;
synchronized (mLock) {
if (!mIsPowerPolicyLocked) {
@@ -2513,11 +2728,34 @@
notifyPowerPolicyChange(policyId, appliedPolicy, accumulatedPolicy);
}
+ @Nullable
+ private CarPowerPolicy getPowerPolicyDefinition(String policyId, long timeoutMs)
+ throws InterruptedException {
+ if (!mPowerPoliciesInitialized) {
+ boolean result =
+ mPowerPolicyInitializationLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+ if (!result) {
+ Slogf.e(TAG, "Failed to get power policy initialization after waiting %d ms",
+ timeoutMs);
+ return null;
+ }
+ }
+ return mPolicyReader.getPowerPolicy(policyId);
+ }
+
private void notifyPowerPolicyChange(CarPowerPolicy accumulatedPowerPolicy) {
String policyId = accumulatedPowerPolicy.getPolicyId();
- CarPowerPolicy appliedPolicy = mPolicyReader.getPowerPolicy(policyId);
- notifyPowerPolicyChange(policyId, appliedPolicy, accumulatedPowerPolicy);
- Slogf.i(TAG, "Power policy change to %s is notified to apps", policyId);
+ try {
+ CarPowerPolicy appliedPolicy =
+ getPowerPolicyDefinition(policyId, CAR_POWER_POLICY_DEFINITION_TIMEOUT_MS);
+ if (appliedPolicy == null) {
+ Slogf.wtf(TAG, "The new power policy(%s) should exist", policyId);
+ return;
+ }
+ notifyPowerPolicyChange(policyId, appliedPolicy, accumulatedPowerPolicy);
+ } catch (InterruptedException e) {
+ Slogf.e(TAG, e, "Failed to get power policy definition for policy ID %s", policyId);
+ }
}
// TODO(b/286303350): after power policy refactor is complete, remove this function and replace
@@ -2529,6 +2767,7 @@
if (appliedPolicy == null) {
Slogf.wtf(TAG, "The new power policy(%s) should exist", policyId);
}
+ Slogf.i(TAG, "Power policy change to %s is notified to apps", policyId);
mBroadcastHandler.post(() -> {
int idx = mPowerPolicyListeners.beginBroadcast();
@@ -2867,6 +3106,7 @@
long retryIntervalMs = INITIAL_SUSPEND_RETRY_INTERVAL_MS;
long totalWaitDurationMs = 0;
while (true) {
+ long suspendStartTime = SystemClock.elapsedRealtime();
Slogf.i(TAG, "Entering %s", suspendTarget);
if (isSuspendToDisk) {
freeMemory();
@@ -2892,7 +3132,7 @@
return false;
}
}
-
+ long suspendStopTime = SystemClock.elapsedRealtime();
Slogf.w(TAG, "Failed to Suspend; will retry after %dms", retryIntervalMs);
try {
mLock.wait(retryIntervalMs);
@@ -2900,6 +3140,7 @@
Thread.currentThread().interrupt();
}
totalWaitDurationMs += retryIntervalMs;
+ totalWaitDurationMs += (suspendStopTime - suspendStartTime);
retryIntervalMs = Math.min(retryIntervalMs * 2, MAX_RETRY_INTERVAL_MS);
}
}
@@ -3078,8 +3319,10 @@
public void forceSimulatedResume() {
synchronized (mLock) {
// Cancel Garage Mode in case it's running
+ boolean isSuspendToDisk = mActionOnFinish == ACTION_ON_FINISH_HIBERNATION;
mPendingPowerStates.addFirst(new CpmsState(CpmsState.WAIT_FOR_VHAL,
- CarPowerManager.STATE_SHUTDOWN_CANCELLED, /* canPostpone= */ false));
+ isSuspendToDisk ? CarPowerManager.STATE_HIBERNATION_EXIT
+ : CarPowerManager.STATE_SUSPEND_EXIT, /* canPostpone= */ false));
mLock.notifyAll();
}
mHandler.handlePowerStateChange();
@@ -3103,14 +3346,42 @@
* This is similar to {@code 'onApPowerStateChange()'} except that it needs to create a
* {@code CpmsState} that is not directly derived from a {@code VehicleApPowerStateReq}.
*/
- // TODO(b/274895468): Add tests
public void simulateSuspendAndMaybeReboot(@PowerState.ShutdownType int shutdownType,
boolean shouldReboot, boolean skipGarageMode, int wakeupAfter, boolean freeMemory) {
+ simulateSuspendAndMaybeReboot(shutdownType, shouldReboot, skipGarageMode, wakeupAfter,
+ CarPowerManagementService.NO_WAKEUP_BY_TIMER, freeMemory);
+ }
+
+ /**
+ * Manually enters simulated suspend (deep sleep or hibernation) mode, trigging Garage mode.
+ *
+ * <p>If {@code shouldReboot} is 'true', reboots the system when Garage Mode completes.
+ *
+ * Can be invoked using
+ * {@code "adb shell cmd car_service suspend --simulate"} or
+ * {@code "adb shell cmd car_service hibernate --simulate"} or
+ * {@code "adb shell cmd car_service garage-mode reboot"}.
+ *
+ * This is similar to {@code 'onApPowerStateChange()'} except that it needs to create a
+ * {@code CpmsState} that is not directly derived from a {@code VehicleApPowerStateReq}.
+ */
+ // TODO(b/274895468): Add tests
+ public void simulateSuspendAndMaybeReboot(@PowerState.ShutdownType int shutdownType,
+ boolean shouldReboot, boolean skipGarageMode, int wakeupAfter, int cancelAfter,
+ boolean freeMemory) {
boolean isDeepSleep = shutdownType == PowerState.SHUTDOWN_TYPE_DEEP_SLEEP;
+ if (cancelAfter >= 0) {
+ Slogf.i(TAG, "Cancel after is: %d", cancelAfter);
+ }
+ if (wakeupAfter >= 0) {
+ Slogf.i(TAG, "Wakeup after is: %d", wakeupAfter);
+ }
synchronized (mSimulationWaitObject) {
mInSimulatedDeepSleepMode = true;
mWakeFromSimulatedSleep = false;
+ mBlockFromSimulatedCancelEvent = false;
mResumeDelayFromSimulatedSuspendSec = wakeupAfter;
+ mCancelDelayFromSimulatedSuspendSec = cancelAfter;
mFreeMemoryBeforeSuspend = freeMemory;
}
synchronized (mLock) {
@@ -3252,6 +3523,7 @@
return false;
}
int status;
+ Slogf.i(TAG, "Applying power policy(%s) from shell command", powerPolicyId);
if (mFeatureFlags.carPowerPolicyRefactoring()) {
status = applyPowerPolicy(powerPolicyId, /* delayNotification= */ false,
/* upToDaemon= */ false, /* force= */ false);
@@ -3429,6 +3701,26 @@
public void setSilentMode(String silentMode) {
CarServiceUtils.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
mSilentModeHandler.setSilentMode(silentMode);
+ if (mFeatureFlags.carPowerPolicyRefactoring()) {
+ ICarPowerPolicyDelegate daemon;
+ synchronized (mLock) {
+ daemon = mRefactoredCarPowerPolicyDaemon;
+ }
+ if (daemon != null) {
+ try {
+ daemon.setSilentMode(silentMode);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, e, "Failed to notify car power policy daemon of the new silent "
+ + "mode(%s)", silentMode);
+ return;
+ }
+ } else {
+ Slogf.w(TAG, "Failed to notify the new silent mode, car power policy daemon"
+ + " is not available");
+ return;
+ }
+ Slogf.i(TAG, "Set the new silent mode(%s) to CPPD", silentMode);
+ }
}
/**
@@ -3553,7 +3845,7 @@
mResumeDelayFromSimulatedSuspendSec);
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(() -> forceSimulatedResume(),
- mResumeDelayFromSimulatedSuspendSec * 1000);
+ mResumeDelayFromSimulatedSuspendSec * 1000L);
}
while (!mWakeFromSimulatedSleep) {
try {
@@ -3587,6 +3879,16 @@
return getCompletionWaitTimeoutConfig(R.integer.config_postShutdownEnterTimeout);
}
+ private int getS2dImportanceLevel() {
+ return convertMemorySuspendConfigToValue(mContext.getResources().getString(
+ R.string.config_suspend_to_disk_memory_savings));
+ }
+
+ private ArraySet<String> getS2dAllowList() {
+ return new ArraySet<>(mContext.getResources().getStringArray(
+ R.array.config_packages_not_to_stop_during_suspend));
+ }
+
private int getCompletionWaitTimeoutConfig(int resourceId) {
int timeout = mContext.getResources().getInteger(resourceId);
return timeout >= 0 ? timeout : DEFAULT_COMPLETION_WAIT_TIMEOUT;
@@ -3622,9 +3924,44 @@
/**
* Utility method to help with memory freeing before entering Suspend-To-Disk
*/
- static void freeMemory() {
- PlatformVersion platformVersion = Car.getPlatformVersion();
+ private void freeMemory() {
ActivityManagerHelper.killAllBackgroundProcesses();
+ if (!mFeatureFlags.stopProcessBeforeSuspendToDisk()) {
+ return;
+ }
+ List<ActivityManager.RunningAppProcessInfo> allRunningAppProcesses =
+ ActivityManagerHelper.getRunningAppProcesses();
+ ArraySet<Integer> safeUids = new ArraySet<>();
+ ArraySet<String> suspendToDiskAllowList = getS2dAllowList();
+ int suspendToDiskImportanceLevel = getS2dImportanceLevel();
+ for (int i = 0; i < allRunningAppProcesses.size(); i++) {
+ ActivityManager.RunningAppProcessInfo info = allRunningAppProcesses.get(i);
+ boolean isCarServiceOrMyPid = ICarImpl.class.getPackage().getName()
+ .equals(info.processName)
+ || info.pid == android.os.Process.myPid();
+ boolean isSystemOrShellUid = info.uid == Process.SYSTEM_UID
+ || info.uid == Process.SHELL_UID;
+ boolean isProcessPersistent = (ActivityManagerHelper
+ .getFlagsForRunningAppProcessInfo(info)
+ & ActivityManagerHelper.PROCESS_INFO_PERSISTENT_FLAG) != 0;
+ boolean isWithinConfig = suspendToDiskImportanceLevel > info.importance;
+ boolean isProcessAllowListed = suspendToDiskAllowList.contains(info.processName);
+ if (isCarServiceOrMyPid || isSystemOrShellUid || isProcessPersistent
+ || isWithinConfig || isProcessAllowListed) {
+ safeUids.add(info.uid);
+ }
+ }
+
+ for (int i = 0; i < allRunningAppProcesses.size(); i++) {
+ ActivityManager.RunningAppProcessInfo info = allRunningAppProcesses.get(i);
+ if (!safeUids.contains(info.uid)) {
+ for (int j = 0; j < info.pkgList.length; j++) {
+ String pkgToStop = info.pkgList[j];
+ PackageManagerHelper.forceStopPackageAsUser(mContext, pkgToStop,
+ UserManagerHelper.USER_ALL);
+ }
+ }
+ }
}
/**
@@ -3638,4 +3975,14 @@
consumer.accept(displayId);
}
}
+
+ private int convertMemorySuspendConfigToValue(String configValue) {
+ return switch (configValue) {
+ case "low" -> ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
+ case "medium" -> ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE;
+ case "high" -> ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+ // none will fallthrough
+ default -> ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+ };
+ }
}
diff --git a/service/src/com/android/car/power/PolicyReader.java b/service/src/com/android/car/power/PolicyReader.java
index 7c899d6..53ac090 100644
--- a/service/src/com/android/car/power/PolicyReader.java
+++ b/service/src/com/android/car/power/PolicyReader.java
@@ -24,7 +24,6 @@
import static android.frameworks.automotive.powerpolicy.PowerComponent.MINIMUM_CUSTOM_COMPONENT_VALUE;
import static com.android.car.internal.common.CommonConstants.EMPTY_INT_ARRAY;
-import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -40,7 +39,6 @@
import android.hardware.automotive.vehicle.VehicleApPowerStateReport;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.Xml;
@@ -70,7 +68,6 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.stream.Collectors;
/**
* Helper class to read and manage vendor power policies.
@@ -824,7 +821,7 @@
if ((defaultGroupPolicyId == null || defaultGroupPolicyId.isEmpty())
&& !policyGroups.isEmpty()) {
- Log.w(TAG, "No defaultGroupPolicyId is defined");
+ Slogf.w(TAG, "No defaultGroupPolicyId is defined");
}
if (defaultGroupPolicyId != null && !policyGroups.containsKey(defaultGroupPolicyId)) {
@@ -836,10 +833,10 @@
private void reconstructSystemPowerPolicy(@Nullable CarPowerPolicy policyOverride) {
if (policyOverride == null) return;
- List<Integer> enabledComponents = Arrays.stream(NO_USER_INTERACTION_ENABLED_COMPONENTS)
- .boxed().collect(Collectors.toList());
- List<Integer> disabledComponents = Arrays.stream(NO_USER_INTERACTION_DISABLED_COMPONENTS)
- .boxed().collect(Collectors.toList());
+ List<Integer> enabledComponents = CarServiceUtils.asList(
+ NO_USER_INTERACTION_ENABLED_COMPONENTS);
+ List<Integer> disabledComponents = CarServiceUtils.asList(
+ NO_USER_INTERACTION_DISABLED_COMPONENTS);
int[] overrideEnabledComponents = policyOverride.getEnabledComponents();
int[] overrideDisabledComponents = policyOverride.getDisabledComponents();
for (int i = 0; i < overrideEnabledComponents.length; i++) {
@@ -929,16 +926,6 @@
|| SYSTEM_POLICY_CONFIGURABLE_COMPONENTS.contains(component);
}
- @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
- private String componentsToString(int[] components) {
- StringBuffer buffer = new StringBuffer();
- for (int i = 0; i < components.length; i++) {
- if (i > 0) buffer.append(", ");
- buffer.append(powerComponentToString(components[i]));
- }
- return buffer.toString();
- }
-
@PolicyOperationStatus.ErrorCode
int parseComponents(String[] componentArr, boolean enabled, SparseBooleanArray components) {
ArrayList<Integer> customComponentIds = new ArrayList<>();
diff --git a/service/src/com/android/car/power/PowerComponentHandler.java b/service/src/com/android/car/power/PowerComponentHandler.java
index 7332bcc..ff55100 100644
--- a/service/src/com/android/car/power/PowerComponentHandler.java
+++ b/service/src/com/android/car/power/PowerComponentHandler.java
@@ -469,7 +469,8 @@
}
}
- private final class VoiceInteractionPowerComponentMediator extends PowerComponentMediator {
+ private static final class VoiceInteractionPowerComponentMediator
+ extends PowerComponentMediator {
private boolean mIsEnabled = true;
diff --git a/service/src/com/android/car/power/SilentModeHandler.java b/service/src/com/android/car/power/SilentModeHandler.java
index 037e446..8f602c0 100644
--- a/service/src/com/android/car/power/SilentModeHandler.java
+++ b/service/src/com/android/car/power/SilentModeHandler.java
@@ -32,7 +32,6 @@
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.car.power.CarPowerDumpProto.SilentModeHandlerProto;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import libcore.io.IoUtils;
@@ -93,11 +92,11 @@
private boolean mSilentModeByHwState;
@GuardedBy("mLock")
private boolean mForcedMode;
+ private boolean mSilentModeSupported;
// Allows for injecting feature flag values during testing
private FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
- @VisibleForTesting
SilentModeHandler(@NonNull CarPowerManagementService service, FeatureFlags featureFlags,
@Nullable String hwStateMonitoringFileName, @Nullable String kernelSilentModeFileName,
@Nullable String bootReason) {
@@ -109,6 +108,8 @@
? sysfsDir + SYSFS_FILENAME_HW_STATE_MONITORING : hwStateMonitoringFileName;
mKernelSilentModeFileName = kernelSilentModeFileName == null
? sysfsDir + SYSFS_FILENAME_KERNEL_SILENTMODE : kernelSilentModeFileName;
+ mSilentModeSupported = fileExists(mHwStateMonitoringFileName)
+ && fileExists(mKernelSilentModeFileName);
String reason = bootReason;
if (reason == null) {
reason = SystemProperties.get(SYSTEM_BOOT_REASON);
@@ -159,6 +160,7 @@
synchronized (mLock) {
writer.printf("mHwStateMonitoringFileName: %s\n", mHwStateMonitoringFileName);
writer.printf("mKernelSilentModeFileName: %s\n", mKernelSilentModeFileName);
+ writer.printf("Silent mode supported: %b\n", mSilentModeSupported);
writer.printf("Monitoring HW state signal: %b\n", mFileObserver != null);
writer.printf("Silent mode by HW state signal: %b\n", mSilentModeByHwState);
writer.printf("Forced silent mode: %b\n", mForcedMode);
@@ -174,6 +176,7 @@
mHwStateMonitoringFileName);
proto.write(SilentModeHandlerProto.KERNEL_SILENT_MODE_FILE_NAME,
mKernelSilentModeFileName);
+ proto.write(SilentModeHandlerProto.IS_SILENT_MODE_SUPPORTED, mSilentModeSupported);
proto.write(
SilentModeHandlerProto.IS_MONITORING_HW_STATE_SIGNAL, mFileObserver != null);
proto.write(SilentModeHandlerProto.SILENT_MODE_BY_HW_STATE, mSilentModeByHwState);
@@ -264,6 +267,10 @@
}
}
+ private boolean fileExists(String filePath) {
+ return Files.exists(Paths.get(filePath));
+ }
+
private void startMonitoringSilentModeHwState() {
File monitorFile = new File(mHwStateMonitoringFileName);
if (!monitorFile.exists()) {
diff --git a/service/src/com/android/car/remoteaccess/CarRemoteAccessService.java b/service/src/com/android/car/remoteaccess/CarRemoteAccessService.java
index c14e3cb..119992b 100644
--- a/service/src/com/android/car/remoteaccess/CarRemoteAccessService.java
+++ b/service/src/com/android/car/remoteaccess/CarRemoteAccessService.java
@@ -102,7 +102,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@@ -366,7 +365,6 @@
@GuardedBy("mLock")
private boolean mRunGarageMode;
private CarPowerManagementService mPowerService;
- private AtomicBoolean mInitialized;
private CarRemoteAccessServiceDep mDep;
@@ -394,7 +392,7 @@
}
@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
- private class CarRemoteAccessServiceDepImpl implements CarRemoteAccessServiceDep {
+ private static class CarRemoteAccessServiceDepImpl implements CarRemoteAccessServiceDep {
public int getCallingUid() {
return Binder.getCallingUid();
}
@@ -1126,6 +1124,24 @@
}
}
+ /**
+ * Returns whether {@code VEHICLE_IN_USE} is supported and getting it returns a valid value.
+ */
+ @Override
+ public boolean isVehicleInUseSupported() {
+ CarServiceUtils.assertPermission(mContext, Car.PERMISSION_CONTROL_REMOTE_ACCESS);
+ return mPowerHalService.isVehicleInUseSupported();
+ }
+
+ /**
+ * Returns whether {@code SHUTDOWN_REQUEST} is supported.
+ */
+ @Override
+ public boolean isShutdownRequestSupported() {
+ CarServiceUtils.assertPermission(mContext, Car.PERMISSION_CONTROL_REMOTE_ACCESS);
+ return mPowerHalService.isShutdownRequestSupported();
+ }
+
@VisibleForTesting
RemoteAccessHalCallback getRemoteAccessHalCallback() {
return mHalCallback;
diff --git a/service/src/com/android/car/remoteaccess/RemoteAccessStorage.java b/service/src/com/android/car/remoteaccess/RemoteAccessStorage.java
index 3b8a431..0ee9656 100644
--- a/service/src/com/android/car/remoteaccess/RemoteAccessStorage.java
+++ b/service/src/com/android/car/remoteaccess/RemoteAccessStorage.java
@@ -157,7 +157,7 @@
}
// Returns ClientIdEntry for the given package name.
- @SuppressWarnings("FormatString")
+ @SuppressWarnings({"FormatString", "StringCharset"})
@Nullable
public static ClientIdEntry queryClientIdEntry(SQLiteDatabase db, String packageName) {
String queryCommand = String.format("SELECT %s, %s, %s, %s FROM %s WHERE %s = ?",
diff --git a/service/src/com/android/car/stats/CarStatsService.java b/service/src/com/android/car/stats/CarStatsService.java
index 7713ec8..66f7db0 100644
--- a/service/src/com/android/car/stats/CarStatsService.java
+++ b/service/src/com/android/car/stats/CarStatsService.java
@@ -23,7 +23,7 @@
import android.car.builtin.util.Slogf;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.util.ArrayMap;
+import android.util.SparseArray;
import android.util.StatsEvent;
import com.android.car.CarLog;
@@ -35,11 +35,10 @@
import com.android.car.stats.VmsClientLogger.ConnectionState;
import com.android.internal.annotations.GuardedBy;
+import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
-import java.util.function.Consumer;
import java.util.function.Function;
/**
@@ -87,7 +86,7 @@
private final StatsManager mStatsManager;
@GuardedBy("mVmsClientStats")
- private final Map<Integer, VmsClientLogger> mVmsClientStats = new ArrayMap<>();
+ private final SparseArray<VmsClientLogger> mVmsClientStats = new SparseArray();
public CarStatsService(Context context) {
mContext = context;
@@ -121,30 +120,37 @@
*/
public VmsClientLogger getVmsClientLogger(int clientUid) {
synchronized (mVmsClientStats) {
- return mVmsClientStats.computeIfAbsent(
- clientUid,
- uid -> {
- String packageName = mPackageManager.getNameForUid(uid);
- if (DEBUG) {
- Slogf.d(TAG, "Created VmsClientLog: " + packageName);
- }
- return new VmsClientLogger(uid, packageName);
- });
+ if (!mVmsClientStats.contains(clientUid)) {
+ String packageName = mPackageManager.getNameForUid(clientUid);
+ if (DEBUG) {
+ Slogf.d(TAG, "Created VmsClientLog: " + packageName);
+ }
+ mVmsClientStats.put(clientUid, new VmsClientLogger(clientUid, packageName));
+ }
+ return mVmsClientStats.get(clientUid);
}
}
+ interface DumpVmsClientStats {
+ void dump(VmsClientStats vmsClientStats);
+ }
+
@Override
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
public void dump(IndentingPrintWriter writer) {
synchronized (mVmsClientStats) {
writer.println(VMS_CONNECTION_STATS_DUMPSYS_HEADER);
- mVmsClientStats.values().stream()
- // Unknown UID will not have connection stats
- .filter(entry -> entry.getUid() > 0)
- // Sort stats by UID
- .sorted(Comparator.comparingInt(VmsClientLogger::getUid))
- .forEachOrdered(entry -> writer.println(
- VMS_CONNECTION_STATS_DUMPSYS_FORMAT.apply(entry)));
+ List<VmsClientLogger> loggers = new ArrayList<>();
+ for (int index = 0; index < mVmsClientStats.size(); index++) {
+ // Unknown UID will not have connection stats
+ if (mVmsClientStats.valueAt(index).getUid() > 0) {
+ loggers.add(mVmsClientStats.valueAt(index));
+ }
+ }
+ loggers.sort(Comparator.comparingInt(VmsClientLogger::getUid));
+ for (int index = 0; index < loggers.size(); index++) {
+ writer.println(VMS_CONNECTION_STATS_DUMPSYS_FORMAT.apply(loggers.get(index)));
+ }
writer.println();
writer.println(VMS_CLIENT_STATS_DUMPSYS_HEADER);
@@ -178,12 +184,16 @@
return StatsManager.PULL_SUCCESS;
}
- private void dumpVmsClientStats(Consumer<VmsClientStats> dumpFn) {
+ private void dumpVmsClientStats(DumpVmsClientStats dumpFn) {
synchronized (mVmsClientStats) {
- mVmsClientStats.values().stream()
- .flatMap(log -> log.getLayerEntries().stream())
- .sorted(VMS_CLIENT_STATS_ORDER)
- .forEachOrdered(dumpFn);
+ List<VmsClientStats> loggers = new ArrayList<>();
+ for (int index = 0; index < mVmsClientStats.size(); index++) {
+ loggers.addAll(mVmsClientStats.valueAt(index).getLayerEntries());
+ }
+ loggers.sort(VMS_CLIENT_STATS_ORDER);
+ for (int index = 0; index < loggers.size(); index++) {
+ dumpFn.dump(loggers.get(index));
+ }
}
}
}
diff --git a/service/src/com/android/car/stats/VmsClientLogger.java b/service/src/com/android/car/stats/VmsClientLogger.java
index 39cade1..b705bac 100644
--- a/service/src/com/android/car/stats/VmsClientLogger.java
+++ b/service/src/com/android/car/stats/VmsClientLogger.java
@@ -19,14 +19,14 @@
import android.annotation.Nullable;
import android.car.vms.VmsLayer;
import android.util.ArrayMap;
+import android.util.SparseArray;
import com.android.car.CarStatsLog;
import com.android.internal.annotations.GuardedBy;
+import java.util.ArrayList;
import java.util.Collection;
-import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.Collectors;
/**
* Logger for per-client VMS statistics.
@@ -59,10 +59,10 @@
private final String mPackageName;
@GuardedBy("mLock")
- private Map<Integer, AtomicLong> mConnectionStateCounters = new ArrayMap<>();
+ private SparseArray<AtomicLong> mConnectionStateCounters = new SparseArray<>();
@GuardedBy("mLock")
- private final Map<VmsLayer, VmsClientStats> mLayerStats = new ArrayMap<>();
+ private final ArrayMap<VmsLayer, VmsClientStats> mLayerStats = new ArrayMap<>();
VmsClientLogger(int clientUid, @Nullable String clientPackage) {
mUid = clientUid;
@@ -88,8 +88,10 @@
AtomicLong counter;
synchronized (mLock) {
- counter = mConnectionStateCounters.computeIfAbsent(connectionState,
- ignored -> new AtomicLong());
+ if (!mConnectionStateCounters.contains(connectionState)) {
+ mConnectionStateCounters.put(connectionState, new AtomicLong());
+ }
+ counter = mConnectionStateCounters.get(connectionState);
}
counter.incrementAndGet();
}
@@ -134,9 +136,11 @@
Collection<VmsClientStats> getLayerEntries() {
synchronized (mLock) {
- return mLayerStats.values().stream()
- .map(VmsClientStats::new) // Make a deep copy of the entries
- .collect(Collectors.toList());
+ ArrayList<VmsClientStats> layerEntries = new ArrayList<>(mLayerStats.size());
+ for (int index = 0; index < mLayerStats.size(); index++) {
+ layerEntries.add(new VmsClientStats(mLayerStats.valueAt(index)));
+ }
+ return layerEntries;
}
}
diff --git a/service/src/com/android/car/storagemonitoring/HealthServiceWearInfoProvider.java b/service/src/com/android/car/storagemonitoring/HealthServiceWearInfoProvider.java
index 7cec7b0..76ab665 100644
--- a/service/src/com/android/car/storagemonitoring/HealthServiceWearInfoProvider.java
+++ b/service/src/com/android/car/storagemonitoring/HealthServiceWearInfoProvider.java
@@ -58,7 +58,7 @@
final MutableInt lifetimeB = new MutableInt(0);
final MutableInt preEol = new MutableInt(0);
- final IHealth.getStorageInfoCallback getStorageInfoCallback =
+ final IHealth.getStorageInfoCallback getStorageInfoCallbackImpl =
new IHealth.getStorageInfoCallback() {
@Override
public void onValues(int result, ArrayList<StorageInfo> value) {
@@ -84,7 +84,7 @@
}
try {
- healthService.getStorageInfo(getStorageInfoCallback);
+ healthService.getStorageInfo(getStorageInfoCallbackImpl);
} catch (Exception e) {
Slogf.w(CarLog.TAG_STORAGE, "Failed to get storage information from"
+ "health service, exception :" + e);
diff --git a/service/src/com/android/car/storagemonitoring/IoStatsTracker.java b/service/src/com/android/car/storagemonitoring/IoStatsTracker.java
index 8d94d29..0443066 100644
--- a/service/src/com/android/car/storagemonitoring/IoStatsTracker.java
+++ b/service/src/com/android/car/storagemonitoring/IoStatsTracker.java
@@ -35,7 +35,7 @@
public class IoStatsTracker {
// NOTE: this class is not thread safe
- private abstract class Lazy<T> {
+ private abstract static class Lazy<T> {
protected Optional<T> mLazy = Optional.empty();
protected abstract T supply();
@@ -101,9 +101,12 @@
if (oldRecord.representsSameMetrics(newRecord)) {
// if no new I/O happened, try to figure out if any process on behalf
// of this user has happened, and use that to update the runtime metrics
- if (processTable.get().stream().anyMatch(pi -> pi.uid == uid)) {
- newStats = new IoStatsEntry(newRecord.delta(oldRecord),
- oldRecord.runtimeMillis + mSampleWindowMs);
+ for (int index = 0; index < processTable.get().size(); index++) {
+ if (processTable.get().get(index).uid == uid) {
+ newStats = new IoStatsEntry(newRecord.delta(oldRecord),
+ oldRecord.runtimeMillis + mSampleWindowMs);
+ break;
+ }
}
// if no new I/O happened and no process is running for this user
// then do not prepare a new sample, as nothing has changed
diff --git a/service/src/com/android/car/storagemonitoring/SysfsLifetimeWriteInfoProvider.java b/service/src/com/android/car/storagemonitoring/SysfsLifetimeWriteInfoProvider.java
index fb7de7e..e7cb809 100644
--- a/service/src/com/android/car/storagemonitoring/SysfsLifetimeWriteInfoProvider.java
+++ b/service/src/com/android/car/storagemonitoring/SysfsLifetimeWriteInfoProvider.java
@@ -27,9 +27,7 @@
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
/**
* <p>Loads lifetime write data for mounted filesystems via sysfs.</p>
@@ -108,10 +106,12 @@
Slogf.e(TAG, "there are no directories at location " + fspath.getAbsolutePath());
continue;
}
- Arrays.stream(files)
- .map(this::tryParse)
- .filter(Objects::nonNull)
- .forEach(writeInfos::add);
+ for (int index = 0; index < files.length; index++) {
+ LifetimeWriteInfo writeInfo = tryParse(files[index]);
+ if (writeInfo != null) {
+ writeInfos.add(writeInfo);
+ }
+ }
}
return writeInfos.toArray(new LifetimeWriteInfo[0]);
diff --git a/service/src/com/android/car/storagemonitoring/WearHistory.java b/service/src/com/android/car/storagemonitoring/WearHistory.java
index 312053d..08ce3e6 100644
--- a/service/src/com/android/car/storagemonitoring/WearHistory.java
+++ b/service/src/com/android/car/storagemonitoring/WearHistory.java
@@ -28,9 +28,9 @@
import java.nio.file.Files;
import java.time.Duration;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.StringJoiner;
/**
* This class represents the entire history of flash wear changes as tracked
@@ -56,7 +56,9 @@
public static WearHistory fromRecords(@NonNull WearEstimateRecord... records) {
WearHistory wearHistory = new WearHistory();
- Arrays.stream(records).forEach(wearHistory::add);
+ for (int index = 0; index < records.length; index++) {
+ wearHistory.add(records[index]);
+ }
return wearHistory;
}
@@ -139,8 +141,11 @@
@Override
public String toString() {
- return mWearHistory.stream().map(WearEstimateRecord::toString).reduce(
- "WearHistory[size = " + size() + "] -> ",
- (String s, String t) -> s + ", " + t);
+ StringJoiner stringJoiner = new StringJoiner(",",
+ "WearHistory[size = " + size() + "] -> ", "");
+ for (int index = 0; index < mWearHistory.size(); index++) {
+ stringJoiner.add(mWearHistory.get(index).toString());
+ }
+ return stringJoiner.toString();
}
}
diff --git a/service/src/com/android/car/systeminterface/DisplayInterface.java b/service/src/com/android/car/systeminterface/DisplayInterface.java
index c6a528c..8aa4bf9 100644
--- a/service/src/com/android/car/systeminterface/DisplayInterface.java
+++ b/service/src/com/android/car/systeminterface/DisplayInterface.java
@@ -173,6 +173,7 @@
private final DisplayManager.DisplayListener mDisplayListener = new DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
+ Slogf.i(TAG, "onDisplayAdded: displayId=%d", displayId);
synchronized (mLock) {
mDisplayStateSet.put(displayId, isDisplayOn(displayId));
mDisplayBrightnessSet.put(displayId, INVALID_DISPLAY_BRIGHTNESS);
@@ -181,6 +182,7 @@
@Override
public void onDisplayRemoved(int displayId) {
+ Slogf.i(TAG, "onDisplayRemoved: displayId=%d", displayId);
synchronized (mLock) {
mDisplayStateSet.delete(displayId);
mDisplayBrightnessSet.delete(displayId);
@@ -189,6 +191,7 @@
@Override
public void onDisplayChanged(int displayId) {
+ Slogf.i(TAG, "onDisplayChanged: displayId=%d", displayId);
handleDisplayChanged(displayId);
}
};
@@ -333,6 +336,7 @@
@Override
public void startDisplayStateMonitoring() {
+ Slogf.i(TAG, "Starting to monitor display state change");
CarPowerManagementService carPowerManagementService;
CarUserService carUserService;
synchronized (mLock) {
@@ -416,7 +420,12 @@
// setDisplayState has a binder call to system_server. Should not wrap setDisplayState
// with a lock.
for (int i = 0; i < displayIds.size(); i++) {
- setDisplayState(displayIds.get(i), on);
+ int displayId = displayIds.get(i);
+ try {
+ setDisplayState(displayId, on);
+ } catch (IllegalArgumentException e) {
+ Slogf.w(TAG, "Cannot set display(%d) state(%b)", displayId, on);
+ }
}
}
diff --git a/service/src/com/android/car/systeminterface/SystemStateInterface.java b/service/src/com/android/car/systeminterface/SystemStateInterface.java
index 2bbfa9f..1b7f0a2 100644
--- a/service/src/com/android/car/systeminterface/SystemStateInterface.java
+++ b/service/src/com/android/car/systeminterface/SystemStateInterface.java
@@ -153,6 +153,7 @@
}
};
+ @SuppressWarnings("FutureReturnValueIgnored")
private void runActionWithDelay(Runnable action, Duration delay) {
long delayInMs = delay.toMillis();
if (DEBUG) {
diff --git a/service/src/com/android/car/telemetry/CarTelemetryService.java b/service/src/com/android/car/telemetry/CarTelemetryService.java
index e2fc287..5d5bdaf 100644
--- a/service/src/com/android/car/telemetry/CarTelemetryService.java
+++ b/service/src/com/android/car/telemetry/CarTelemetryService.java
@@ -25,8 +25,6 @@
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
-import static java.util.stream.Collectors.toList;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -80,6 +78,7 @@
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
@@ -256,7 +255,7 @@
mSystemMonitor = SystemMonitor.create(activityManager, mTelemetryHandler);
mSystemMonitor.setSystemMonitorCallback(this::onSystemMonitorEvent);
} else {
- Log.w(TAG, "Not creating mSystemMonitor due to bug 233973826");
+ Slogf.w(TAG, "Not creating mSystemMonitor due to bug 233973826");
}
mTelemetryThreadTraceLog.traceEnd();
// save state at reboot and shutdown
@@ -561,9 +560,14 @@
*/
@NonNull
public List<String> getActiveMetricsConfigDetails() {
- return mMetricsConfigStore.getActiveMetricsConfigs().stream()
- .map((config) -> config.getName() + " version=" + config.getVersion())
- .collect(toList());
+ List<TelemetryProto.MetricsConfig> activeMetricsConfigs = mMetricsConfigStore
+ .getActiveMetricsConfigs();
+ List<String> activeMetricsConfigDetails = new ArrayList<>(activeMetricsConfigs.size());
+ for (int index = 0; index < activeMetricsConfigs.size(); index++) {
+ TelemetryProto.MetricsConfig config = activeMetricsConfigs.get(index);
+ activeMetricsConfigDetails.add(config.getName() + " version=" + config.getVersion());
+ }
+ return activeMetricsConfigDetails;
}
/**
diff --git a/service/src/com/android/car/telemetry/UidPackageMapper.java b/service/src/com/android/car/telemetry/UidPackageMapper.java
index 54b11ef..195f5fc 100644
--- a/service/src/com/android/car/telemetry/UidPackageMapper.java
+++ b/service/src/com/android/car/telemetry/UidPackageMapper.java
@@ -181,7 +181,6 @@
.removeIf(app -> app.mPackageName.equals(completelyRemoved.mPackageName));
}
}
- return;
}
/** Returns installed and uninstalled packages, including Apex packages. */
@@ -244,7 +243,7 @@
+ " Ignoring.");
return;
}
- /**
+ /*
* App updates (ACTION_PACKAGE_REPLACED) actually consist of REMOVE, ADD, and then
* REPLACE broadcasts. To avoid waste, we ignore the extra REMOVE and ADD broadcasts
* that contain the replacing flag (EXTRA_REPLACING).
diff --git a/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
index 91e9af5..d98e089 100644
--- a/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
+++ b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
@@ -287,12 +287,18 @@
private void disableBroker() {
mDisabled = true;
// remove all MetricConfigs, disable all publishers, stop receiving data
+ List<String> keysToRemove = new ArrayList<>();
for (String configName : mSubscriptionMap.keySet()) {
- // get the metrics config from the DataSubscriber and remove the metrics config
+ // get the metrics config from the DataSubscriber and store in a intermediate
+ // collection. It is not safe to modify a collection while iterating on it.
if (mSubscriptionMap.get(configName).size() != 0) {
- removeMetricsConfig(configName);
+ keysToRemove.add(configName);
}
}
+ // remove metrics configurations
+ for (String configName : keysToRemove) {
+ removeMetricsConfig(configName);
+ }
mSubscriptionMap.clear();
}
diff --git a/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java b/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java
index 9127767..61d095f 100644
--- a/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java
+++ b/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java
@@ -70,7 +70,7 @@
/**
* Creates a SystemMonitor instance set with default loadavg path.
*
- * @param context the context this is running in.
+ * @param activityManager Activity manager this is running in.
* @param workerHandler a handler for running monitoring jobs.
* @return SystemMonitor instance.
*/
diff --git a/service/src/com/android/car/user/ActivityManagerCurrentUserFetcher.java b/service/src/com/android/car/user/ActivityManagerCurrentUserFetcher.java
new file mode 100644
index 0000000..97d36c2
--- /dev/null
+++ b/service/src/com/android/car/user/ActivityManagerCurrentUserFetcher.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.user;
+
+import android.app.ActivityManager;
+
+/**
+ * An implementation for {@link CurrentUserFetcher} using {@link ActivityManager};
+ */
+public final class ActivityManagerCurrentUserFetcher implements CurrentUserFetcher {
+ /**
+ * See {@link android.app.ActivityManager.getCurrentUser}.
+ */
+ @Override
+ public int getCurrentUser() {
+ return ActivityManager.getCurrentUser();
+ }
+}
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 93e8aa9..5ef6c0d 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -38,14 +38,11 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
-import android.car.Car;
import android.car.CarOccupantZoneManager;
import android.car.CarOccupantZoneManager.OccupantZoneInfo;
-import android.car.CarVersion;
import android.car.ICarOccupantZoneCallback;
import android.car.ICarResultReceiver;
import android.car.ICarUserService;
-import android.car.PlatformVersion;
import android.car.VehicleAreaSeat;
import android.car.builtin.app.ActivityManagerHelper;
import android.car.builtin.content.pm.PackageManagerHelper;
@@ -128,6 +125,7 @@
import com.android.car.internal.ResultCallbackImpl;
import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
import com.android.car.internal.common.UserHelperLite;
+import com.android.car.internal.dep.Trace;
import com.android.car.internal.os.CarSystemProperties;
import com.android.car.internal.util.ArrayUtils;
import com.android.car.internal.util.DebugUtils;
@@ -215,11 +213,9 @@
static final String ERROR_TEMPLATE_DISALLOW_ADD_USER =
"Cannot create user because calling user %s has the '%s' restriction";
- /** Timeout for pre-populating users. */
- private static final int USER_CREATION_TIMEOUT_MS = 5_000;
-
private static final String BG_HANDLER_THREAD_NAME = "UserService.BG";
+ private final CurrentUserFetcher mCurrentUserFetcher;
private final Context mContext;
private final ActivityManager mAm;
private final UserManager mUserManager;
@@ -355,11 +351,12 @@
context.getSystemService(DevicePolicyManager.class),
context.getSystemService(ActivityManager.class), maxRunningUsers,
/* initialUserSetter= */ null, uxRestrictionService, /* handler= */ null,
- carPackageManagerService, carOccupantZoneService);
+ carPackageManagerService, carOccupantZoneService,
+ new ActivityManagerCurrentUserFetcher());
}
@VisibleForTesting
- CarUserService(@NonNull Context context, @NonNull UserHalService hal,
+ public CarUserService(@NonNull Context context, @NonNull UserHalService hal,
@NonNull UserManager userManager,
@NonNull UserHandleHelper userHandleHelper,
@NonNull DevicePolicyManager dpm,
@@ -369,8 +366,10 @@
@NonNull CarUxRestrictionsManagerService uxRestrictionService,
@Nullable Handler handler,
@NonNull CarPackageManagerService carPackageManagerService,
- @NonNull CarOccupantZoneService carOccupantZoneService) {
+ @NonNull CarOccupantZoneService carOccupantZoneService,
+ @NonNull CurrentUserFetcher currentUserFetcher) {
Slogf.d(TAG, "CarUserService(): DBG=%b, user=%s", DBG, context.getUser());
+ mCurrentUserFetcher = currentUserFetcher;
mContext = context;
mHal = hal;
mAm = am;
@@ -416,7 +415,7 @@
// and user assignments are changed. So it's safe not to register if visible background
// users are disabled. But, if we'll add more functionalies in the callback, consider to
// move the condition into the callback.
- if (mIsVisibleBackgroundUsersOnDefaultDisplaySupported) {
+ if (isMultipleUsersOnMultipleDisplaysSupported(mUserManager)) {
mCarOccupantZoneService.registerCallback(mOccupantZoneCallback);
}
CarServiceHelperWrapper.getInstance().runOnConnection(() ->
@@ -478,6 +477,9 @@
writer.printf("Initial user: %s\n", mInitialUser);
writer.println("Users not visible at starting: " + mNotVisibleAtStartingUsers);
writer.println("createUser queue size: " + mCreateUserQueue.size());
+ writer.printf("User switch in process=%d\n", mUserIdForUserSwitchInProcess);
+ writer.printf("Request Id for the user switch in process=%d\n ",
+ mRequestIdForUserSwitchInProcess);
}
writer.println("SwitchGuestUserBeforeSleep: " + mSwitchGuestUserBeforeSleep);
@@ -493,9 +495,6 @@
writer.printf("config_multiuserMaxRunningUsers=%d\n",
UserManagerHelper.getMaxRunningUsers(mContext));
writer.decreaseIndent();
- writer.printf("User switch in process=%d\n", mUserIdForUserSwitchInProcess);
- writer.printf("Request Id for the user switch in process=%d\n ",
- mRequestIdForUserSwitchInProcess);
writer.printf("System UI package name=%s\n",
PackageManagerHelper.getSystemUiPackageName(mContext));
@@ -707,7 +706,7 @@
}
private void initResumeReplaceGuest() {
- int currentUserId = ActivityManager.getCurrentUser();
+ int currentUserId = mCurrentUserFetcher.getCurrentUser();
UserHandle currentUser = mUserHandleHelper.getExistingUserHandle(currentUserId);
if (currentUser == null) {
@@ -754,6 +753,8 @@
}
private void initBootUser(int requestType) {
+ Trace.asyncTraceBegin(TraceHelper.TRACE_TAG_CAR_SERVICE, "initBootUser",
+ requestType);
boolean replaceGuest =
requestType == InitialUserInfoRequestType.RESUME && !mSwitchGuestUserBeforeSleep;
checkManageUsersPermission("startInitialUser");
@@ -763,6 +764,8 @@
fallbackToDefaultInitialUserBehavior(/* userLocales= */ null, replaceGuest,
/* supportsOverrideUserIdProperty= */ true, requestType);
EventLogHelper.writeCarUserServiceInitialUserInfoReqComplete(requestType);
+ Trace.asyncTraceEnd(TraceHelper.TRACE_TAG_CAR_SERVICE, "initBootUser",
+ requestType);
return;
}
@@ -828,6 +831,8 @@
/* supportsOverrideUserIdProperty= */ false, requestType);
}
EventLogHelper.writeCarUserServiceInitialUserInfoReqComplete(requestType);
+ Trace.asyncTraceEnd(TraceHelper.TRACE_TAG_CAR_SERVICE, "initBootUser",
+ requestType);
});
}
@@ -912,7 +917,7 @@
*
* @param targetUserId - target user Id
* @param timeoutMs - timeout for HAL to wait
- * @param receiver - receiver for the results
+ * @param callback - callback for the results
*/
@Override
public void switchUser(@UserIdInt int targetUserId, int timeoutMs,
@@ -959,7 +964,7 @@
private void handleSwitchUser(@NonNull UserHandle targetUser, int timeoutMs,
@NonNull ResultCallbackImpl<UserSwitchResult> callback, boolean isLogout,
boolean ignoreUxRestriction) {
- int currentUser = ActivityManager.getCurrentUser();
+ int currentUser = mCurrentUserFetcher.getCurrentUser();
int targetUserId = targetUser.getIdentifier();
if (currentUser == targetUserId) {
if (DBG) {
@@ -1896,7 +1901,7 @@
Integer user = userId;
if (isPersistentUser(userId)) {
// current foreground user should stay in top priority.
- if (userId == ActivityManager.getCurrentUser()) {
+ if (userId == mCurrentUserFetcher.getCurrentUser()) {
mBackgroundUsersToRestart.remove(user);
mBackgroundUsersToRestart.add(0, user);
}
@@ -1936,7 +1941,7 @@
// Non-current user only
// TODO(b/270719791): Keep track of the current user to avoid IPC to AM.
- if (userId == ActivityManager.getCurrentUser()) {
+ if (userId == mCurrentUserFetcher.getCurrentUser()) {
if (DBG) {
Slogf.d(TAG, "onUserStarting: user %d is the current user, skipping", userId);
}
@@ -1992,7 +1997,7 @@
// Non-current user only
// TODO(b/270719791): Keep track of the current user to avoid IPC to AM.
- if (userId == ActivityManager.getCurrentUser()) {
+ if (userId == mCurrentUserFetcher.getCurrentUser()) {
if (DBG) {
Slogf.d(TAG, "onUserVisible: user %d is the current user, skipping", userId);
}
@@ -2060,7 +2065,7 @@
}
// Run from here only when CMUMD is supported.
- if (userId == ActivityManager.getCurrentUser()) {
+ if (userId == mCurrentUserFetcher.getCurrentUser()) {
mBgHandler.post(() -> startUserPickerOnOtherDisplays(/* currentUserId= */ userId));
} else {
mBgHandler.post(() -> startLauncherForVisibleUser(userId));
@@ -2173,7 +2178,7 @@
private @UserStartResult.Status int startUserInBackgroundInternal(@UserIdInt int userId) {
// If the requested user is the current user, do nothing and return success.
- if (ActivityManager.getCurrentUser() == userId) {
+ if (mCurrentUserFetcher.getCurrentUser() == userId) {
return UserStartResult.STATUS_SUCCESSFUL_USER_IS_CURRENT_USER;
}
// If requested user does not exist, return error.
@@ -2222,7 +2227,7 @@
}
ArrayList<Integer> startedUsers = new ArrayList<>();
for (Integer user : users) {
- if (user == ActivityManager.getCurrentUser()) {
+ if (user == mCurrentUserFetcher.getCurrentUser()) {
continue;
}
if (ActivityManagerHelper.startUserInBackground(user)) {
@@ -2560,7 +2565,7 @@
return;
}
if (userId == UserHandle.SYSTEM.getIdentifier()
- || userId == ActivityManager.getCurrentUser()) {
+ || userId == mCurrentUserFetcher.getCurrentUser()) {
Slogf.w(TAG, "Cannot start SystemUI for current or system user (userId=%d)", userId);
return;
}
@@ -2636,32 +2641,6 @@
t.traceBegin("notify-app-listeners-user-" + userId + "-event-" + eventType);
for (int i = 0; i < listenersSize; i++) {
AppLifecycleListener listener = mAppLifecycleListeners.valueAt(i);
- if (eventType == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_CREATED
- || eventType == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_REMOVED) {
- PlatformVersion platformVersion = Car.getPlatformVersion();
- // Perform platform version check to ensure the support for these new events
- // is consistent with the platform version declared in their ApiRequirements.
- if (!platformVersion.isAtLeast(PlatformVersion.VERSION_CODES.TIRAMISU_1)) {
- if (DBG) {
- Slogf.d(TAG, "Skipping app listener %s for event %s due to unsupported"
- + " car platform version %s.", listener, event, platformVersion);
- }
- continue;
- }
- // Perform target car version check to ensure only apps expecting the new
- // lifecycle event types will have the events sent to them.
- // TODO(b/235524989): Cache the target car version for packages in
- // CarPackageManagerService.
- CarVersion targetCarVersion = mCarPackageManagerService.getTargetCarVersion(
- listener.packageName);
- if (!targetCarVersion.isAtLeast(CarVersion.VERSION_CODES.TIRAMISU_1)) {
- if (DBG) {
- Slogf.d(TAG, "Skipping app listener %s for event %s due to incompatible"
- + " target car version %s.", listener, event, targetCarVersion);
- }
- continue;
- }
- }
if (!listener.applyFilters(event)) {
if (DBG) {
Slogf.d(TAG, "Skipping app listener %s for event %s due to the filters"
diff --git a/service/src/com/android/car/user/CurrentUserFetcher.java b/service/src/com/android/car/user/CurrentUserFetcher.java
new file mode 100644
index 0000000..8b0acb1
--- /dev/null
+++ b/service/src/com/android/car/user/CurrentUserFetcher.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.user;
+
+/**
+ * An interface to get current user. Designed to be faked for unit testing.
+ */
+public interface CurrentUserFetcher {
+ /**
+ * See {@link android.app.ActivityManager.getCurrentUser}.
+ */
+ int getCurrentUser();
+}
diff --git a/service/src/com/android/car/user/InitialUserSetter.java b/service/src/com/android/car/user/InitialUserSetter.java
index e2d4189..45bd14c 100644
--- a/service/src/com/android/car/user/InitialUserSetter.java
+++ b/service/src/com/android/car/user/InitialUserSetter.java
@@ -27,6 +27,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.car.builtin.app.ActivityManagerHelper;
+import android.car.builtin.os.TraceHelper;
import android.car.builtin.os.UserManagerHelper;
import android.car.builtin.provider.SettingsHelper;
import android.car.builtin.util.EventLogHelper;
@@ -39,6 +40,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.Log;
import android.util.Pair;
import com.android.car.CarLog;
@@ -46,6 +48,7 @@
import com.android.car.hal.UserHalHelper;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.common.UserHelperLite;
+import com.android.car.internal.dep.Trace;
import com.android.car.internal.os.CarSystemProperties;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
@@ -67,7 +70,7 @@
@VisibleForTesting
static final String TAG = CarLog.tagFor(InitialUserSetter.class);
- private static final boolean DBG = false;
+ private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
private static final int BOOT_USER_NOT_FOUND = -1;
/**
@@ -348,6 +351,8 @@
public void set(@NonNull InitialUserInfo info) {
Preconditions.checkArgument(info != null, "info cannot be null");
+ Trace.traceBegin(TraceHelper.TRACE_TAG_CAR_SERVICE, "InitialuserSetter.set");
+
EventLogHelper.writeCarInitialUserInfo(info.type, info.replaceGuest, info.switchUserId,
info.newUserName, info.newUserFlags,
info.supportsOverrideUserIdProperty, info.userLocales);
@@ -384,8 +389,10 @@
}
break;
default:
+ Trace.traceEnd(TraceHelper.TRACE_TAG_CAR_SERVICE);
throw new IllegalArgumentException("invalid InitialUserInfo type: " + info.type);
}
+ Trace.traceEnd(TraceHelper.TRACE_TAG_CAR_SERVICE);
}
private void replaceUser(InitialUserInfo info, boolean fallback) {
diff --git a/service/src/com/android/car/util/LimitedTimingsTraceLog.java b/service/src/com/android/car/util/LimitedTimingsTraceLog.java
deleted file mode 100644
index 331ea96..0000000
--- a/service/src/com/android/car/util/LimitedTimingsTraceLog.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.util;
-
-import android.car.builtin.util.TimingsTraceLog;
-
-/**
- * {@link TimingsTraceLog} with additional minimum duration.
- *
- * <p>Any interval shorter than it will be added to trace but will not have logcat. This can be
- * useful to prevent log spam for short durations.
- */
-public final class LimitedTimingsTraceLog extends TimingsTraceLog {
-
- private final int mMinDurationMs;
-
- /**
- * Same as {@link TimingsTraceLog} except last argument {@code minDurationMs} which specifies
- * the minimum duration to log the duration.
- */
- public LimitedTimingsTraceLog(String tag, long traceTag, int minDurationMs) {
- super(tag, traceTag);
- mMinDurationMs = minDurationMs;
- }
-
- @Override
- public void logDuration(String name, long timeMs) {
- if (timeMs >= mMinDurationMs) {
- super.logDuration(name, timeMs);
- }
- }
-}
diff --git a/service/src/com/android/car/vms/VmsBrokerService.java b/service/src/com/android/car/vms/VmsBrokerService.java
index 5a27567..6d3e6a9 100644
--- a/service/src/com/android/car/vms/VmsBrokerService.java
+++ b/service/src/com/android/car/vms/VmsBrokerService.java
@@ -40,6 +40,7 @@
import android.os.SharedMemory;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.car.CarLog;
@@ -57,10 +58,8 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.function.IntSupplier;
-import java.util.stream.Collectors;
/**
* Message broker service for routing Vehicle Map Service messages between clients.
@@ -69,9 +68,10 @@
* notifications to clients about layer offering or subscription state changes.
*/
public class VmsBrokerService extends IVmsBrokerService.Stub implements CarServiceBase {
- private static final boolean DBG = false;
private static final String TAG = CarLog.tagFor(VmsBrokerService.class);
+ private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
+
private final Context mContext;
private final PackageManager mPackageManager;
private final CarStatsService mStatsService;
@@ -82,7 +82,7 @@
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final Map<IBinder /* clientToken */, VmsClientInfo> mClientMap = new ArrayMap<>();
+ private final ArrayMap<IBinder /* clientToken */, VmsClientInfo> mClientMap = new ArrayMap<>();
@GuardedBy("mLock")
private Set<VmsLayersOffering> mAllOfferings = Collections.emptySet();
@GuardedBy("mLock")
@@ -122,9 +122,14 @@
writer.println("mSubscriptionState: " + mSubscriptionState);
writer.println();
writer.println("mClientMap:");
- mClientMap.values().stream()
- .sorted(Comparator.comparingInt(VmsClientInfo::getUid))
- .forEach(client -> client.dump(writer, " "));
+ ArrayList<VmsClientInfo> clientInfos = new ArrayList<>(mClientMap.size());
+ for (int i = 0; i < mClientMap.size(); i++) {
+ clientInfos.add(mClientMap.valueAt(i));
+ }
+ clientInfos.sort(Comparator.comparingInt(VmsClientInfo::getUid));
+ for (int i = 0; i < clientInfos.size(); i++) {
+ clientInfos.get(i).dump(writer, " ");
+ }
}
}
@@ -243,11 +248,13 @@
mStatsService.getVmsClientLogger(client.getUid())
.logPacketSent(layer, packetLength);
- Collection<VmsClientInfo> subscribers;
+ ArrayList<VmsClientInfo> subscribers = new ArrayList<>();
synchronized (mLock) {
- subscribers = mClientMap.values().stream()
- .filter(subscriber -> subscriber.isSubscribed(providerId, layer))
- .collect(Collectors.toList());
+ for (int index = 0; index < mClientMap.size(); index++) {
+ if (mClientMap.valueAt(index).isSubscribed(providerId, layer)) {
+ subscribers.add(mClientMap.valueAt(index));
+ }
+ }
}
if (DBG) Slogf.d(TAG, "Number of subscribers: %d", subscribers.size());
@@ -304,10 +311,10 @@
private void updateAvailableLayers() {
synchronized (mLock) {
// Fuse layer offerings
- Set<VmsLayersOffering> allOfferings = mClientMap.values().stream()
- .map(VmsClientInfo::getAllOfferings)
- .flatMap(Collection::stream)
- .collect(Collectors.toCollection(ArraySet::new));
+ ArraySet<VmsLayersOffering> allOfferings = new ArraySet<>();
+ for (int index = 0; index < mClientMap.size(); index++) {
+ allOfferings.addAll(mClientMap.valueAt(index).getAllOfferings());
+ }
// Ignore update if offerings are unchanged
if (mAllOfferings.equals(allOfferings)) {
@@ -335,8 +342,8 @@
private void updateSubscriptionState() {
VmsSubscriptionState subscriptionState;
synchronized (mLock) {
- Set<VmsLayer> layerSubscriptions = new ArraySet<>();
- Map<VmsLayer, Set<Integer>> layerAndProviderSubscriptions = new ArrayMap<>();
+ ArraySet<VmsLayer> layerSubscriptions = new ArraySet<>();
+ ArrayMap<VmsLayer, Set<Integer>> layerAndProviderSubscriptions = new ArrayMap<>();
// Fuse subscriptions
for (VmsClientInfo client : mClientMap.values()) {
layerSubscriptions.addAll(client.getLayerSubscriptions());
@@ -353,10 +360,13 @@
layerSubscriptions.forEach(layerAndProviderSubscriptions::remove);
// Transform provider-specific subscriptions into VmsAssociatedLayers
- Set<VmsAssociatedLayer> associatedLayers =
- layerAndProviderSubscriptions.entrySet().stream()
- .map(entry -> new VmsAssociatedLayer(entry.getKey(), entry.getValue()))
- .collect(Collectors.toCollection(ArraySet::new));
+ ArraySet<VmsAssociatedLayer> associatedLayers =
+ new ArraySet<>(layerAndProviderSubscriptions.size());
+ for (int index = 0; index < layerAndProviderSubscriptions.size(); index++) {
+ associatedLayers.add(new VmsAssociatedLayer(
+ layerAndProviderSubscriptions.keyAt(index),
+ layerAndProviderSubscriptions.valueAt(index)));
+ }
// Ignore update if subscriptions are unchanged
if (mSubscriptionState.getLayers().equals(layerSubscriptions)
diff --git a/service/src/com/android/car/vms/VmsClientInfo.java b/service/src/com/android/car/vms/VmsClientInfo.java
index 2481f6a..98fa301 100644
--- a/service/src/com/android/car/vms/VmsClientInfo.java
+++ b/service/src/com/android/car/vms/VmsClientInfo.java
@@ -24,6 +24,7 @@
import android.car.vms.VmsLayerDependency;
import android.car.vms.VmsLayersOffering;
import android.os.IBinder;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -36,9 +37,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import java.util.Set;
-import java.util.stream.Collectors;
/**
* Class for tracking Vehicle Map Service client information, offerings, and subscriptions.
@@ -60,7 +59,7 @@
@GuardedBy("mLock")
private Set<VmsLayer> mLayerSubscriptions = Collections.emptySet();
@GuardedBy("mLock")
- private Map<VmsLayer, Set<Integer>> mLayerAndProviderSubscriptions = Collections.emptyMap();
+ private ArrayMap<VmsLayer, ArraySet<Integer>> mLayerAndProviderSubscriptions = new ArrayMap<>();
@GuardedBy("mLock")
private boolean mMonitoringEnabled;
@@ -105,7 +104,7 @@
}
}
- boolean setProviderOfferings(int providerId, Collection<VmsLayerDependency> offerings) {
+ boolean setProviderOfferings(int providerId, List<VmsLayerDependency> offerings) {
synchronized (mLock) {
Set<VmsLayerDependency> providerOfferings = mOfferings.get(providerId);
@@ -118,15 +117,23 @@
// Otherwise, update the offerings and return true
mOfferings.put(providerId, new ArraySet<>(offerings));
- mPotentialOfferings.put(providerId, offerings.stream()
- .map(VmsLayerDependency::getLayer)
- .collect(Collectors.toSet()));
+ ArraySet<VmsLayer> layerSet = new ArraySet<>(offerings.size());
+ for (int i = 0; i < offerings.size(); i++) {
+ layerSet.add(offerings.get(i).getLayer());
+ }
+ mPotentialOfferings.put(providerId, layerSet);
return true;
}
}
- @GuardedBy("mLock")
Collection<VmsLayersOffering> getAllOfferings() {
+ synchronized (mLock) {
+ return getAllOfferingsLcoked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ Collection<VmsLayersOffering> getAllOfferingsLcoked() {
List<VmsLayersOffering> result = new ArrayList<>(mOfferings.size());
for (int i = 0; i < mOfferings.size(); i++) {
int providerId = mOfferings.keyAt(i);
@@ -143,16 +150,20 @@
}
void setSubscriptions(List<VmsAssociatedLayer> layers) {
+ ArraySet<VmsLayer> layerSubscriptions = new ArraySet<>();
+ ArrayMap<VmsLayer, ArraySet<Integer>> layerAndProviderSubscriptions = new ArrayMap<>();
+ for (int index = 0; index < layers.size(); index++) {
+ VmsAssociatedLayer associatedLayer = layers.get(index);
+ if (associatedLayer.getProviderIds().isEmpty()) {
+ layerSubscriptions.add(associatedLayer.getVmsLayer());
+ } else {
+ layerAndProviderSubscriptions.put(associatedLayer.getVmsLayer(),
+ new ArraySet<>(associatedLayer.getProviderIds()));
+ }
+ }
synchronized (mLock) {
- mLayerSubscriptions = layers.stream()
- .filter(associatedLayer -> associatedLayer.getProviderIds().isEmpty())
- .map(VmsAssociatedLayer::getVmsLayer)
- .collect(Collectors.toSet());
- mLayerAndProviderSubscriptions = layers.stream()
- .filter(associatedLayer -> !associatedLayer.getProviderIds().isEmpty())
- .collect(Collectors.toMap(
- VmsAssociatedLayer::getVmsLayer,
- associatedLayer -> new ArraySet<>(associatedLayer.getProviderIds())));
+ mLayerSubscriptions = layerSubscriptions;
+ mLayerAndProviderSubscriptions = layerAndProviderSubscriptions;
}
}
@@ -162,7 +173,7 @@
}
}
- Map<VmsLayer, Set<Integer>> getLayerAndProviderSubscriptions() {
+ ArrayMap<VmsLayer, ArraySet<Integer>> getLayerAndProviderSubscriptions() {
synchronized (mLock) {
return deepCopy(mLayerAndProviderSubscriptions);
}
@@ -178,7 +189,7 @@
synchronized (mLock) {
return mMonitoringEnabled
|| mLayerSubscriptions.contains(layer)
- || mLayerAndProviderSubscriptions.getOrDefault(layer, Collections.emptySet())
+ || mLayerAndProviderSubscriptions.getOrDefault(layer, new ArraySet<>())
.contains(providerId);
}
}
@@ -222,17 +233,19 @@
for (VmsLayer layer : mLayerSubscriptions) {
writer.println(prefix + layer);
}
- for (Map.Entry<VmsLayer, Set<Integer>> layerEntry :
- mLayerAndProviderSubscriptions.entrySet()) {
- writer.println(prefix + layerEntry.getKey() + ": " + layerEntry.getValue());
+ for (int i = 0; i < mLayerAndProviderSubscriptions.size(); i++) {
+ writer.println(prefix + mLayerAndProviderSubscriptions.keyAt(i) + ": "
+ + mLayerAndProviderSubscriptions.valueAt(i));
}
}
}
}
- private static <K, V> Map<K, Set<V>> deepCopy(Map<K, Set<V>> original) {
- return original.entrySet().stream().collect(Collectors.toMap(
- Map.Entry::getKey,
- entry -> new ArraySet<>(entry.getValue())));
+ private static <K, V> ArrayMap<K, ArraySet<V>> deepCopy(ArrayMap<K, ArraySet<V>> original) {
+ ArrayMap<K, ArraySet<V>> newMap = new ArrayMap<>(original.size());
+ for (int i = 0; i < original.size(); i++) {
+ newMap.put(original.keyAt(i), new ArraySet<>(original.valueAt(i)));
+ }
+ return newMap;
}
}
diff --git a/service/src/com/android/car/vms/VmsLayerAvailability.java b/service/src/com/android/car/vms/VmsLayerAvailability.java
index 3eb03ee..3c45bd4 100644
--- a/service/src/com/android/car/vms/VmsLayerAvailability.java
+++ b/service/src/com/android/car/vms/VmsLayerAvailability.java
@@ -22,17 +22,17 @@
import android.car.vms.VmsLayer;
import android.car.vms.VmsLayerDependency;
import android.car.vms.VmsLayersOffering;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
import com.android.car.CarLog;
import com.android.internal.annotations.GuardedBy;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import java.util.stream.Collectors;
/**
* Manages VMS availability for layers.
@@ -43,17 +43,17 @@
*/
class VmsLayerAvailability {
- private static final boolean DBG = false;
private static final String TAG = CarLog.tagFor(VmsLayerAvailability.class);
+ private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
private final Object mLock = new Object();
@GuardedBy("mLock")
private final Map<VmsLayer, Set<Set<VmsLayer>>> mPotentialLayersAndDependencies =
- new HashMap<>();
+ new ArrayMap<>();
@GuardedBy("mLock")
private Set<VmsAssociatedLayer> mAvailableAssociatedLayers = Collections.emptySet();
@GuardedBy("mLock")
- private Map<VmsLayer, Set<Integer>> mPotentialLayersAndPublishers = new HashMap<>();
+ private Map<VmsLayer, Set<Integer>> mPotentialLayersAndPublishers = new ArrayMap<>();
@GuardedBy("mLock")
private int mSeq = 0;
@@ -69,11 +69,11 @@
VmsLayer layer = dependency.getLayer();
// Associate publishers with layers.
- mPotentialLayersAndPublishers.computeIfAbsent(layer, k -> new HashSet<>())
+ mPotentialLayersAndPublishers.computeIfAbsent(layer, k -> new ArraySet<>())
.add(offering.getPublisherId());
// Add dependencies for availability calculation.
- mPotentialLayersAndDependencies.computeIfAbsent(layer, k -> new HashSet<>())
+ mPotentialLayersAndDependencies.computeIfAbsent(layer, k -> new ArraySet<>())
.add(dependency.getDependencies());
}
}
@@ -101,8 +101,8 @@
private void calculateLayers() {
synchronized (mLock) {
- Set<VmsLayer> availableLayersSet = new HashSet<>();
- Set<VmsLayer> cyclicAvoidanceAuxiliarySet = new HashSet<>();
+ ArraySet<VmsLayer> availableLayersSet = new ArraySet<>();
+ ArraySet<VmsLayer> cyclicAvoidanceAuxiliarySet = new ArraySet<>();
for (VmsLayer layer : mPotentialLayersAndDependencies.keySet()) {
addLayerToAvailabilityCalculationLocked(layer,
@@ -110,12 +110,14 @@
cyclicAvoidanceAuxiliarySet);
}
- mAvailableAssociatedLayers = Collections.unmodifiableSet(
- availableLayersSet
- .stream()
- .map(l -> new VmsAssociatedLayer(l,
- mPotentialLayersAndPublishers.get(l)))
- .collect(Collectors.toSet()));
+ ArraySet<VmsAssociatedLayer> associatedLayerSet = new ArraySet<>();
+ for (int index = 0; index < availableLayersSet.size(); index++) {
+ VmsLayer availableLayer = availableLayersSet.valueAt(index);
+ VmsAssociatedLayer associatedLayer = new VmsAssociatedLayer(availableLayer,
+ mPotentialLayersAndPublishers.get(availableLayer));
+ associatedLayerSet.add(associatedLayer);
+ }
+ mAvailableAssociatedLayers = Collections.unmodifiableSet(associatedLayerSet);
}
}
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 25441f7..5424f0a 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -16,7 +16,6 @@
package com.android.car.watchdog;
-import static android.car.PlatformVersion.VERSION_CODES;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
@@ -81,6 +80,7 @@
import com.android.car.CarLog;
import com.android.car.CarServiceBase;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+import com.android.car.internal.dep.Trace;
import com.android.car.internal.util.ArrayUtils;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.car.power.CarPowerManagementService;
@@ -144,6 +144,7 @@
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
+ Trace.beginSection("CarWatchdogService-broadcast-" + action);
switch (action) {
case CAR_WATCHDOG_ACTION_DISMISS_RESOURCE_OVERUSE_NOTIFICATION:
case CAR_WATCHDOG_ACTION_LAUNCH_APP_SETTINGS:
@@ -207,6 +208,7 @@
default:
Slogf.i(TAG, "Ignoring unknown intent %s", intent);
}
+ Trace.endSection();
}
};
@@ -219,7 +221,13 @@
if (powerService == null) {
return;
}
- int powerCycle = carPowerStateToPowerCycle(powerService.getPowerState());
+ int powerState = powerService.getPowerState();
+ int powerCycle = carPowerStateToPowerCycle(powerState);
+ if (powerCycle < 0) {
+ return;
+ }
+ Trace.beginSection("CarWatchdogService-powerStateChanged-"
+ + CarPowerManagementService.powerStateToString(powerState));
switch (powerCycle) {
case PowerCycle.POWER_CYCLE_SHUTDOWN_PREPARE:
// Perform time consuming disk I/O operation during shutdown prepare to avoid
@@ -243,6 +251,7 @@
return;
}
notifyPowerCycleChange(powerCycle);
+ Trace.endSection();
}
};
@@ -251,6 +260,8 @@
@Override
public void onPolicyChanged(CarPowerPolicy appliedPolicy,
CarPowerPolicy accumulatedPolicy) {
+ Trace.beginSection("CarWatchdogService-carPowerPolicyChanged-"
+ + appliedPolicy.getPolicyId());
boolean isDisplayEnabled =
appliedPolicy.isComponentEnabled(PowerComponent.DISPLAY);
boolean didStateChange = false;
@@ -261,6 +272,7 @@
if (didStateChange) {
mWatchdogPerfHandler.onDisplayStateChanged(isDisplayEnabled);
}
+ Trace.endSection();;
}
};
@@ -322,6 +334,7 @@
@Override
public void init() {
+ Trace.beginSection("CarWatchdogService.init");
// TODO(b/266008677): The daemon reads the sendResourceUsageStatsEnabled sysprop at the
// moment the CarWatchdogService connects to it. Therefore, the property must be set by
// CarWatchdogService before connecting with the CarWatchdog daemon. Set the property to
@@ -340,16 +353,19 @@
if (DEBUG) {
Slogf.d(TAG, "CarWatchdogService is initialized");
}
+ Trace.endSection();
}
@Override
public void release() {
+ Trace.beginSection("CarWatchdogService.release");
mContext.unregisterReceiver(mBroadcastReceiver);
unsubscribePowerManagementService();
mWatchdogPerfHandler.release();
mWatchdogStorage.release();
unregisterFromDaemon();
mCarWatchdogDaemonHelper.disconnect();
+ Trace.endSection();
}
@Override
@@ -597,6 +613,7 @@
}
private void notifyAllUserStates() {
+ Trace.beginSection("CarWatchdogService.notifyAllUserStates");
UserManager userManager = mContext.getSystemService(UserManager.class);
List<UserHandle> users = userManager.getUserHandles(/* excludeDying= */ false);
try {
@@ -619,14 +636,10 @@
// throws IllegalStateException. Catch the exception to avoid crashing the process.
Slogf.w(TAG, e, "Notifying latest user states failed");
}
+ Trace.endSection();
}
private void notifyPowerCycleChange(@PowerCycle int powerCycle) {
- // TODO(b/236876940): Change version check to TIRAMISU_2 when cherry picking to T-QPR2.
- if (!Car.getPlatformVersion().isAtLeast(VERSION_CODES.UPSIDE_DOWN_CAKE_0)
- && powerCycle == PowerCycle.POWER_CYCLE_SUSPEND_EXIT) {
- return;
- }
try {
mCarWatchdogDaemonHelper.notifySystemStateChange(
StateType.POWER_CYCLE, powerCycle, MISSING_ARG_VALUE);
@@ -669,6 +682,7 @@
return;
}
}
+ Trace.beginSection("CarWatchdogService.registerToDaemon");
try {
mCarWatchdogDaemonHelper.registerCarWatchdogService(mWatchdogServiceForSystem);
if (DEBUG) {
@@ -700,9 +714,11 @@
garageMode = mCurrentGarageMode;
}
notifyGarageModeChange(garageMode);
+ Trace.endSection();
}
private void unregisterFromDaemon() {
+ Trace.beginSection("CarWatchdogService.unregisterFromDaemon");
try {
mCarWatchdogDaemonHelper.unregisterCarWatchdogService(mWatchdogServiceForSystem);
if (DEBUG) {
@@ -713,6 +729,7 @@
// throws IllegalStateException. Catch the exception to avoid crashing the process.
Slogf.w(TAG, e, "Cannot unregister from car watchdog daemon");
}
+ Trace.endSection();
}
private void subscribePowerManagementService() {
@@ -758,11 +775,6 @@
USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED, USER_LIFECYCLE_EVENT_TYPE_STOPPED)) {
return;
}
- if (!Car.getPlatformVersion().isAtLeast(VERSION_CODES.TIRAMISU_1)
- && !isEventAnyOfTypes(TAG, event,
- USER_LIFECYCLE_EVENT_TYPE_STARTING, USER_LIFECYCLE_EVENT_TYPE_STOPPED)) {
- return;
- }
int userId = event.getUserHandle().getIdentifier();
int userState;
diff --git a/service/src/com/android/car/watchdog/PackageInfoHandler.java b/service/src/com/android/car/watchdog/PackageInfoHandler.java
index 591bc48..ad30679 100644
--- a/service/src/com/android/car/watchdog/PackageInfoHandler.java
+++ b/service/src/com/android/car/watchdog/PackageInfoHandler.java
@@ -123,7 +123,7 @@
}
try {
return getNameForPackage(PackageManagerHelper.getPackageInfoAsUser(
- mPackageManager, packageName, /* flags= */ 0, userId));
+ mPackageManager, packageName, /* packageInfoFlags= */ 0, userId));
} catch (PackageManager.NameNotFoundException e) {
Slogf.e(TAG, "Package '%s' not found for user %d: %s", packageName, userId, e);
}
diff --git a/service/src/com/android/car/watchdog/WatchdogProcessHandler.java b/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
index 67a9812..249c1ff 100644
--- a/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
@@ -43,6 +43,7 @@
import com.android.car.CarServiceHelperWrapper;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+import com.android.car.internal.dep.Trace;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
@@ -338,10 +339,12 @@
*/
public void asyncFetchAidlVhalPid() {
mServiceHandler.post(() -> {
+ Trace.beginSection("WatchdogProcessHandler.asyncFetchAidlVhalPid");
int pid = CarServiceHelperWrapper.getInstance().fetchAidlVhalPid();
if (pid < 0) {
Slogf.e(CarWatchdogService.TAG, "Failed to fetch AIDL VHAL pid from"
+ " CarServiceHelperService");
+ Trace.endSection();
return;
}
try {
@@ -350,17 +353,20 @@
Slogf.e(CarWatchdogService.TAG,
"Failed to notify car watchdog daemon of the AIDL VHAL pid");
}
+ Trace.endSection();
});
}
/** Enables/disables the watchdog daemon client health check process. */
void controlProcessHealthCheck(boolean enable) {
+ Trace.beginSection("WatchdogProcessHandler-healthCheckEnabled-" + enable);
try {
mCarWatchdogDaemonHelper.controlProcessHealthCheck(enable);
} catch (RemoteException e) {
Slogf.w(CarWatchdogService.TAG,
"Cannot enable/disable the car watchdog daemon health check process: %s", e);
}
+ Trace.endSection();
}
private void onClientDeath(ICarWatchdogServiceCallback client, int timeout) {
@@ -373,16 +379,19 @@
// For critical clients, the response status are checked just before reporting to car
// watchdog daemon. For moderate and normal clients, the status are checked after allowed
// delay per timeout.
+ Trace.beginSection("WatchdogProcessHandler.doHealthCheck");
analyzeClientResponse(TIMEOUT_CRITICAL);
reportHealthCheckResult(sessionId);
sendPingToClients(TIMEOUT_CRITICAL);
sendPingToClientsAndCheck(TIMEOUT_MODERATE);
sendPingToClientsAndCheck(TIMEOUT_NORMAL);
+ Trace.endSection();
}
private void analyzeClientResponse(int timeout) {
// Clients which are not responding are stored in mClientsNotResponding, and will be dumped
// and killed at the next response of CarWatchdogService to car watchdog daemon.
+ Trace.beginSection("WatchdogProcessHandler.analyzeClientResponse");
synchronized (mLock) {
SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout);
for (int i = 0; i < pingedClients.size(); i++) {
@@ -395,9 +404,11 @@
}
mClientCheckInProgress.setValueAt(timeout, false);
}
+ Trace.endSection();
}
private void sendPingToClients(int timeout) {
+ Trace.beginSection("WatchdogProcessHandler.sendPingToClients");
ArrayList<ClientInfo> clientsToCheck;
synchronized (mLock) {
SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout);
@@ -428,6 +439,7 @@
}
}
}
+ Trace.endSection();
}
private void sendPingToClientsAndCheck(int timeout) {
@@ -463,6 +475,7 @@
}
private void reportHealthCheckResult(int sessionId) {
+ Trace.beginSection("WatchdogProcessHandler.reportHealthCheckResult");
List<ProcessIdentifier> clientsNotResponding;
ArrayList<ClientInfo> clientsToNotify;
synchronized (mLock) {
@@ -488,6 +501,7 @@
Slogf.w(CarWatchdogService.TAG,
"Cannot respond to car watchdog daemon (sessionId=%d): %s", sessionId, e);
}
+ Trace.endSection();
}
@NonNull
diff --git a/service/src/com/android/car/wifi/CarWifiService.java b/service/src/com/android/car/wifi/CarWifiService.java
index 7b47c86..cb763c6 100644
--- a/service/src/com/android/car/wifi/CarWifiService.java
+++ b/service/src/com/android/car/wifi/CarWifiService.java
@@ -23,6 +23,7 @@
import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
import static com.android.car.CarServiceUtils.getHandlerThread;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
import android.car.Car;
import android.car.builtin.util.Slogf;
@@ -45,10 +46,12 @@
import android.text.TextUtils;
import android.util.proto.ProtoOutputStream;
+import com.android.car.CarLocalServices;
import com.android.car.CarLog;
import com.android.car.CarServiceBase;
import com.android.car.CarServiceUtils;
import com.android.car.R;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.car.power.CarPowerManagementService;
import com.android.car.user.CarUserService;
@@ -144,11 +147,6 @@
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
- if (!mIsPersistTetheringCapabilitiesEnabled) {
- Slogf.i(TAG, "Persist tethering capability is not enabled");
- return;
- }
-
Slogf.i(TAG, "%s setting has changed", ENABLE_PERSISTENT_TETHERING);
// If the persist tethering setting is turned off, auto shutdown must be
// re-enabled.
@@ -163,8 +161,7 @@
@GuardedBy("mLock")
private SharedPreferences mSharedPreferences;
- public CarWifiService(Context context, CarPowerManagementService powerManagementService,
- CarUserService userService) {
+ public CarWifiService(Context context) {
mContext = context;
mIsPersistTetheringCapabilitiesEnabled = context.getResources().getBoolean(
R.bool.config_enablePersistTetheringCapabilities);
@@ -174,8 +171,8 @@
ENABLE_PERSISTENT_TETHERING));
mWifiManager = context.getSystemService(WifiManager.class);
mTetheringManager = context.getSystemService(TetheringManager.class);
- mCarPowerManagementService = powerManagementService;
- mCarUserService = userService;
+ mCarPowerManagementService = CarLocalServices.getService(CarPowerManagementService.class);
+ mCarUserService = CarLocalServices.getService(CarUserService.class);
}
@Override
@@ -185,8 +182,8 @@
return;
}
- // Listeners should be set if there's capability so tethering can be ready to turn on if
- // setting is enabled, on next boot.
+ // Listeners should only be set if there's capability (also allows for
+ // tethering persisting if setting is enabled, on next boot).
mWifiManager.registerSoftApCallback(mHandler::post, mSoftApCallback);
mCarUserService.runOnUser0Unlock(this::onSystemUserUnlocked);
mCarPowerManagementService.registerListener(mCarPowerStateListener);
@@ -214,6 +211,7 @@
}
@Override
+ @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
public void dumpProto(ProtoOutputStream proto) {
proto.write(CarWifiDumpProto.PERSIST_TETHERING_CAPABILITIES_ENABLED,
mIsPersistTetheringCapabilitiesEnabled);
@@ -225,6 +223,7 @@
}
@Override
+ @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
public void dump(IndentingPrintWriter writer) {
writer.println("**CarWifiService**");
writer.println();
@@ -275,20 +274,6 @@
}
});
}
-
- @GuardedBy("mLock")
- private void initSharedPreferencesLocked() {
- // SharedPreferences are shared among different users thus only need initialized once. They
- // should be initialized after user 0 is unlocked because SharedPreferences in
- // credential encrypted storage are not available until after user 0 is unlocked.
- if (mSharedPreferences != null) {
- Slogf.d(TAG, "SharedPreferences store has already been initialized");
- return;
- }
-
- mSharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
- }
-
private void onStateOn() {
synchronized (mLock) {
if (mSharedPreferences == null) {
@@ -303,7 +288,11 @@
private void onSystemUserUnlocked() {
synchronized (mLock) {
- initSharedPreferencesLocked();
+ // SharedPreferences are shared among different users thus only need initialized once.
+ // They should be initialized after user 0 is unlocked because SharedPreferences in
+ // credential encrypted storage are not available until after user 0 is unlocked.
+ mSharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME,
+ Context.MODE_PRIVATE);
}
if (mCarPowerManagementService.getPowerState() == CarPowerManager.STATE_ON) {
diff --git a/tests/BugReportApp/Android.bp b/tests/BugReportApp/Android.bp
index d880643..946ba02 100644
--- a/tests/BugReportApp/Android.bp
+++ b/tests/BugReportApp/Android.bp
@@ -29,14 +29,14 @@
aaptflags: ["--auto-add-overlay"],
- certificate: "platform",
-
- privileged: true,
-
optimize: {
enabled: false,
},
+ certificate: "platform",
+
+ privileged: true,
+
enforce_uses_libs: false,
dex_preopt: {
enabled: false,
@@ -45,6 +45,7 @@
libs: [
"android.car",
"auto_value_annotations",
+ "androidx.annotation_annotation",
],
static_libs: [
diff --git a/tests/BugReportApp/AndroidManifest.xml b/tests/BugReportApp/AndroidManifest.xml
index 64a5eb3..8725d17 100644
--- a/tests/BugReportApp/AndroidManifest.xml
+++ b/tests/BugReportApp/AndroidManifest.xml
@@ -56,6 +56,19 @@
</intent-filter>
</activity>
+ <activity android:name=".ChoiceDialogActivity"
+ android:theme="@style/DialogWithNullBackground"
+ android:exported="true"
+ android:launchMode="singleInstance"
+ android:excludeFromRecents="true">
+ <meta-data android:name="distractionOptimized" android:value="true"/>
+ <intent-filter>
+ <action android:name="com.android.car.bugreport.action.START_BUG_REPORT" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ </intent-filter>
+ </activity>
+
<!--
singleInstance allows starting bugreport dialog when BugReportInfoActivity is open.
-->
diff --git a/tests/BugReportApp/TEST_MAPPING b/tests/BugReportApp/TEST_MAPPING
index a94007d..4d4a2a0 100644
--- a/tests/BugReportApp/TEST_MAPPING
+++ b/tests/BugReportApp/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "auto-presubmit": [
{
"name": "BugReportAppTest"
}
diff --git a/tests/BugReportApp/res/layout/choice_dialog_activity.xml b/tests/BugReportApp/res/layout/choice_dialog_activity.xml
new file mode 100644
index 0000000..80e7b86
--- /dev/null
+++ b/tests/BugReportApp/res/layout/choice_dialog_activity.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@color/bugreport_background"
+ android:padding="@dimen/bug_report_padding"
+ android:layout_gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/choice_dialog_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textColor="@color/bugreport_text"
+ android:gravity="center"
+ android:text="@string/choice_dialog_title"/>
+
+ <Button
+ android:id="@+id/button_begin_now"
+ style="@style/standard_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/bug_report_button_margin_top"
+ android:text="@string/choice_dialog_begin_now"/>
+
+ <Button
+ android:id="@+id/button_speak_later"
+ style="@style/transparent_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/bug_report_button_margin_top"
+ android:text="@string/choice_dialog_speak_later"/>
+
+</LinearLayout>
diff --git a/tests/BugReportApp/res/values-af/strings.xml b/tests/BugReportApp/res/values-af/strings.xml
index 8743851..266793b 100644
--- a/tests/BugReportApp/res/values-af/strings.xml
+++ b/tests/BugReportApp/res/values-af/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Foutverslag se statuskanaal"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Neem ’n skermskoot"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Foutverslagskermskootkanaal"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Gereed om foutbeskrywing te gee"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Begin nou"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Praat later"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-am/strings.xml b/tests/BugReportApp/res/values-am/strings.xml
index 38ea67b..5129379 100644
--- a/tests/BugReportApp/res/values-am/strings.xml
+++ b/tests/BugReportApp/res/values-am/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"የሳንካ ሪፖርት ሁኔታ ሰርጥ"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"ቅጽበታዊ ገፅ ዕይታን ማንሳት"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"የሳንካ ሪፖርት ቅጽበታዊ ገፅ ዕይታ ሰርጥ"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"የሳንካ መግለጫ ለመናገር ዝግጁ"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"አሁን ይጀምሩ"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"ቆይተው ይናገሩ"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-ar/strings.xml b/tests/BugReportApp/res/values-ar/strings.xml
index ac8bc2f..0ff0b03 100644
--- a/tests/BugReportApp/res/values-ar/strings.xml
+++ b/tests/BugReportApp/res/values-ar/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"قناة حالة تقرير الأخطاء"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"أخذ لقطة شاشة"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"قناة لمزيد من لقطات الشاشة لتقرير الخطأ"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"جاهز لوصف الخطأ بالتحدّث"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"البدء الآن"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"التحدّث لاحقًا"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-as/strings.xml b/tests/BugReportApp/res/values-as/strings.xml
index 88a3258..9665b9b 100644
--- a/tests/BugReportApp/res/values-as/strings.xml
+++ b/tests/BugReportApp/res/values-as/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"বাগ ৰিপ’ৰ্টৰ স্থিতিৰ চেনেল"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"স্ক্ৰীনশ্বট লোৱা"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"বাগ ৰিপ’ৰ্টৰ স্ক্ৰীনশ্বটৰ চেনেল"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"বাগৰ বিৱৰণৰ সম্পৰ্কে ক’বলৈ সাজু"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"এতিয়াই আৰম্ভ কৰক"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"পাছত কওক"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-az/strings.xml b/tests/BugReportApp/res/values-az/strings.xml
index d8bf045..e3839c0 100644
--- a/tests/BugReportApp/res/values-az/strings.xml
+++ b/tests/BugReportApp/res/values-az/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Baq hesabatı status kanalı"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Skrinşot çəkilir"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Baq hesabatı skrinşotu kanalı"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Baqı təsvir etməyə hazır"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"İndi başlayın"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Sonra danışın"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-b+sr+Latn/strings.xml b/tests/BugReportApp/res/values-b+sr+Latn/strings.xml
index 0eba9de..f343a70 100644
--- a/tests/BugReportApp/res/values-b+sr+Latn/strings.xml
+++ b/tests/BugReportApp/res/values-b+sr+Latn/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kanal statusa izveštaja o grešci"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Pravi se snimak ekrana"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanal za snimke ekrana izveštaja o grešci"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Opis greške „Spreman za govor“"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Započnite"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Govorite kasnije"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-be/strings.xml b/tests/BugReportApp/res/values-be/strings.xml
index 230d2e3..24d9f1d 100644
--- a/tests/BugReportApp/res/values-be/strings.xml
+++ b/tests/BugReportApp/res/values-be/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Канал стану справаздачы пра памылкі"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Стварэнне здымка экрана"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Канал для здымкаў экрана, далучаных да справаздачы пра памылку"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Цяпер можна агучыць апісанне памылкі"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Пачаць"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Адкласці агучванне"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-bg/strings.xml b/tests/BugReportApp/res/values-bg/strings.xml
index d20b185f3..6dc9ef1 100644
--- a/tests/BugReportApp/res/values-bg/strings.xml
+++ b/tests/BugReportApp/res/values-bg/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Канал за състоянието на сигнала за програмна грешка"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Правене на екранна снимка"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Канал с екранни снимки на сигнали за програмна грешка"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"В готовност за изговаряне на описанието на програмната грешка"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Започнете сега"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Говорете по-късно"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-bn/strings.xml b/tests/BugReportApp/res/values-bn/strings.xml
index 5e28386..2cab59b 100644
--- a/tests/BugReportApp/res/values-bn/strings.xml
+++ b/tests/BugReportApp/res/values-bn/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"সমস্যার বিষয়ে রিপোর্টের স্ট্যাটাসের চ্যানেল"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"স্ক্রিনশট নেওয়া হচ্ছে"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"বাগ রিপোর্ট সংক্রান্ত স্ক্রিনশট চ্যানেল"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"সমস্যার বিবরণ সম্পর্কে বলতে প্রস্তুত"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"এখনই শুরু করুন"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"পরে বলুন"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-bs/strings.xml b/tests/BugReportApp/res/values-bs/strings.xml
index 6d8a624..2121f62 100644
--- a/tests/BugReportApp/res/values-bs/strings.xml
+++ b/tests/BugReportApp/res/values-bs/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Status kanala izvještaja o greškama"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Snimanje ekrana"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanal snimka ekrana za izvještaj o grešci"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Opis greške \"Spremni za govor\""</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Započnite sada"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Govorite kasnije"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-ca/strings.xml b/tests/BugReportApp/res/values-ca/strings.xml
index 4fb9721..5b53a4f 100644
--- a/tests/BugReportApp/res/values-ca/strings.xml
+++ b/tests/BugReportApp/res/values-ca/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Canal de l\'estat de l\'informe d\'errors"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"S\'està fent una captura de pantalla"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Canal de captures de pantalla d\'un informe d\'errors"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Tot a punt per descriure l\'error"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Comença ara"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Parla més tard"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-cs/strings.xml b/tests/BugReportApp/res/values-cs/strings.xml
index 3b5371b..b48076c 100644
--- a/tests/BugReportApp/res/values-cs/strings.xml
+++ b/tests/BugReportApp/res/values-cs/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kanál stavu zprávy o chybě"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Pořízení snímku obrazovky"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanál pro snímek obrazovky přiložený ke zprávě o chybě"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Mluvení připraveno – popis chyby"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Začít"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Mluvit později"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-da/strings.xml b/tests/BugReportApp/res/values-da/strings.xml
index 4ad3087..50bf4e1 100644
--- a/tests/BugReportApp/res/values-da/strings.xml
+++ b/tests/BugReportApp/res/values-da/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Statuskanal for fejlrapporten"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Sådan tager du et screenshot"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanal til screenshots af fejlrapport"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Fejlbeskrivelse: Klar til at tale"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Start nu"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Tal senere"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-de/strings.xml b/tests/BugReportApp/res/values-de/strings.xml
index 314fc31..5027cf9 100644
--- a/tests/BugReportApp/res/values-de/strings.xml
+++ b/tests/BugReportApp/res/values-de/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kanal für den Fehlerbericht-Status"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Screenshot aufnehmen"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanal für Screenshots von Fehlerberichten"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Bereit zur Aufnahme der Fehlerbeschreibung"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Jetzt starten"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Später aufnehmen"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-el/strings.xml b/tests/BugReportApp/res/values-el/strings.xml
index cf27a88..98322f4 100644
--- a/tests/BugReportApp/res/values-el/strings.xml
+++ b/tests/BugReportApp/res/values-el/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Κανάλι κατάστασης αναφοράς σφάλματος"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Γίνεται λήψη στιγμιότυπου οθόνης"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Κανάλι στιγμιότυπου οθόνης για αναφορά σφάλματος"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Έτοιμος να σας μιλήσει σχετικά με την περιγραφή του σφάλματος"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Ξεκινήστε τώρα"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Μιλήστε αργότερα"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-en-rAU/strings.xml b/tests/BugReportApp/res/values-en-rAU/strings.xml
index e93397a..3de589e 100644
--- a/tests/BugReportApp/res/values-en-rAU/strings.xml
+++ b/tests/BugReportApp/res/values-en-rAU/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Bug report status channel"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Taking a screenshot"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Bug report screenshot channel"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Ready to say bug description"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Begin now"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Speak later"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-en-rCA/strings.xml b/tests/BugReportApp/res/values-en-rCA/strings.xml
index 2c51edb..9bcef51 100644
--- a/tests/BugReportApp/res/values-en-rCA/strings.xml
+++ b/tests/BugReportApp/res/values-en-rCA/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Bug report status channel"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Taking a screenshot"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Bug report screenshot channel"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Ready to speak bug description"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Begin now"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Speak later"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-en-rGB/strings.xml b/tests/BugReportApp/res/values-en-rGB/strings.xml
index e93397a..3de589e 100644
--- a/tests/BugReportApp/res/values-en-rGB/strings.xml
+++ b/tests/BugReportApp/res/values-en-rGB/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Bug report status channel"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Taking a screenshot"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Bug report screenshot channel"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Ready to say bug description"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Begin now"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Speak later"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-en-rIN/strings.xml b/tests/BugReportApp/res/values-en-rIN/strings.xml
index e93397a..3de589e 100644
--- a/tests/BugReportApp/res/values-en-rIN/strings.xml
+++ b/tests/BugReportApp/res/values-en-rIN/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Bug report status channel"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Taking a screenshot"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Bug report screenshot channel"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Ready to say bug description"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Begin now"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Speak later"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-en-rXC/strings.xml b/tests/BugReportApp/res/values-en-rXC/strings.xml
index 7e5c8a1..2952e03 100644
--- a/tests/BugReportApp/res/values-en-rXC/strings.xml
+++ b/tests/BugReportApp/res/values-en-rXC/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Bug report status channel"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Taking a screenshot"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Bug report screenshot channel"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Ready to speak bug description"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Begin now"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Speak later"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-es-rUS/strings.xml b/tests/BugReportApp/res/values-es-rUS/strings.xml
index e176b84..5327ad5 100644
--- a/tests/BugReportApp/res/values-es-rUS/strings.xml
+++ b/tests/BugReportApp/res/values-es-rUS/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Canal de estado del informe de errores"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Tomando captura de pantalla"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Canal para informar errores con capturas de pantalla"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Describir el error ahora"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Comenzar ahora"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Hablar más tarde"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-es/strings.xml b/tests/BugReportApp/res/values-es/strings.xml
index 3087148..cb232f4 100644
--- a/tests/BugReportApp/res/values-es/strings.xml
+++ b/tests/BugReportApp/res/values-es/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Canal de estado de informes de errores"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Haciendo captura de pantalla"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Canal de capturas de pantalla de un informe de error"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Listo para describir por voz el error"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Empezar ahora"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Hablar más tarde"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-et/strings.xml b/tests/BugReportApp/res/values-et/strings.xml
index af11f4f..611c3e4 100644
--- a/tests/BugReportApp/res/values-et/strings.xml
+++ b/tests/BugReportApp/res/values-et/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Veaaruande oleku kanal"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Ekraanipildi tegemine"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Veaaruande ekraanipildi kanal"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Vea kirjeldus on ettelugemiseks valmis"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Alustage kohe"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Lugege hiljem"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-eu/strings.xml b/tests/BugReportApp/res/values-eu/strings.xml
index 497c53a..40234d8 100644
--- a/tests/BugReportApp/res/values-eu/strings.xml
+++ b/tests/BugReportApp/res/values-eu/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Akatsen txostenaren egoeraren kanala"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Pantaila-argazki bat ateratzen"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Akatsen txostenen pantaila-argazkietarako kanala"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"\"Hitz egiteko prest\" eginbidearen akatsaren azalpena"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Hasi orain"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Hitz egin geroago"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-fa/strings.xml b/tests/BugReportApp/res/values-fa/strings.xml
index e6146bd..9451729 100644
--- a/tests/BugReportApp/res/values-fa/strings.xml
+++ b/tests/BugReportApp/res/values-fa/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"کانال وضعیت گزارش اشکال"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"گرفتن نماگرفت"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"کانال نماگرفت گزارش اشکال"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"آماده برای گفتن شرح اشکال"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"اکنون شروع میکنم"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"بعداً صحبت میکنم"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-fi/strings.xml b/tests/BugReportApp/res/values-fi/strings.xml
index 1c5864e..937037e 100644
--- a/tests/BugReportApp/res/values-fi/strings.xml
+++ b/tests/BugReportApp/res/values-fi/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Virheraportin tilakanava"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Kuvakaappauksen ottaminen"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kuvakaappauskanavan virheraportti"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Valmis puhumaan ‑virheen kuvaus"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Aloita nyt"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Puhu myöhemmin"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-fr-rCA/strings.xml b/tests/BugReportApp/res/values-fr-rCA/strings.xml
index 1d23f3c..54d8db3 100644
--- a/tests/BugReportApp/res/values-fr-rCA/strings.xml
+++ b/tests/BugReportApp/res/values-fr-rCA/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Chaîne d\'état du rapport de bogue"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Prise de capture d\'écran en cours…"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Canal de capture d\'écran du rapport de bogue"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Prêt à décrire le bogue"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Commencer maintenant"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Parler plus tard"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-fr/strings.xml b/tests/BugReportApp/res/values-fr/strings.xml
index 2939c46..811c273 100644
--- a/tests/BugReportApp/res/values-fr/strings.xml
+++ b/tests/BugReportApp/res/values-fr/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Chaîne d\'état du rapport de bug"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Capture d\'écran"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Canal de capture d\'écran du rapport de bug"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Prêt à énoncer la description du bug"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Commencer"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Parler plus tard"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-gl/strings.xml b/tests/BugReportApp/res/values-gl/strings.xml
index 02e173a..8431607 100644
--- a/tests/BugReportApp/res/values-gl/strings.xml
+++ b/tests/BugReportApp/res/values-gl/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Canle de estado do informe de erros"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Creando captura de pantalla"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Canle de capturas de pantalla do informe de erros"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Todo preparado para describir o erro"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Comezar agora"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Falar máis adiante"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-gu/strings.xml b/tests/BugReportApp/res/values-gu/strings.xml
index 262e2dd..b689945 100644
--- a/tests/BugReportApp/res/values-gu/strings.xml
+++ b/tests/BugReportApp/res/values-gu/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"ખામીની જાણકારી સ્ટેટસ ચૅનલ"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"કોઈ સ્ક્રીનશૉટ લઈ રહ્યાં છીએ"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"બગ રિપોર્ટ સ્ક્રીનશૉટ ચૅનલ"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"ખામીના વર્ણન વિશે બોલવા માટે તૈયાર"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"હમણાં શરૂ કરો"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"પછીથી બોલો"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-hi/strings.xml b/tests/BugReportApp/res/values-hi/strings.xml
index a5e9581..b4d5b31 100644
--- a/tests/BugReportApp/res/values-hi/strings.xml
+++ b/tests/BugReportApp/res/values-hi/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"गड़बड़ी की रिपोर्ट की स्थिति का चैनल"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"स्क्रीनशॉट लिया जा रहा है"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"गड़बड़ी की रिपोर्ट का स्क्रीनशॉट सबमिट करने का चैनल"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"क्या आप गड़बड़ी के बारे में जानकारी देने के लिए तैयार हैं"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"अभी शुरुआत करें"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"बाद में बोलें"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-hr/strings.xml b/tests/BugReportApp/res/values-hr/strings.xml
index 7900119..db4f216 100644
--- a/tests/BugReportApp/res/values-hr/strings.xml
+++ b/tests/BugReportApp/res/values-hr/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kanal statusa izvješća o programskoj pogrešci"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Izrada snimke zaslona"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanal snimke zaslona za izvješće o programskim pogreškama"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Opis programske pogreške spremnosti za govor"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Započni sada"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Govori kasnije"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-hu/strings.xml b/tests/BugReportApp/res/values-hu/strings.xml
index 71e3b06..234b39e 100644
--- a/tests/BugReportApp/res/values-hu/strings.xml
+++ b/tests/BugReportApp/res/values-hu/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Hibajelentés állapotcsatornája"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Képernyőkép készítése…"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Hibajelentés képernyőképe – csatorna"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"El lehet mondani a programhiba leírását"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Kezdés"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Később beszélek"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-hy/strings.xml b/tests/BugReportApp/res/values-hy/strings.xml
index 5222970..e99b8ff 100644
--- a/tests/BugReportApp/res/values-hy/strings.xml
+++ b/tests/BugReportApp/res/values-hy/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Վրիպակի մասին հաղորդման կարգավիճակի ալիք"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Սքրինշոթի նկարում"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Վրիպակի մասին հաղորդման սքրինշոթի ալիք"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Պատրաստ է ներկայացնելու վրիպակի նկարագրությունը"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Սկսել հիմա"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Խոսել հետո"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-in/strings.xml b/tests/BugReportApp/res/values-in/strings.xml
index 1c0c263..974ccb2 100644
--- a/tests/BugReportApp/res/values-in/strings.xml
+++ b/tests/BugReportApp/res/values-in/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Saluran status laporan bug"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Mengambil screenshot"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Saluran screenshot laporan bug"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Siap menjelaskan deskripsi bug"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Mulai sekarang"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Bicara nanti"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-is/strings.xml b/tests/BugReportApp/res/values-is/strings.xml
index 4bd3f92..5305fc9 100644
--- a/tests/BugReportApp/res/values-is/strings.xml
+++ b/tests/BugReportApp/res/values-is/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Stöðurás villutilkynninga"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Skjámynd tekin"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Skjámyndarás villutilkynninga"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Tilbúið að tala villulýsingu"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Byrja núna"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Tala seinna"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-it/strings.xml b/tests/BugReportApp/res/values-it/strings.xml
index 7747bac..5dc4862 100644
--- a/tests/BugReportApp/res/values-it/strings.xml
+++ b/tests/BugReportApp/res/values-it/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Canale stato segnalazione di bug"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Creazione di uno screenshot"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Canale per screenshot di segnalazione di bug"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"È possibile parlare della descrizione del bug"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Inizia ora"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Parla più tardi"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-iw/strings.xml b/tests/BugReportApp/res/values-iw/strings.xml
index a3c8489..e06225e 100644
--- a/tests/BugReportApp/res/values-iw/strings.xml
+++ b/tests/BugReportApp/res/values-iw/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"ערוץ סטטוס של דוח על באג"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"ביצוע של צילום מסך"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"ערוץ צילומי מסך של דוחות לאיתור באגים"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"תיאור הבאג של \'מוכן/ה לדבר\'"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"אפשר להתחיל עכשיו"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"אפשר לדבר מאוחר יותר"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-ja/strings.xml b/tests/BugReportApp/res/values-ja/strings.xml
index 8f56bb1..64eae77 100644
--- a/tests/BugReportApp/res/values-ja/strings.xml
+++ b/tests/BugReportApp/res/values-ja/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"バグレポート ステータス チャンネル"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"スクリーンショットの作成"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"バグレポート スクリーンショット チャンネル"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"バグについて説明する準備ができました"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"今すぐ始める"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"後で話す"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-ka/strings.xml b/tests/BugReportApp/res/values-ka/strings.xml
index 0c56775..70e20ee 100644
--- a/tests/BugReportApp/res/values-ka/strings.xml
+++ b/tests/BugReportApp/res/values-ka/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"სისტემის ხარვეზის ანგარიშის სტატუსის არხი"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"ეკრანის ანაბეჭდის გადაღება"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"სისტემის ხარვეზის ანგარიშის ეკრანის ანაბეჭდის არხი"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"მზადაა ხარვეზის აღწერის შესახებ სასაუბროდ"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"დაიწყეთ ახლა"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"მოგვიანებით ისაუბრეთ"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-kk/strings.xml b/tests/BugReportApp/res/values-kk/strings.xml
index 2738b9c..3eb38cf 100644
--- a/tests/BugReportApp/res/values-kk/strings.xml
+++ b/tests/BugReportApp/res/values-kk/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Қате туралы есеп арнасы"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Скриншот жасау"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Скриншот арқылы қате туралы хабарлау арнасы"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Қатенің сипаттамасын айтуға дайын"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Қазір бастау"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Кейінірек айту"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-km/strings.xml b/tests/BugReportApp/res/values-km/strings.xml
index 59b8411..6469b37 100644
--- a/tests/BugReportApp/res/values-km/strings.xml
+++ b/tests/BugReportApp/res/values-km/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"បណ្ដាញស្ថានភាពនៃរបាយការណ៍អំពីបញ្ហា"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"កំពុងថតរូបអេក្រង់"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"បណ្ដាញរូបថតអេក្រង់នៃរបាយការណ៍អំពីបញ្ហា"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"អាចនិយាយអំពីការពណ៌នាបញ្ហាបានហើយ"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"ចាប់ផ្ដើមឥឡូវនេះ"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"និយាយនៅពេលក្រោយ"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-kn/strings.xml b/tests/BugReportApp/res/values-kn/strings.xml
index d25056e..15d6f53 100644
--- a/tests/BugReportApp/res/values-kn/strings.xml
+++ b/tests/BugReportApp/res/values-kn/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"ಬಗ್ ವರದಿಮಾಡುವಿಕೆ ಸ್ಥಿತಿ ಚಾನಲ್"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ತೆಗೆದುಕೊಳ್ಳಲಾಗುತ್ತಿದೆ"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"ಬಗ್ ವರದಿಯ ಸ್ಕ್ರೀನ್ಶಾಟ್ ಚಾನಲ್"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"ಬಗ್ ವಿವರಣೆ ಕುರಿತು ಮಾತನಾಡಲು ಸಿದ್ಧವಾಗಿರುವೆ"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"ಈಗಲೇ ಪ್ರಾರಂಭಿಸಿ"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"ನಂತರ ಮಾತನಾಡಿ"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-ko/strings.xml b/tests/BugReportApp/res/values-ko/strings.xml
index 7df9c95..59ac0df 100644
--- a/tests/BugReportApp/res/values-ko/strings.xml
+++ b/tests/BugReportApp/res/values-ko/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"버그 신고 상태 채널"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"스크리샷 찍기"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"버그 신고 스크린샷 채널"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"버그를 설명할 준비가 됨"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"지금 시작"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"나중에 말하기"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-ky/strings.xml b/tests/BugReportApp/res/values-ky/strings.xml
index ba97350..e6b52b9 100644
--- a/tests/BugReportApp/res/values-ky/strings.xml
+++ b/tests/BugReportApp/res/values-ky/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Мүчүлүштүк тууралуу кабарлоо абалынын каналы"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Скриншот тартуу"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Мүчүлүштүк тууралуу кабарлардын скриншоттору менен канал"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Мүчүлүштүк тууралуу айтып берүүгө даяр"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Азыр баштоо"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Кийинчерээк сүйлөө"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-lo/strings.xml b/tests/BugReportApp/res/values-lo/strings.xml
index ec3c1cd..bc3c2c5 100644
--- a/tests/BugReportApp/res/values-lo/strings.xml
+++ b/tests/BugReportApp/res/values-lo/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"ຊ່ອງທາງຂອງສະຖານະລາຍງານບັນຫາ"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"ຖ່າຍຮູບໜ້າຈໍ"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"ຊ່ອງຮູບໜ້າຈໍລາຍງານຂໍ້ຜິດພາດ"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"ພ້ອມລົມກ່ຽວກັບຄຳອະທິບາຍຂໍ້ຜິດພາດ"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"ເລີ່ມຕົ້ນເລີຍ"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"ລົມກັນໃນພາຍຫຼັງ"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-lt/strings.xml b/tests/BugReportApp/res/values-lt/strings.xml
index 9b02c39..2b5bc3c 100644
--- a/tests/BugReportApp/res/values-lt/strings.xml
+++ b/tests/BugReportApp/res/values-lt/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Pranešimo apie riktą kanalas"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Ekrano kopijos kūrimas"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Pranešimo apie riktą ekrano kopijos kanalas"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Paruošta kalbėti – rikto aprašas"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Pradėti dabar"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Kalbėti vėliau"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-lv/strings.xml b/tests/BugReportApp/res/values-lv/strings.xml
index 58a13f6..0080d4b 100644
--- a/tests/BugReportApp/res/values-lv/strings.xml
+++ b/tests/BugReportApp/res/values-lv/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kļūdas pārskata statusa kanāls"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Notiek ekrānuzņēmuma izveide"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanāls kļūdu pārskatu ekrānuzņēmumiem"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Gatavs runāt — kļūdas apraksts"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Sākt tūlīt"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Runāt vēlāk"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-mk/strings.xml b/tests/BugReportApp/res/values-mk/strings.xml
index 6ebd335..0986ac8 100644
--- a/tests/BugReportApp/res/values-mk/strings.xml
+++ b/tests/BugReportApp/res/values-mk/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Канал за статус на извештајот за грешка"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Се снима слика од екранот"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Канал за сликa од екранот за извештај за грешки"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Подгответе се да ја опишете грешката вербално"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Започнете сега"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Зборувајте подоцна"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-ml/strings.xml b/tests/BugReportApp/res/values-ml/strings.xml
index 1f5b413..f974617 100644
--- a/tests/BugReportApp/res/values-ml/strings.xml
+++ b/tests/BugReportApp/res/values-ml/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"ബഗ് റിപ്പോർട്ട് സ്റ്റാറ്റസ് ചാനൽ"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"സ്ക്രീൻഷോട്ട് എടുക്കുന്നു"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"ബഗ് റിപ്പോർട്ടിനുള്ള സ്ക്രീൻഷോട്ട് ചാനൽ"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"ബഗ്ഗിന്റെ വിശദാംശങ്ങളെക്കുറിച്ച് സംസാരിക്കാൻ തയ്യാറാണ്"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"ഇപ്പോൾ ആരംഭിക്കുക"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"പിന്നീട് സംസാരിക്കുക"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-mn/strings.xml b/tests/BugReportApp/res/values-mn/strings.xml
index 1c474cd..d414853 100644
--- a/tests/BugReportApp/res/values-mn/strings.xml
+++ b/tests/BugReportApp/res/values-mn/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Алдааны мэдээний төлөвийн суваг"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Дэлгэцийн агшин авч байна"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Алдааны мэдээний дэлгэцийн агшны суваг"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Уншихад бэлэн алдааны тайлбар"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Одоо эхлүүлэх"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Дараа ярих"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-mr/strings.xml b/tests/BugReportApp/res/values-mr/strings.xml
index a27c758..bd78878 100644
--- a/tests/BugReportApp/res/values-mr/strings.xml
+++ b/tests/BugReportApp/res/values-mr/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"बग रिपोर्ट स्थिती चॅनल"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"स्क्रीनशॉट घेत आहे"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"बग अहवाल स्क्रीनशॉट चॅनल"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"बगचे वर्णन बोलून दाखवण्यासाठी तयार आहे"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"आता सुरुवात करा"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"नंतर बोला"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-ms/strings.xml b/tests/BugReportApp/res/values-ms/strings.xml
index aa21518..9113e62 100644
--- a/tests/BugReportApp/res/values-ms/strings.xml
+++ b/tests/BugReportApp/res/values-ms/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Saluran status laporan pepijat"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Mengambil tangkapan skrin"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Saluran tangkapan skrin laporan pepijat"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Sedia untuk bercakap perihalan pepijat"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Mulakan sekarang"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Bercakap nanti"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-my/strings.xml b/tests/BugReportApp/res/values-my/strings.xml
index 61febae..20fb811 100644
--- a/tests/BugReportApp/res/values-my/strings.xml
+++ b/tests/BugReportApp/res/values-my/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"ချွတ်ယွင်းမှု အစီရင်ခံချက် အခြေအနေ ချန်နယ်"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"ဖန်သားပြင်ဓာတ်ပုံ ရိုက်နေသည်"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"ချွတ်ယွင်းချက်အစီရင်ခံစာ ဖန်သားပြင်ဓာတ်ပုံချန်နယ်"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"ချွတ်ယွင်းမှုအကြောင်းအရာကို ပြောရန် အသင့်ဖြစ်ပါပြီ"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"ယခုစတင်ပါ"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"နောက်မှပြောပါ"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-nb/strings.xml b/tests/BugReportApp/res/values-nb/strings.xml
index a0d5f91..a66ef8be 100644
--- a/tests/BugReportApp/res/values-nb/strings.xml
+++ b/tests/BugReportApp/res/values-nb/strings.xml
@@ -46,10 +46,13 @@
<string name="toast_status_failed" msgid="6365384202315043395">"Feilrapporten mislyktes"</string>
<string name="toast_status_screencap_failed" msgid="2187083897594745149">"Skjermdumpen mislyktes"</string>
<string name="toast_status_dump_state_failed" msgid="3496460783060512078">"Dumptilstand mislyktes"</string>
- <string name="toast_screenshot_finished" msgid="539313383958984557">"Lagrede skjermbilder"</string>
+ <string name="toast_screenshot_finished" msgid="539313383958984557">"Lagrede skjermdumper"</string>
<string name="notification_bugreport_in_progress" msgid="8486454116357963238">"En feilrapport pågår"</string>
<string name="notification_bugreport_finished_title" msgid="1188447311929693472">"En feilrapport er samlet inn"</string>
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Statuskanal for feilrapport"</string>
- <string name="notification_screenshot_message" msgid="4334354123333893911">"Ta skjermbilder"</string>
- <string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanal for skjermbilde av feilrapport"</string>
+ <string name="notification_screenshot_message" msgid="4334354123333893911">"Ta skjermdumper"</string>
+ <string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanal for skjermdump av feilrapport"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Beskrivelse av feil knyttet til klarhet for å snakke"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Begynn nå"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Snakk senere"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-ne/strings.xml b/tests/BugReportApp/res/values-ne/strings.xml
index 9322493..b6e0193 100644
--- a/tests/BugReportApp/res/values-ne/strings.xml
+++ b/tests/BugReportApp/res/values-ne/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"बग रिपोर्टको स्थिति देखाउने च्यानल"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"स्क्रिनसट लिइँदै छ"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"त्रुटिका सम्बन्धमा स्क्रिनसटसहित रिपोर्ट गर्ने च्यानल"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"बोल्न तयार हुँदा आउने त्रुटिसम्बन्धी विवरण"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"अहिले नै सुरु गर्नुहोस्"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"पछि बोल्नुहोस्"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-nl/strings.xml b/tests/BugReportApp/res/values-nl/strings.xml
index eddf008..4d9e810 100644
--- a/tests/BugReportApp/res/values-nl/strings.xml
+++ b/tests/BugReportApp/res/values-nl/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kanaal voor bugrapportstatus"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Een screenshot maken"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanaal voor screenshots voor bugrapport"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Klaar om bugbeschrijving uit te spreken"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Nu beginnen"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Later spreken"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-or/strings.xml b/tests/BugReportApp/res/values-or/strings.xml
index 9cd6fdf..a7ae577 100644
--- a/tests/BugReportApp/res/values-or/strings.xml
+++ b/tests/BugReportApp/res/values-or/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"ବଗ୍ ରିପୋର୍ଟ ସ୍ଥିତି ଚ୍ୟାନେଲ୍"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"ଏକ ସ୍କ୍ରିନସଟ ନିଆଯାଉଛି"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"ବଗ ରିପୋର୍ଟ ସ୍କ୍ରିନସଟ ଚେନେଲ"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"ବଗର ବର୍ଣ୍ଣନା ବିଷୟରେ କହିବାକୁ ପ୍ରସ୍ତୁତ"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"ବର୍ତ୍ତମାନ ଆରମ୍ଭ କରନ୍ତୁ"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"ପରେ କୁହନ୍ତୁ"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-pa/strings.xml b/tests/BugReportApp/res/values-pa/strings.xml
index 414f22b..76a212e 100644
--- a/tests/BugReportApp/res/values-pa/strings.xml
+++ b/tests/BugReportApp/res/values-pa/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"ਬੱਗ ਰਿਪੋਰਟ ਦੀ ਸਥਿਤੀ ਸੰਬੰਧੀ ਚੈਨਲ"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਲਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"ਬੱਗ ਰਿਪੋਰਟ ਸਕ੍ਰੀਨਸ਼ਾਟ ਚੈਨਲ"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"ਬੱਗ ਦੇ ਵਰਣਨ ਸੰਬੰਧੀ ਬੋਲਣ ਲਈ ਤਿਆਰ ਰਹੋ"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"ਹੁਣੇ ਸ਼ੁਰੂਆਤ ਕਰੋ"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"ਬਾਅਦ ਵਿੱਚ ਬੋਲੋ"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-pl/strings.xml b/tests/BugReportApp/res/values-pl/strings.xml
index c82eff9..c3ccf25 100644
--- a/tests/BugReportApp/res/values-pl/strings.xml
+++ b/tests/BugReportApp/res/values-pl/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kanał stanu zgłaszania błędu"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Tworzenie zrzutu ekranu"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanał Zrzut ekranu z raportu o błędzie"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Gotowość do mówienia – opis błędu"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Zacznij teraz"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Powiedz później"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-pt-rPT/strings.xml b/tests/BugReportApp/res/values-pt-rPT/strings.xml
index 30c9c08..9a46dde 100644
--- a/tests/BugReportApp/res/values-pt-rPT/strings.xml
+++ b/tests/BugReportApp/res/values-pt-rPT/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Canal do estado do relatório de erro"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"A fazer uma captura de ecrã"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Canal de captura de ecrã do relatório de erro"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"A postos para dizer a descrição do erro"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Comece já"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Fale mais tarde"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-pt/strings.xml b/tests/BugReportApp/res/values-pt/strings.xml
index bd3f490..8e0d08f 100644
--- a/tests/BugReportApp/res/values-pt/strings.xml
+++ b/tests/BugReportApp/res/values-pt/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Canal de status do relatório do bug"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Como fazer a captura de tela"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Canal de captura de tela do relatório do bug"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Tudo pronto para descrever o bug"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Começar agora"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Falar mais tarde"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-ro/strings.xml b/tests/BugReportApp/res/values-ro/strings.xml
index 8fe6b2d..273f627 100644
--- a/tests/BugReportApp/res/values-ro/strings.xml
+++ b/tests/BugReportApp/res/values-ro/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Canalul de stare a raportului de eroare"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Realizarea unei capturi de ecran"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Canal pentru capturi de ecran cu rapoarte de eroare"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Gata pentru a rosti descrierea erorii"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Începe acum"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Vorbește mai târziu"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-ru/strings.xml b/tests/BugReportApp/res/values-ru/strings.xml
index f2ed8cd..6bef7f3 100644
--- a/tests/BugReportApp/res/values-ru/strings.xml
+++ b/tests/BugReportApp/res/values-ru/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Канал статуса отчета об ошибке"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Делаем скриншот"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Канал со скриншотами отчетов об ошибках"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Опишите ошибку вслух"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Начать"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Позже"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-si/strings.xml b/tests/BugReportApp/res/values-si/strings.xml
index ad8bd75..d1a25b6 100644
--- a/tests/BugReportApp/res/values-si/strings.xml
+++ b/tests/BugReportApp/res/values-si/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"දෝෂ වාර්තා තත්ත්ව නාලිකාව"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"තිර රුවක් ගැනීම"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"දෝෂ වාර්තා තිර රූ නාලිකාව"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"කථා කිරීමට සූදානම් දෝෂ විස්තරය"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"දැන් ආරම්භ කරන්න"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"පසුව කථා කරන්න"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-sk/strings.xml b/tests/BugReportApp/res/values-sk/strings.xml
index 9113a79..aa23468 100644
--- a/tests/BugReportApp/res/values-sk/strings.xml
+++ b/tests/BugReportApp/res/values-sk/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kanál stavu hlásenia chyby"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Vytvorenie snímky obrazovky"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanál pre snímku obrazovky priloženú k hláseniu chyby"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Hovorenie je pripravené – opis chyby"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Začať"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Hovoriť neskôr"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-sl/strings.xml b/tests/BugReportApp/res/values-sl/strings.xml
index 4a5e4f1..a2fe019 100644
--- a/tests/BugReportApp/res/values-sl/strings.xml
+++ b/tests/BugReportApp/res/values-sl/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kanala stanja poročila o napakah"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Ustvarjanje posnetka zaslona"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanal za posnetek zaslona poročila o napakah"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Pripravljenost na govor – opis napake"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Začni zdaj"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Govori pozneje"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-sq/strings.xml b/tests/BugReportApp/res/values-sq/strings.xml
index 78d6fcf..ecc688f 100644
--- a/tests/BugReportApp/res/values-sq/strings.xml
+++ b/tests/BugReportApp/res/values-sq/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kanali i statusit të raportit të defekteve në kod"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Po nxirret një pamje e ekranit"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kanal me pamjen e ekranit të një raporti defekti në kod"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Gati për të ligjëruar përshkrimin e defektit në kod"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Fillo tani"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Fol më vonë"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-sr/strings.xml b/tests/BugReportApp/res/values-sr/strings.xml
index 9775ff3..0e87a3a 100644
--- a/tests/BugReportApp/res/values-sr/strings.xml
+++ b/tests/BugReportApp/res/values-sr/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Канал статуса извештаја о грешци"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Прави се снимак екрана"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Канал за снимке екрана извештаја о грешци"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Опис грешке „Спреман за говор“"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Започните"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Говорите касније"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-sv/strings.xml b/tests/BugReportApp/res/values-sv/strings.xml
index cfacc1b..6829cdb 100644
--- a/tests/BugReportApp/res/values-sv/strings.xml
+++ b/tests/BugReportApp/res/values-sv/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kanal för felrapportstatus"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Tar en skärmbild"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Skärmbildskanal för felrapportering"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Redo att tala in felbeskrivning"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Börja nu"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Tala in senare"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-sw/strings.xml b/tests/BugReportApp/res/values-sw/strings.xml
index d323ddf..34caabf 100644
--- a/tests/BugReportApp/res/values-sw/strings.xml
+++ b/tests/BugReportApp/res/values-sw/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kituo cha hali ya ripoti ya hitilafu"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Kupiga picha ya skrini"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kituo cha picha za skrini zilizo na ripoti za hitilafu"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Tayari kutoa maelezo ya hitilafu"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Anza sasa"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Zungumza baadaye"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-ta/strings.xml b/tests/BugReportApp/res/values-ta/strings.xml
index 96fea55..3648407 100644
--- a/tests/BugReportApp/res/values-ta/strings.xml
+++ b/tests/BugReportApp/res/values-ta/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"பிழை அறிக்கை நிலை தொடர்பான சேனல்"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"ஸ்கிரீன்ஷாட் எடுக்கிறது"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"பிழை அறிக்கைக்கான ஸ்கிரீன்ஷாட் சேனல்"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"பிழை விளக்கம் குறித்துப் பேசுவதற்குத் தயாரா?"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"இப்போது தொடங்கு"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"பிறகு பேசுகிறேன்"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-te/strings.xml b/tests/BugReportApp/res/values-te/strings.xml
index bf6ad2a..4a6f9b9 100644
--- a/tests/BugReportApp/res/values-te/strings.xml
+++ b/tests/BugReportApp/res/values-te/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"బగ్ రిపోర్ట్ స్టేటస్ ఛానెల్"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"స్క్రీన్షాట్ తీయడం"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"బగ్ రిపోర్ట్ స్క్రీన్షాట్ ఛానెల్"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"బగ్ వివరణను గురించి మాట్లాడటానికి సిద్ధంగా ఉంది"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"ఇప్పుడే ప్రారంభించండి"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"తర్వాత మాట్లాడండి"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-th/strings.xml b/tests/BugReportApp/res/values-th/strings.xml
index 12d0280..6b6947c 100644
--- a/tests/BugReportApp/res/values-th/strings.xml
+++ b/tests/BugReportApp/res/values-th/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"ช่องทางของสถานะรายงานข้อบกพร่อง"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"กำลังจับภาพหน้าจอ"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"ช่องทางส่งภาพหน้าจอสำหรับรายงานข้อบกพร่อง"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"พร้อมที่จะอธิบายเกี่ยวกับข้อบกพร่อง"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"เริ่มเลย"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"อธิบายภายหลัง"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-tl/strings.xml b/tests/BugReportApp/res/values-tl/strings.xml
index e949383..6722fd3 100644
--- a/tests/BugReportApp/res/values-tl/strings.xml
+++ b/tests/BugReportApp/res/values-tl/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Channel ng status ng ulat ng bug"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Pagkuha ng screenshot"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Channel ng screenshot ng ulat ng bug"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Paglalarawan ng bug sa Handa nang magsalita"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Magsimula ngayon"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Magsalita mamaya"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-tr/strings.xml b/tests/BugReportApp/res/values-tr/strings.xml
index 3a679b9..b2f4245 100644
--- a/tests/BugReportApp/res/values-tr/strings.xml
+++ b/tests/BugReportApp/res/values-tr/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Hata raporu durum kanalı"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Ekran görüntüsü alma"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Hata raporu ekran görüntüsü kanalı"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Hata açıklamasını söylemeye hazır olun"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Şimdi başlayın"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Daha sonra konuşun"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-uk/strings.xml b/tests/BugReportApp/res/values-uk/strings.xml
index a2156a5..2f38602 100644
--- a/tests/BugReportApp/res/values-uk/strings.xml
+++ b/tests/BugReportApp/res/values-uk/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Канал стану звіту про помилку"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Створення знімка екрана"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Канал для знімків екрана зі звітом про помилку"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Приготуйтеся розказати про помилку"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Почати зараз"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Розказати пізніше"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-ur/strings.xml b/tests/BugReportApp/res/values-ur/strings.xml
index 1de546f..0defd1d 100644
--- a/tests/BugReportApp/res/values-ur/strings.xml
+++ b/tests/BugReportApp/res/values-ur/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"بگ رپورٹ کی صورتحال کا چینل"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"اسکرین شاٹ لینا"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"اسکرین شاٹس کے ساتھ بگ کی اطلاع دینے والا چینل"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"کیا آپ بگ کی تفصیل کے بارے میں بولنے کے لیے تیار ہیں"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"ابھی شروع کریں"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"بعد میں بولیں"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-uz/strings.xml b/tests/BugReportApp/res/values-uz/strings.xml
index 0c56def..849cd42 100644
--- a/tests/BugReportApp/res/values-uz/strings.xml
+++ b/tests/BugReportApp/res/values-uz/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Xatoliklar hisoboti holati kanali"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Skrinshot olish"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Xatolar haqida hisobot skrinshotlari kanali"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Xatoni tavsifi haqida gapirishga tayyorman"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Boshlash"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Keyinroq gapirish"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-vi/strings.xml b/tests/BugReportApp/res/values-vi/strings.xml
index 47e1c74..a777f2e 100644
--- a/tests/BugReportApp/res/values-vi/strings.xml
+++ b/tests/BugReportApp/res/values-vi/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kênh trạng thái báo cáo lỗi"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Chụp ảnh màn hình"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Kênh ảnh chụp màn hình báo cáo lỗi"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Sẵn sàng mô tả lỗi bằng giọng nói"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Bắt đầu ngay"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Nói sau"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-zh-rCN/strings.xml b/tests/BugReportApp/res/values-zh-rCN/strings.xml
index b607ce4..845c551 100644
--- a/tests/BugReportApp/res/values-zh-rCN/strings.xml
+++ b/tests/BugReportApp/res/values-zh-rCN/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"错误报告状态渠道"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"正在截取屏幕截图"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"错误报告屏幕截图频道"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"准备录制关于错误的语音描述"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"立即开始"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"稍后提供语音描述"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-zh-rHK/strings.xml b/tests/BugReportApp/res/values-zh-rHK/strings.xml
index f20d72c..ff1b0f1 100644
--- a/tests/BugReportApp/res/values-zh-rHK/strings.xml
+++ b/tests/BugReportApp/res/values-zh-rHK/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"錯誤報告狀態頻道"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"正在擷取螢幕截圖"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"提供錯誤報告螢幕截圖的渠道"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"準備好說出錯誤描述"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"立即開始"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"稍後再說話"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-zh-rTW/strings.xml b/tests/BugReportApp/res/values-zh-rTW/strings.xml
index e3282eb..7448c31 100644
--- a/tests/BugReportApp/res/values-zh-rTW/strings.xml
+++ b/tests/BugReportApp/res/values-zh-rTW/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"錯誤報告狀態頻道"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"正在拍攝螢幕截圖"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"錯誤報告螢幕截圖頻道"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"我準備好說明錯誤了"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"立即開始"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"之後再說明"</string>
</resources>
diff --git a/tests/BugReportApp/res/values-zu/strings.xml b/tests/BugReportApp/res/values-zu/strings.xml
index bb750e1..f1a5266 100644
--- a/tests/BugReportApp/res/values-zu/strings.xml
+++ b/tests/BugReportApp/res/values-zu/strings.xml
@@ -52,4 +52,7 @@
<string name="notification_bugreport_channel_name" msgid="776902295433824255">"Isiteshi sesimo sombiko wesiphazamisi"</string>
<string name="notification_screenshot_message" msgid="4334354123333893911">"Ukuthatha isithombe-skrini"</string>
<string name="notification_screenshot_channel_name" msgid="147055389545365419">"Isiteshi sesithombe-skrini sombiko wesiphazamisi"</string>
+ <string name="choice_dialog_title" msgid="2105355715733829907">"Incazelo yesiphazamisi sokulungela ukukhuluma"</string>
+ <string name="choice_dialog_begin_now" msgid="7326462054665389346">"Qala manje"</string>
+ <string name="choice_dialog_speak_later" msgid="1297471567273228307">"Khuluma kamuva"</string>
</resources>
diff --git a/tests/BugReportApp/res/values/strings.xml b/tests/BugReportApp/res/values/strings.xml
index 513e2a8..614df8c 100644
--- a/tests/BugReportApp/res/values/strings.xml
+++ b/tests/BugReportApp/res/values/strings.xml
@@ -59,4 +59,9 @@
<string name="notification_bugreport_channel_name">Bug report status channel</string>
<string name="notification_screenshot_message">Taking a screenshot</string>
<string name="notification_screenshot_channel_name">Bug report screenshot channel</string>
+
+ <!-- Choice dialog strings -->
+ <string name="choice_dialog_title">Ready to speak bug description</string>
+ <string name="choice_dialog_begin_now">Begin now</string>
+ <string name="choice_dialog_speak_later">Speak later</string>
</resources>
diff --git a/tests/BugReportApp/res/values/styles.xml b/tests/BugReportApp/res/values/styles.xml
index 137caab..7313126 100644
--- a/tests/BugReportApp/res/values/styles.xml
+++ b/tests/BugReportApp/res/values/styles.xml
@@ -21,6 +21,10 @@
<item name="android:clickable">true</item>
</style>
+ <style name="transparent_button" parent="standard_button">
+ <item name="android:background">@null</item>
+ </style>
+
<style name="Widget.BugReportUi.InfoActionButton" parent="android:Widget.DeviceDefault.Button">
<item name="android:textSize">@dimen/bug_report_button_text_size</item>
</style>
@@ -33,4 +37,9 @@
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">@dimen/bug_report_small_text_size</item>
</style>
+
+ <style
+ name="DialogWithNullBackground" parent="@android:style/Theme.DeviceDefault.Dialog">
+ <item name="android:windowBackground">@null</item>
+ </style>
</resources>
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/BugInfoAdapter.java b/tests/BugReportApp/src/com/android/car/bugreport/BugInfoAdapter.java
index 8b33d96..90df590 100644
--- a/tests/BugReportApp/src/com/android/car/bugreport/BugInfoAdapter.java
+++ b/tests/BugReportApp/src/com/android/car/bugreport/BugInfoAdapter.java
@@ -15,7 +15,6 @@
*/
package com.android.car.bugreport;
-import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -156,7 +155,7 @@
private void showOrHideUploadButton(MetaBugReport bugReport, BugInfoViewHolder holder) {
// Enable the upload button only for userdebug/eng builds.
- if (pendingUserAction(bugReport) && Build.IS_DEBUGGABLE) {
+ if (pendingUserAction(bugReport) && Config.isDebuggable()) {
holder.mUploadButton.setText(R.string.bugreport_upload_gcs_button_text);
holder.mUploadButton.setEnabled(true);
holder.mUploadButton.setVisibility(View.VISIBLE);
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/BugReportActivity.java b/tests/BugReportApp/src/com/android/car/bugreport/BugReportActivity.java
index 50a0ec1..06a0303 100644
--- a/tests/BugReportApp/src/com/android/car/bugreport/BugReportActivity.java
+++ b/tests/BugReportApp/src/com/android/car/bugreport/BugReportActivity.java
@@ -110,6 +110,8 @@
/** Look up string length, e.g. [ABCDEF]. */
static final int LOOKUP_STRING_LENGTH = 6;
+ private static boolean sIsOnActivityStartedWithBugReportServiceBoundCalled = false;
+
private TextView mInProgressTitleText;
private ProgressBar mProgressBar;
private TextView mProgressText;
@@ -128,7 +130,6 @@
/** Audio recording using MIC is running (permission given). */
private boolean mAudioRecordingIsRunning;
private boolean mIsNewBugReport;
- private boolean mIsOnActivityStartedWithBugReportServiceBoundCalled;
private boolean mIsSubmitButtonClicked;
private BugReportService mService;
private MediaRecorder mRecorder;
@@ -179,6 +180,10 @@
return intent;
}
+ static boolean isOnActivityStarted() {
+ return sIsOnActivityStartedWithBugReportServiceBoundCalled;
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
Preconditions.checkState(Config.isBugReportEnabled(), "BugReport is disabled.");
@@ -214,7 +219,7 @@
mAudioRecordingStarted = false;
mAudioRecordingIsRunning = false;
mIsSubmitButtonClicked = false;
- mIsOnActivityStartedWithBugReportServiceBoundCalled = false;
+ sIsOnActivityStartedWithBugReportServiceBoundCalled = false;
mMetaBugReport = null;
mTempAudioFile = null;
}
@@ -268,7 +273,7 @@
// Connect to the services here, because they are used only when showing the dialog.
// We need to minimize system state change when performing TYPE_AUDIO_LATER bug report.
- mConfig = Config.create();
+ mConfig = Config.create(getApplicationContext());
mCar = Car.createCar(this, /* handler= */ null,
Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, this::onCarLifecycleChanged);
@@ -355,10 +360,10 @@
* <p>This method expected to be called when the activity is started and bound to the service.
*/
private void onActivityStartedWithBugReportServiceBound() {
- if (mIsOnActivityStartedWithBugReportServiceBoundCalled) {
+ if (sIsOnActivityStartedWithBugReportServiceBoundCalled) {
return;
}
- mIsOnActivityStartedWithBugReportServiceBoundCalled = true;
+ sIsOnActivityStartedWithBugReportServiceBoundCalled = true;
if (mService.isCollectingBugReport()) {
Log.i(TAG, "Bug report is already being collected.");
@@ -632,7 +637,9 @@
}
private CountDownTimer createCountDownTimer() {
- return new CountDownTimer(VOICE_MESSAGE_MAX_DURATION_MILLIS, 1000) {
+ return new CountDownTimer(VOICE_MESSAGE_MAX_DURATION_MILLIS,
+ /* countDownInterval= */ 1000) {
+ @Override
public void onTick(long millisUntilFinished) {
long secondsRemaining = millisUntilFinished / 1000;
String secondText = secondsRemaining > 1 ? "seconds" : "second";
@@ -640,6 +647,7 @@
secondText));
}
+ @Override
public void onFinish() {
Log.i(TAG, "Timed out while recording voice message.");
stopAudioRecording();
@@ -673,7 +681,7 @@
}
private static String getCurrentUserName(Context context) {
- UserManager um = UserManager.get(context);
+ UserManager um = context.getSystemService(UserManager.class);
return um.getUserName();
}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/BugReportInfoActivity.java b/tests/BugReportApp/src/com/android/car/bugreport/BugReportInfoActivity.java
index fbe525c..3a66ba2 100644
--- a/tests/BugReportApp/src/com/android/car/bugreport/BugReportInfoActivity.java
+++ b/tests/BugReportApp/src/com/android/car/bugreport/BugReportInfoActivity.java
@@ -20,6 +20,7 @@
import android.app.Activity;
import android.app.NotificationManager;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.database.ContentObserver;
@@ -93,7 +94,7 @@
mRecyclerView.addItemDecoration(new DividerItemDecoration(mRecyclerView.getContext(),
DividerItemDecoration.VERTICAL));
- mConfig = Config.create();
+ mConfig = Config.create(getApplicationContext());
mBugInfoAdapter = new BugInfoAdapter(this::onBugReportItemClicked, mConfig);
mRecyclerView.setAdapter(mBugInfoAdapter);
@@ -113,9 +114,12 @@
protected void onStart() {
super.onStart();
new BugReportsLoaderAsyncTask(this).execute();
- // As BugStorageProvider is running under user0, we register using USER_ALL.
- getContentResolver().registerContentObserver(BugStorageProvider.BUGREPORT_CONTENT_URI, true,
- mBugStorageObserver, UserHandle.USER_ALL);
+ // As BugStorageProvider is running under user0, we register using UserHandle.ALL.
+ Context context = getApplicationContext().createContextAsUser(UserHandle.ALL, /* flags= */
+ 0);
+ context.getContentResolver().registerContentObserver(
+ BugStorageProvider.BUGREPORT_CONTENT_URI, true,
+ mBugStorageObserver);
}
@Override
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/BugReportService.java b/tests/BugReportApp/src/com/android/car/bugreport/BugReportService.java
index 72ff49a..a55a88b 100644
--- a/tests/BugReportApp/src/com/android/car/bugreport/BugReportService.java
+++ b/tests/BugReportApp/src/com/android/car/bugreport/BugReportService.java
@@ -23,8 +23,6 @@
import static com.android.car.bugreport.PackageUtils.getPackageVersion;
-import android.annotation.FloatRange;
-import android.annotation.StringRes;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -42,7 +40,6 @@
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Binder;
-import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -51,6 +48,9 @@
import android.view.Display;
import android.widget.Toast;
+import androidx.annotation.FloatRange;
+import androidx.annotation.StringRes;
+
import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
import com.google.common.util.concurrent.AtomicDouble;
@@ -208,6 +208,12 @@
}
}
+ static Intent buildStartBugReportIntent(Context context) {
+ Intent intent = new Intent(context, BugReportService.class);
+ intent.setAction(ACTION_START_AUDIO_LATER);
+ return intent;
+ }
+
@Override
public void onCreate() {
Preconditions.checkState(Config.isBugReportEnabled(), "BugReport is disabled.");
@@ -229,7 +235,7 @@
mSingleThreadExecutor = Executors.newSingleThreadScheduledExecutor();
mHandler = new BugReportHandler();
mHandlerStartedToast = new Handler();
- mConfig = Config.create();
+ mConfig = Config.create(getApplicationContext());
}
@Override
@@ -380,7 +386,7 @@
// BugReportService doesn't automatically reconnect to it.
connectToCarServiceSync();
- if (Build.IS_USERDEBUG || Build.IS_ENG) {
+ if (Config.isDebuggable()) {
mSingleThreadExecutor.schedule(
this::grabBtSnoopLog, ACTIVITY_FINISH_DELAY_MILLIS, TimeUnit.MILLISECONDS);
}
@@ -439,7 +445,7 @@
}
mCallback = new CarBugreportManager.CarBugreportManagerCallback() {
@Override
- public void onError(@CarBugreportErrorCode int errorCode) {
+ public void onError(int errorCode) {
Log.e(TAG, "CarBugreportManager failed: " + errorCode);
disconnectFromCarService();
handleBugReportManagerError(errorCode);
@@ -469,8 +475,7 @@
mBugreportManager.requestBugreport(outFd, extraOutFd, mCallback);
}
- private void handleBugReportManagerError(
- @CarBugreportManager.CarBugreportManagerCallback.CarBugreportErrorCode int errorCode) {
+ private void handleBugReportManagerError(int errorCode) {
if (mMetaBugReport == null) {
Log.w(TAG, "No bugreport is running");
mIsCollectingBugReport.set(false);
@@ -493,8 +498,7 @@
mIsCollectingBugReport.set(false);
}
- private static String getBugReportFailureStatusMessage(
- @CarBugreportManager.CarBugreportManagerCallback.CarBugreportErrorCode int errorCode) {
+ private static String getBugReportFailureStatusMessage(int errorCode) {
switch (errorCode) {
case CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED:
case CAR_BUGREPORT_DUMPSTATE_FAILED:
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/BugStorageProvider.java b/tests/BugReportApp/src/com/android/car/bugreport/BugStorageProvider.java
index 8b5e46e..cbf8bd9 100644
--- a/tests/BugReportApp/src/com/android/car/bugreport/BugStorageProvider.java
+++ b/tests/BugReportApp/src/com/android/car/bugreport/BugStorageProvider.java
@@ -15,9 +15,6 @@
*/
package com.android.car.bugreport;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringDef;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
@@ -30,6 +27,10 @@
import android.os.ParcelFileDescriptor;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringDef;
+
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
@@ -237,7 +238,7 @@
return false;
}
mDatabaseHelper = new DatabaseHelper(getContext());
- mConfig = Config.create();
+ mConfig = Config.create(getContext());
return true;
}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/BugStorageUtils.java b/tests/BugReportApp/src/com/android/car/bugreport/BugStorageUtils.java
index 07067ef..6cd3745 100644
--- a/tests/BugReportApp/src/com/android/car/bugreport/BugStorageUtils.java
+++ b/tests/BugReportApp/src/com/android/car/bugreport/BugStorageUtils.java
@@ -27,8 +27,6 @@
import static com.android.car.bugreport.BugStorageProvider.COLUMN_TYPE;
import static com.android.car.bugreport.BugStorageProvider.COLUMN_USERNAME;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -36,6 +34,9 @@
import android.net.Uri;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.google.api.client.auth.oauth2.TokenResponseException;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/ChoiceDialogActivity.java b/tests/BugReportApp/src/com/android/car/bugreport/ChoiceDialogActivity.java
new file mode 100644
index 0000000..6e6f808
--- /dev/null
+++ b/tests/BugReportApp/src/com/android/car/bugreport/ChoiceDialogActivity.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.bugreport;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.View;
+import android.view.Window;
+import android.widget.TextView;
+
+import com.google.common.base.Preconditions;
+
+import java.util.Locale;
+
+public class ChoiceDialogActivity extends Activity {
+
+ private static final String TAG = ChoiceDialogActivity.class.getSimpleName();
+
+ /** This is used by other apps. */
+ private static final String ACTION_START_BUG_REPORT =
+ "com.android.car.bugreport.action.START_BUG_REPORT";
+
+ private static final int DIALOG_MAX_DURATION_MILLIS = 5 * 1000;
+
+ private boolean mBound;
+ private TextView mTitleText;
+ private BugReportService mService;
+ private CountDownTimer mCountDownTimer;
+
+ /** Defines callbacks for service binding, passed to bindService() */
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ BugReportService.ServiceBinder binder = (BugReportService.ServiceBinder) service;
+ mService = binder.getService();
+ mBound = true;
+ startActivityWithService();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ // called when service connection breaks unexpectedly.
+ mBound = false;
+ }
+ };
+
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Preconditions.checkState(Config.isBugReportEnabled(), "BugReport is disabled.");
+
+ super.onCreate(savedInstanceState);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ // Bind to BugReportService.
+ Intent intent = new Intent(this, BugReportService.class);
+ bindService(intent, mConnection, BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ if (BugReportActivity.isOnActivityStarted()) {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+
+ if (mBound) {
+ startActivityWithService();
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mBound) {
+ unbindService(mConnection);
+ mBound = false;
+ }
+ super.onDestroy();
+ }
+
+ private void startActivityWithService() {
+ if (mService.isCollectingBugReport()) {
+ startNextAction(/* isAudioFirst= */ true);
+ return;
+ }
+
+ if (ACTION_START_BUG_REPORT.equals(getIntent().getAction())) {
+ prepareUi();
+ mCountDownTimer = createCountDownTimer();
+ mCountDownTimer.start();
+ } else {
+ Log.w(TAG, "Unsupported intent action provided: " + getIntent().getAction());
+ finish();
+ }
+ }
+
+ private void prepareUi() {
+ setContentView(R.layout.choice_dialog_activity);
+
+ mTitleText = findViewById(R.id.choice_dialog_title);
+
+ findViewById(R.id.button_speak_later).setOnClickListener(this::onClickSpeakLaterButton);
+ findViewById(R.id.button_begin_now).setOnClickListener(this::onClickBeginNowButton);
+ }
+
+ private String getTimerTextMessage(long millisUntilFinished) {
+ long secondsRemaining = millisUntilFinished / 1000;
+ String secondText = secondsRemaining > 1 ? "secs" : "sec";
+ return String.format(Locale.US, "%s in %d %s",
+ getString(R.string.choice_dialog_title), secondsRemaining, secondText);
+ }
+
+ private CountDownTimer createCountDownTimer() {
+ return new CountDownTimer(DIALOG_MAX_DURATION_MILLIS, /* countDownInterval= */ 1000) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ mTitleText.setText(getTimerTextMessage(millisUntilFinished));
+ }
+
+ @Override
+ public void onFinish() {
+ startNextAction(/* isAudioFirst= */ true);
+ }
+ };
+ }
+
+ private void startNextAction(boolean isAudioFirst) {
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ }
+
+ if (isAudioFirst) {
+ startBugReportWithAudioFirst();
+ } else {
+ startBugReportWithAudioLater();
+ }
+
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+
+ private void startBugReportWithAudioFirst() {
+ Log.i(TAG, "Start audio recording first.");
+ Intent intent = BugReportActivity.buildStartBugReportIntent(this);
+ startActivity(intent);
+ }
+
+ private void startBugReportWithAudioLater() {
+ Log.i(TAG, "Start collecting a bug report first.");
+ Intent intent = BugReportService.buildStartBugReportIntent(this);
+ startService(intent);
+ }
+
+ private void onClickBeginNowButton(View view) {
+ startNextAction(/* isAudioFirst= */ true);
+ }
+
+ private void onClickSpeakLaterButton(View view) {
+ startNextAction(/* isAudioFirst= */ false);
+ }
+}
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/Config.java b/tests/BugReportApp/src/com/android/car/bugreport/Config.java
index 343b609..87801de 100644
--- a/tests/BugReportApp/src/com/android/car/bugreport/Config.java
+++ b/tests/BugReportApp/src/com/android/car/bugreport/Config.java
@@ -15,13 +15,14 @@
*/
package com.android.car.bugreport;
-import android.app.ActivityThread;
+import android.content.Context;
import android.os.Build;
import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.util.Log;
-import com.android.internal.annotations.GuardedBy;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
import com.google.common.collect.ImmutableSet;
@@ -69,7 +70,6 @@
/**
* A system property to override GCS bucket name that is defined in {@code configs.xml}.
*/
-
private static final String PROP_GCS_BUCKET = "android.car.bugreport.gcs_bucket";
/*
@@ -84,17 +84,17 @@
@GuardedBy("mLock")
private String mUploadDestination = null;
- static Config create() {
+ static Config create(@NonNull Context context) {
Config config = new Config();
- config.start();
+ config.start(context);
return config;
}
private Config() {}
- private void start() {
+ private void start(@NonNull Context context) {
DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_CAR,
- ActivityThread.currentApplication().getMainExecutor(), this::onPropertiesChanged);
+ context.getMainExecutor(), this::onPropertiesChanged);
updateConstants();
}
@@ -104,9 +104,15 @@
}
}
+ /** Returns true if the device build is debuggable.*/
+ static boolean isDebuggable() {
+ return "userdebug".equals(Build.TYPE) || "eng".equals(Build.TYPE);
+ }
+
/** Returns true if bugreport app is enabled for this device. */
static boolean isBugReportEnabled() {
- return Build.IS_DEBUGGABLE || SystemProperties.getBoolean(PROP_FORCE_ENABLE, false);
+ return isDebuggable()
+ || SystemProperties.getBoolean(PROP_FORCE_ENABLE, false);
}
/** Returns GCS bucket system property. */
@@ -130,7 +136,7 @@
return true;
}
// NOTE: enable it only for userdebug/eng builds.
- return UPLOAD_DESTINATION_GCS.equals(getUploadDestination()) && Build.IS_DEBUGGABLE;
+ return UPLOAD_DESTINATION_GCS.equals(getUploadDestination()) && isDebuggable();
}
private static boolean isTempForceAutoUploadGcsEnabled() {
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/FileUtils.java b/tests/BugReportApp/src/com/android/car/bugreport/FileUtils.java
index 1730113..8ea6a55 100644
--- a/tests/BugReportApp/src/com/android/car/bugreport/FileUtils.java
+++ b/tests/BugReportApp/src/com/android/car/bugreport/FileUtils.java
@@ -16,6 +16,7 @@
package com.android.car.bugreport;
import android.content.Context;
+import android.os.UserManager;
import android.util.Log;
import com.google.common.base.Preconditions;
@@ -48,7 +49,7 @@
private static final String FS = "@";
static File getPendingDir(Context context) {
- Preconditions.checkArgument(context.getUser().isSystem(),
+ Preconditions.checkArgument(context.getSystemService(UserManager.class).isSystemUser(),
"Must be called from the system user.");
File dir = new File(context.getDataDir(), PENDING_DIR);
dir.mkdirs();
@@ -60,7 +61,7 @@
* single file.
*/
static File getTempDir(Context context, String timestamp) {
- Preconditions.checkArgument(!context.getUser().isSystem(),
+ Preconditions.checkArgument(!context.getSystemService(UserManager.class).isSystemUser(),
"Must be called from the current user.");
File dir = new File(context.getDataDir(), TEMP_DIR + "/" + timestamp);
dir.mkdirs();
@@ -102,9 +103,9 @@
* Returns a {@link File} object pointing to a path in a temp directory under current users
* {@link Context#getDataDir}.
*
- * @param context - an application context.
- * @param timestamp - generates file for this timestamp
- * @param suffix - a filename suffix.
+ * @param context - an application context.
+ * @param timestamp - generates file for this timestamp
+ * @param suffix - a filename suffix.
* @return A file.
*/
static File getFileWithSuffix(Context context, String timestamp, String suffix) {
@@ -115,9 +116,9 @@
* Returns a {@link File} object pointing to a path in a temp directory under current users
* {@link Context#getDataDir}.
*
- * @param context - an application context.
- * @param timestamp - generates file for this timestamp.
- * @param name - a filename
+ * @param context - an application context.
+ * @param timestamp - generates file for this timestamp.
+ * @param name - a filename
* @return A file.
*/
static File getFile(Context context, String timestamp, String name) {
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/MetaBugReport.java b/tests/BugReportApp/src/com/android/car/bugreport/MetaBugReport.java
index aca3143..a7d20c3 100644
--- a/tests/BugReportApp/src/com/android/car/bugreport/MetaBugReport.java
+++ b/tests/BugReportApp/src/com/android/car/bugreport/MetaBugReport.java
@@ -17,10 +17,11 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
-import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
+import androidx.annotation.IntDef;
+
import com.google.auto.value.AutoValue;
import java.lang.annotation.Retention;
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/SimpleUploaderAsyncTask.java b/tests/BugReportApp/src/com/android/car/bugreport/SimpleUploaderAsyncTask.java
index 96f9690..b493734 100644
--- a/tests/BugReportApp/src/com/android/car/bugreport/SimpleUploaderAsyncTask.java
+++ b/tests/BugReportApp/src/com/android/car/bugreport/SimpleUploaderAsyncTask.java
@@ -15,12 +15,13 @@
*/
package com.android.car.bugreport;
-import android.annotation.NonNull;
import android.content.Context;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.json.GoogleJsonError;
diff --git a/tests/BugReportApp/src/com/android/car/bugreport/VoiceRecordingView.java b/tests/BugReportApp/src/com/android/car/bugreport/VoiceRecordingView.java
index 32e66c4..fc97926 100644
--- a/tests/BugReportApp/src/com/android/car/bugreport/VoiceRecordingView.java
+++ b/tests/BugReportApp/src/com/android/car/bugreport/VoiceRecordingView.java
@@ -15,7 +15,6 @@
*/
package com.android.car.bugreport;
-import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -27,6 +26,8 @@
import android.util.Log;
import android.view.View;
+import androidx.annotation.Nullable;
+
/**
* A view that draws MIC icon and an animated ellipsoid. The ellipsoid animation shows the sound
* amplitude from {@link MediaRecorder}.
diff --git a/tests/BugReportApp/tests/Android.bp b/tests/BugReportApp/tests/Android.bp
index 41ccdd8..f925f25 100644
--- a/tests/BugReportApp/tests/Android.bp
+++ b/tests/BugReportApp/tests/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_automotive",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/CarCtsFakeLauncher/Android.bp b/tests/CarCtsFakeLauncher/Android.bp
index 60640a2..65af428 100644
--- a/tests/CarCtsFakeLauncher/Android.bp
+++ b/tests/CarCtsFakeLauncher/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/CarEvsCameraPreviewApp/OWNERS b/tests/CarEvsCameraPreviewApp/OWNERS
index 9b47ecd..88b80c5 100644
--- a/tests/CarEvsCameraPreviewApp/OWNERS
+++ b/tests/CarEvsCameraPreviewApp/OWNERS
@@ -1,4 +1,3 @@
# Project owners
ankitarora@google.com
changyeon@google.com
-ycheo@google.com
diff --git a/tests/CarEvsCameraPreviewApp/res/values/themes.xml b/tests/CarEvsCameraPreviewApp/res/values/themes.xml
index 14b98bb..9f8e2cc 100644
--- a/tests/CarEvsCameraPreviewApp/res/values/themes.xml
+++ b/tests/CarEvsCameraPreviewApp/res/values/themes.xml
@@ -21,5 +21,6 @@
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:backgroundDimEnabled">false</item>
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
</resources>
\ No newline at end of file
diff --git a/tests/CarEvsMultiCameraPreviewApp/AndroidManifest.xml b/tests/CarEvsMultiCameraPreviewApp/AndroidManifest.xml
index b334725..60c00c4 100644
--- a/tests/CarEvsMultiCameraPreviewApp/AndroidManifest.xml
+++ b/tests/CarEvsMultiCameraPreviewApp/AndroidManifest.xml
@@ -38,7 +38,7 @@
android:launchMode="singleTask"
android:resizeableActivity="false"
android:showForAllUsers="true"
- android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+ android:theme="@style/Theme.Transparent"
android:turnScreenOn="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/tests/CarEvsMultiCameraPreviewApp/res/values/themes.xml b/tests/CarEvsMultiCameraPreviewApp/res/values/themes.xml
index 5bcefcf..1fcab90 100644
--- a/tests/CarEvsMultiCameraPreviewApp/res/values/themes.xml
+++ b/tests/CarEvsMultiCameraPreviewApp/res/values/themes.xml
@@ -21,5 +21,6 @@
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:backgroundDimEnabled">false</item>
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
</resources>
diff --git a/tests/CarEvsMultiCameraPreviewApp/src/com/google/android/car/evs/multi/CarEvsCameraClient.java b/tests/CarEvsMultiCameraPreviewApp/src/com/google/android/car/evs/multi/CarEvsCameraClient.java
index df70dc4..5911418 100644
--- a/tests/CarEvsMultiCameraPreviewApp/src/com/google/android/car/evs/multi/CarEvsCameraClient.java
+++ b/tests/CarEvsMultiCameraPreviewApp/src/com/google/android/car/evs/multi/CarEvsCameraClient.java
@@ -116,16 +116,16 @@
final class StreamHandler implements CarEvsManager.CarEvsStreamCallback {
@Override
- public void onStreamEvent(int event) {
+ public void onStreamEvent(int from, int event) {
// TOOD: handle stream events.
- Log.i(TAG, "Client " + this + " received " + event);
+ Log.i(TAG, "Client " + this + " received " + event + " from " + from);
}
@Override
public void onNewFrame(CarEvsBufferDescriptor desc) {
mLock.lock();
try {
- @CarEvsServiceType int type = getServiceType(desc.getId());
+ @CarEvsServiceType int type = desc.getType();
ArrayList bufferQueue = mBufferQueue.get(type);
if (bufferQueue == null) {
return;
@@ -381,10 +381,6 @@
Log.i(TAG, "Completed: " + streamStateToString(mStreamState));
}
- private static @CarEvsServiceType int getServiceType(int descId) {
- return descId >> BUFFER_ID_BITDEPTH;
- }
-
private static String streamStateToString(int state) {
switch (state) {
case STREAM_STATE_STOPPED:
diff --git a/tests/CarFrameworkPackageStubsTest/src/com/android/car/frameworkpackagestubs/test/StubsTest.java b/tests/CarFrameworkPackageStubsTest/src/com/android/car/frameworkpackagestubs/test/StubsTest.java
index 2462f56..4242010 100644
--- a/tests/CarFrameworkPackageStubsTest/src/com/android/car/frameworkpackagestubs/test/StubsTest.java
+++ b/tests/CarFrameworkPackageStubsTest/src/com/android/car/frameworkpackagestubs/test/StubsTest.java
@@ -21,7 +21,6 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import android.app.DownloadManager;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
@@ -91,55 +90,6 @@
checkIfHandleByStub(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
}
- @Test
- public void testOpenDocument() {
- mMimeType = "*/*";
- mCategory = Intent.CATEGORY_OPENABLE;
- mExpectedResult = RESULT_CANCELED;
- checkIfHandleByStub(Intent.ACTION_OPEN_DOCUMENT);
- }
-
- @Test
- public void testCreateDocument() {
- mMimeType = "*/*";
- mCategory = Intent.CATEGORY_OPENABLE;
- mExpectedResult = RESULT_CANCELED;
- checkIfHandleByStub(Intent.ACTION_CREATE_DOCUMENT);
- }
-
- @Test
- public void testGetContent() {
- // A media picker, etc. may support ACTION_GET_CONTENT with other MIME-types. Therefore,
- // a non-existent MIME-type: "type/nonexistent" is used to check the general file picker.
- mMimeType = "type/nonexistent";
- mCategory = Intent.CATEGORY_OPENABLE;
- mExpectedResult = RESULT_CANCELED;
- checkIfHandleByStub(Intent.ACTION_GET_CONTENT);
- }
-
- @Test
- public void testOpenDocumentTree() {
- mExpectedResult = RESULT_CANCELED;
- checkIfHandleByStub(Intent.ACTION_OPEN_DOCUMENT_TREE);
- }
-
- @Test
- public void testViewDocumentRoot() {
- mMimeType = "vnd.android.document/root";
- checkIfHandleByStub(Intent.ACTION_VIEW);
- }
-
- @Test
- public void testViewDocumentDirectory() {
- mMimeType = "vnd.android.document/directory";
- checkIfHandleByStub(Intent.ACTION_VIEW);
- }
-
- @Test
- public void testViewDownloads() {
- checkIfHandleByStub(DownloadManager.ACTION_VIEW_DOWNLOADS);
- }
-
private void checkIfHandleByStub(String strIntent) {
Intent intent = new Intent(strIntent);
if (mMimeType != null) {
diff --git a/tests/CarLibHostUnitTest/fake_dep/src/android/car/builtin/os/BuildHelper.java b/tests/CarLibHostUnitTest/fake_dep/src/android/car/builtin/os/BuildHelper.java
deleted file mode 100644
index 9042123..0000000
--- a/tests/CarLibHostUnitTest/fake_dep/src/android/car/builtin/os/BuildHelper.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.car.builtin.os;
-
-import android.os.Build;
-import android.text.TextUtils;
-
-/**
- * Fake implementation for BuildHelper, similar to {link android.car.builtin.os.BuildHelper} but
- * avoids hidden APIs.
- */
-public final class BuildHelper {
- private BuildHelper() {
- throw new UnsupportedOperationException();
- }
-
- /** Tells if it is {@code user} build. */
- public static boolean isUserBuild() {
- return TextUtils.equals(Build.TYPE, "user");
- }
-
- /** Tells if it is {@code eng} build. */
- public static boolean isEngBuild() {
- return TextUtils.equals(Build.TYPE, "eng");
- }
-
- /** Tells if it is {@code userdebug} build. */
- public static boolean isUserDebugBuild() {
- return TextUtils.equals(Build.TYPE, "userdebug");
- }
-
- /** Tells if the build is debuggable ({@code eng} or {@code userdebug}) */
- public static boolean isDebuggableBuild() {
- return isEngBuild() || isUserDebugBuild();
- }
-}
diff --git a/tests/CarLibHostUnitTest/lint-baseline.xml b/tests/CarLibHostUnitTest/lint-baseline.xml
new file mode 100644
index 0000000..3143d69
--- /dev/null
+++ b/tests/CarLibHostUnitTest/lint-baseline.xml
@@ -0,0 +1,664 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="Class `ClusterHomeManager` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" CAR_SERVICE_NAMES.put(ClusterHomeManager.class, CLUSTER_HOME_SERVICE);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="1724"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `ClusterHomeManager()` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `createCarManagerLocked` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" manager = new ClusterHomeManager(this, binder);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="2633"
+ column="27"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onAudioZoneConfigurationsChanged()` is a flagged API and should be inside an `if (Flags.carAudioDynamicDevices())` check (or annotate the surrounding method `onAudioZoneConfigurationsChanged` with `@FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) to transfer requirement to caller`)"
+ errorLine1=" mExecutor.execute(() -> mCallback.onAudioZoneConfigurationsChanged(configs,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/media/CarAudioManager.java"
+ line="2249"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `CarEvsBufferDescriptor()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `CarEvsBufferDescriptor` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" this(id, Flags.carEvsStreamManagement() ?"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsBufferDescriptor.java"
+ line="79"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1228"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1229"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1229"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1229"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1259"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1260"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1260"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1260"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1290"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of(subscription), /* callbackExecutor= */ null,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1348"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getPropertyId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.propertyId = clientOption.getPropertyId();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1484"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getAreaIds()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.areaIds = clientOption.getAreaIds();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1485"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.updateRateHz = clientOption.getUpdateRateHz();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1486"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateEnabled()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" internalOption.enableVariableUpdateRate = clientOption.isVariableUpdateRateEnabled();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1487"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getResolution()` is a flagged API and should be inside an `if (Flags.subscriptionWithResolution())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_SUBSCRIPTION_WITH_RESOLUTION) to transfer requirement to caller`)"
+ errorLine1=" internalOption.resolution = clientOption.getResolution();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1488"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `InVehicleTaskScheduler()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" private final InVehicleTaskScheduler mInVehicleTaskScheduler = new InVehicleTaskScheduler();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="102"
+ column="68"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onServerlessClientRegistered()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `onServerlessClientRegistered` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" executor.execute(() -> callback.onServerlessClientRegistered());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="224"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateSupported()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `sanitizeEnableVariableUpdateRate` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" if (carPropertyConfig.getAreaIdConfig(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/com/android/car/internal/property/InputSanitizationUtils.java"
+ line="159"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Class `ClusterHomeManager` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" CAR_SERVICE_NAMES.put(ClusterHomeManager.class, CLUSTER_HOME_SERVICE);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="1724"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `ClusterHomeManager()` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `createCarManagerLocked` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" manager = new ClusterHomeManager(this, binder);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="2633"
+ column="27"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onAudioZoneConfigurationsChanged()` is a flagged API and should be inside an `if (Flags.carAudioDynamicDevices())` check (or annotate the surrounding method `onAudioZoneConfigurationsChanged` with `@FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) to transfer requirement to caller`)"
+ errorLine1=" mExecutor.execute(() -> mCallback.onAudioZoneConfigurationsChanged(configs,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/media/CarAudioManager.java"
+ line="2249"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `CarEvsBufferDescriptor()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `CarEvsBufferDescriptor` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" this(id, Flags.carEvsStreamManagement() ?"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsBufferDescriptor.java"
+ line="79"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1228"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1229"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1229"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1229"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1259"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1260"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1260"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1260"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1290"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1346"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of(subscription), /* callbackExecutor= */ null,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1348"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getPropertyId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.propertyId = clientOption.getPropertyId();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1484"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getAreaIds()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.areaIds = clientOption.getAreaIds();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1485"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.updateRateHz = clientOption.getUpdateRateHz();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1486"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateEnabled()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" internalOption.enableVariableUpdateRate = clientOption.isVariableUpdateRateEnabled();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1487"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getResolution()` is a flagged API and should be inside an `if (Flags.subscriptionWithResolution())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_SUBSCRIPTION_WITH_RESOLUTION) to transfer requirement to caller`)"
+ errorLine1=" internalOption.resolution = clientOption.getResolution();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1488"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `InVehicleTaskScheduler()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" private final InVehicleTaskScheduler mInVehicleTaskScheduler = new InVehicleTaskScheduler();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="102"
+ column="68"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onServerlessClientRegistered()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `onServerlessClientRegistered` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" executor.execute(() -> callback.onServerlessClientRegistered());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="224"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateSupported()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `sanitizeEnableVariableUpdateRate` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" if (carPropertyConfig.getAreaIdConfig(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/com/android/car/internal/property/InputSanitizationUtils.java"
+ line="159"
+ column="21"/>
+ </issue>
+
+</issues>
diff --git a/tests/CarLibTests/Android.bp b/tests/CarLibTests/Android.bp
index cb02b5d..66e5f4b 100644
--- a/tests/CarLibTests/Android.bp
+++ b/tests/CarLibTests/Android.bp
@@ -45,4 +45,5 @@
instrumentation_for: "CarLibTestApp",
upstream: true,
+ strict_mode: false,
}
diff --git a/tests/CarLibTests/OWNERS b/tests/CarLibTests/OWNERS
index d6b8a0c..2b3f2b1 100644
--- a/tests/CarLibTests/OWNERS
+++ b/tests/CarLibTests/OWNERS
@@ -1,7 +1,7 @@
# Bug component: 1316886
# AppFocus
-per-file src/android/car/CarAppFocusManagerTest.java = ycheo@google.com
+per-file src/android/car/CarAppFocusManagerTest.java = bkchoi@google.com
# Navigation
-per-file src/android/car/CarNavigationStatusManagerTest.java = ycheo@google.com
+per-file src/android/car/CarNavigationStatusManagerTest.java = bkchoi@google.com
diff --git a/tests/CarLibHostUnitTest/Android.bp b/tests/CarLibUnitTest/Android.bp
similarity index 64%
rename from tests/CarLibHostUnitTest/Android.bp
rename to tests/CarLibUnitTest/Android.bp
index b15d698..5bf2adb 100644
--- a/tests/CarLibHostUnitTest/Android.bp
+++ b/tests/CarLibUnitTest/Android.bp
@@ -31,15 +31,21 @@
"fake.com.android.car.internal.dep",
],
sdk_version: "module_current",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
-android_ravenwood_test {
- name: "CarLibHostUnitTest",
+java_defaults {
+ name: "CarLibUnitTestDefault",
static_libs: [
"android.car-no-dep",
"android.car.test.utils",
"androidx.annotation_annotation",
"androidx.test.rules",
+ "flag-junit",
+ "platform-test-annotations",
+ "truth",
"vehicle-hal-support-lib",
],
srcs: [
@@ -49,5 +55,40 @@
"android.test.base",
"framework-annotations-lib",
],
+}
+
+android_ravenwood_test {
+ name: "CarLibHostUnitTest",
+ static_libs: [
+ "android.car.builtin.testonly",
+ ],
+ defaults: [
+ "CarLibUnitTestDefault",
+ ],
auto_gen_config: true,
}
+
+android_test {
+ name: "CarLibDeviceUnitTest",
+ static_libs: [
+ "mockito-target-extended",
+ "ravenwood-junit",
+ ],
+ libs: [
+ "android.car.builtin",
+ ],
+ defaults: [
+ "CarLibUnitTestDefault",
+ ],
+ test_suites: [
+ "general-tests",
+ "automotive-tests",
+ "automotive-general-tests",
+ ],
+ // mockito-target-inline dependency
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+ platform_apis: true,
+}
diff --git a/tests/CarLibUnitTest/AndroidManifest.xml b/tests/CarLibUnitTest/AndroidManifest.xml
new file mode 100644
index 0000000..5906d29
--- /dev/null
+++ b/tests/CarLibUnitTest/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.car.car_lib_unit_test">
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.car.car_lib_unit_test"
+ android:label="Unit Tests for Car Lib APIs"/>
+</manifest>
diff --git a/tests/CarLibHostUnitTest/OWNERS b/tests/CarLibUnitTest/OWNERS
similarity index 100%
rename from tests/CarLibHostUnitTest/OWNERS
rename to tests/CarLibUnitTest/OWNERS
diff --git a/tests/CarLibHostUnitTest/fake_dep/Android.bp b/tests/CarLibUnitTest/fake_dep/Android.bp
similarity index 100%
rename from tests/CarLibHostUnitTest/fake_dep/Android.bp
rename to tests/CarLibUnitTest/fake_dep/Android.bp
diff --git a/tests/CarLibHostUnitTest/fake_dep/src/com/android/car/internal/dep/SystemProperties.java b/tests/CarLibUnitTest/fake_dep/src/com/android/car/internal/dep/SystemProperties.java
similarity index 100%
rename from tests/CarLibHostUnitTest/fake_dep/src/com/android/car/internal/dep/SystemProperties.java
rename to tests/CarLibUnitTest/fake_dep/src/com/android/car/internal/dep/SystemProperties.java
diff --git a/tests/CarLibHostUnitTest/fake_dep/src/com/android/car/internal/dep/Trace.java b/tests/CarLibUnitTest/fake_dep/src/com/android/car/internal/dep/Trace.java
similarity index 61%
rename from tests/CarLibHostUnitTest/fake_dep/src/com/android/car/internal/dep/Trace.java
rename to tests/CarLibUnitTest/fake_dep/src/com/android/car/internal/dep/Trace.java
index 0493d37..e9a4cc0 100644
--- a/tests/CarLibHostUnitTest/fake_dep/src/com/android/car/internal/dep/Trace.java
+++ b/tests/CarLibUnitTest/fake_dep/src/com/android/car/internal/dep/Trace.java
@@ -44,4 +44,40 @@
public static void endSection() {
// Do nothing.
}
+
+ /**
+ * Begins async trace.
+ *
+ * Do nothing in the fake implementation.
+ */
+ public static void asyncTraceBegin(long traceTag, String methodName, int cookie) {
+ // Do nothing.
+ }
+
+ /**
+ * Ends async trace.
+ *
+ * Do nothing in the fake implementation.
+ */
+ public static void asyncTraceEnd(long traceTag, String methodName, int cookie) {
+ // Do nothing.
+ }
+
+ /**
+ * Begins trace.
+ *
+ * Do nothing in the fake implementation.
+ */
+ public static void traceBegin(long traceTag, String methodName) {
+ // Do nothing.
+ }
+
+ /**
+ * Ends trace.
+ *
+ * Do nothing in the fake implementation.
+ */
+ public static void traceEnd(long traceTag) {
+ // Do nothing.
+ }
}
diff --git a/tests/CarLibUnitTest/lint-baseline.xml b/tests/CarLibUnitTest/lint-baseline.xml
new file mode 100644
index 0000000..a88195d
--- /dev/null
+++ b/tests/CarLibUnitTest/lint-baseline.xml
@@ -0,0 +1,367 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="Class `ClusterHomeManager` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" CAR_SERVICE_NAMES.put(ClusterHomeManager.class, CLUSTER_HOME_SERVICE);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="1741"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `ClusterHomeManager()` is a flagged API and should be inside an `if (Flags.clusterHealthMonitoring())` check (or annotate the surrounding method `createCarManagerLocked` with `@FlaggedApi(Flags.FLAG_CLUSTER_HEALTH_MONITORING) to transfer requirement to caller`)"
+ errorLine1=" manager = new ClusterHomeManager(this, binder);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/Car.java"
+ line="2713"
+ column="27"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onAudioZoneConfigurationsChanged()` is a flagged API and should be inside an `if (Flags.carAudioDynamicDevices())` check (or annotate the surrounding method `onAudioZoneConfigurationsChanged` with `@FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) to transfer requirement to caller`)"
+ errorLine1=" mExecutor.execute(() -> mCallback.onAudioZoneConfigurationsChanged(configs,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/media/CarAudioManager.java"
+ line="2252"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `CarEvsBufferDescriptor()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `CarEvsBufferDescriptor` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" this(id, Flags.carEvsStreamManagement() ?"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsBufferDescriptor.java"
+ line="79"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onStreamEvent()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `handleStreamEventLocked` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" executor.execute(() -> cb.onStreamEvent(origin, event));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsManager.java"
+ line="595"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getType()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `handleNewFrame` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" buffer.getType() : CarEvsUtils.getTag(buffer.getId());"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsManager.java"
+ line="610"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getType()` is a flagged API and should be inside an `if (Flags.carEvsStreamManagement())` check (or annotate the surrounding method `returnFrameBuffer` with `@FlaggedApi(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT) to transfer requirement to caller`)"
+ errorLine1=" buffer.getType() : CarEvsUtils.getTag(buffer.getId());"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/evs/CarEvsManager.java"
+ line="719"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1243"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1244"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1244"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(DEFAULT_UPDATE_RATE_HZ)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1244"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1274"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1275"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1275"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).setUpdateRateHz(updateRateHz).build()),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1275"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of("
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1305"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1306"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1306"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1306"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" new Subscription.Builder(propertyId).addAreaId(areaId).setUpdateRateHz(1f)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1306"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1361"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `addAreaId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1361"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1361"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" Subscription subscription = new Subscription.Builder(propertyId).addAreaId(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1361"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `subscribePropertyEvents()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `subscribePropertyEvents` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" return subscribePropertyEvents(List.of(subscription), /* callbackExecutor= */ null,"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1363"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getPropertyId()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.propertyId = clientOption.getPropertyId();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1499"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getAreaIds()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.areaIds = clientOption.getAreaIds();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1500"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getUpdateRateHz()` is a flagged API and should be inside an `if (Flags.batchedSubscriptions())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_BATCHED_SUBSCRIPTIONS) to transfer requirement to caller`)"
+ errorLine1=" internalOption.updateRateHz = clientOption.getUpdateRateHz();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1501"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateEnabled()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" internalOption.enableVariableUpdateRate = clientOption.isVariableUpdateRateEnabled();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1502"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getResolution()` is a flagged API and should be inside an `if (Flags.subscriptionWithResolution())` check (or annotate the surrounding method `convertToCarSubscribeOptions` with `@FlaggedApi(Flags.FLAG_SUBSCRIPTION_WITH_RESOLUTION) to transfer requirement to caller`)"
+ errorLine1=" internalOption.resolution = clientOption.getResolution();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java"
+ line="1503"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `InVehicleTaskScheduler()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" private final InVehicleTaskScheduler mInVehicleTaskScheduler = new InVehicleTaskScheduler();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="102"
+ column="68"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `onServerlessClientRegistered()` is a flagged API and should be inside an `if (Flags.serverlessRemoteAccess())` check (or annotate the surrounding method `onServerlessClientRegistered` with `@FlaggedApi(Flags.FLAG_SERVERLESS_REMOTE_ACCESS) to transfer requirement to caller`)"
+ errorLine1=" executor.execute(() -> callback.onServerlessClientRegistered());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Car/car-lib/src/android/car/remoteaccess/CarRemoteAccessManager.java"
+ line="224"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `isVariableUpdateRateSupported()` is a flagged API and should be inside an `if (Flags.variableUpdateRate())` check (or annotate the surrounding method `sanitizeEnableVariableUpdateRate` with `@FlaggedApi(Flags.FLAG_VARIABLE_UPDATE_RATE) to transfer requirement to caller`)"
+ errorLine1=" if (carPropertyConfig.getAreaIdConfig(areaId)"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Car/car-lib/src/com/android/car/internal/property/InputSanitizationUtils.java"
+ line="160"
+ column="21"/>
+ </issue>
+
+</issues>
diff --git a/tests/CarLibUnitTest/src/android/car/CarUnitTest.java b/tests/CarLibUnitTest/src/android/car/CarUnitTest.java
new file mode 100644
index 0000000..3ba30ed
--- /dev/null
+++ b/tests/CarLibUnitTest/src/android/car/CarUnitTest.java
@@ -0,0 +1,1299 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car;
+
+import static android.car.Car.CAR_SERVICE_BINDER_SERVICE_NAME;
+import static android.car.feature.Flags.FLAG_DISPLAY_COMPATIBILITY;
+import static android.car.feature.Flags.FLAG_PERSIST_AP_SETTINGS;
+import static android.car.feature.Flags.FLAG_CREATE_CAR_USE_NOTIFICATIONS;
+
+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.eq;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.app.Activity;
+import android.app.Service;
+import android.car.Car.CarBuilder;
+import android.car.Car.Deps;
+import android.car.builtin.os.ServiceManagerHelper.IServiceRegistrationCallback;
+import android.car.hardware.property.CarPropertyManager;
+import android.car.hardware.property.ICarProperty;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Pair;
+
+import com.android.car.internal.ICarServiceHelper;
+import com.android.car.internal.os.Process;
+import com.android.car.internal.os.ServiceManager;
+import com.android.internal.annotations.GuardedBy;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Unit test for Car API.
+ */
+@RunWith(MockitoJUnitRunner.Silent.class)
+@EnableFlags({FLAG_PERSIST_AP_SETTINGS, FLAG_DISPLAY_COMPATIBILITY})
+public final class CarUnitTest {
+
+ private static final String TAG = CarUnitTest.class.getSimpleName();
+ private static final String PKG_NAME = "Bond.James.Bond";
+ private static final int DEFAULT_TIMEOUT_MS = 1000;
+ private static final int MY_PID = 1234;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().setProvideMainThread(true)
+ .build();
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private ServiceConnection mServiceConnectionListener;
+ @Mock
+ private ComponentName mCarServiceComponentName;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private ServiceManager mServiceManager;
+ @Mock
+ private Process mFakeProcess;
+ @Mock
+ private ICarProperty.Stub mICarProperty;
+ @Mock
+ private ApplicationInfo mApplicationInfo;
+
+ private HandlerThread mEventHandlerThread;
+ private Handler mEventHandler;
+ private Handler mMainHandler;
+ private HandlerThread mServiceManagerHandlerThread;
+ private Handler mServiceManagerHandler;
+ private CarBuilder mCarBuilder;
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final List<ServiceConnection> mBindServiceConnections = new ArrayList<>();
+ @GuardedBy("mLock")
+ private final List<IServiceRegistrationCallback> mServiceCallbacks = new ArrayList<>();
+ @GuardedBy("mLock")
+ private boolean mCarServiceRegistered;
+ @GuardedBy("mLock")
+ private IBinder.DeathRecipient mDeathRecipient;
+
+ // It is tricky to mock this. So create placeholder version instead.
+ private final class FakeService extends ICar.Stub {
+
+ @Override
+ public void setSystemServerConnections(ICarServiceHelper helper,
+ ICarResultReceiver receiver) throws RemoteException {
+ }
+
+ @Override
+ public boolean isFeatureEnabled(String featureName) {
+ return false;
+ }
+
+ @Override
+ public int enableFeature(String featureName) {
+ return Car.FEATURE_REQUEST_SUCCESS;
+ }
+
+ @Override
+ public int disableFeature(String featureName) {
+ return Car.FEATURE_REQUEST_SUCCESS;
+ }
+
+ @Override
+ public List<String> getAllEnabledFeatures() {
+ return Collections.EMPTY_LIST;
+ }
+
+ @Override
+ public List<String> getAllPendingDisabledFeatures() {
+ return Collections.EMPTY_LIST;
+ }
+
+ @Override
+ public List<String> getAllPendingEnabledFeatures() {
+ return Collections.EMPTY_LIST;
+ }
+
+ @Override
+ public String getCarManagerClassForFeature(String featureName) {
+ return null;
+ }
+
+ @Override
+ public IBinder getCarService(java.lang.String serviceName) {
+ if (serviceName.equals(Car.PROPERTY_SERVICE)) {
+ return mICarProperty;
+ }
+ return null;
+ }
+
+ @Override
+ public int getCarConnectionType() {
+ return 0;
+ }
+
+ @Override
+ public void linkToDeath(IBinder.DeathRecipient deathRecipient, int flags) {
+ synchronized (mLock) {
+ mDeathRecipient = deathRecipient;
+ }
+ }
+ };
+
+ private final FakeService mService = new FakeService();
+
+ private static final class LifecycleListener implements Car.CarServiceLifecycleListener {
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final ArrayList<Pair<Car, Boolean>> mEvents = new ArrayList<>();
+
+ @Override
+ public void onLifecycleChanged(Car car, boolean ready) {
+ synchronized (mLock) {
+ assertThat(Looper.getMainLooper()).isEqualTo(Looper.myLooper());
+ mEvents.add(new Pair<>(car, ready));
+ mLock.notifyAll();
+ }
+ }
+
+ void waitForEvent(int count, int timeoutInMs) throws InterruptedException {
+ synchronized (mLock) {
+ while (mEvents.size() < count) {
+ mLock.wait(timeoutInMs);
+ }
+ }
+ }
+
+ void assertOneListenerCallAndClear(Car expectedCar, boolean ready) {
+ synchronized (mLock) {
+ assertThat(mEvents).containsExactly(new Pair<>(expectedCar, ready));
+ mEvents.clear();
+ }
+ }
+
+ void assertNoEvent() {
+ synchronized (mLock) {
+ assertThat(mEvents).isEmpty();
+ }
+ }
+ }
+
+ private final LifecycleListener mLifecycleListener = new LifecycleListener();
+
+ @Before
+ public void setUp() throws Exception {
+ mEventHandlerThread = new HandlerThread("CarTestEvent");
+ mEventHandlerThread.start();
+ mEventHandler = new Handler(mEventHandlerThread.getLooper());
+ mServiceManagerHandlerThread = new HandlerThread("CarTestEvent");
+ mServiceManagerHandlerThread.start();
+ mServiceManagerHandler = new Handler(mServiceManagerHandlerThread.getLooper());
+ mMainHandler = new Handler(Looper.getMainLooper());
+ // Inject fake dependencies.
+ mCarBuilder = new CarBuilder().setFakeDeps(new Deps(
+ mServiceManager, mFakeProcess, /* carServiceBindRetryIntervalMs= */ 10,
+ /* carServiceBindMaxRetry= */ 2));
+ when(mFakeProcess.myPid()).thenReturn(MY_PID);
+
+ when(mContext.getPackageName()).thenReturn(PKG_NAME);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true);
+ setupFakeServiceManager();
+
+ // Setup context for CarPropertyManager
+ mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
+ when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
+ }
+
+ @After
+ public void tearDown() {
+ mEventHandlerThread.quitSafely();
+ mServiceManagerHandlerThread.quitSafely();
+ }
+
+ private void setupFakeServiceManager() throws Exception {
+ setupFakeServiceManager(mContext);
+ }
+
+ private void setupFakeServiceManager(Context context) throws Exception {
+ when(context.bindService(any(), any(), anyInt())).thenAnswer((inv) -> {
+ ServiceConnection serviceConnection = inv.getArgument(1);
+
+ synchronized (mLock) {
+ if (mCarServiceRegistered) {
+ mMainHandler.post(() -> serviceConnection.onServiceConnected(
+ mCarServiceComponentName, mService));
+ }
+ mBindServiceConnections.add(serviceConnection);
+ }
+
+ return true;
+ });
+
+ doAnswer((inv) -> {
+ ServiceConnection serviceConnection = inv.getArgument(0);
+
+ synchronized (mLock) {
+ mBindServiceConnections.remove(serviceConnection);
+ }
+ return null;
+ }).when(context).unbindService(any());
+
+ doAnswer((inv) -> {
+ synchronized (mLock) {
+ if (mCarServiceRegistered) {
+ ((IServiceRegistrationCallback) inv.getArgument(1))
+ .onRegistration(CAR_SERVICE_BINDER_SERVICE_NAME, mService);
+ }
+ mServiceCallbacks.add(inv.getArgument(1));
+ }
+ return null;
+ }).when(mServiceManager).registerForNotifications(
+ eq(CAR_SERVICE_BINDER_SERVICE_NAME), any());
+
+ when(mServiceManager.getService(CAR_SERVICE_BINDER_SERVICE_NAME))
+ .thenAnswer((inv) -> {
+ synchronized (mLock) {
+ if (mCarServiceRegistered) {
+ return mService;
+ }
+ return null;
+ }
+ });
+ }
+
+ private void setCarServiceRegistered() {
+ synchronized (mLock) {
+ mCarServiceRegistered = true;
+ for (int i = 0; i < mBindServiceConnections.size(); i++) {
+ var serviceConnection = mBindServiceConnections.get(i);
+ mMainHandler.post(() -> serviceConnection.onServiceConnected(
+ mCarServiceComponentName, mService));
+ }
+ for (int i = 0; i < mServiceCallbacks.size(); i++) {
+ IServiceRegistrationCallback callback = mServiceCallbacks.get(i);
+ mServiceManagerHandler.post(() -> callback.onRegistration(
+ CAR_SERVICE_BINDER_SERVICE_NAME, mService));
+ }
+ }
+ }
+
+ private void setCarServiceDisconnected() {
+ synchronized (mLock) {
+ mCarServiceRegistered = false;
+ for (int i = 0; i < mBindServiceConnections.size(); i++) {
+ var serviceConnection = mBindServiceConnections.get(i);
+ mMainHandler.post(() -> serviceConnection.onServiceDisconnected(
+ mCarServiceComponentName));
+ }
+ if (mDeathRecipient != null) {
+ // Copy mDeathRecipient to be catpured outside of the lock.
+ IBinder.DeathRecipient deathRecipient = mDeathRecipient;
+ mServiceManagerHandler.post(() -> deathRecipient.binderDied());
+ }
+ }
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_ServiceConnection_Handler_oldLogic() {
+ createCar_Context_ServiceConnection_Handler();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_ServiceConnection_Handler_newLogic() {
+ createCar_Context_ServiceConnection_Handler();
+ }
+
+ private void createCar_Context_ServiceConnection_Handler() {
+ Car car = mCarBuilder.createCar(mContext, mServiceConnectionListener, mEventHandler);
+
+ assertThat(car).isNotNull();
+
+ car.connect();
+
+ assertThat(car.isConnecting()).isTrue();
+ assertThat(car.isConnected()).isFalse();
+
+ setCarServiceRegistered();
+
+ verify(mServiceConnectionListener, timeout(DEFAULT_TIMEOUT_MS)).onServiceConnected(
+ any(), eq(mService));
+ assertThat(car.isConnected()).isTrue();
+
+ car.disconnect();
+ assertThat(car.isConnected()).isFalse();
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_ServiceConnection_DefaultHandler_oldLogic() {
+ createCar_Context_ServiceConnection_DefaultHandler();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_ServiceConnection_DefaultHandler_newLogic() {
+ createCar_Context_ServiceConnection_DefaultHandler();
+ }
+
+ private void createCar_Context_ServiceConnection_DefaultHandler() {
+ Car car = mCarBuilder.createCar(mContext, mServiceConnectionListener);
+
+ assertThat(car).isNotNull();
+
+ car.connect();
+
+ assertThat(car.isConnecting()).isTrue();
+ assertThat(car.isConnected()).isFalse();
+
+ setCarServiceRegistered();
+
+ verify(mServiceConnectionListener, timeout(DEFAULT_TIMEOUT_MS)).onServiceConnected(
+ any(), eq(mService));
+ assertThat(car.isConnected()).isTrue();
+
+ car.disconnect();
+ assertThat(car.isConnected()).isFalse();
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_ServiceConnection_Handler_CarServiceRegistered_oldLogic() {
+ createCar_Context_ServiceConnection_Handler_CarServiceRegistered();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_ServiceConnection_Handler_CarServiceRegistered_newLogic() {
+ createCar_Context_ServiceConnection_Handler_CarServiceRegistered();
+ }
+
+ private void createCar_Context_ServiceConnection_Handler_CarServiceRegistered() {
+ setCarServiceRegistered();
+
+ Car car = mCarBuilder.createCar(mContext, mServiceConnectionListener, mEventHandler);
+
+ assertThat(car).isNotNull();
+
+ car.connect();
+
+ verify(mServiceConnectionListener, timeout(DEFAULT_TIMEOUT_MS)).onServiceConnected(
+ any(), eq(mService));
+ assertThat(car.isConnected()).isTrue();
+
+ car.disconnect();
+ assertThat(car.isConnected()).isFalse();
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_ServiceConnection_Handler_Disconnect_Reconnect_oldLogic() {
+ createCar_Context_ServiceConnection_Handler_Disconnect_Reconnect();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_ServiceConnection_Handler_Disconnect_Reconnect_newLogic() {
+ createCar_Context_ServiceConnection_Handler_Disconnect_Reconnect();
+ }
+
+ private void createCar_Context_ServiceConnection_Handler_Disconnect_Reconnect() {
+ setCarServiceRegistered();
+
+ Car car = mCarBuilder.createCar(mContext, mServiceConnectionListener, mEventHandler);
+ car.connect();
+
+ verify(mServiceConnectionListener, timeout(DEFAULT_TIMEOUT_MS)).onServiceConnected(
+ any(), eq(mService));
+ clearInvocations(mServiceConnectionListener);
+
+ car.disconnect();
+ car.connect();
+
+ verify(mServiceConnectionListener, timeout(DEFAULT_TIMEOUT_MS)).onServiceConnected(
+ any(), eq(mService));
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_SC_Handler_Disconnect_IgnoreCallback_oldLogic() {
+ createCar_Context_ServiceConnection_Handler_Disconnect_IgnoreCallback();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_SC_Handler_Disconnect_IgnoreCallback_newLogic() {
+ createCar_Context_ServiceConnection_Handler_Disconnect_IgnoreCallback();
+ }
+
+ private void createCar_Context_ServiceConnection_Handler_Disconnect_IgnoreCallback() {
+ Car car = mCarBuilder.createCar(mContext, mServiceConnectionListener, mEventHandler);
+ car.connect();
+ car.disconnect();
+
+ setCarServiceRegistered();
+
+ // Callback must not be invoked while car is disconnected.
+ verify(mServiceConnectionListener, after(DEFAULT_TIMEOUT_MS).never()).onServiceConnected(
+ any(), eq(mService));
+
+ car.connect();
+
+ // Callback should be invoked after connect again.
+ verify(mServiceConnectionListener, timeout(DEFAULT_TIMEOUT_MS)).onServiceConnected(
+ any(), eq(mService));
+ }
+
+ @Test
+ public void testCreateCar_Context_ServiceConnection_Handler_ContextIsNull() {
+ assertThrows(NullPointerException.class, () -> mCarBuilder.createCar(
+ /* context= */ null, mServiceConnectionListener, mEventHandler));
+ }
+
+ @Test
+ public void testCreateCar_Context_ServiceConnection_Handler_NoAutoFeature() {
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
+
+ Car car = mCarBuilder.createCar(mContext, mServiceConnectionListener, mEventHandler);
+
+ assertThat(car).isNull();
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceRegistered_oldLogic() throws Exception {
+ createCar_Context_CarServiceRegistered();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceRegistered_newLogic() throws Exception {
+ createCar_Context_CarServiceRegistered();
+ }
+
+ private void createCar_Context_CarServiceRegistered() throws Exception {
+ setCarServiceRegistered();
+
+ Car car = mCarBuilder.createCar(mContext);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isTrue();
+
+ // In the legacy implementation, createCar will bind to car service and cause an
+ // onServiceConnected callback to be invoked later. We must make sure this callback is
+ // invoked before disconnect, otherwise, the callback will set isConnected to true again.
+ finishTasksOnMain();
+
+ car.disconnect();
+ assertThat(car.isConnected()).isFalse();
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceRegistered_DisconnectReconnect_oldLogic()
+ throws Exception {
+ createCar_Context_CarServiceRegistered_DisconnectReconnect();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceRegistered_DisconnectReconnect_newLogic()
+ throws Exception {
+ createCar_Context_CarServiceRegistered_DisconnectReconnect();
+ }
+
+ private void createCar_Context_CarServiceRegistered_DisconnectReconnect() throws Exception {
+ setCarServiceRegistered();
+
+ Car car = mCarBuilder.createCar(mContext);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isTrue();
+
+ // In the legacy implementation, createCar will bind to car service and cause an
+ // onServiceConnected callback to be invoked later. We must make sure this callback is
+ // invoked before disconnect, otherwise, the callback will set isConnected to true again.
+ finishTasksOnMain();
+
+ car.disconnect();
+ car.connect();
+
+ // It takes a while for the callback to set connection state to connected.
+ long currentTimeMs = SystemClock.elapsedRealtime();
+ long timeout = currentTimeMs + DEFAULT_TIMEOUT_MS;
+ while (!car.isConnected() && SystemClock.elapsedRealtime() < timeout) {
+ Thread.sleep(100);
+ }
+
+ assertThat(car.isConnected()).isTrue();
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceNeverRegistered_Timeout_oldLogic() {
+ createCar_Context_CarServiceNeverRegistered_Timeout();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceNeverRegistered_Timeout_newLogic() {
+ createCar_Context_CarServiceNeverRegistered_Timeout();
+ }
+
+ private void createCar_Context_CarServiceNeverRegistered_Timeout() {
+ // This should timeout.
+ Car car = mCarBuilder.createCar(mContext);
+
+ assertThat(car).isNull();
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceRegisteredLater_BeforeTimeout_oldLogic()
+ throws Exception {
+ createCar_Context_CarServiceRegisteredLater_BeforeTimeout();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceRegisteredLater_BeforeTimeout_newLogic()
+ throws Exception {
+ createCar_Context_CarServiceRegisteredLater_BeforeTimeout();
+ }
+
+ private void createCar_Context_CarServiceRegisteredLater_BeforeTimeout() throws Exception {
+ // Car service is registered after 200ms.
+ mEventHandler.postDelayed(() -> setCarServiceRegistered(), 200);
+
+ // This should block until car service is registered.
+ Car car = mCarBuilder.createCar(mContext);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isTrue();
+
+ // In the legacy implementation, createCar will bind to car service and cause an
+ // onServiceConnected callback to be invoked later. We must make sure this callback is
+ // invoked before disconnect, otherwise, the callback will set isConnected to true again.
+ finishTasksOnMain();
+
+ car.disconnect();
+ assertThat(car.isConnected()).isFalse();
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_InvokeFromMain_oldLogic() throws Exception {
+ createCar_Context_InvokeFromMain();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_InvokeFromMain_newLogic() throws Exception {
+ createCar_Context_InvokeFromMain();
+ }
+
+ private void createCar_Context_InvokeFromMain() throws Exception {
+ // Car service is registered after 200ms.
+ mEventHandler.postDelayed(() -> setCarServiceRegistered(), 200);
+
+ runOnMain(() -> {
+ // This should block until car service is registered.
+ Car car = mCarBuilder.createCar(mContext);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isTrue();
+
+ car.disconnect();
+ assertThat(car.isConnected()).isFalse();
+ });
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceCrash_killClient_oldLogic() throws Exception {
+ createCar_Context_CarServiceCrash_killClient();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceCrash_killClient_newLogic() throws Exception {
+ createCar_Context_CarServiceCrash_killClient();
+ }
+
+ private void createCar_Context_CarServiceCrash_killClient() throws Exception {
+ setCarServiceRegistered();
+ mCarBuilder.createCar(mContext);
+
+ // Simulate car service crash.
+ setCarServiceDisconnected();
+
+ verify(mFakeProcess, timeout(DEFAULT_TIMEOUT_MS)).killProcess(MY_PID);
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceCrash_killClientService_oldLogic()
+ throws Exception {
+ createCar_Context_CarServiceCrash_killClientService();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceCrash_killClientService_newLogic()
+ throws Exception {
+ createCar_Context_CarServiceCrash_killClientService();
+ }
+
+ private void createCar_Context_CarServiceCrash_killClientService() throws Exception {
+ Service serviceContext = mock(Service.class);
+ setupFakeServiceManager(serviceContext);
+ setCarServiceRegistered();
+ when(serviceContext.getBaseContext()).thenReturn(mContext);
+ when(serviceContext.getPackageName()).thenReturn("package");
+ mCarBuilder.createCar(serviceContext);
+
+ // Simulate car service crash.
+ setCarServiceDisconnected();
+
+ verify(mFakeProcess, timeout(DEFAULT_TIMEOUT_MS)).killProcess(MY_PID);
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceCrash_stopClientActivity_oldLogic()
+ throws Exception {
+ createCar_Context_CarServiceCrash_stopClientActivity();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceCrash_stopClientActivity_newLogic()
+ throws Exception {
+ createCar_Context_CarServiceCrash_stopClientActivity();
+ }
+
+ private void createCar_Context_CarServiceCrash_stopClientActivity() throws Exception {
+ Activity activityContext = mock(Activity.class);
+ setupFakeServiceManager(activityContext);
+ setCarServiceRegistered();
+ when(activityContext.getBaseContext()).thenReturn(mContext);
+ when(activityContext.isFinishing()).thenReturn(false);
+ mCarBuilder.createCar(activityContext);
+
+ // Simulate car service crash.
+ setCarServiceDisconnected();
+
+ verify(activityContext, timeout(DEFAULT_TIMEOUT_MS)).finish();
+ verify(mFakeProcess, never()).killProcess(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_NullBaseContext_oldLogic() throws Exception {
+ createCar_Context_NullBaseContext();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_NullBaseContext_newLogic() throws Exception {
+ createCar_Context_NullBaseContext();
+ }
+
+ private void createCar_Context_NullBaseContext() throws Exception {
+ // Base context is null.
+ Service serviceContext = mock(Service.class);
+
+ assertThrows(NullPointerException.class, () -> mCarBuilder.createCar(serviceContext));
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceCrashAfterDisconnect_oldLogic() throws Exception {
+ createCar_Context_CarServiceCrashAfterDisconnect();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_CarServiceCrashAfterDisconnect_newLogic() throws Exception {
+ createCar_Context_CarServiceCrashAfterDisconnect();
+ }
+
+ private void createCar_Context_CarServiceCrashAfterDisconnect() throws Exception {
+ setCarServiceRegistered();
+ Car car = mCarBuilder.createCar(mContext);
+
+ // In the legacy implementation, createCar will bind to car service and cause an
+ // onServiceConnected callback to be invoked later. We must make sure this callback is
+ // invoked before disconnect, otherwise, the callback will set isConnected to true again.
+ finishTasksOnMain();
+ car.disconnect();
+
+ setCarServiceDisconnected();
+ finishTasksOnMain();
+
+ verify(mFakeProcess, never()).killProcess(anyInt());
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WaitForever_Lclistener_CarServiceRegisteredLater_oldLogic()
+ throws Exception {
+ createCar_Context_WaitForever_Lclistener_CarServiceRegisteredLater(false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WaitForever_Lclistener_CarServiceRegisteredLater_newLogic()
+ throws Exception {
+ createCar_Context_WaitForever_Lclistener_CarServiceRegisteredLater(true);
+ }
+
+ private void createCar_Context_WaitForever_Lclistener_CarServiceRegisteredLater(
+ boolean flagCreateCarUseNotifications) throws Exception {
+ // Car service is registered after 200ms.
+ mEventHandler.postDelayed(() -> setCarServiceRegistered(), 200);
+
+ Car car = mCarBuilder.createCar(mContext, null,
+ Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isTrue();
+ if (!flagCreateCarUseNotifications) {
+ verify(mContext).bindService(any(), any(), anyInt());
+ }
+ // The callback will be called from the main thread, so it is not guaranteed to be called
+ // after createCar returns.
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+
+ if (flagCreateCarUseNotifications) {
+ // The rest of the test is for the old logic.
+ return;
+ }
+
+ // Just call these to guarantee that nothing crashes with these call.
+ ServiceConnection serviceConnection;
+ synchronized (mLock) {
+ serviceConnection = mBindServiceConnections.get(0);
+ }
+ runOnMain(() -> {
+ serviceConnection.onServiceConnected(new ComponentName("", ""), mService);
+ serviceConnection.onServiceDisconnected(new ComponentName("", ""));
+ });
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WaitForever_Lclistener_ConnectCrashRestart_oldLogic()
+ throws Exception {
+ createCar_Context_WaitForever_Lclistener_ConnectCrashRestart();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WaitForever_Lclistener_ConnectCrashRestart_newLogic()
+ throws Exception {
+ createCar_Context_WaitForever_Lclistener_ConnectCrashRestart();
+ }
+
+ private void createCar_Context_WaitForever_Lclistener_ConnectCrashRestart()
+ throws Exception {
+ // Car service is registered after 100ms.
+ mEventHandler.postDelayed(() -> setCarServiceRegistered(), 100);
+
+ Car car = mCarBuilder.createCar(mContext, null,
+ Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isTrue();
+ // The callback will be called from the main thread, so it is not guaranteed to be called
+ // after createCar returns.
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+
+ // Fake crash.
+ mEventHandler.post(() -> setCarServiceDisconnected());
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+
+ mLifecycleListener.assertOneListenerCallAndClear(car, false);
+ assertThat(car.isConnected()).isFalse();
+
+ // fake restart
+ mEventHandler.post(() -> setCarServiceRegistered());
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+ assertThat(car.isConnected()).isTrue();
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WaitForever_Lclistener_CSAlreadyRegistered_oldLogic()
+ throws Exception {
+ createCar_Context_WaitForever_Lclistener_CarServiceAlreadyRegistered();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WaitForever_Lclistener_CSAlreadyRegistered_newLogic()
+ throws Exception {
+ createCar_Context_WaitForever_Lclistener_CarServiceAlreadyRegistered();
+ }
+
+ private void createCar_Context_WaitForever_Lclistener_CarServiceAlreadyRegistered()
+ throws Exception {
+ setCarServiceRegistered();
+
+ runOnMain(() -> {
+ Car car = mCarBuilder.createCar(mContext, null,
+ Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isTrue();
+
+ // mLifecycleListener should have been called as this is main thread.
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+ });
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WaitForever_Lclistener_MgrNotTheSameAfterReconnect_oldLogic()
+ throws Exception {
+ createCar_Context_WaitForever_Lclistener_ManagerNotTheSameAfterReconnect();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WaitForever_Lclistener_MgrNotTheSameAfterReconnect_newLogic()
+ throws Exception {
+ createCar_Context_WaitForever_Lclistener_ManagerNotTheSameAfterReconnect();
+ }
+
+ private void createCar_Context_WaitForever_Lclistener_ManagerNotTheSameAfterReconnect()
+ throws Exception {
+ setCarServiceRegistered();
+
+ Car car = mCarBuilder.createCar(mContext, null,
+ Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
+
+ CarPropertyManager oldMgr = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
+
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+
+ // Simulate car service crash.
+ setCarServiceDisconnected();
+
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+ mLifecycleListener.assertOneListenerCallAndClear(car, false);
+
+ // Simulate car service restore.
+ setCarServiceRegistered();
+
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+ CarPropertyManager newMgr = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
+
+ assertThat(oldMgr).isNotEqualTo(newMgr);
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_DoNotWait_CarServiceRegistered_oldLogic()
+ throws Exception {
+ createCar_Context_DoNotWait_CarServiceRegistered();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_DoNotWait_CarServiceRegistered_newLogic()
+ throws Exception {
+ createCar_Context_DoNotWait_CarServiceRegistered();
+ }
+
+ private void createCar_Context_DoNotWait_CarServiceRegistered()
+ throws Exception {
+ setCarServiceRegistered();
+
+ Car car = mCarBuilder.createCar(mContext, null,
+ Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, mLifecycleListener);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isTrue();
+
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_DoNotWait_CarServiceCrash_Restore_oldLogic()
+ throws Exception {
+ createCar_Context_DoNotWait_CarServiceCrash_Restore();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_DoNotWait_CarServiceCrash_Restore_newLogic()
+ throws Exception {
+ createCar_Context_DoNotWait_CarServiceCrash_Restore();
+ }
+
+ private void createCar_Context_DoNotWait_CarServiceCrash_Restore()
+ throws Exception {
+ setCarServiceRegistered();
+
+ Car car = mCarBuilder.createCar(mContext, null,
+ Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, mLifecycleListener);
+
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+
+ // Simulate car service crash.
+ setCarServiceDisconnected();
+
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+ mLifecycleListener.assertOneListenerCallAndClear(car, false);
+ assertThat(car.isConnected()).isFalse();
+
+ // Simulate car service restore.
+ setCarServiceRegistered();
+
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+ assertThat(car.isConnected()).isTrue();
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_DoNotWait_InvokeFromMain_CarSvcRegistered_oldLogic()
+ throws Exception {
+ createCar_Context_DoNotWait_InvokeFromMain_CarServiceRegistered();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_DoNotWait_InvokeFromMain_CarSvcRegistered_newLogic()
+ throws Exception {
+ createCar_Context_DoNotWait_InvokeFromMain_CarServiceRegistered();
+ }
+
+ private void createCar_Context_DoNotWait_InvokeFromMain_CarServiceRegistered()
+ throws Exception {
+ setCarServiceRegistered();
+
+ runOnMain(() -> {
+ Car car = mCarBuilder.createCar(mContext, null,
+ Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, mLifecycleListener);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isTrue();
+ // createCar is called from main handler, so callback must have already been called.
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+ });
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_DoNotWait_CarServiceRegisteredLater_oldLogic()
+ throws Exception {
+ createCar_Context_DoNotWait_CarServiceRegisteredLater();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_DoNotWait_CarServiceRegisteredLater_newLogic()
+ throws Exception {
+ createCar_Context_DoNotWait_CarServiceRegisteredLater();
+ }
+
+ private void createCar_Context_DoNotWait_CarServiceRegisteredLater()
+ throws Exception {
+ Car car = mCarBuilder.createCar(mContext, null,
+ Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, mLifecycleListener);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isFalse();
+
+ setCarServiceRegistered();
+
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_DoNotWait_CarSvcRegisteredAfterDisconnect_oldLogic()
+ throws Exception {
+ createCar_Context_DoNotWait_CarServiceRegisteredAfterDisconnect();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_DoNotWait_CarSvcRegisteredAfterDisconnect_newLogic()
+ throws Exception {
+ createCar_Context_DoNotWait_CarServiceRegisteredAfterDisconnect();
+ }
+
+ private void createCar_Context_DoNotWait_CarServiceRegisteredAfterDisconnect()
+ throws Exception {
+ Car car = mCarBuilder.createCar(mContext, null,
+ Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, mLifecycleListener);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isFalse();
+
+ car.disconnect();
+
+ // Car service is registered after disconnect, must not invoke callback.
+ setCarServiceRegistered();
+
+ Thread.sleep(DEFAULT_TIMEOUT_MS);
+ mLifecycleListener.assertNoEvent();
+
+ // After connect, the callback must be invoked.
+ car.connect();
+
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_DoNotWait_InvokeFromMain_CarSvcRegisteredLater_oldLogic()
+ throws Exception {
+ createCar_Context_DoNotWait_InvokeFromMain_CarServiceRegisteredLater();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_DoNotWait_InvokeFromMain_CarSvcRegisteredLater_newLogic()
+ throws Exception {
+ createCar_Context_DoNotWait_InvokeFromMain_CarServiceRegisteredLater();
+ }
+
+ private void createCar_Context_DoNotWait_InvokeFromMain_CarServiceRegisteredLater()
+ throws Exception {
+ setCarServiceRegistered();
+
+ runOnMain(() -> {
+ Car car = mCarBuilder.createCar(mContext, null,
+ Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, mLifecycleListener);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isTrue();
+ // createCar is called from main handler, so callback must have already been called.
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+ });
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WithTimeout_InvokeFromMain_CarSvcRegisteredLater_oldLogic()
+ throws Exception {
+ createCar_Context_WithTimeout_InvokeFromMain_CarServiceRegisteredLater();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WithTimeout_InvokeFromMain_CarSvcRegisteredLater_newLogic()
+ throws Exception {
+ createCar_Context_WithTimeout_InvokeFromMain_CarServiceRegisteredLater();
+ }
+
+ private void createCar_Context_WithTimeout_InvokeFromMain_CarServiceRegisteredLater()
+ throws Exception {
+ // Car service is registered after 200ms.
+ mEventHandler.postDelayed(() -> setCarServiceRegistered(), 200);
+
+ runOnMain(() -> {
+ Car car = mCarBuilder.createCar(mContext, null, DEFAULT_TIMEOUT_MS, mLifecycleListener);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isTrue();
+ // createCar is called from main handler, so callback must have already been called.
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+ });
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WithTimeout_CarSvcRegisteredAfterTimeout_oldLogic()
+ throws Exception {
+ createCar_Context_WithTimeout_CarServiceRegisteredAfterTimeout();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WithTimeout_CarSvcRegisteredAfterTimeout_newLogic()
+ throws Exception {
+ createCar_Context_WithTimeout_CarServiceRegisteredAfterTimeout();
+ }
+
+ private void createCar_Context_WithTimeout_CarServiceRegisteredAfterTimeout()
+ throws Exception {
+ // Car service is registered after 200ms.
+ mEventHandler.postDelayed(() -> setCarServiceRegistered(), 200);
+
+ Car car = mCarBuilder.createCar(mContext, null, /* waitTimeoutMs= */50, mLifecycleListener);
+ assertThat(car).isNotNull();
+
+ // The callback should be invoked after 200ms.
+ mLifecycleListener.waitForEvent(1, DEFAULT_TIMEOUT_MS);
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+ assertThat(car.isConnected()).isTrue();
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WaitForever_InvokeFromMain_CarSvcRegisteredLater_oldLogic()
+ throws Exception {
+ createCar_Context_WaitForever_InvokeFromMain_CarServiceRegisteredLater();
+ }
+
+ @Test
+ @EnableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_WaitForever_InvokeFromMain_CarSvcRegisteredLater_newLogic()
+ throws Exception {
+ createCar_Context_WaitForever_InvokeFromMain_CarServiceRegisteredLater();
+ }
+
+ /**
+ * The following test cases are for old logic only.
+ */
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_bindServiceFailed() throws Exception {
+ when(mContext.bindService(any(), any(), anyInt())).thenReturn(false);
+ when(mServiceManager.getService(CAR_SERVICE_BINDER_SERVICE_NAME))
+ .thenReturn(mService);
+
+ mCarBuilder.createCar(mContext);
+
+ // bindService failures will cause onServiceDisconnected which will kill the client.
+ verify(mFakeProcess, timeout(DEFAULT_TIMEOUT_MS)).killProcess(MY_PID);
+ }
+
+ @Test
+ @DisableFlags(FLAG_CREATE_CAR_USE_NOTIFICATIONS)
+ public void testCreateCar_Context_bindService_retry_success() throws Exception {
+ when(mContext.bindService(any(), any(), anyInt())).thenReturn(false).thenReturn(true);
+ when(mServiceManager.getService(CAR_SERVICE_BINDER_SERVICE_NAME))
+ .thenReturn(mService);
+
+ mCarBuilder.createCar(mContext);
+
+ Thread.sleep(DEFAULT_TIMEOUT_MS);
+ verify(mFakeProcess, never()).killProcess(anyInt());
+ }
+
+ private void createCar_Context_WaitForever_InvokeFromMain_CarServiceRegisteredLater()
+ throws Exception {
+ // Car service is registered after 200ms.
+ mEventHandler.postDelayed(() -> setCarServiceRegistered(), 200);
+
+ runOnMain(() -> {
+ Car car = mCarBuilder.createCar(mContext, null,
+ Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
+
+ assertThat(car).isNotNull();
+ assertThat(car.isConnected()).isTrue();
+
+ // mLifecycleListener should have been called as this is main thread.
+ mLifecycleListener.assertOneListenerCallAndClear(car, true);
+ });
+ }
+
+ private void runOnMain(Runnable runnable) throws InterruptedException {
+ var cdLatch = new CountDownLatch(1);
+ // Use a list to be effectively final. We only store one exception.
+ List<RuntimeException> exceptions = new ArrayList<>();
+ List<AssertionError> assertionErrors = new ArrayList<>();
+ mMainHandler.post(() -> {
+ try {
+ runnable.run();
+ } catch (RuntimeException e) {
+ exceptions.add(e);
+ } catch (AssertionError e) {
+ assertionErrors.add(e);
+ } finally {
+ cdLatch.countDown();
+ }
+ });
+ assertWithMessage("Main thread not finish before: " + DEFAULT_TIMEOUT_MS + " ms").that(
+ cdLatch.await(DEFAULT_TIMEOUT_MS, MILLISECONDS)).isTrue();
+ // Rethrow the caught errors to fail the test, if any.
+ if (!exceptions.isEmpty()) {
+ throw exceptions.get(0);
+ }
+ if (!assertionErrors.isEmpty()) {
+ throw assertionErrors.get(0);
+ }
+ }
+
+ private void finishTasksOnMain() throws InterruptedException {
+ // Do nothing on main just to make sure main finished handling the callbacks.
+ runOnMain(() -> {});
+ }
+}
diff --git a/tests/carservice_unit_test/src/android/car/cluster/ClusterHomeManagerUnitTest.java b/tests/CarLibUnitTest/src/android/car/cluster/ClusterHomeManagerUnitTest.java
similarity index 96%
rename from tests/carservice_unit_test/src/android/car/cluster/ClusterHomeManagerUnitTest.java
rename to tests/CarLibUnitTest/src/android/car/cluster/ClusterHomeManagerUnitTest.java
index 09a975c..50f7671 100644
--- a/tests/carservice_unit_test/src/android/car/cluster/ClusterHomeManagerUnitTest.java
+++ b/tests/CarLibUnitTest/src/android/car/cluster/ClusterHomeManagerUnitTest.java
@@ -34,18 +34,22 @@
import static org.mockito.Mockito.when;
import android.app.Activity;
-import android.car.Car;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.Window;
+import com.android.car.internal.ICarBase;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -58,9 +62,15 @@
* Unit tests for {@link ClusterHomeManager}
*/
@RunWith(MockitoJUnitRunner.class)
+@DisabledOnRavenwood(
+ reason = "SurfaceControl cannot be mocked because it uses dalvik.system.CloseGuard")
public final class ClusterHomeManagerUnitTest {
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().build();
+
@Mock
- private Car mCar;
+ private ICarBase mCar;
@Mock
private IBinder mBinder;
diff --git a/tests/carservice_unit_test/src/com/android/car/content/pm/CarPackageManagerUnitTest.java b/tests/CarLibUnitTest/src/android/car/content/pm/CarPackageManagerUnitTest.java
similarity index 65%
rename from tests/carservice_unit_test/src/com/android/car/content/pm/CarPackageManagerUnitTest.java
rename to tests/CarLibUnitTest/src/android/car/content/pm/CarPackageManagerUnitTest.java
index d2ff26e..5b31d8c 100644
--- a/tests/carservice_unit_test/src/com/android/car/content/pm/CarPackageManagerUnitTest.java
+++ b/tests/CarLibUnitTest/src/android/car/content/pm/CarPackageManagerUnitTest.java
@@ -13,38 +13,59 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.car.content.pm;
+package android.car.content.pm;
-import static android.car.testapi.CarMockitoHelper.mockHandleRemoteExceptionFromCarServiceWithDefaultValue;
+import static android.view.Display.DEFAULT_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.car.Car;
import android.car.CarVersion;
-import android.car.content.pm.CarPackageManager;
-import android.car.content.pm.ICarPackageManager;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.car.internal.ICarBase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
-public final class CarPackageManagerUnitTest extends AbstractExtendedMockitoTestCase {
+import java.util.concurrent.Executor;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class CarPackageManagerUnitTest {
+
+ // Need to fake Process.myUserHandle().
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProcessApp()
+ .build();
@Mock
- private Car mCar;
+ private ICarBase mCar;
@Mock
private ICarPackageManager mService;
+ @Mock
+ private Executor mExecutor;
+
+ @Mock
+ private CarPackageManager.BlockingUiCommandListener mBlockingUiCommandListener;
+
private CarPackageManager mMgr;
@Before
@@ -82,7 +103,6 @@
@Test
public void testGetTargetCarVersion_runtimeException() throws Exception {
- mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
RemoteException cause = new RemoteException("D'OH!");
when(mService.getTargetCarVersion("the.meaning.of.life")).thenThrow(cause);
@@ -94,7 +114,6 @@
@Test
public void testGetTargetCarVersion_serviceException_unexpectedErrorCode() throws Exception {
- mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
ServiceSpecificException cause = new ServiceSpecificException(666, "D'OH!");
when(mService.getTargetCarVersion("the.meaning.of.life")).thenThrow(cause);
@@ -105,8 +124,8 @@
}
@Test
+ @DisabledOnRavenwood(blockedBy = NameNotFoundException.class)
public void testGetTargetCarVersion_serviceException_notFound() throws Exception {
- mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
when(mService.getTargetCarVersion("the.meaning.of.life"))
.thenThrow(new ServiceSpecificException(CarPackageManager.ERROR_CODE_NO_PACKAGE,
"D'OH!"));
@@ -118,6 +137,37 @@
assertThat(e.getMessage()).doesNotContain("D'OH!");
}
+ @Test
+ public void registerBlockingUiCommandListener() throws Exception {
+ mMgr.registerBlockingUiCommandListener(DEFAULT_DISPLAY, mExecutor,
+ mBlockingUiCommandListener);
+
+ verify(mService).registerBlockingUiCommandListener(any(), anyInt());
+ }
+
+ @Test
+ public void registerBlockingUiCommandListener_sameListenerNotRegisterForAnotherDisplay()
+ throws Exception {
+ int tempDisplayId = 1;
+
+ mMgr.registerBlockingUiCommandListener(DEFAULT_DISPLAY, mExecutor,
+ mBlockingUiCommandListener);
+
+ assertThrows(IllegalStateException.class,
+ () -> mMgr.registerBlockingUiCommandListener(tempDisplayId, mExecutor,
+ mBlockingUiCommandListener));
+ }
+
+ @Test
+ public void unregisterBlockingUiCommandListener() throws Exception {
+ mMgr.registerBlockingUiCommandListener(DEFAULT_DISPLAY, mExecutor,
+ mBlockingUiCommandListener);
+
+ mMgr.unregisterBlockingUiCommandListener(mBlockingUiCommandListener);
+
+ verify(mService).unregisterBlockingUiCommandListener(any());
+ }
+
private void mockPackageName(String name) {
Context context = mock(Context.class);
when(context.getPackageName()).thenReturn(name);
diff --git a/tests/carservice_unit_test/src/android/car/evs/CarEvsManagerUnitTest.java b/tests/CarLibUnitTest/src/android/car/evs/CarEvsManagerUnitTest.java
similarity index 83%
rename from tests/carservice_unit_test/src/android/car/evs/CarEvsManagerUnitTest.java
rename to tests/CarLibUnitTest/src/android/car/evs/CarEvsManagerUnitTest.java
index 19f5ec2..a23751c 100644
--- a/tests/carservice_unit_test/src/android/car/evs/CarEvsManagerUnitTest.java
+++ b/tests/CarLibUnitTest/src/android/car/evs/CarEvsManagerUnitTest.java
@@ -16,94 +16,55 @@
package android.car.evs;
-import static android.car.evs.CarEvsManager.ERROR_BUSY;
import static android.car.evs.CarEvsManager.ERROR_NONE;
import static android.car.evs.CarEvsManager.ERROR_UNAVAILABLE;
-import static android.car.evs.CarEvsManager.SERVICE_STATE_ACTIVE;
-import static android.car.evs.CarEvsManager.SERVICE_STATE_INACTIVE;
-import static android.car.evs.CarEvsManager.SERVICE_STATE_REQUESTED;
-import static android.car.evs.CarEvsManager.SERVICE_STATE_UNAVAILABLE;
-import static android.car.evs.CarEvsManager.STREAM_EVENT_STREAM_STOPPED;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static android.car.feature.Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT;
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.AdditionalMatchers.or;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyFloat;
import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyList;
import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.car.evs.CarEvsBufferDescriptor;
-import android.car.evs.CarEvsManager;
-import android.car.evs.CarEvsManager.CarEvsError;
-import android.car.evs.CarEvsManager.CarEvsServiceState;
-import android.car.evs.CarEvsManager.CarEvsServiceType;
import android.car.evs.CarEvsManager.CarEvsStatusListener;
import android.car.evs.CarEvsManager.CarEvsStreamCallback;
-import android.car.evs.CarEvsManager.CarEvsStreamEvent;
-import android.car.evs.CarEvsStatus;
-import android.car.evs.ICarEvsService;
-import android.car.evs.ICarEvsStatusListener;
-import android.car.evs.ICarEvsStreamCallback;
-import android.car.feature.Flags;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
-import android.car.Car;
-import android.content.Context;
+import android.car.feature.FakeFeatureFlagsImpl;
import android.hardware.HardwareBuffer;
import android.os.IBinder;
-import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.platform.test.flag.junit.SetFlagsRule;
-import android.util.Log;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
-import com.android.car.evs.CarEvsService;
+import com.android.car.internal.ICarBase;
import org.junit.After;
import org.junit.Before;
-import org.junit.runner.RunWith;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-import java.util.concurrent.Executor;
+import org.mockito.junit.MockitoJUnitRunner;
/**
* <p>This class contains unit tests for the {@link CarEvsManager}.
*/
@RunWith(MockitoJUnitRunner.class)
-public final class CarEvsManagerUnitTest extends AbstractExtendedMockitoTestCase {
+public final class CarEvsManagerUnitTest {
- private static final String TAG = CarEvsManagerUnitTest.class.getSimpleName();
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().build();
- private static final int SERVICE_TYPES[] = {
+ private static final int[] SERVICE_TYPES = {
CarEvsManager.SERVICE_TYPE_REARVIEW, CarEvsManager.SERVICE_TYPE_SURROUNDVIEW,
CarEvsManager.SERVICE_TYPE_FRONTVIEW, CarEvsManager.SERVICE_TYPE_LEFTVIEW,
CarEvsManager.SERVICE_TYPE_RIGHTVIEW, CarEvsManager.SERVICE_TYPE_DRIVERVIEW,
@@ -111,7 +72,7 @@
CarEvsManager.SERVICE_TYPE_REAR_PASSENGERSVIEW,
};
- @Mock private Car mMockCar;
+ @Mock private ICarBase mMockCar;
@Mock private CarEvsStatusListener mMockCarEvsStatusListener;
@Mock private CarEvsStreamCallback mMockCarEvsStreamCallback;
@Mock private IBinder mMockBinder;
@@ -121,20 +82,15 @@
@Captor private ArgumentCaptor<ICarEvsStreamCallback> mCarEvsStreamCallbackCaptor;
@Captor private ArgumentCaptor<CarEvsStatus> mCarEvsStatusCaptor;
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
+ private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl();
private CarEvsManager mManager;
- public CarEvsManagerUnitTest() {
- super(TAG);
- }
-
@Before
public void setUp() throws Exception {
+ mFakeFeatureFlags.setFlag(FLAG_CAR_EVS_STREAM_MANAGEMENT, true);
when(mMockBinder.queryLocalInterface(anyString())).thenReturn(mMockICarEvsService);
- mManager = new CarEvsManager(mMockCar, mMockBinder);
+ mManager = new CarEvsManager(mMockCar, mMockBinder, mFakeFeatureFlags);
assertThat(mManager).isNotNull();
}
@@ -146,9 +102,9 @@
@Test
public void testSetStatusListenerWithInvalidArguments() {
assertThrows(NullPointerException.class,
- () -> mManager.setStatusListener(/* executor= */ null, /* listener= */ null));
+ () -> mManager.setStatusListener(/* executor= */ null, /* listener= */ null));
assertThrows(NullPointerException.class,
- () -> mManager.setStatusListener(/* executor= */ null, mMockCarEvsStatusListener));
+ () -> mManager.setStatusListener(/* executor= */ null, mMockCarEvsStatusListener));
}
@Test
@@ -217,6 +173,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = HardwareBuffer.class)
public void testStartVideoStreamWithoutToken() throws Exception {
when(mMockICarEvsService
.startVideoStream(anyInt(), any(), mCarEvsStreamCallbackCaptor.capture()))
@@ -228,9 +185,10 @@
.isEqualTo(ERROR_NONE);
ICarEvsStreamCallback cb = mCarEvsStreamCallbackCaptor.getValue();
- cb.onStreamEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED);
- verify(mMockCarEvsStreamCallback, atLeastOnce())
- .onStreamEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED);
+ cb.onStreamEvent(CarEvsManager.SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED);
+ verify(mMockCarEvsStreamCallback, atLeastOnce()).onStreamEvent(
+ CarEvsManager.SERVICE_TYPE_REARVIEW, CarEvsManager.STREAM_EVENT_STREAM_STOPPED);
int bufferId = 1;
HardwareBuffer hwbuffer =
@@ -238,7 +196,8 @@
/* format= */ HardwareBuffer.RGBA_8888,
/* layers= */ 1,
/* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN);
- CarEvsBufferDescriptor buffer = new CarEvsBufferDescriptor(bufferId, hwbuffer);
+ CarEvsBufferDescriptor buffer = new CarEvsBufferDescriptor(bufferId,
+ CarEvsManager.SERVICE_TYPE_REARVIEW, hwbuffer);
cb.onNewFrame(buffer);
verify(mMockCarEvsStreamCallback, atLeastOnce()).onNewFrame(buffer);
}
@@ -289,7 +248,6 @@
@Test
public void testGetCurrentStatus() throws Exception {
- mSetFlagsRule.disableFlags(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT);
when(mMockICarEvsService.getCurrentStatus(anyInt()))
.thenReturn(new CarEvsStatus(CarEvsManager.SERVICE_TYPE_REARVIEW,
CarEvsManager.SERVICE_STATE_INACTIVE));
@@ -303,7 +261,6 @@
@Test
public void testGetCurrentStatusWithType() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT);
for (int type : SERVICE_TYPES) {
when(mMockICarEvsService.getCurrentStatus(anyInt()))
.thenReturn(new CarEvsStatus(type, CarEvsManager.SERVICE_STATE_INACTIVE));
@@ -318,7 +275,6 @@
@Test
public void testGetCurrentStatusRemoteExceptionThrown() throws Exception {
- mSetFlagsRule.disableFlags(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT);
when(mMockICarEvsService.getCurrentStatus(anyInt())).thenThrow(new RemoteException());
CarEvsStatus currentStatus = mManager.getCurrentStatus();
@@ -329,7 +285,6 @@
@Test
public void testGetCurrentStatusWithTypeRemoteExceptionThrown() throws Exception {
- mSetFlagsRule.enableFlags(Flags.FLAG_CAR_EVS_STREAM_MANAGEMENT);
when(mMockICarEvsService.getCurrentStatus(anyInt())).thenThrow(new RemoteException());
for (int type : SERVICE_TYPES) {
@@ -370,6 +325,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = HardwareBuffer.class)
public void testReturnFrameBuffer() throws Exception {
int bufferId = 1;
HardwareBuffer hwbuffer =
@@ -385,6 +341,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = HardwareBuffer.class)
public void testReturnFrameBufferRemoteExceptionThrown() throws Exception {
doThrow(new RemoteException()).when(mMockICarEvsService).returnFrameBuffer(any());
int bufferId = 1;
@@ -457,6 +414,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = HardwareBuffer.class)
public void testCarEvsBufferDescriptor() {
CarEvsBufferDescriptor[] arr = CarEvsBufferDescriptor.CREATOR.newArray(3);
assertThat(arr.length).isEqualTo(3);
diff --git a/tests/CarLibHostUnitTest/src/android/car/hardware/property/AreaIdConfigTest.java b/tests/CarLibUnitTest/src/android/car/hardware/property/AreaIdConfigTest.java
similarity index 100%
rename from tests/CarLibHostUnitTest/src/android/car/hardware/property/AreaIdConfigTest.java
rename to tests/CarLibUnitTest/src/android/car/hardware/property/AreaIdConfigTest.java
diff --git a/tests/CarLibHostUnitTest/src/android/car/hardware/property/CarPropertyErrorCodesUnitTest.java b/tests/CarLibUnitTest/src/android/car/hardware/property/CarPropertyErrorCodesUnitTest.java
similarity index 100%
rename from tests/CarLibHostUnitTest/src/android/car/hardware/property/CarPropertyErrorCodesUnitTest.java
rename to tests/CarLibUnitTest/src/android/car/hardware/property/CarPropertyErrorCodesUnitTest.java
diff --git a/tests/CarLibHostUnitTest/src/android/car/hardware/property/CarPropertyManagerUnitTest.java b/tests/CarLibUnitTest/src/android/car/hardware/property/CarPropertyManagerUnitTest.java
similarity index 99%
rename from tests/CarLibHostUnitTest/src/android/car/hardware/property/CarPropertyManagerUnitTest.java
rename to tests/CarLibUnitTest/src/android/car/hardware/property/CarPropertyManagerUnitTest.java
index 8b48120..4084663 100644
--- a/tests/CarLibHostUnitTest/src/android/car/hardware/property/CarPropertyManagerUnitTest.java
+++ b/tests/CarLibUnitTest/src/android/car/hardware/property/CarPropertyManagerUnitTest.java
@@ -84,10 +84,11 @@
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.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
import java.time.Duration;
import java.util.ArrayList;
@@ -98,6 +99,7 @@
/**
* <p>This class contains unit tests for the {@link CarPropertyManager}.
*/
+@RunWith(MockitoJUnitRunner.class)
public final class CarPropertyManagerUnitTest {
// Required to set the process ID and set the "main" thread for this test, otherwise
// getMainLooper will return null.
@@ -227,8 +229,6 @@
@Before
public void setUp() throws RemoteException {
- MockitoAnnotations.initMocks(this);
-
mMainHandler = new Handler(Looper.getMainLooper());
when(mCar.getContext()).thenReturn(mContext);
when(mCar.getEventHandler()).thenReturn(mMainHandler);
diff --git a/tests/carservice_unit_test/src/android/car/media/CarAudioManagerUnitTest.java b/tests/CarLibUnitTest/src/android/car/media/CarAudioManagerUnitTest.java
similarity index 98%
rename from tests/carservice_unit_test/src/android/car/media/CarAudioManagerUnitTest.java
rename to tests/CarLibUnitTest/src/android/car/media/CarAudioManagerUnitTest.java
index 3564069..52c75b4 100644
--- a/tests/carservice_unit_test/src/android/car/media/CarAudioManagerUnitTest.java
+++ b/tests/CarLibUnitTest/src/android/car/media/CarAudioManagerUnitTest.java
@@ -38,7 +38,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.car.Car;
import android.car.CarOccupantZoneManager.OccupantZoneInfo;
import android.car.test.AbstractExpectableTestCase;
import android.content.Context;
@@ -50,11 +49,14 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
+import android.platform.test.ravenwood.RavenwoodRule;
-import com.android.car.CarServiceUtils;
import com.android.car.audio.AudioDeviceInfoBuilder;
+import com.android.car.internal.ICarBase;
+import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -68,6 +70,11 @@
@RunWith(MockitoJUnitRunner.class)
public final class CarAudioManagerUnitTest extends AbstractExpectableTestCase {
+ // Required to set the process ID and set the "main" thread for this test, otherwise
+ // getMainLooper will return null.
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().setProcessApp()
+ .setProvideMainThread(true).build();
private static final String Car_AUDIO_MANAGER_TEST_THREAD_NAME = "CarAudioManagerUnitTest";
private static final String MICROPHONE_ADDRESS = "Built-In Mic";
@@ -101,13 +108,12 @@
private static final Executor DIRECT_EXECUTOR = Runnable::run;
- private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
- Car_AUDIO_MANAGER_TEST_THREAD_NAME);
- private final Handler mHandler = new Handler(mHandlerThread.getLooper());
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
private final RemoteException mRemoteException = new RemoteException();
@Mock
- private Car mCar;
+ private ICarBase mCar;
@Mock
private IBinder mBinderMock;
@Mock
@@ -152,6 +158,10 @@
@Before
public void setUp() throws Exception {
+ mHandlerThread = new HandlerThread(Car_AUDIO_MANAGER_TEST_THREAD_NAME);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
when(mBinderMock.queryLocalInterface(anyString())).thenReturn(mServiceMock);
when(mCar.getContext()).thenReturn(mContextMock);
when(mCar.getEventHandler()).thenReturn(mHandler);
@@ -163,6 +173,12 @@
when(mServiceMock.registerAudioZoneConfigsChangeCallback(any())).thenReturn(true);
}
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quitSafely();
+ mHandlerThread.join();
+ }
+
@Test
public void isAudioFeatureEnabled() throws Exception {
when(mServiceMock.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING)).thenReturn(true);
@@ -1756,9 +1772,11 @@
@Test
public void getInputDevicesForZoneId() throws Exception {
- List<AudioDeviceAttributes> inputDevicesFromService = List.of(
- new AudioDeviceAttributes(TEST_MICROPHONE_INPUT_DEVICE), new AudioDeviceAttributes(
- TEST_TUNER_INPUT_DEVICE));
+ AudioDeviceAttributes microphoneAttr = mock(AudioDeviceAttributes.class);
+ when(microphoneAttr.getAddress()).thenReturn(MICROPHONE_ADDRESS);
+ AudioDeviceAttributes tunerAttr = mock(AudioDeviceAttributes.class);
+ when(tunerAttr.getAddress()).thenReturn(FM_TUNER_ADDRESS);
+ List<AudioDeviceAttributes> inputDevicesFromService = List.of(microphoneAttr, tunerAttr);
when(mServiceMock.getInputDevicesForZoneId(PRIMARY_AUDIO_ZONE)).thenReturn(
inputDevicesFromService);
when(mAudioManagerMock.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
@@ -1773,10 +1791,10 @@
@Test
public void getInputDevicesForZoneId_withInvalidDevicesInAudioManager() throws Exception {
- List<AudioDeviceAttributes> inputDevicesFromService = List.of(new AudioDeviceAttributes(
- TEST_MICROPHONE_INPUT_DEVICE));
+ AudioDeviceAttributes microphoneAttr = mock(AudioDeviceAttributes.class);
+ when(microphoneAttr.getAddress()).thenReturn(MICROPHONE_ADDRESS);
when(mServiceMock.getInputDevicesForZoneId(PRIMARY_AUDIO_ZONE)).thenReturn(
- inputDevicesFromService);
+ List.of(microphoneAttr));
when(mAudioManagerMock.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
new AudioDeviceInfo[]{TEST_MEDIA_OUTPUT_DEVICE, TEST_TUNER_INPUT_DEVICE});
diff --git a/tests/carservice_unit_test/src/android/car/media/CarMediaManagerUnitTest.java b/tests/CarLibUnitTest/src/android/car/media/CarMediaManagerUnitTest.java
similarity index 95%
rename from tests/carservice_unit_test/src/android/car/media/CarMediaManagerUnitTest.java
rename to tests/CarLibUnitTest/src/android/car/media/CarMediaManagerUnitTest.java
index 0812a2c..90f214e 100644
--- a/tests/carservice_unit_test/src/android/car/media/CarMediaManagerUnitTest.java
+++ b/tests/CarLibUnitTest/src/android/car/media/CarMediaManagerUnitTest.java
@@ -24,15 +24,18 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.car.Car;
import android.car.test.AbstractExpectableTestCase;
import android.content.ComponentName;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.car.internal.ICarBase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -44,10 +47,15 @@
@RunWith(MockitoJUnitRunner.class)
public final class CarMediaManagerUnitTest extends AbstractExpectableTestCase {
+ // Need to setup ravenwood rule to stub out Binder.clearCallingIdentity/restoreCallingIdentity
+ // on host.
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().build();
+
private static final int TEST_MODE = CarMediaManager.MEDIA_SOURCE_MODE_BROWSE;
private static final int TEST_USER_ID = 100;
@Mock
- private Car mCarMock;
+ private ICarBase mCarMock;
@Mock
private IBinder mBinderMock;
@Mock
diff --git a/tests/CarLibHostUnitTest/src/android/car/remoteaccess/CarRemoteAccessManagerUnitTest.java b/tests/CarLibUnitTest/src/android/car/remoteaccess/CarRemoteAccessManagerUnitTest.java
similarity index 99%
rename from tests/CarLibHostUnitTest/src/android/car/remoteaccess/CarRemoteAccessManagerUnitTest.java
rename to tests/CarLibUnitTest/src/android/car/remoteaccess/CarRemoteAccessManagerUnitTest.java
index 5872d50..f3c32b6 100644
--- a/tests/CarLibHostUnitTest/src/android/car/remoteaccess/CarRemoteAccessManagerUnitTest.java
+++ b/tests/CarLibUnitTest/src/android/car/remoteaccess/CarRemoteAccessManagerUnitTest.java
@@ -52,10 +52,11 @@
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.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
import java.time.Duration;
import java.util.List;
@@ -65,6 +66,7 @@
/**
* <p>This class contains unit tests for the {@link CarRemoteAccessManager}.
*/
+@RunWith(MockitoJUnitRunner.class)
public final class CarRemoteAccessManagerUnitTest extends AbstractExpectableTestCase {
private static final int DEFAULT_TIMEOUT = 3000;
@@ -93,8 +95,6 @@
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
when(mBinder.queryLocalInterface(anyString())).thenReturn(mService);
when(mCar.handleRemoteExceptionFromCarService(any(RemoteException.class), any()))
.thenAnswer((inv) -> {
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java b/tests/CarLibUnitTest/src/android/car/user/CarUserManagerUnitTest.java
similarity index 95%
rename from tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
rename to tests/CarLibUnitTest/src/android/car/user/CarUserManagerUnitTest.java
index d70d59e..d125c8b 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
+++ b/tests/CarLibUnitTest/src/android/car/user/CarUserManagerUnitTest.java
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.car.user;
+package android.car.user;
-import static android.car.testapi.CarMockitoHelper.mockHandleRemoteExceptionFromCarServiceWithDefaultValue;
-import static android.car.testapi.CarTestingHelper.getResult;
+import static android.car.test.mock.CarMockitoHelper.mockHandleRemoteExceptionFromCarServiceWithDefaultValue;
+import static android.car.test.util.CarTestingHelper.getResult;
import static android.os.UserHandle.SYSTEM;
import static android.os.UserHandle.USER_SYSTEM;
@@ -39,23 +39,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.car.Car;
import android.car.ICarUserService;
import android.car.SyncResultCallback;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.AbstractExpectableTestCase;
import android.car.test.util.UserTestingHelper;
-import android.car.user.CarUserManager;
import android.car.user.CarUserManager.UserHandleSwitchUiCallback;
import android.car.user.CarUserManager.UserLifecycleListener;
import android.car.user.CarUserManager.UserSwitchUiCallback;
-import android.car.user.UserCreationRequest;
-import android.car.user.UserCreationResult;
-import android.car.user.UserIdentificationAssociationResponse;
-import android.car.user.UserLifecycleEventFilter;
-import android.car.user.UserRemovalRequest;
-import android.car.user.UserRemovalResult;
-import android.car.user.UserSwitchRequest;
-import android.car.user.UserSwitchResult;
import android.car.util.concurrent.AndroidFuture;
import android.car.util.concurrent.AsyncFuture;
import android.content.Context;
@@ -67,21 +57,36 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.ravenwood.RavenwoodRule;
+import com.android.car.internal.ICarBase;
import com.android.car.internal.ResultCallbackImpl;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
-public final class CarUserManagerUnitTest extends AbstractExtendedMockitoTestCase {
+@RunWith(MockitoJUnitRunner.class)
+public final class CarUserManagerUnitTest extends AbstractExpectableTestCase {
+
+ // Need to a rule to setup host implementation for SystemProperties.get which is used inside
+ // CarSystemProperties.getUserHalTimeout() during CarUserManager initialization.
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setSystemPropertyImmutable("android.car.user_hal_timeout", "")
+ // AndroidFuture uses getMainHandler
+ .setProvideMainThread(true)
+ .build();
@Mock
- private Car mCar;
+ private ICarBase mCar;
@Mock
private UserManager mUserManager;
@Mock
@@ -91,18 +96,10 @@
private CarUserManager mMgr;
- public CarUserManagerUnitTest() {
- super(CarUserManager.TAG);
- }
-
- @Override
- protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
- session.spyStatic(UserManager.class);
- }
-
@Before
public void setFixtures() {
- mMgr = new CarUserManager(mCar, mService, mUserManager);
+ mMgr = new CarUserManager(mCar, mService, mUserManager,
+ /* isHeadlessSystemUserMode= */ true);
when(mCar.getContext()).thenReturn(mMockContext);
}
@@ -145,7 +142,6 @@
@Test
public void testIsValidUserId_headlessSystemUser() {
- mockIsHeadlessSystemUserMode(true);
setExistingUsers(USER_SYSTEM);
assertThat(mMgr.isValidUser(USER_SYSTEM)).isFalse();
@@ -153,7 +149,6 @@
@Test
public void testIsValidUser_headlessSystemUser() {
- mockIsHeadlessSystemUserMode(true);
setExistingUsers(USER_SYSTEM);
assertThat(mMgr.isValidUser(SYSTEM)).isFalse();
@@ -521,7 +516,7 @@
@Test
public void testCreateUser_remoteException() throws Exception {
- expectServiceCreateUserFails("dude", UserManager.USER_TYPE_FULL_SECONDARY, 42);
+ expectServiceCreateUserFails();
mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
SyncResultCallback<UserCreationResult> userCreationResultCallback =
new SyncResultCallback<>();
@@ -538,7 +533,7 @@
@Test
public void testCreateUserId_remoteException() throws Exception {
- expectServiceCreateUserFails("dude", UserManager.USER_TYPE_FULL_SECONDARY, 42);
+ expectServiceCreateUserFails();
mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
AsyncFuture<UserCreationResult> future = mMgr.createUser("dude", 42);
@@ -603,7 +598,7 @@
@Test
public void testCreateGuest_remoteException() throws Exception {
- expectServiceCreateUserFails("dudeGuest", UserManager.USER_TYPE_FULL_GUEST, 0);
+ expectServiceCreateUserFails();
mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
AsyncFuture<UserCreationResult> future = mMgr.createGuest("dudeGuest");
@@ -840,8 +835,7 @@
}).when(mService).createUser(notNull(), anyInt(), notNull());
}
- private void expectServiceCreateUserFails(@Nullable String name,
- @NonNull String userType, @UserInfoFlag int flags) throws RemoteException {
+ private void expectServiceCreateUserFails() throws RemoteException {
doThrow(new RemoteException("D'OH!")).when(mService).createUser(notNull(), anyInt(),
notNull());
}
@@ -854,4 +848,9 @@
.collect(Collectors.toList());
when(mUserManager.getUserHandles(/* excludeDying= */ true)).thenReturn(userHandles);
}
+
+ private void mockIsHeadlessSystemUserMode(boolean mode) {
+ mMgr = new CarUserManager(mCar, mService, mUserManager,
+ /* isHeadlessSystemUserMode= */ mode);
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/wifi/CarWifiManagerUnitTest.java b/tests/CarLibUnitTest/src/android/car/wifi/CarWifiManagerUnitTest.java
similarity index 95%
rename from tests/carservice_unit_test/src/com/android/car/wifi/CarWifiManagerUnitTest.java
rename to tests/CarLibUnitTest/src/android/car/wifi/CarWifiManagerUnitTest.java
index 3289cbc..4527885 100644
--- a/tests/carservice_unit_test/src/com/android/car/wifi/CarWifiManagerUnitTest.java
+++ b/tests/CarLibUnitTest/src/android/car/wifi/CarWifiManagerUnitTest.java
@@ -22,12 +22,13 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.car.Car;
import android.car.wifi.CarWifiManager;
import android.car.wifi.ICarWifi;
import android.os.IBinder;
import android.os.RemoteException;
+import com.android.car.internal.ICarBase;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -36,7 +37,7 @@
@RunWith(MockitoJUnitRunner.class)
public class CarWifiManagerUnitTest {
- @Mock private Car mCar;
+ @Mock private ICarBase mCar;
@Mock private IBinder mBinder;
@Mock private ICarWifi mService;
diff --git a/tests/CarLibUnitTest/src/com/android/car/audio/AudioDeviceInfoBuilder.java b/tests/CarLibUnitTest/src/com/android/car/audio/AudioDeviceInfoBuilder.java
new file mode 100644
index 0000000..f5989e1
--- /dev/null
+++ b/tests/CarLibUnitTest/src/com/android/car/audio/AudioDeviceInfoBuilder.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.audio;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.media.AudioDeviceInfo;
+import android.media.AudioGain;
+
+public final class AudioDeviceInfoBuilder {
+
+ private String mAddressName;
+ private AudioGain[] mAudioGains;
+ private int mDeviceType = AudioDeviceInfo.TYPE_BUS;
+ private boolean mIsSource;
+ private boolean mBuilt = false;
+
+ /**
+ * Sets the audio gains.
+ */
+ public AudioDeviceInfoBuilder setAudioGains(AudioGain ... audioGains) {
+ mAudioGains = audioGains;
+ return this;
+ }
+
+ /**
+ * Sets the address name.
+ */
+ public AudioDeviceInfoBuilder setAddressName(String addressName) {
+ mAddressName = addressName;
+ return this;
+ }
+
+ /**
+ * Sets the device type.
+ */
+ public AudioDeviceInfoBuilder setType(int deviceType) {
+ mDeviceType = deviceType;
+ return this;
+ }
+
+ /**
+ * Sets whether is source.
+ */
+ public AudioDeviceInfoBuilder setIsSource(boolean isSource) {
+ mIsSource = isSource;
+ return this;
+ }
+
+ /**
+ * Builds the audio device info.
+ */
+ public AudioDeviceInfo build() {
+ if (mBuilt) {
+ throw new IllegalStateException("A builder is only supposed to be built once");
+ }
+ mBuilt = true;
+ AudioDeviceInfo audioDeviceInfo = mock(AudioDeviceInfo.class);
+ when(audioDeviceInfo.getAddress()).thenReturn(mAddressName);
+ when(audioDeviceInfo.isSource()).thenReturn(mIsSource);
+ return audioDeviceInfo;
+ }
+}
diff --git a/tests/CarLibHostUnitTest/src/com/android/car/internal/property/CarPropertyHelperUnitTest.java b/tests/CarLibUnitTest/src/com/android/car/internal/property/CarPropertyHelperUnitTest.java
similarity index 100%
rename from tests/CarLibHostUnitTest/src/com/android/car/internal/property/CarPropertyHelperUnitTest.java
rename to tests/CarLibUnitTest/src/com/android/car/internal/property/CarPropertyHelperUnitTest.java
diff --git a/tests/carservice_unit_test/src/com/android/car/internal/util/PairSparseArrayUnitTest.java b/tests/CarLibUnitTest/src/com/android/car/internal/util/PairSparseArrayUnitTest.java
similarity index 88%
rename from tests/carservice_unit_test/src/com/android/car/internal/util/PairSparseArrayUnitTest.java
rename to tests/CarLibUnitTest/src/com/android/car/internal/util/PairSparseArrayUnitTest.java
index 2b98cef..aa7ffdf 100644
--- a/tests/carservice_unit_test/src/com/android/car/internal/util/PairSparseArrayUnitTest.java
+++ b/tests/CarLibUnitTest/src/com/android/car/internal/util/PairSparseArrayUnitTest.java
@@ -18,11 +18,11 @@
import static com.google.common.truth.Truth.assertThat;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.AbstractExpectableTestCase;
import org.junit.Test;
-public class PairSparseArrayUnitTest extends AbstractExtendedMockitoTestCase {
+public class PairSparseArrayUnitTest extends AbstractExpectableTestCase {
private static final int FIRST_KEY = 10;
private static final int SECOND_KEY = 20;
private static final int THIRD_KEY = 30;
@@ -177,4 +177,24 @@
assertThat(map.getFirstKeys()).containsExactly(
FIRST_KEY, SECOND_KEY, THIRD_KEY, FOURTH_KEY);
}
+
+ @Test
+ public void test_clone() {
+ PairSparseArray<Integer> map = new PairSparseArray<>(16);
+
+ map.put(FIRST_KEY, FIRST_KEY, VALUE_1);
+ map.put(SECOND_KEY, FIRST_KEY, VALUE_2);
+ map.put(SECOND_KEY, SECOND_KEY, VALUE_2);
+
+ PairSparseArray<Integer> cloned = map.clone();
+
+ expectThat(cloned.get(FIRST_KEY, FIRST_KEY)).isEqualTo(VALUE_1);
+ expectThat(cloned.get(SECOND_KEY, FIRST_KEY)).isEqualTo(VALUE_2);
+ expectThat(cloned.get(SECOND_KEY, SECOND_KEY)).isEqualTo(VALUE_2);
+
+ cloned.put(SECOND_KEY, SECOND_KEY, VALUE_1);
+
+ expectThat(cloned.get(SECOND_KEY, SECOND_KEY)).isEqualTo(VALUE_1);
+ expectThat(map.get(SECOND_KEY, SECOND_KEY)).isEqualTo(VALUE_2);
+ }
}
diff --git a/tests/CarTelemetryApp/tests/Android.bp b/tests/CarTelemetryApp/tests/Android.bp
index 82b7159..620689b 100644
--- a/tests/CarTelemetryApp/tests/Android.bp
+++ b/tests/CarTelemetryApp/tests/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_automotive",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/CarVoiceServiceTriggerApp/Android.bp b/tests/CarVoiceServiceTriggerApp/Android.bp
index 9882701..12f2c19 100644
--- a/tests/CarVoiceServiceTriggerApp/Android.bp
+++ b/tests/CarVoiceServiceTriggerApp/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/ConcurrentHotwordDetector/Android.bp b/tests/ConcurrentHotwordDetector/Android.bp
index e149446..6a6de4d 100644
--- a/tests/ConcurrentHotwordDetector/Android.bp
+++ b/tests/ConcurrentHotwordDetector/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/ConcurrentHotwordDetectorOne/Android.bp b/tests/ConcurrentHotwordDetectorOne/Android.bp
index feb8275..2214432 100644
--- a/tests/ConcurrentHotwordDetectorOne/Android.bp
+++ b/tests/ConcurrentHotwordDetectorOne/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/CustomizationTool/Android.bp b/tests/CustomizationTool/Android.bp
new file mode 100644
index 0000000..e7f143a
--- /dev/null
+++ b/tests/CustomizationTool/Android.bp
@@ -0,0 +1,55 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+ name: "allowed_privapp_com.android.car.customization.tool",
+ sub_dir: "permissions",
+ src: "com.android.car.customization.tool.xml",
+ filename_from_src: true,
+}
+
+android_app {
+ name: "AaosCustomizationTool",
+ srcs: [
+ "src/**/*.kt",
+ "src/**/*.java",
+ ],
+ resource_dirs: ["res"],
+
+ platform_apis: true,
+ certificate: "platform",
+ privileged: true,
+
+ required: [
+ "allowed_privapp_com.android.car.customization.tool",
+ ],
+ optimize: {
+ optimize: true,
+ enabled: true,
+ },
+ enforce_uses_libs: false,
+ static_libs: [
+ "androidx.recyclerview_recyclerview",
+ "androidx-constraintlayout_constraintlayout",
+ "dagger2",
+ "jsr330",
+ ],
+ plugins: ["dagger2-compiler"],
+ manifest: "AndroidManifest.xml",
+}
diff --git a/tests/CustomizationTool/AndroidManifest.xml b/tests/CustomizationTool/AndroidManifest.xml
new file mode 100644
index 0000000..37530e4
--- /dev/null
+++ b/tests/CustomizationTool/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.car.customization.tool">
+
+ <!-- Required to discover all packages connected to RROs -->
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
+ <!-- Required to enable/disable RROs -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES" />
+
+ <!-- Required to enable/disable plugins -->
+ <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+
+ <application>
+ <service
+ android:name="com.android.car.customization.tool.CustomizationToolService"
+ android:exported="true"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService" />
+ </intent-filter>
+ <meta-data
+ android:name="android.accessibilityservice"
+ android:resource="@xml/customization_tool_service" />
+ </service>
+ <uses-library
+ android:name="com.android.oem.tokens"
+ android:required="false" />
+ </application>
+
+</manifest>
diff --git a/tests/CustomizationTool/OWNERS b/tests/CustomizationTool/OWNERS
new file mode 100644
index 0000000..882a608
--- /dev/null
+++ b/tests/CustomizationTool/OWNERS
@@ -0,0 +1 @@
+arecchi@google.com
diff --git a/tests/CustomizationTool/README.md b/tests/CustomizationTool/README.md
new file mode 100644
index 0000000..0cd23c4
--- /dev/null
+++ b/tests/CustomizationTool/README.md
@@ -0,0 +1,28 @@
+# Customization Tool
+
+This is a privileged app for testing UI customizations (e.g. RROs) on AAOS.
+
+## Usage
+
+**Build and install**
+
+```
+m -j CustomizationTool && adb install $ANDROID_PRODUCT_OUT/system/priv-app/CustomizationTool/CustomizationTool.apk
+```
+
+**Start the service**
+
+The easiest way to start the service is through KitchenSink through the "Customization Tool" option
+in the menu. Otherwise adb can be used:
+
+```
+adb shell settings put secure enabled_accessibility_services com.android.car.customization.tool/com.android.car.customization.tool.CustomizationToolService
+```
+
+**Stop the service**
+
+The service can be stopped using KitchenSink or using adb:
+
+```
+adb shell settings put secure enabled_accessibility_services null
+```
diff --git a/tests/CustomizationTool/com.android.car.customization.tool.xml b/tests/CustomizationTool/com.android.car.customization.tool.xml
new file mode 100644
index 0000000..7cee382
--- /dev/null
+++ b/tests/CustomizationTool/com.android.car.customization.tool.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<permissions>
+ <privapp-permissions package="com.android.car.customization.tool">
+ <!-- Required to enable/disable RROs -->
+ <permission name="android.permission.CHANGE_OVERLAY_PACKAGES" />
+ <permission name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <!-- Required to discover all packages connected to RROs -->
+ <permission name="android.permission.QUERY_ALL_PACKAGES" />
+ <!-- Required to enable/disable plugins -->
+ <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+ </privapp-permissions>
+</permissions>
diff --git a/tests/CustomizationTool/res/color/menu_text_color.xml b/tests/CustomizationTool/res/color/menu_text_color.xml
new file mode 100644
index 0000000..640eebd
--- /dev/null
+++ b/tests/CustomizationTool/res/color/menu_text_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="#929292" android:state_enabled="false" />
+ <item android:color="@color/text_primary" />
+</selector>
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/drawable/circle_bg.xml b/tests/CustomizationTool/res/drawable/circle_bg.xml
new file mode 100644
index 0000000..9498f39
--- /dev/null
+++ b/tests/CustomizationTool/res/drawable/circle_bg.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/ripple_color">
+ <item>
+ <shape android:shape="oval">
+ <solid android:color="@color/component_background_color" />
+ <size
+ android:width="1dp"
+ android:height="1dp" />
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/drawable/dropdown_background.xml b/tests/CustomizationTool/res/drawable/dropdown_background.xml
new file mode 100644
index 0000000..89504bc
--- /dev/null
+++ b/tests/CustomizationTool/res/drawable/dropdown_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/dropdown_background_color" />
+ <corners android:radius="10dp" />
+</shape>
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/drawable/dropdown_item_background.xml b/tests/CustomizationTool/res/drawable/dropdown_item_background.xml
new file mode 100644
index 0000000..9ddc1e5
--- /dev/null
+++ b/tests/CustomizationTool/res/drawable/dropdown_item_background.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/ripple_color">
+ <item>
+ <selector>
+ <item android:state_selected="true">
+ <shape android:shape="rectangle">
+ <solid android:color="@color/dropdown_item_background_color" />
+ <corners android:radius="40dp" />
+ </shape>
+ </item>
+
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/dropdown_background_color" />
+ <corners android:radius="40dp" />
+ </shape>
+ </item>
+ </selector>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/drawable/ic_back.xml b/tests/CustomizationTool/res/drawable/ic_back.xml
new file mode 100644
index 0000000..530c743
--- /dev/null
+++ b/tests/CustomizationTool/res/drawable/ic_back.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="40dp"
+ android:height="40dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@color/text_primary"
+ android:pathData="M313,520L537,744L480,800L160,480L480,160L537,216L313,440L800,440L800,520L313,520Z" />
+</vector>
diff --git a/tests/CustomizationTool/res/drawable/ic_expand_more.xml b/tests/CustomizationTool/res/drawable/ic_expand_more.xml
new file mode 100644
index 0000000..7b7aaf0
--- /dev/null
+++ b/tests/CustomizationTool/res/drawable/ic_expand_more.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="40dp"
+ android:height="40dp"
+ android:tint="?android:textColor"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <group android:translateY="100">
+ <path
+ android:fillColor="@color/menu_text_color"
+ android:pathData="M480,615L240,375L296,319L480,503L664,319L720,375L480,615Z" />
+ </group>
+
+</vector>
diff --git a/tests/CustomizationTool/res/drawable/ic_tune.xml b/tests/CustomizationTool/res/drawable/ic_tune.xml
new file mode 100644
index 0000000..32b21e1
--- /dev/null
+++ b/tests/CustomizationTool/res/drawable/ic_tune.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="40dp"
+ android:height="40dp"
+ android:tint="?android:textColor"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M440,840L440,600L520,600L520,680L840,680L840,760L520,760L520,840L440,840ZM120,760L120,680L360,680L360,760L120,760ZM280,600L280,520L120,520L120,440L280,440L280,360L360,360L360,600L280,600ZM440,520L440,440L840,440L840,520L440,520ZM600,360L600,120L680,120L680,200L840,200L840,280L680,280L680,360L600,360ZM120,280L120,200L520,200L520,280L120,280Z" />
+</vector>
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/drawable/ic_up.xml b/tests/CustomizationTool/res/drawable/ic_up.xml
new file mode 100644
index 0000000..95e6c39
--- /dev/null
+++ b/tests/CustomizationTool/res/drawable/ic_up.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="40dp"
+ android:height="40dp"
+ android:tint="?android:textColor"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M440,800L440,313L216,537L160,480L480,160L800,480L744,537L520,313L520,800L440,800Z" />
+</vector>
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/drawable/panel_background.xml b/tests/CustomizationTool/res/drawable/panel_background.xml
new file mode 100644
index 0000000..eb585fd
--- /dev/null
+++ b/tests/CustomizationTool/res/drawable/panel_background.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#C0202020" />
+ <corners android:radius="10dp" />
+</shape>
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/drawable/round_corner_bg.xml b/tests/CustomizationTool/res/drawable/round_corner_bg.xml
new file mode 100644
index 0000000..1ac79a3
--- /dev/null
+++ b/tests/CustomizationTool/res/drawable/round_corner_bg.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/ripple_color">
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/component_background_color" />
+ <corners android:radius="32dp" />
+ </shape>
+ </item>
+</ripple>
diff --git a/tests/CustomizationTool/res/layout/main_layout.xml b/tests/CustomizationTool/res/layout/main_layout.xml
new file mode 100644
index 0000000..ced9ee9
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/main_layout.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<com.android.car.customization.tool.ui.MainLayoutView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/main_Layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="10dp"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <ImageButton
+ android:id="@+id/entry_point"
+ android:layout_width="@dimen/entry_point_button_size"
+ android:layout_height="@dimen/entry_point_button_size"
+ android:background="@drawable/circle_bg"
+ android:src="@drawable/ic_tune" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/menu"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp" />
+
+ <LinearLayout
+ android:id="@+id/control_panel_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:orientation="vertical"
+ android:background="@drawable/panel_background">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/control_panel_header"
+ android:layout_width="match_parent"
+ android:layout_height="60dp"
+ android:layout_marginHorizontal="10dp"
+ android:layout_marginVertical="20dp" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/control_panel_items"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dp"
+ android:layout_marginHorizontal="25dp" />
+ </LinearLayout>
+
+</com.android.car.customization.tool.ui.MainLayoutView>
+
diff --git a/tests/CustomizationTool/res/layout/vh_menu_dropdown.xml b/tests/CustomizationTool/res/layout/vh_menu_dropdown.xml
new file mode 100644
index 0000000..946c86d
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_menu_dropdown.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/menu_dropdown"
+ style="@style/MenuTextStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/menu_item_height"
+ android:background="@drawable/round_corner_bg"
+ android:drawableEnd="@drawable/ic_expand_more"
+ android:enabled="false"
+ android:gravity="center"
+ android:paddingHorizontal="@dimen/navigation_item_horizontal_padding"
+ tools:text="DropDown" />
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/layout/vh_menu_dropdown_popup.xml b/tests/CustomizationTool/res/layout/vh_menu_dropdown_popup.xml
new file mode 100644
index 0000000..6c409c5
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_menu_dropdown_popup.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/dropdown_background"
+ android:paddingVertical="16dp">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/dropdown_popup_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:minWidth="200dp"
+ tools:listitem="@layout/vh_menu_dropdown_popup_item" />
+</FrameLayout>
diff --git a/tests/CustomizationTool/res/layout/vh_menu_dropdown_popup_item.xml b/tests/CustomizationTool/res/layout/vh_menu_dropdown_popup_item.xml
new file mode 100644
index 0000000..eb765a1
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_menu_dropdown_popup_item.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="8dp"
+ android:background="@drawable/dropdown_item_background"
+ android:paddingHorizontal="24dp">
+
+ <TextView
+ android:id="@+id/dropdown_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:gravity="center_vertical"
+ android:lines="2"
+ android:textAppearance="@style/DropDownTitleTextStyle"
+ app:layout_constraintVertical_chainStyle="packed"
+ tools:text="Title" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/layout/vh_menu_navigation.xml b/tests/CustomizationTool/res/layout/vh_menu_navigation.xml
new file mode 100644
index 0000000..a63aed7
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_menu_navigation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/menu_navigation"
+ style="@style/MenuTextStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/menu_item_height"
+ android:background="@drawable/round_corner_bg"
+ android:gravity="center"
+ android:paddingHorizontal="@dimen/navigation_item_horizontal_padding"
+ tools:text="Navigate" />
+
diff --git a/tests/CustomizationTool/res/layout/vh_menu_panel_launcher.xml b/tests/CustomizationTool/res/layout/vh_menu_panel_launcher.xml
new file mode 100644
index 0000000..0229c55
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_menu_panel_launcher.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/menu_panel_launcher"
+ style="@style/MenuTextStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/menu_item_height"
+ android:background="@drawable/round_corner_bg"
+ android:gravity="center"
+ android:paddingHorizontal="@dimen/navigation_item_horizontal_padding"
+ tools:text="Control Panel" />
+
diff --git a/tests/CustomizationTool/res/layout/vh_menu_switch.xml b/tests/CustomizationTool/res/layout/vh_menu_switch.xml
new file mode 100644
index 0000000..8e04055
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_menu_switch.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<Switch xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/menu_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/menu_item_height"
+ android:paddingStart="15dp"
+ android:background="@drawable/round_corner_bg"
+ android:textAppearance="@style/MenuTextStyle"
+ tools:ignore="RtlSymmetry,UseSwitchCompatOrMaterialXml"
+ tools:text="Switch" />
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/layout/vh_menu_up_navigation.xml b/tests/CustomizationTool/res/layout/vh_menu_up_navigation.xml
new file mode 100644
index 0000000..74ad533
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_menu_up_navigation.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/menu_up_navigation"
+ android:layout_width="@dimen/menu_item_height"
+ android:layout_height="@dimen/menu_item_height"
+ android:background="@drawable/circle_bg"
+ android:src="@drawable/ic_up" />
+
diff --git a/tests/CustomizationTool/res/layout/vh_panel_header_close.xml b/tests/CustomizationTool/res/layout/vh_panel_header_close.xml
new file mode 100644
index 0000000..31542f9
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_panel_header_close.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/control_panel_close"
+ android:layout_width="54dp"
+ android:layout_height="54dp"
+ android:layout_gravity="end"
+ android:layout_marginTop="10dp"
+ android:layout_marginEnd="10dp"
+ android:background="@color/transparent"
+ android:src="@drawable/ic_back" />
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/layout/vh_panel_header_dropdown.xml b/tests/CustomizationTool/res/layout/vh_panel_header_dropdown.xml
new file mode 100644
index 0000000..6592d02
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_panel_header_dropdown.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/panel_header_dropdown_text"
+ style="@style/PanelHeaderDropDownStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Spinner
+ android:id="@+id/panel_header_dropdown"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="10dp" />
+</LinearLayout>
diff --git a/tests/CustomizationTool/res/layout/vh_panel_header_dropdown_item.xml b/tests/CustomizationTool/res/layout/vh_panel_header_dropdown_item.xml
new file mode 100644
index 0000000..2660e11
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_panel_header_dropdown_item.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/panel_header_dropdown_item"
+ style="@style/PanelHeaderDropDownStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:paddingHorizontal="20dp"
+ android:paddingVertical="10dp"
+ android:singleLine="true" />
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/layout/vh_panel_header_dropdown_item_selected.xml b/tests/CustomizationTool/res/layout/vh_panel_header_dropdown_item_selected.xml
new file mode 100644
index 0000000..47fefac
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_panel_header_dropdown_item_selected.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/panel_header_dropdown_item"
+ style="@style/PanelHeaderDropDownStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/layout/vh_panel_header_search.xml b/tests/CustomizationTool/res/layout/vh_panel_header_search.xml
new file mode 100644
index 0000000..d73e953
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_panel_header_search.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/panel_header_search"
+ style="@style/PanelHeaderSearchStyle"
+ android:layout_width="220dp"
+ android:layout_marginTop="10dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="20dp"
+ android:importantForAutofill="no"
+ android:inputType="text" />
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/layout/vh_panel_section_title.xml b/tests/CustomizationTool/res/layout/vh_panel_section_title.xml
new file mode 100644
index 0000000..fd85e2d
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_panel_section_title.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/PanelSectionTitleStyle"
+ android:id="@+id/panel_section_title"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:layout_width="match_parent" />
+
diff --git a/tests/CustomizationTool/res/layout/vh_panel_switch.xml b/tests/CustomizationTool/res/layout/vh_panel_switch.xml
new file mode 100644
index 0000000..cf29234
--- /dev/null
+++ b/tests/CustomizationTool/res/layout/vh_panel_switch.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/panel_switch_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="start"
+ android:maxLines="1"
+ android:textAppearance="@style/PanelTextStyle"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintBottom_toTopOf="@id/panel_switch_error"
+ app:layout_constraintEnd_toStartOf="@id/panel_switch_toggle"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_chainStyle="packed"
+ tools:text="Switch text" />
+
+ <TextView
+ android:id="@+id/panel_switch_error"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1"
+ android:textAppearance="@style/PanelErrorStyle"
+ android:visibility="gone"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/panel_switch_toggle"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/panel_switch_text"
+ tools:text="Error" />
+
+ <Switch
+ android:id="@+id/panel_switch_toggle"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/menu_item_height"
+ android:paddingStart="15dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:ignore="RtlSymmetry,UseSwitchCompatOrMaterialXml" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/tests/CustomizationTool/res/values/colors.xml b/tests/CustomizationTool/res/values/colors.xml
new file mode 100644
index 0000000..f4ee067
--- /dev/null
+++ b/tests/CustomizationTool/res/values/colors.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+ <color name="component_background_color">#626262</color>
+ <color name="control_panel_background">#AA000000</color>
+ <color name="dropdown_background_color">#272B2C</color>
+ <color name="dropdown_item_background_color">#3D94CBFF</color>
+ <color name="text_primary">#FFFFFF</color>
+ <color name="text_accent">#6F9FDD</color>
+ <color name="text_error">#FF0000</color>
+ <color name="transparent">#00FFFFFF</color>
+ <color name="ripple_color">#42ffffff</color>
+</resources>
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/values/dimens.xml b/tests/CustomizationTool/res/values/dimens.xml
new file mode 100644
index 0000000..20a86f2
--- /dev/null
+++ b/tests/CustomizationTool/res/values/dimens.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+ <dimen name="component_rounded_corners">76dp</dimen>
+ <dimen name="entry_point_button_size">76dp</dimen>
+ <dimen name="navigation_item_horizontal_padding">15dp</dimen>
+ <dimen name="menu_item_height">76dp</dimen>
+ <dimen name="menu_items_horizontal_margin">15dp</dimen>
+
+ <dimen name="text_size_big">40sp</dimen>
+ <dimen name="text_size_medium">32sp</dimen>
+ <dimen name="text_size_small">20sp</dimen>
+ <dimen name="text_size_panel_header">28sp</dimen>
+
+
+</resources>
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/values/strings.xml b/tests/CustomizationTool/res/values/strings.xml
new file mode 100644
index 0000000..079f525
--- /dev/null
+++ b/tests/CustomizationTool/res/values/strings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+ <!-- MENU IDs -->
+ <string name="menu_root">-</string>
+ <string name="menu_back">-</string>
+
+ <string name="menu_rro">RRO</string>
+ <string name="menu_rro_list_control_panel">RRO List</string>
+ <string name="menu_rro_unbundled">Unbundled Apps RRO</string>
+ <string name="menu_rro_unbundled_dialer">Dialer RROs</string>
+ <string name="menu_rro_unbundled_media">Media RROs</string>
+ <string name="menu_rro_unbundled_messenger">Messenger RROs</string>
+ <string name="menu_rro_unbundled_calendar">Calendar RROs</string>
+ <string name="menu_rro_systemui">System UI</string>
+ <string name="menu_rro_systemui_bars">System Bars</string>
+ <string name="menu_rro_systemui_theme">Themes</string>
+ <string name="menu_rro_systemui_cutouts">Cutouts</string>
+
+ <string name="menu_plugin">Plugin</string>
+ <string name="menu_plugin_global_switch">Car UI Lib Plugin</string>
+
+ <string name="menu_oem_tokens">OEM Design Tokens</string>
+ <string name="menu_oem_tokens_toggle">Tokens enabled</string>
+
+ <!-- Copies -->
+ <string name="dropdown_default">Default</string>
+</resources>
\ No newline at end of file
diff --git a/tests/CustomizationTool/res/values/styles.xml b/tests/CustomizationTool/res/values/styles.xml
new file mode 100644
index 0000000..30727ab
--- /dev/null
+++ b/tests/CustomizationTool/res/values/styles.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+
+ <style name="MenuTextStyle" parent="BaseTheme">
+ <item name="android:textSize">@dimen/text_size_medium</item>
+ <item name="android:textColor">@color/menu_text_color</item>
+ </style>
+
+ <style name="DropDownTitleTextStyle" parent="BaseTheme">
+ <item name="android:textSize">@dimen/text_size_medium</item>
+ <item name="android:textColor">@color/text_primary</item>
+ </style>
+
+ <style name="DropDownSubTitleTextStyle" parent="BaseTheme">
+ <item name="android:textSize">@dimen/text_size_small</item>
+ <item name="android:textColor">@color/text_accent</item>
+ </style>
+
+ <style name="PanelHeaderSearchStyle" parent="BaseTheme">
+ <item name="android:textSize">@dimen/text_size_panel_header</item>
+ <item name="android:textColor">@color/text_primary</item>
+ </style>
+
+ <style name="PanelHeaderDropDownStyle" parent="BaseTheme">
+ <item name="android:textSize">@dimen/text_size_panel_header</item>
+ <item name="android:textColor">@color/text_primary</item>
+ </style>
+
+ <style name="PanelSectionTitleStyle" parent="BaseTheme">
+ <item name="android:textSize">@dimen/text_size_big</item>
+ <item name="android:textColor">@color/text_primary</item>
+ </style>
+
+ <style name="PanelTextStyle" parent="BaseTheme">
+ <item name="android:textSize">@dimen/text_size_medium</item>
+ <item name="android:textColor">@color/text_primary</item>
+ </style>
+
+ <style name="PanelErrorStyle" parent="BaseTheme">
+ <item name="android:textSize">@dimen/text_size_small</item>
+ <item name="android:textColor">@color/text_error</item>
+ </style>
+</resources>
diff --git a/tests/CustomizationTool/res/values/themes.xml b/tests/CustomizationTool/res/values/themes.xml
new file mode 100644
index 0000000..f33844b
--- /dev/null
+++ b/tests/CustomizationTool/res/values/themes.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+
+ <style name="BaseTheme" parent="@android:style/Theme.DeviceDefault" />
+</resources>
diff --git a/tests/CustomizationTool/res/xml/customization_tool_service.xml b/tests/CustomizationTool/res/xml/customization_tool_service.xml
new file mode 100644
index 0000000..121822b
--- /dev/null
+++ b/tests/CustomizationTool/res/xml/customization_tool_service.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:accessibilityFlags="flagDefault"
+ android:canPerformGestures="true"
+ android:canRetrieveWindowContent="true" />
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/CustomizationToolService.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/CustomizationToolService.kt
new file mode 100644
index 0000000..ddfa62d
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/CustomizationToolService.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool
+
+import android.accessibilityservice.AccessibilityService
+import android.os.Handler
+import android.os.Looper
+import android.view.accessibility.AccessibilityEvent
+import com.android.car.customization.tool.di.DaggerCustomizationToolComponent
+import com.android.car.customization.tool.domain.CustomizationToolStateMachine
+import com.android.car.customization.tool.domain.ReloadStateAction
+import com.android.car.customization.tool.ui.CustomizationToolUI
+import javax.inject.Inject
+
+/**
+ * Entry point of the Customization Tool.
+ */
+internal class CustomizationToolService : AccessibilityService() {
+
+ @Inject
+ lateinit var customizationToolStateMachine: CustomizationToolStateMachine
+
+ @Inject
+ lateinit var customizationToolUI: CustomizationToolUI
+
+ override fun onServiceConnected() {
+ super.onServiceConnected()
+
+ DaggerCustomizationToolComponent.factory().create(this).inject(this)
+
+ customizationToolStateMachine.register(customizationToolUI)
+ customizationToolUI.handleAction = customizationToolStateMachine::handleAction
+
+ val handler = Handler(Looper.getMainLooper())
+ val delay = 5000L
+
+ handler.postDelayed(
+ object : Runnable {
+ override fun run() {
+ customizationToolStateMachine.handleAction(ReloadStateAction)
+ handler.postDelayed(this, delay)
+ }
+ },
+ delay
+ )
+ }
+
+ override fun onDestroy() {
+ customizationToolStateMachine.unregister()
+ super.onDestroy()
+ }
+
+ override fun onAccessibilityEvent(p0: AccessibilityEvent?) {
+ }
+
+ override fun onInterrupt() {
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/di/CustomizationToolComponent.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/di/CustomizationToolComponent.kt
new file mode 100644
index 0000000..f5e95d2
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/di/CustomizationToolComponent.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.di
+
+import android.accessibilityservice.AccessibilityService
+import com.android.car.customization.tool.CustomizationToolService
+import com.android.car.customization.tool.features.FeaturesModule
+import dagger.BindsInstance
+import dagger.Component
+import javax.inject.Singleton
+
+@Singleton
+@Component(
+ modules = [
+ ServiceModule::class,
+ FeaturesModule::class,
+ MenuModule::class
+ ]
+)
+internal interface CustomizationToolComponent {
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance service: AccessibilityService): CustomizationToolComponent
+ }
+
+ fun inject(service: CustomizationToolService)
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/di/MenuActionKey.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/di/MenuActionKey.kt
new file mode 100644
index 0000000..63742ee
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/di/MenuActionKey.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.di
+
+import com.android.car.customization.tool.domain.Action
+import dagger.MapKey
+import kotlin.reflect.KClass
+
+/**
+ * The key used in the map of [MenuActionReducer]
+ */
+@Retention(AnnotationRetention.BINARY)
+@MapKey
+internal annotation class MenuActionKey(val value: KClass<out Action>)
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/di/MenuModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/di/MenuModule.kt
new file mode 100644
index 0000000..dc79081
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/di/MenuModule.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.di
+
+import android.content.Context
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.domain.menu.toSubMenu
+import dagger.Module
+import dagger.Provides
+import javax.inject.Qualifier
+
+/**
+ * Qualifier for the root menu, use this to provide elements to the root menu.
+ */
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+internal annotation class RootMenu
+
+/**
+ * The base module for the menu.
+ *
+ * Provides the root element of the menu.
+ *
+ */
+@Module
+internal class MenuModule {
+
+ @Provides
+ fun provideRootMenu(
+ @RootMenu rootMenu: Set<@JvmSuppressWildcards MenuItem>,
+ @UIContext context: Context,
+ ): MenuItem.SubMenuNavigation = MenuItem.SubMenuNavigation(
+ displayTextRes = R.string.menu_root,
+ subMenu = rootMenu.toSubMenu(context, addBackButton = false)
+ )
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/di/PanelReducerKey.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/di/PanelReducerKey.kt
new file mode 100644
index 0000000..ceb5781
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/di/PanelReducerKey.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.di
+
+import com.android.car.customization.tool.domain.panel.PanelActionReducer
+import dagger.MapKey
+import kotlin.reflect.KClass
+
+/**
+ * The key in the map of [PanelActionReducer]
+ */
+@Retention(AnnotationRetention.BINARY)
+@MapKey
+internal annotation class PanelReducerKey(val value: KClass<out PanelActionReducer>)
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/di/ServiceModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/di/ServiceModule.kt
new file mode 100644
index 0000000..4e41c0e
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/di/ServiceModule.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.di
+
+import android.accessibilityservice.AccessibilityService
+import android.content.Context
+import android.content.om.OverlayManager
+import android.content.pm.PackageManager
+import android.hardware.display.DisplayManager
+import android.view.ContextThemeWrapper
+import android.view.Display
+import android.view.LayoutInflater
+import android.view.WindowManager
+import com.android.car.customization.tool.R
+import dagger.Module
+import dagger.Provides
+import javax.inject.Qualifier
+import javax.inject.Singleton
+
+/**
+ * Qualifier used to provide a UI valid [Context].
+ */
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+internal annotation class UIContext
+
+@Module
+internal class ServiceModule {
+
+ /**
+ * Provides a [Context] configured for UI operations since the standard [Context] provided by
+ * an [AccessibilityService] is not.
+ */
+ @Provides
+ @UIContext
+ @Singleton
+ fun provideUiContext(service: AccessibilityService): Context {
+ val displayManager = service.getSystemService(DisplayManager::class.java) as DisplayManager
+ val primaryDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY)
+ val baseContext = service.createWindowContext(
+ primaryDisplay,
+ WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
+ /* options= */ null
+ )
+ return ContextThemeWrapper(baseContext, R.style.BaseTheme)
+ }
+
+ @Provides
+ fun provideWindowManager(@UIContext context: Context): WindowManager =
+ context.getSystemService(AccessibilityService.WINDOW_SERVICE) as WindowManager
+
+ @Provides
+ fun provideLayoutInflater(@UIContext context: Context): LayoutInflater =
+ context.getSystemService(AccessibilityService.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+
+ @Provides
+ fun providePackageManager(@UIContext context: Context): PackageManager =
+ context.packageManager
+
+ @Provides
+ @Singleton
+ fun provideOverlayManager(@UIContext context: Context): OverlayManager =
+ context.getSystemService(android.content.om.OverlayManager::class.java) as OverlayManager
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/Action.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/Action.kt
new file mode 100644
index 0000000..076bece
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/Action.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain
+
+/**
+ * The base of all of the actions available in the tool.
+ *
+ * As a naming convention, all actions should include a verb in the name and end with
+ * the "Action" suffix.
+ */
+internal interface Action
+
+/**
+ * Toggles the UI state of the tool from closed to open and vice-versa.
+ *
+ * When the tool is "closed" only the entry point button is visible to the user.
+ */
+internal object ToggleUiAction : Action
+
+/**
+ * The action that is triggered when a refresh of the State is necessary.
+ *
+ * Since there are no reactive listeners on system properties available (like RRO state)
+ * this actions polls an update from the system.
+ */
+internal object ReloadStateAction : Action
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/CustomizationToolStateMachine.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/CustomizationToolStateMachine.kt
new file mode 100644
index 0000000..3d3c38e
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/CustomizationToolStateMachine.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain
+
+import com.android.car.customization.tool.domain.menu.MenuAction
+import com.android.car.customization.tool.domain.menu.MenuController
+import com.android.car.customization.tool.domain.menu.ReloadMenuAction
+import com.android.car.customization.tool.domain.panel.PanelAction
+import com.android.car.customization.tool.domain.panel.PanelController
+import com.android.car.customization.tool.domain.panel.ReloadPanelAction
+import javax.inject.Inject
+
+/**
+ * Main StateMachine, accepts [Action] and creates new [PageState].
+ *
+ * This state machine is directly responsible for actions that change the state of the whole tool
+ * (like [Action.ToggleToolAction]) and redirects all of the other actions to the [MenuController]
+ * and the [PanelController].
+ *
+ * @property menuController the [MenuController].
+ * @property panelController the [PanelController].
+ */
+internal class CustomizationToolStateMachine @Inject constructor(
+ private val menuController: MenuController,
+ private val panelController: PanelController,
+) : StateMachine<PageState, Action>(allowDuplicates = false) {
+
+ override var currentState: PageState = PageState(
+ isOpen = false,
+ menu = menuController.buildMenu(),
+ panel = null
+ )
+
+ override fun reducer(state: PageState, action: Action): PageState = when (action) {
+ ToggleUiAction -> state.copy(isOpen = !state.isOpen)
+
+ is ReloadStateAction -> {
+ state.copy(
+ menu = menuController.handleAction(state.menu, ReloadMenuAction),
+ panel = panelController.handleAction(state.panel, ReloadPanelAction)
+ )
+ }
+
+ is MenuAction -> {
+ state.copy(
+ menu = menuController.handleAction(state.menu, action)
+ )
+ }
+
+ is PanelAction -> {
+ state.copy(
+ panel = panelController.handleAction(state.panel, action)
+ )
+ }
+
+ else -> throw IllegalArgumentException("Action $action has not been implemented")
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/Observer.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/Observer.kt
new file mode 100644
index 0000000..d6d1931
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/Observer.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain
+
+/**
+ * Basic implementation of the Observer from the Observer pattern
+ */
+internal interface Observer<STATE> {
+ /**
+ * Function that renders the new state in the UI
+ */
+ fun render(state: STATE)
+}
+
+/**
+ * Basic implementation of the Subject from the Observer pattern
+ */
+internal abstract class Subject<STATE> {
+
+ /**
+ * Registers an observer for state updates.
+ */
+ abstract fun register(observer: Observer<STATE>)
+
+ /**
+ * Removes an observer from the list.
+ */
+ abstract fun unregister()
+
+ /**
+ * Notifies the new state to all of the [Observer].
+ */
+ protected abstract fun updateState(newState: STATE)
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/PageState.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/PageState.kt
new file mode 100644
index 0000000..bf209c3
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/PageState.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain
+
+import com.android.car.customization.tool.domain.menu.Menu
+import com.android.car.customization.tool.domain.panel.Panel
+
+/**
+ * The state of the tool.
+ *
+ * @param isOpen if the tool is opened or closed
+ * @param menu the current [Menu]
+ * @param panel the current [Panel] if present
+ */
+internal data class PageState(
+ val isOpen: Boolean,
+ val menu: Menu,
+ val panel: Panel?,
+)
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/StateMachine.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/StateMachine.kt
new file mode 100644
index 0000000..cdcce43
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/StateMachine.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain
+
+/**
+ * A base class for state machines.
+ *
+ * It provides all the basic properties and functions and also implements the Observer pattern
+ * to make the state machine "reactive".
+ *
+ * @property allowDuplicates defines if the state observers should be notified or not
+ * if the new state produced by the reducer function is the same as the previous one.
+ */
+internal abstract class StateMachine<STATE, ACTION>(
+ private val allowDuplicates: Boolean,
+) : Subject<STATE>() {
+
+ /**
+ * The current state of the tool.
+ */
+ protected abstract var currentState: STATE
+
+ /**
+ * The entity that observes the changes in the state.
+ */
+ private var observable: Observer<STATE>? = null
+
+ /**
+ * Registers an observer for state updates. When registering the observer also receives
+ * immediately the current state (Hot observable).
+ */
+ final override fun register(observer: Observer<STATE>) {
+ observable = observer
+ observer.render(currentState)
+ }
+
+ /**
+ * Removes an observer from the list.
+ */
+ final override fun unregister() {
+ observable = null
+ }
+
+ /**
+ * Notifies the new state to all of the [Observer].
+ */
+ final override fun updateState(newState: STATE) {
+ if (!allowDuplicates && currentState != newState) {
+ currentState = newState
+ observable?.render(newState)
+ }
+ }
+
+ /**
+ * Public function to accept actions from outside.
+ */
+ fun handleAction(action: ACTION) {
+ updateState(reducer(currentState, action))
+ }
+
+ /**
+ * This function processes the current state and an action to produce a new one.
+ */
+ protected abstract fun reducer(state: STATE, action: ACTION): STATE
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/Menu.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/Menu.kt
new file mode 100644
index 0000000..9cbd870
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/Menu.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain.menu
+
+import androidx.annotation.StringRes
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.panel.OpenPanelAction
+
+/**
+ * The tool's visual menu.
+ *
+ * The menu has a tree structure and is composed of different [MenuItem] nodes.
+ * Only nodes of type [MenuItem.SubMenuNavigation] can have children and allow the user to navigate
+ * to sub menus.
+ *
+ * @property rootNode The root element of the menu.
+ * @property currentParentNode The parent of the current menu shown to the user. For example if
+ * [currentParentNode] is the same as [rootNode] then the first layer of sub menu is shown (e.g. the
+ * menu containing the items: RRO, Plugin, OEM Design Tokens).
+ */
+internal data class Menu(
+ val rootNode: MenuItem.SubMenuNavigation,
+ val currentParentNode: MenuItem.SubMenuNavigation,
+)
+
+/**
+ * The base for all visual items available in Menu.
+ *
+ * @property displayTextRes the string resource (and ID) of a MenuItem.
+ * @property isEnabled defines if an item is enabled, users can't interact with disabled items.
+ */
+internal sealed class MenuItem(
+ @StringRes open val displayTextRes: Int,
+ open val isEnabled: Boolean,
+) {
+
+ /**
+ * The menu item that allows to navigate up the menu.
+ *
+ * Apart from the main level (RRO, Plugins...) all the other sub menus will contain
+ * a UpNavigation item.
+ */
+ object UpNavigation : MenuItem(R.string.menu_back, isEnabled = true) {
+ val action: MenuAction = NavigateUpAction
+ }
+
+ /**
+ * The menu item that allows to navigate down the menu tree.
+ *
+ * @property displayTextRes the string resource (and ID) of the item.
+ * @property subMenu the list of child nodes of this item.
+ */
+ data class SubMenuNavigation(
+ @StringRes override val displayTextRes: Int,
+ val subMenu: List<MenuItem>,
+ ) : MenuItem(displayTextRes, isEnabled = true) {
+
+ val action: MenuAction = OpenSubMenuAction(displayTextRes)
+ }
+
+ /**
+ * The menu item that represents a switch on the menu.
+ *
+ * @property displayTextRes the string resource (and ID) of the item.
+ * @property isEnabled defines if an item is enabled, users can't interact with disabled items.
+ * @property isChecked defines if the switch is checked or not.
+ * @property action [ToggleMenuAction] that will be triggered when the user taps on the Switch
+ */
+ data class Switch(
+ @StringRes override val displayTextRes: Int,
+ override val isEnabled: Boolean,
+ val isChecked: Boolean,
+ val action: ToggleMenuAction,
+ ) : MenuItem(displayTextRes, isEnabled)
+
+ /**
+ * The menu item that represents a DropDown item on the menu.
+ *
+ * Tapping on the DropDown shows the options defined in the [items] property.
+ *
+ * @property displayTextRes the string resource (and ID) of the item.
+ * @property isEnabled defines if an item is enabled, users can't interact with disabled items.
+ * @property items contains a list of DropDown.Item that will be shown when the user taps on the
+ * DropDown.
+ */
+ data class DropDown(
+ @StringRes override val displayTextRes: Int,
+ override val isEnabled: Boolean,
+ val items: List<Item>,
+ ) : MenuItem(displayTextRes, isEnabled) {
+
+ /**
+ * An item inside a DropDown popup list.
+ *
+ * @property title: the text on the item.
+ * @property isActive a boolean that define which is the active option from the list. Only one
+ * item should be active at any time.
+ * @property action the action that is triggered when the user taps on the item.
+ */
+ data class Item(
+ val title: String,
+ val isActive: Boolean = false,
+ val action: MenuAction,
+ )
+ }
+
+ /**
+ * The menu item that has the responsibility of launching a [Panel]
+ *
+ * @property displayTextRes the string resource (and ID) of the item.
+ * @property isEnabled defines if an item is enabled, users can't interact with disabled items.
+ * @property action [OpenPanelAction] that opens a specific panel.
+ */
+ data class PanelLauncher(
+ @StringRes override val displayTextRes: Int,
+ override val isEnabled: Boolean = true,
+ val action: OpenPanelAction,
+ ) : MenuItem(displayTextRes, isEnabled)
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/MenuAction.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/MenuAction.kt
new file mode 100644
index 0000000..49c7ca7
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/MenuAction.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain.menu
+
+import androidx.annotation.StringRes
+import com.android.car.customization.tool.domain.Action
+
+/**
+ * Base for all the menu actions.
+ *
+ * Menu Actions are actions that trigger a change in the menu.
+ */
+internal interface MenuAction : Action
+
+/**
+ * This action triggers an update of the whole menu.
+ *
+ * It's used to keep the menu up to date with the system.
+ */
+object ReloadMenuAction : MenuAction
+
+/**
+ * Navigate up the menu tree.
+ */
+object NavigateUpAction : MenuAction
+
+/**
+ * Open a sub menu and navigate down the menu tree.
+ *
+ * @property newParentId the id of the new parent that will be used in [Menu.currentParentNode].
+ */
+data class OpenSubMenuAction(
+ @StringRes val newParentId: Int,
+) : MenuAction
+
+/**
+ * A generic action for [MenuItem.Switch] menu elements.
+ */
+internal interface ToggleMenuAction : MenuAction {
+
+ @get:StringRes
+ val itemId: Int
+
+ /**
+ * The new value of a [MenuItem.Switch] after the action is completed.
+ */
+ val newValue: Boolean
+
+ /**
+ * A helper function to make sure the action can be copied, changing only the [newValue] property.
+ */
+ fun clone(newValue: Boolean): ToggleMenuAction
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/MenuActionReducer.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/MenuActionReducer.kt
new file mode 100644
index 0000000..cc57b11
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/MenuActionReducer.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain.menu
+
+/**
+ * The base for expanding functionality of the tool.
+ *
+ * Implementing this interface allows to define a function that will be executed when a specific
+ * action is triggered.
+ */
+internal interface MenuActionReducer {
+
+ /**
+ * Updates the menu based on the action received.
+ *
+ * @param menu the current Menu.
+ * @param action the action triggered.
+ *
+ * @return A new menu, updated with the effect of the [action].
+ */
+ fun reduce(menu: Menu, action: MenuAction): Menu
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/MenuController.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/MenuController.kt
new file mode 100644
index 0000000..ad2cb8f
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/MenuController.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain.menu
+
+import android.content.Context
+import com.android.car.customization.tool.di.UIContext
+import com.android.car.customization.tool.domain.Action
+import java.util.LinkedList
+import java.util.Queue
+import javax.inject.Inject
+import javax.inject.Provider
+import javax.inject.Singleton
+
+/**
+ * The Controller for the Menu.
+ *
+ * The main responsibility of this class is to provide a new Menu at the start of the service and to
+ * update the Menu based on the actions that are sent by the [CustomizationToolStateMachine]
+ *
+ * @param rootProvider a Dagger [Provider] that contains the root node for the Menu.
+ * @param context Android [Context], mainly used for making exceptions messages more useful.
+ * @param menuActionReducers used to extend the actions supported by the [MenuController].
+ */
+@Singleton
+internal class MenuController @Inject constructor(
+ private val rootProvider: Provider<MenuItem.SubMenuNavigation>,
+ @UIContext private val context: Context,
+ private val menuActionReducers: Map<Class<out Action>, @JvmSuppressWildcards MenuActionReducer>,
+) {
+
+ fun buildMenu(): Menu {
+ val root = rootProvider.get()
+ assertUniqueIds(root)
+ return Menu(rootNode = root, currentParentNode = root)
+ }
+
+ fun handleAction(menu: Menu, action: MenuAction): Menu = when (action) {
+ ReloadMenuAction -> reloadMenu(menu)
+ NavigateUpAction -> navigateUp(menu)
+ is OpenSubMenuAction -> navigateToSubMenu(menu, action)
+ else -> menuActionReducers[action::class.java]?.reduce(
+ menu,
+ action
+ )
+ ?: throw IllegalArgumentException("Action not implemented for this MenuController $action")
+ }
+
+ private fun assertUniqueIds(
+ root: MenuItem.SubMenuNavigation,
+ ) {
+ val existingIds = mutableSetOf<Int>()
+ val nodesToVisit: Queue<MenuItem> = LinkedList()
+ nodesToVisit.add(root)
+
+ while (nodesToVisit.isNotEmpty()) {
+ val node = requireNotNull(nodesToVisit.poll())
+ if (existingIds.contains(node.displayTextRes)) {
+ throw IllegalStateException(
+ "The menu tree contains duplicate IDs: ${
+ context.resources.getResourceName(node.displayTextRes)
+ }"
+ )
+ }
+ if (node is MenuItem.SubMenuNavigation) {
+ node.subMenu.forEach {
+ if (it !is MenuItem.UpNavigation) nodesToVisit.add(it)
+ }
+ }
+ existingIds.add(node.displayTextRes)
+ }
+ }
+
+ /**
+ * The function executed when the [MenuAction.NavigateUpAction] action is triggered.
+ *
+ * @param oldMenu the current [Menu].
+ * @return A new [Menu] with an updated [Menu.currentParentNode] value.
+ * @throws IllegalStateException in case the new parent node is not found.
+ */
+ private fun navigateUp(oldMenu: Menu): Menu {
+ val newParent = oldMenu.rootNode.findParentOf(oldMenu.currentParentNode)
+ ?: throw IllegalStateException(
+ "No parent has been found from the node: ${oldMenu.currentParentNode}"
+ )
+ return oldMenu.copy(currentParentNode = newParent)
+ }
+
+ /**
+ * The function executed when the [MenuAction.OpenSubMenuAction] action is triggered. It changes the
+ * [Menu] activating a new sub menu.
+ *
+ * @param oldMenu the current [Menu]
+ * @param action A [MenuAction.OpenSubMenuAction] action that contains the information of the new sub
+ * menu to show
+ */
+ private fun navigateToSubMenu(
+ oldMenu: Menu,
+ action: OpenSubMenuAction,
+ ): Menu {
+ val newParent =
+ oldMenu.currentParentNode.subMenu.find { it.displayTextRes == action.newParentId }
+ ?: throw IllegalArgumentException(
+ "The new menu is not a direct child of the current menu: " +
+ "current parent: ${oldMenu.currentParentNode}, " +
+ "new parent id: ${context.getString(action.newParentId)}"
+ )
+
+ return oldMenu.copy(currentParentNode = newParent as MenuItem.SubMenuNavigation)
+ }
+
+ /**
+ * The function executed when the [MenuAction.ReloadMenuAction] action is triggered. It creates a
+ * whole new [Menu], updating all of the [MenuItem] and in doing so makes sure that the
+ * [Menu] always reflects the actual state of the system
+ *
+ * @param oldMenu the current [Menu]
+ * @return a new [Menu] with updated nodes
+ */
+ private fun reloadMenu(oldMenu: Menu): Menu {
+ val newRoot = rootProvider.get()
+ val newParentNode = newRoot.findSubMenuNavigationNode(oldMenu.currentParentNode.displayTextRes)
+ ?: throw IllegalStateException(
+ "The new menu tree doesn't contain ID: ${
+ context.getString(oldMenu.currentParentNode.displayTextRes)
+ }"
+ )
+ return Menu(rootNode = newRoot, currentParentNode = newParentNode)
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/MenuUtils.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/MenuUtils.kt
new file mode 100644
index 0000000..88afc4a
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/menu/MenuUtils.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain.menu
+
+import android.content.Context
+import androidx.annotation.StringRes
+
+/**
+ * Finds the parent of a given node.
+ *
+ * @receiver the root node where the search starts.
+ * @param node the node whose parent needs to be found.
+ * @return the parent of the input [node] or null if the parent has not been found
+ */
+internal fun MenuItem.SubMenuNavigation.findParentOf(
+ node: MenuItem.SubMenuNavigation,
+): MenuItem.SubMenuNavigation? {
+ if (subMenu.contains(node)) return this
+
+ subMenu.filterIsInstance<MenuItem.SubMenuNavigation>().forEach {
+ val newParent = it.findParentOf(node)
+ if (newParent != null) {
+ return newParent
+ }
+ }
+
+ return null
+}
+
+/**
+ * Finds a node in the tree.
+ *
+ * @receiver the starting Node for the search.
+ * @param nodeId the ID of the node to find.
+ * @return the node or null if the node has not been found.
+ */
+internal fun MenuItem.SubMenuNavigation.findSubMenuNavigationNode(
+ @StringRes nodeId: Int,
+): MenuItem.SubMenuNavigation? {
+ if (displayTextRes == nodeId) return this
+
+ subMenu.filterIsInstance<MenuItem.SubMenuNavigation>().forEach {
+ val node = it.findSubMenuNavigationNode(nodeId)
+ if (node != null) {
+ return node
+ }
+ }
+ return null
+}
+
+/**
+ * Modifies the state of a [MenuItem.Switch] item.
+ *
+ * @receiver the current [Menu].
+ * @param context used for making the exception message more useful.
+ * @param itemId the ID of the [MenuItem.Switch] to modify.
+ * @param newState the new state the switch should have.
+ * @return A new [Menu] where the [MenuItem.Switch] value has been updated.
+ * @throws IllegalStateException if the [Menu] tree doesn't contain the element.
+ */
+internal fun Menu.modifySwitch(
+ context: Context,
+ @StringRes itemId: Int,
+ newState: Boolean,
+): Menu = modifyMenu(context, oldMenu = this, itemId) {
+ it as MenuItem.Switch
+
+ it.copy(
+ isChecked = newState,
+ action = it.action.clone(newValue = newState)
+ )
+}
+
+/**
+ * Modifies which [MenuItem.DropDown.Item] is active in a [MenuItem.DropDown].
+ *
+ * @receiver the current [Menu].
+ * @param context used for making the exception message more useful.
+ * @param itemId the ID of the [MenuItem.DropDown] to modify.
+ * @param action the action that will be used to select the new active item.
+ * @return A new [Menu] where the [MenuItem.DropDown] value has been updated.
+ * @throws IllegalStateException if the [Menu] tree doesn't contain the element.
+ */
+internal fun Menu.modifyDropDown(
+ context: Context,
+ itemId: Int,
+ action: MenuAction,
+): Menu = modifyMenu(context, oldMenu = this, itemId) {
+ it as MenuItem.DropDown
+
+ val newItems = it.items.map { item ->
+ if (item.action == action) {
+ item.copy(isActive = true)
+ } else {
+ item.copy(isActive = false)
+ }
+ }
+ it.copy(items = newItems)
+}
+
+/**
+ * Finds a [MenuItem], applies a lambda and returns a new updated [Menu].
+ *
+ * @param context used for making the exception message more useful.
+ * @param oldMenu the current [Menu].
+ * @param itemId the ID of the [MenuItem] to find.
+ * @param block the lambda to apply to the [MenuItem].
+ * @return A new [Menu] where the [MenuItem] has been updated.
+ * @throws IllegalStateException if the [Menu] tree doesn't contain the element.
+ */
+private fun modifyMenu(
+ context: Context,
+ oldMenu: Menu,
+ @StringRes itemId: Int,
+ block: (MenuItem) -> MenuItem,
+): Menu {
+ val newRoot = modifyNode(oldMenu.rootNode, itemId, block)
+ val newParentNode = newRoot.findSubMenuNavigationNode(oldMenu.currentParentNode.displayTextRes)
+ ?: throw IllegalStateException(
+ "The new menu tree doesn't contain ID: " + context.getString(
+ oldMenu.currentParentNode.displayTextRes
+ )
+ )
+ return Menu(newRoot, newParentNode)
+}
+
+/**
+ * Finds a [MenuItem], applies a lambda and returns a new updated root element.
+ *
+ * @param root the root node where the search starts.
+ * @param itemId the ID of the [MenuItem] to find.
+ * @param block the lambda to apply to the [MenuItem].
+ * @return A new root [MenuItem.SubMenuNavigation] where the [MenuItem] has been updated.
+ * @throws IllegalStateException if the [Menu] tree doesn't contain the element.
+ */
+private fun modifyNode(
+ root: MenuItem.SubMenuNavigation,
+ @StringRes itemId: Int,
+ block: (MenuItem) -> MenuItem,
+): MenuItem.SubMenuNavigation {
+ val newSubmenu: List<MenuItem> = if (root.subMenu.any { it.displayTextRes == itemId }) {
+ root.subMenu.map { if (it.displayTextRes == itemId) block(it) else it }
+ } else {
+ root.subMenu.map {
+ if (it is MenuItem.SubMenuNavigation) {
+ modifyNode(it, itemId, block)
+ } else {
+ it
+ }
+ }
+ }
+ return root.copy(
+ subMenu = newSubmenu
+ )
+}
+
+/**
+ * Formats a sub menu correctly starting from a [Set] of [MenuItem].
+ *
+ * @receiver the [Set] of [MenuItem].
+ * @param context used to sort the values of the element, this helps with keeping the UI consistent.
+ * @param addBackButton decides if this sub menu will have a "up button" or not.
+ * @return A list of [MenuItem] properly formatted as a sub menu
+ */
+internal fun Set<MenuItem>.toSubMenu(
+ context: Context,
+ addBackButton: Boolean = true,
+): List<MenuItem> {
+ val submenu = this.toList().sortedBy { context.getString(it.displayTextRes) }
+ return if (addBackButton) submenu.plus(MenuItem.UpNavigation) else submenu
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/panel/Panel.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/panel/Panel.kt
new file mode 100644
index 0000000..7f8e337
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/panel/Panel.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain.panel
+
+/**
+ * The model of a Panel.
+ *
+ * @property items the list of [PanelItem] that are shown in a panel.
+ */
+internal data class Panel(
+ val headerItems: List<PanelHeaderItem> = listOf(PanelHeaderItem.CloseButton),
+ val items: List<PanelItem>
+)
+
+internal sealed class PanelHeaderItem {
+
+ data class SearchBox(
+ val searchHint: String,
+ val searchAction: (String) -> PanelAction,
+ ) : PanelHeaderItem()
+
+ object CloseButton : PanelHeaderItem() {
+ val action: PanelAction = ClosePanelAction
+ }
+
+ data class DropDown(
+ val text: String,
+ val items: List<Item>
+ ) : PanelHeaderItem() {
+ data class Item(
+ val text: String,
+ val action: PanelAction,
+ val isActive: Boolean
+ )
+
+ fun selectDropDownItem(action: PanelAction): DropDown = copy(
+ items = items.map { item -> item.copy(isActive = item.action == action) }
+ )
+ }
+}
+
+/**
+ * The base for all items that can be shown in a Panel.
+ *
+ * @property isEnabled defines if an item is enabled, users can't interact with disabled items.
+ */
+internal sealed class PanelItem(open val isEnabled: Boolean) {
+
+ data class SectionTitle(
+ val text: String,
+ ) : PanelItem(isEnabled = true)
+
+ data class Switch(
+ val text: String,
+ val errorText: String?,
+ val isChecked: Boolean,
+ override val isEnabled: Boolean,
+ val action: PanelAction,
+ ) : PanelItem(isEnabled)
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/panel/PanelAction.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/panel/PanelAction.kt
new file mode 100644
index 0000000..6da1ed8
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/panel/PanelAction.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain.panel
+
+import com.android.car.customization.tool.domain.Action
+import kotlin.reflect.KClass
+
+/**
+ * The base for all actions that trigger change in a panel.
+ */
+internal interface PanelAction : Action
+
+/**
+ * This action triggers an update of the whole [Panel].
+ *
+ * It's used to keep the [Panel] up to date with the system.
+ */
+object ReloadPanelAction : PanelAction
+
+/**
+ * The action responsible for closing a [Panel].
+ */
+internal object ClosePanelAction : PanelAction
+
+/**
+ * The action responsible for opening a [Panel].
+ *
+ * @property panelClass the class of the [Panel] that needs to be opened.
+ * @property bundle a map where extra information can be specified.
+ */
+internal data class OpenPanelAction(
+ val panelClass: KClass<out PanelActionReducer>,
+ val bundle: Map<String, Any> = emptyMap(),
+) : PanelAction
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/panel/PanelActionReducer.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/panel/PanelActionReducer.kt
new file mode 100644
index 0000000..1bed76c
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/panel/PanelActionReducer.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain.panel
+
+/**
+ * The business logic of a [Panel].
+ *
+ * It can be seen as a small and specialized state machine that is responsible for creating a
+ * specific Panel and process the relevant actions for it.
+ */
+internal interface PanelActionReducer {
+
+ /**
+ * Extra information that can be be passed by the [OpenPanelAction] action.
+ */
+ var bundle: Map<String, Any>
+
+ /**
+ * Creates a brand new instance of a [Panel].
+ *
+ * The function is also used by [ReloadPanelAction] to create a new instance with the updated
+ * values from the system.
+ */
+ fun build(): Panel
+
+ /**
+ * Process a [PanelAction] and returns an updated [Panel].
+ */
+ fun reduce(panel: Panel, action: PanelAction): Panel
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/domain/panel/PanelController.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/panel/PanelController.kt
new file mode 100644
index 0000000..8c421e7
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/domain/panel/PanelController.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain.panel
+
+import javax.inject.Inject
+import javax.inject.Provider
+
+/**
+ * The Controller for all the possible panels in the tool.
+ *
+ * It receives [PanelAction] from the main state machine and then processes it or redirects
+ * it to a specific [PanelActionReducer].
+ *
+ * @property panelActionReducers a map of all of the [PanelActionReducer] available in the system.
+ */
+internal class PanelController @Inject constructor(
+ private val panelActionReducers: Map<
+ Class<out PanelActionReducer>,
+ @JvmSuppressWildcards Provider<PanelActionReducer>
+ >,
+) {
+
+ private var currentPanelActionReducer: PanelActionReducer? = null
+
+ fun handleAction(panel: Panel?, action: PanelAction): Panel? = when (action) {
+ is OpenPanelAction -> {
+ currentPanelActionReducer = panelActionReducers[action.panelClass.java]?.get()
+ currentPanelActionReducer?.bundle = action.bundle
+ currentPanelActionReducer?.build()
+ }
+
+ ClosePanelAction -> {
+ currentPanelActionReducer = null
+ null
+ }
+
+ ReloadPanelAction -> {
+ currentPanelActionReducer?.build()
+ }
+
+ else -> {
+ // Here there has to be a valid panel and a valid panelReducer, otherwise it should crash.
+ requireNotNull(panel)
+ currentPanelActionReducer?.reduce(panel, action)
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/FeaturesModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/FeaturesModule.kt
new file mode 100644
index 0000000..c56ff4e
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/FeaturesModule.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features
+
+import com.android.car.customization.tool.features.oemtokens.oemtokenstoggle.OemTokensToggleModule
+import com.android.car.customization.tool.features.oemtokens.submenu.OemDesignTokensMenuModule
+import com.android.car.customization.tool.features.plugins.plugintoggle.PluginToggleModule
+import com.android.car.customization.tool.features.plugins.submenu.PluginMenuModule
+import com.android.car.customization.tool.features.rro.list.RroListPanelModule
+import com.android.car.customization.tool.features.rro.submenu.RroMenuModule
+import com.android.car.customization.tool.features.rro.systemui.cutouts.DisplayCutoutsModule
+import com.android.car.customization.tool.features.rro.systemui.submenu.SystemUiMenuModule
+import com.android.car.customization.tool.features.rro.systemui.systembarpresets.SystemBarPresetsModule
+import com.android.car.customization.tool.features.rro.systemui.themepresets.ThemePresetsModule
+import com.android.car.customization.tool.features.rro.unbundled.apprro.UnbundledRroModule
+import com.android.car.customization.tool.features.rro.unbundled.submenu.UnbundledMenuModule
+import dagger.Module
+
+/**
+ * The container of all the feature modules.
+ *
+ * Whenever a new feature is added its modules should be added here.
+ */
+@Module(
+ includes = [
+ PluginMenuModule::class,
+ PluginToggleModule::class,
+ OemDesignTokensMenuModule::class,
+ OemTokensToggleModule::class,
+ RroMenuModule::class,
+ RroListPanelModule::class,
+ UnbundledMenuModule::class,
+ UnbundledRroModule::class,
+ SystemUiMenuModule::class,
+ ThemePresetsModule::class,
+ SystemBarPresetsModule::class,
+ DisplayCutoutsModule::class,
+ ]
+)
+class FeaturesModule
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/common/RroUtils.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/common/RroUtils.kt
new file mode 100644
index 0000000..e3dfe92
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/common/RroUtils.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.common
+
+import android.content.om.OverlayInfo
+
+fun OverlayInfo.isValid(): Boolean = state == 2 /*STATE_DISABLED*/ || state == 3 /*STATE_ENABLED*/
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/oemtokens/oemtokenstoggle/OemTokensToggleModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/oemtokens/oemtokenstoggle/OemTokensToggleModule.kt
new file mode 100644
index 0000000..24be4b1
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/oemtokens/oemtokenstoggle/OemTokensToggleModule.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.oemtokens.oemtokenstoggle
+
+import android.content.Context
+import android.content.om.OverlayInfo
+import android.content.om.OverlayManager
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.di.MenuActionKey
+import com.android.car.customization.tool.di.UIContext
+import com.android.car.customization.tool.domain.menu.MenuActionReducer
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.features.common.isValid
+import com.android.car.customization.tool.features.oemtokens.submenu.OemDesignTokensMenu
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+/**
+ * OEM Design Tokens toggle.
+ *
+ * Adds a [MenuItem.Switch] to the menu that toggles the RRO related to OEM Design Tokens.
+ * The module also provides the reducer for the item.
+ */
+@Module
+internal class OemTokensToggleModule {
+
+ @Provides
+ @OemDesignTokensMenu
+ @IntoSet
+ fun provideOemTokenSwitch(
+ packageManager: PackageManager,
+ overlayManager: OverlayManager,
+ ): MenuItem {
+ val displayText = R.string.menu_oem_tokens_toggle
+ val sharedLib: String? = getTokenSharedLibPackageName(packageManager)
+ var isEnabled = false
+ var isChecked = false
+ var rroPackage = ""
+ if (sharedLib != null) {
+ val overlayInfo: List<OverlayInfo> =
+ overlayManager.getOverlayInfosForTarget(sharedLib, UserHandle.CURRENT)
+ if (overlayInfo.isNotEmpty() && overlayInfo[0].isValid()) {
+ isEnabled = true
+ isChecked = overlayInfo[0].isEnabled
+ rroPackage = overlayInfo[0].packageName
+ }
+ }
+
+ return MenuItem.Switch(
+ displayTextRes = displayText,
+ isEnabled = isEnabled,
+ isChecked = isChecked,
+ action = ToggleOemTokensAction(displayText, rroPackage, !isChecked)
+ )
+ }
+
+ @Provides
+ @IntoMap
+ @MenuActionKey(ToggleOemTokensAction::class)
+ fun provideOemTokensToggleActionReducer(
+ @UIContext context: Context,
+ overlayManager: OverlayManager,
+ ): MenuActionReducer = OemTokensToggleReducer(context, overlayManager)
+}
+
+private fun getTokenSharedLibPackageName(packageManager: PackageManager): String? {
+ return packageManager.getSharedLibraries(/*flags=*/0)
+ .find { info -> info.name == "com.android.oem.tokens" }
+ ?.declaringPackage
+ ?.packageName
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/oemtokens/oemtokenstoggle/OemTokensToggleReducer.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/oemtokens/oemtokenstoggle/OemTokensToggleReducer.kt
new file mode 100644
index 0000000..ad03371
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/oemtokens/oemtokenstoggle/OemTokensToggleReducer.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.oemtokens.oemtokenstoggle
+
+import android.content.Context
+import android.content.om.OverlayManager
+import android.os.UserHandle
+import androidx.annotation.StringRes
+import com.android.car.customization.tool.domain.menu.Menu
+import com.android.car.customization.tool.domain.menu.MenuAction
+import com.android.car.customization.tool.domain.menu.MenuActionReducer
+import com.android.car.customization.tool.domain.menu.ToggleMenuAction
+import com.android.car.customization.tool.domain.menu.modifySwitch
+
+internal data class ToggleOemTokensAction(
+ @StringRes override val itemId: Int,
+ val rroPackage: String,
+ override val newValue: Boolean,
+) : ToggleMenuAction {
+
+ override fun clone(newValue: Boolean) = copy(newValue = newValue)
+}
+
+internal class OemTokensToggleReducer(
+ private val context: Context,
+ private val overlayManager: OverlayManager,
+) : MenuActionReducer {
+
+ override fun reduce(menu: Menu, action: MenuAction): Menu {
+ action as ToggleOemTokensAction
+ overlayManager.setEnabled(action.rroPackage, action.newValue, UserHandle.CURRENT)
+
+ return menu.modifySwitch(context, action.itemId, action.newValue)
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/oemtokens/submenu/OemTokensMenuModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/oemtokens/submenu/OemTokensMenuModule.kt
new file mode 100644
index 0000000..9f5b30f
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/oemtokens/submenu/OemTokensMenuModule.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.oemtokens.submenu
+
+import android.content.Context
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.di.RootMenu
+import com.android.car.customization.tool.di.UIContext
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.domain.menu.toSubMenu
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Qualifier
+
+/**
+ * Qualifier used to provide elements to the OEM Design Tokens sub menu.
+ */
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+internal annotation class OemDesignTokensMenu
+
+/**
+ * Provides the Menu element for the OEM Design Tokens sub menu.
+ */
+@Module
+internal class OemDesignTokensMenuModule {
+
+ @Provides
+ @RootMenu
+ @IntoSet
+ fun provideOemDesignTokensMenu(
+ @OemDesignTokensMenu items: Set<@JvmSuppressWildcards MenuItem>,
+ @UIContext context: Context,
+ ): MenuItem = MenuItem.SubMenuNavigation(
+ displayTextRes = R.string.menu_oem_tokens,
+ subMenu = items.toSubMenu(context)
+ )
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/plugins/plugintoggle/PluginManager.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/plugins/plugintoggle/PluginManager.kt
new file mode 100644
index 0000000..9ac5951
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/plugins/plugintoggle/PluginManager.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.plugins.plugintoggle
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS
+import android.content.pm.PackageManager.MATCH_SYSTEM_ONLY
+import android.content.pm.ProviderInfo
+import com.android.car.customization.tool.di.UIContext
+import javax.inject.Inject
+
+/**
+ * Toggles the state of the Car-UI-LIb Plugin in the system.
+ */
+internal class PluginManager @Inject constructor(
+ @UIContext private val context: Context,
+) {
+
+ private val providerInfo: ProviderInfo? = context.packageManager.resolveContentProvider(
+ AUTHORITY,
+ /*flags =*/ MATCH_DISABLED_COMPONENTS or MATCH_SYSTEM_ONLY
+ )
+
+ fun isPluginEnabled(): Boolean {
+ val packageManager = context.packageManager
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) ||
+ providerInfo == null) {
+ return false
+ }
+ val componentName = ComponentName(providerInfo.packageName, providerInfo.name)
+ val state = packageManager.getComponentEnabledSetting(componentName)
+ return if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+ providerInfo.enabled
+ } else {
+ state == COMPONENT_ENABLED_STATE_ENABLED
+ }
+ }
+
+ fun getPluginPackageName(): String? {
+ if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ return null
+ }
+ return providerInfo?.packageName
+ }
+
+ fun changeCarUiPluginState(newValue: Boolean) {
+ if (providerInfo == null) {
+ throw Exception("Plugin is disabled or does not exist")
+ }
+ val componentName = ComponentName(providerInfo.packageName, providerInfo.name)
+ val state =
+ if (newValue) COMPONENT_ENABLED_STATE_ENABLED else COMPONENT_ENABLED_STATE_DISABLED
+
+ context.packageManager.setComponentEnabledSetting(componentName, state, 0)
+ }
+
+ companion object {
+ private const val AUTHORITY: String = "com.android.car.ui.plugin"
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/plugins/plugintoggle/PluginToggleModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/plugins/plugintoggle/PluginToggleModule.kt
new file mode 100644
index 0000000..5221492
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/plugins/plugintoggle/PluginToggleModule.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.plugins.plugintoggle
+
+import android.content.Context
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.di.MenuActionKey
+import com.android.car.customization.tool.di.UIContext
+import com.android.car.customization.tool.domain.menu.MenuActionReducer
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.features.plugins.submenu.PluginsMenu
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+/**
+ * Toggle Car Ui Lib Plugin.
+ *
+ * Provides a [MenuItem.Switch] that toggles the Car Ui Lib Plugin. The module also provides the
+ * reducer for the item.
+ */
+/**
+ * Dagger Module for the feature: . It provides the [MenuItem] and the
+ * [MenuActionReducer] for the feature
+ */
+@Module
+internal class PluginToggleModule {
+
+ @Provides
+ @PluginsMenu
+ @IntoSet
+ fun providePluginToggle(
+ pluginManager: PluginManager,
+ ): MenuItem {
+ val isChecked = pluginManager.isPluginEnabled()
+ val displayText = R.string.menu_plugin_global_switch
+ return MenuItem.Switch(
+ displayTextRes = displayText,
+ isChecked = isChecked,
+ isEnabled = pluginManager.getPluginPackageName() != null,
+ action = ToggleCarUiLibPluginAction(displayText, !isChecked)
+ )
+ }
+
+ @Provides
+ @IntoMap
+ @MenuActionKey(ToggleCarUiLibPluginAction::class)
+ fun provideTogglePluginReducer(
+ @UIContext context: Context,
+ pluginManager: PluginManager,
+ ): MenuActionReducer = PluginToggleReducer(context, pluginManager)
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/plugins/plugintoggle/PluginToggleReducer.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/plugins/plugintoggle/PluginToggleReducer.kt
new file mode 100644
index 0000000..30aec15
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/plugins/plugintoggle/PluginToggleReducer.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.plugins.plugintoggle
+
+import android.content.Context
+import androidx.annotation.StringRes
+import com.android.car.customization.tool.domain.menu.Menu
+import com.android.car.customization.tool.domain.menu.MenuAction
+import com.android.car.customization.tool.domain.menu.MenuActionReducer
+import com.android.car.customization.tool.domain.menu.ToggleMenuAction
+import com.android.car.customization.tool.domain.menu.modifySwitch
+
+internal data class ToggleCarUiLibPluginAction(
+ @StringRes override val itemId: Int,
+ override val newValue: Boolean,
+) : ToggleMenuAction {
+
+ override fun clone(newValue: Boolean) = copy(newValue = newValue)
+}
+
+internal class PluginToggleReducer(
+ private val context: Context,
+ private val pluginManager: PluginManager,
+) : MenuActionReducer {
+
+ override fun reduce(menu: Menu, action: MenuAction): Menu {
+ action as ToggleCarUiLibPluginAction
+
+ pluginManager.changeCarUiPluginState(action.newValue)
+ return menu.modifySwitch(context, action.itemId, action.newValue)
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/plugins/submenu/PluginMenuModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/plugins/submenu/PluginMenuModule.kt
new file mode 100644
index 0000000..7e4a6ae
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/plugins/submenu/PluginMenuModule.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.plugins.submenu
+
+import android.content.Context
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.di.RootMenu
+import com.android.car.customization.tool.di.UIContext
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.domain.menu.toSubMenu
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Qualifier
+
+/**
+ * Qualifier used to provide elements to the Plugin sub menu.
+ */
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+internal annotation class PluginsMenu
+
+/**
+ * Provides the Menu element for the Plugin sub menu.
+ */
+@Module
+internal class PluginMenuModule {
+
+ @Provides
+ @RootMenu
+ @IntoSet
+ fun providePluginsMenu(
+ @PluginsMenu items: Set<@JvmSuppressWildcards MenuItem>,
+ @UIContext context: Context,
+ ): MenuItem = MenuItem.SubMenuNavigation(
+ displayTextRes = R.string.menu_plugin,
+ subMenu = items.toSubMenu(context)
+ )
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/list/RroListPanelModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/list/RroListPanelModule.kt
new file mode 100644
index 0000000..44016a3
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/list/RroListPanelModule.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.list
+
+import android.content.om.OverlayManager
+import android.content.pm.PackageManager
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.di.PanelReducerKey
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.domain.panel.OpenPanelAction
+import com.android.car.customization.tool.domain.panel.PanelActionReducer
+import com.android.car.customization.tool.features.rro.submenu.RroMenu
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+/**
+ * RRO List Panel.
+ *
+ * Shows a Panel that contains all of the RROs installed in the system and if they are dynamic RROs
+ * also allows the user to switch their state.
+ * The module provides a [MenuItem.PanelLauncher] to access the panel and the [RroListPanelReducer]
+ * for the Panel logic.
+ */
+@Module
+internal class RroListPanelModule {
+
+ @Provides
+ @RroMenu
+ @IntoSet
+ fun provideRroListPanelLauncher(): MenuItem =
+ MenuItem.PanelLauncher(
+ displayTextRes = R.string.menu_rro_list_control_panel,
+ isEnabled = true,
+ action = OpenPanelAction(RroListPanelReducer::class)
+ )
+
+ @Provides
+ @IntoMap
+ @PanelReducerKey(RroListPanelReducer::class)
+ fun provideRROListPanel(
+ packageManager: PackageManager,
+ overlayManager: OverlayManager,
+ ): PanelActionReducer = RroListPanelReducer(packageManager, overlayManager)
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/list/RroListPanelReducer.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/list/RroListPanelReducer.kt
new file mode 100644
index 0000000..7244b48
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/list/RroListPanelReducer.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.list
+
+import android.content.om.OverlayInfo
+import android.content.om.OverlayManager
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import com.android.car.customization.tool.domain.panel.Panel
+import com.android.car.customization.tool.domain.panel.PanelAction
+import com.android.car.customization.tool.domain.panel.PanelActionReducer
+import com.android.car.customization.tool.domain.panel.PanelHeaderItem
+import com.android.car.customization.tool.domain.panel.PanelItem
+import com.android.car.customization.tool.features.common.isValid
+
+internal data class PanelToggleRroAction(
+ val rroPackage: String,
+ val newState: Boolean,
+) : PanelAction
+
+internal data class PanelSearchTargetPackageAction(
+ val packageToSearch: String
+) : PanelAction
+
+internal data class PanelSearchRroPackageAction(
+ val packageToSearch: String
+) : PanelAction
+
+internal data class PanelSelectUserHandleAction(
+ val userHandle: UserHandle
+) : PanelAction
+
+internal class RroListPanelReducer(
+ private val packageManager: PackageManager,
+ private val overlayManager: OverlayManager,
+) : PanelActionReducer {
+
+ override lateinit var bundle: Map<String, Any>
+
+ private lateinit var header: List<PanelHeaderItem>
+
+ private var currentTargetPackageFilter: String = ""
+ private var currentRroPackageFilter: String = ""
+ private var currentUserHandle: UserHandle = UserHandle.CURRENT
+
+ data class RroInfo(
+ val packageName: String,
+ val targetPackage: String,
+ val isChecked: Boolean,
+ val isEnabled: Boolean,
+ val errorText: String?
+ )
+
+ private var rroState: List<Pair<String, List<RroInfo>>> = listOf()
+
+ override fun build(): Panel {
+ if (!this::header.isInitialized) {
+ header = listOf(
+ PanelHeaderItem.CloseButton,
+ PanelHeaderItem.SearchBox(
+ searchHint = "Target Package",
+ searchAction = { text -> PanelSearchTargetPackageAction(text) },
+ ),
+ PanelHeaderItem.SearchBox(
+ searchHint = "RRO Package",
+ searchAction = { text -> PanelSearchRroPackageAction(text) },
+ ),
+ PanelHeaderItem.DropDown(
+ text = "UserHandle:",
+ items = listOf(
+ PanelHeaderItem.DropDown.Item(
+ text = "CURRENT",
+ action = PanelSelectUserHandleAction(UserHandle.CURRENT),
+ isActive = true
+ ),
+ PanelHeaderItem.DropDown.Item(
+ text = "SYSTEM",
+ action = PanelSelectUserHandleAction(UserHandle.SYSTEM),
+ isActive = false
+ ),
+ )
+ )
+ )
+ }
+
+ rroState = getOverlayInfoFromSystem()
+ return Panel(headerItems = header, items = createUiItems())
+ }
+
+ override fun reduce(panel: Panel, action: PanelAction): Panel = when (action) {
+ is PanelToggleRroAction -> toggleRro(action)
+ is PanelSearchTargetPackageAction -> filterByTargetPackage(action)
+ is PanelSearchRroPackageAction -> filterByRroPackage(action)
+ is PanelSelectUserHandleAction -> selectUserHandle(action)
+ else -> throw NotImplementedError("Action $action not implemented for this Panel")
+ }
+
+ private fun toggleRro(action: PanelToggleRroAction): Panel {
+ overlayManager.setEnabled(action.rroPackage, action.newState, currentUserHandle)
+ rroState = rroState.map { (targetPackage, overlays) ->
+ Pair(
+ targetPackage,
+ overlays.map { rroInfo ->
+ if (rroInfo.packageName == action.rroPackage) {
+ rroInfo.copy(isChecked = action.newState)
+ } else {
+ rroInfo
+ }
+ }
+ )
+ }
+
+ return Panel(headerItems = header, items = createUiItems())
+ }
+
+ private fun filterByTargetPackage(action: PanelSearchTargetPackageAction): Panel {
+ currentTargetPackageFilter = action.packageToSearch
+ return Panel(headerItems = header, items = createUiItems())
+ }
+
+ private fun filterByRroPackage(action: PanelSearchRroPackageAction): Panel {
+ currentRroPackageFilter = action.packageToSearch
+ return Panel(headerItems = header, items = createUiItems())
+ }
+
+ private fun selectUserHandle(action: PanelSelectUserHandleAction): Panel {
+ currentUserHandle = action.userHandle
+ header = header.map { headerItem ->
+ if (headerItem is PanelHeaderItem.DropDown) {
+ headerItem.selectDropDownItem(action)
+ } else {
+ headerItem
+ }
+ }
+
+ rroState = getOverlayInfoFromSystem()
+ return Panel(header, createUiItems())
+ }
+
+ private fun getOverlayInfoFromSystem(): List<Pair<String, List<RroInfo>>> {
+ return packageManager.getInstalledPackages(/*flags=*/0).map { packageInfo ->
+ Pair(
+ packageInfo.packageName,
+ overlayManager.getOverlayInfosForTarget(
+ packageInfo.packageName,
+ currentUserHandle
+ ).map { overlayInfo ->
+ RroInfo(
+ packageName = overlayInfo.packageName,
+ targetPackage = overlayInfo.targetPackageName,
+ isChecked = overlayInfo.isEnabled,
+ isEnabled = overlayInfo.isMutable && overlayInfo.isValid(),
+ errorText = if (!overlayInfo.isValid()) {
+ OverlayInfo.stateToString(overlayInfo.state)
+ } else {
+ null
+ }
+ )
+ }
+ )
+ }
+ }
+
+ private fun createUiItems(): List<PanelItem> = rroState
+ // Filtering all packages that don't have overlays
+ .filter { (_, overlays) -> overlays.isNotEmpty() }
+ .filter { (targetPackage, _) -> targetPackage.contains(currentTargetPackageFilter) }
+ .map { (targetPackage, overlays) ->
+ Pair(
+ targetPackage,
+ overlays.filter { rroInfo ->
+ rroInfo.packageName.contains(currentRroPackageFilter)
+ }
+ )
+ }
+ // Removing packages that had all of the overlays filtered out
+ .filter { (_, overlays) -> overlays.isNotEmpty() }
+ .sortedBy { (packageName, _) -> packageName }
+ .flatMap { (packageName, overlays) ->
+ listOf(PanelItem.SectionTitle(packageName)).plus(
+ overlays.map { rroInfo ->
+ PanelItem.Switch(
+ text = rroInfo.packageName,
+ errorText = rroInfo.errorText,
+ isChecked = rroInfo.isChecked,
+ isEnabled = rroInfo.isEnabled,
+ PanelToggleRroAction(rroInfo.packageName, !rroInfo.isChecked)
+ )
+ }
+ )
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/submenu/RroMenuModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/submenu/RroMenuModule.kt
new file mode 100644
index 0000000..d455d36
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/submenu/RroMenuModule.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.submenu
+
+import android.content.Context
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.di.RootMenu
+import com.android.car.customization.tool.di.UIContext
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.domain.menu.toSubMenu
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Qualifier
+
+/**
+ * Qualifier used to provide elements to the RRO sub menu.
+ */
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+internal annotation class RroMenu
+
+/**
+ * Provides the Menu element for the RRO sub menu.
+ */
+@Module
+internal class RroMenuModule {
+
+ @Provides
+ @IntoSet
+ @RootMenu
+ fun provideRROMenu(
+ @UIContext context: Context,
+ @RroMenu items: Set<@JvmSuppressWildcards MenuItem>,
+ ): MenuItem = MenuItem.SubMenuNavigation(
+ displayTextRes = R.string.menu_rro,
+ subMenu = items.toSubMenu(context)
+ )
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/cutouts/DisplayCutoutsItem.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/cutouts/DisplayCutoutsItem.kt
new file mode 100644
index 0000000..9b253ef
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/cutouts/DisplayCutoutsItem.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.systemui.cutouts
+
+import android.content.om.OverlayManager
+import android.os.UserHandle
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.features.common.isValid
+import com.android.car.customization.tool.features.rro.systemui.systembarpresets.SelectSystemBarPresetAction
+
+internal fun cutoutsDropDownItem(overlayManager: OverlayManager): MenuItem.DropDown {
+ val textRes = R.string.menu_rro_systemui_cutouts
+
+ val dropDownItems = mutableListOf<MenuItem.DropDown.Item>()
+ val cutoutsConfiguration = mapOf(
+ "Free Form" to "com.android.internal.display.cutout.emulation.free_form",
+ "Top and Left" to "com.android.internal.display.cutout.emulation.top_and_left",
+ "Top and Right" to "com.android.internal.display.cutout.emulation.top_and_right",
+ "Left and Right" to "com.android.internal.display.cutout.emulation.left_and_right",
+ "Left and Right Rounded" to
+ "com.android.internal.display.cutout.emulation.left_and_right_rounded",
+ "Emu01" to "com.android.internal.display.cutout.emulation.emu01",
+ ).mapValues { (_, rroPackage) ->
+ overlayManager.getOverlayInfo(
+ /*packageName =*/ rroPackage,
+ /*userHandle =*/ UserHandle.SYSTEM
+ )
+ }.filterValues { overlayInfo ->
+ overlayInfo?.isValid() ?: false
+ }.mapValues { (_, overlayInfo) ->
+ // Changes the type of the values from OverlayInfo? to OverlayInfo.
+ requireNotNull(overlayInfo)
+ }
+
+ cutoutsConfiguration.forEach { (title, overlayInfo) ->
+ dropDownItems.add(
+ MenuItem.DropDown.Item(
+ title = title,
+ isActive = overlayInfo.isEnabled,
+ action = SelectSystemBarPresetAction(
+ itemId = textRes,
+ packageToEnable = overlayInfo.packageName,
+ packagesToDisable = cutoutsConfiguration.minus(title).values.map { it.packageName }
+ )
+ )
+ )
+ }
+
+ val isDropDownEnabled = dropDownItems.isNotEmpty()
+ if (isDropDownEnabled) {
+ dropDownItems.add(
+ index = 0,
+ MenuItem.DropDown.Item(
+ title = "Default",
+ isActive = dropDownItems.none { it.isActive },
+ action = SelectSystemBarPresetAction(
+ itemId = textRes,
+ // Default needs no package since it's there just to disable the others
+ packageToEnable = null,
+ packagesToDisable = cutoutsConfiguration.values.map { it.packageName }
+ )
+ )
+ )
+ }
+
+ return MenuItem.DropDown(
+ displayTextRes = textRes,
+ isEnabled = isDropDownEnabled,
+ items = dropDownItems
+ )
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/cutouts/DisplayCutoutsModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/cutouts/DisplayCutoutsModule.kt
new file mode 100644
index 0000000..0c665c3
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/cutouts/DisplayCutoutsModule.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.systemui.cutouts
+
+import android.content.Context
+import android.content.om.OverlayManager
+import com.android.car.customization.tool.di.MenuActionKey
+import com.android.car.customization.tool.di.UIContext
+import com.android.car.customization.tool.domain.menu.MenuActionReducer
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.features.rro.systemui.submenu.RroSystemUiMenu
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+/**
+ * Display Cutouts DropDown.
+ *
+ * Adds a [MenuItem.DropDown] to the menu that shows preconfigured RROs that change the shape of
+ * the display.
+ * The module provides a [MenuItem.DropDown] and the reducer for the item.
+ */
+@Module
+internal class DisplayCutoutsModule {
+
+ @Provides
+ @RroSystemUiMenu
+ @IntoSet
+ fun provideCutoutsItem(overlayManager: OverlayManager): MenuItem =
+ cutoutsDropDownItem(overlayManager)
+
+ @Provides
+ @IntoMap
+ @MenuActionKey(SelectDisplayCutoutPresetAction::class)
+ fun provideCutoutsReducer(
+ @UIContext context: Context,
+ overlayManager: OverlayManager,
+ ): MenuActionReducer = DisplayCutoutsReducer(context, overlayManager)
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/cutouts/DisplayCutoutsReducer.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/cutouts/DisplayCutoutsReducer.kt
new file mode 100644
index 0000000..4f4a59b
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/cutouts/DisplayCutoutsReducer.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.systemui.cutouts
+
+import android.content.Context
+import android.content.om.OverlayManager
+import android.os.UserHandle
+import androidx.annotation.StringRes
+import com.android.car.customization.tool.domain.menu.Menu
+import com.android.car.customization.tool.domain.menu.MenuAction
+import com.android.car.customization.tool.domain.menu.MenuActionReducer
+import com.android.car.customization.tool.domain.menu.modifyDropDown
+
+internal data class SelectDisplayCutoutPresetAction(
+ @StringRes val itemId: Int,
+ val packageToEnable: String?,
+ val packagesToDisable: List<String>,
+) : MenuAction
+
+internal class DisplayCutoutsReducer(
+ val context: Context,
+ val overlayManager: OverlayManager,
+) : MenuActionReducer {
+
+ override fun reduce(menu: Menu, action: MenuAction): Menu {
+ action as SelectDisplayCutoutPresetAction
+
+ if (action.packageToEnable != null) {
+ overlayManager.setEnabled(
+ /*packageName =*/ action.packageToEnable,
+ /*enable =*/ true,
+ /*user =*/ UserHandle.SYSTEM
+ )
+ }
+
+ action.packagesToDisable.forEach { rroPackage ->
+ overlayManager.setEnabled(
+ /*packageName =*/ rroPackage,
+ /*enable =*/ false,
+ /*user =*/ UserHandle.SYSTEM
+ )
+ }
+
+ return menu.modifyDropDown(context, action.itemId, action)
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/submenu/SystemUiMenuModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/submenu/SystemUiMenuModule.kt
new file mode 100644
index 0000000..2d94ca9
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/submenu/SystemUiMenuModule.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.systemui.submenu
+
+import android.content.Context
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.di.UIContext
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.domain.menu.toSubMenu
+import com.android.car.customization.tool.features.rro.submenu.RroMenu
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Qualifier
+
+/**
+ * Qualifier used to provide elements to the RRO - SystemUi sub menu.
+ */
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+internal annotation class RroSystemUiMenu
+
+/**
+ * Provides the Menu element for the RRO - SystemUi menu.
+ */
+@Module
+internal class SystemUiMenuModule {
+
+ @Provides
+ @IntoSet
+ @RroMenu
+ fun provideSystemUiRroMenu(
+ @RroSystemUiMenu items: Set<@JvmSuppressWildcards MenuItem>,
+ @UIContext context: Context,
+ ): MenuItem = MenuItem.SubMenuNavigation(
+ displayTextRes = R.string.menu_rro_systemui,
+ subMenu = items.toSubMenu(context)
+ )
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/systembarpresets/SystemBarPresetsItem.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/systembarpresets/SystemBarPresetsItem.kt
new file mode 100644
index 0000000..c30bdba
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/systembarpresets/SystemBarPresetsItem.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.systemui.systembarpresets
+
+import android.content.om.OverlayManager
+import android.os.UserHandle
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.features.common.isValid
+
+internal fun systemBarPresetsDropDownItem(overlayManager: OverlayManager): MenuItem.DropDown {
+ val textRes = R.string.menu_rro_systemui_bars
+ val dropDownItems = mutableListOf<MenuItem.DropDown.Item>()
+ val barConfigurations = mapOf(
+ "Bottom" to "com.android.systemui.rro.bottom",
+ "BottomRounded" to "com.android.systemui.rro.bottom.rounded",
+ "Right" to "com.android.systemui.rro.right",
+ "Left" to "com.android.systemui.rro.left"
+ ).mapValues { (_, rroPackage) ->
+ overlayManager.getOverlayInfo(
+ /*packageName =*/ rroPackage,
+ /*userHandle =*/ UserHandle.SYSTEM
+ )
+ }.filterValues { overlayInfo ->
+ overlayInfo?.isValid() ?: false
+ }.mapValues { (_, overlayInfo) ->
+ // Changes the type of the values from OverlayInfo? to OverlayInfo.
+ requireNotNull(overlayInfo)
+ }
+
+ barConfigurations.forEach { (title, overlayInfo) ->
+ dropDownItems.add(
+ MenuItem.DropDown.Item(
+ title = title,
+ isActive = overlayInfo.isEnabled,
+ action = SelectSystemBarPresetAction(
+ itemId = textRes,
+ packageToEnable = overlayInfo.packageName,
+ packagesToDisable = barConfigurations.minus(title).values.map { it.packageName }
+ )
+ )
+ )
+ }
+
+ val isDropDownEnabled = dropDownItems.isNotEmpty()
+ if (isDropDownEnabled) {
+ dropDownItems.add(
+ index = 0,
+ MenuItem.DropDown.Item(
+ title = "Default",
+ isActive = dropDownItems.none { it.isActive },
+ action = SelectSystemBarPresetAction(
+ itemId = textRes,
+ // Default needs no package since it's there just to disable the others
+ packageToEnable = null,
+ packagesToDisable = barConfigurations.values.map { it.packageName }
+ )
+ )
+ )
+ }
+
+ return MenuItem.DropDown(
+ displayTextRes = textRes,
+ isEnabled = isDropDownEnabled,
+ items = dropDownItems
+ )
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/systembarpresets/SystemBarPresetsModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/systembarpresets/SystemBarPresetsModule.kt
new file mode 100644
index 0000000..47b61b9
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/systembarpresets/SystemBarPresetsModule.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.systemui.systembarpresets
+
+import android.content.Context
+import android.content.om.OverlayManager
+import com.android.car.customization.tool.di.MenuActionKey
+import com.android.car.customization.tool.di.UIContext
+import com.android.car.customization.tool.domain.menu.MenuActionReducer
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.features.rro.systemui.submenu.RroSystemUiMenu
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+/**
+ * SystemBar DropDown.
+ *
+ * Adds a [MenuItem.DropDown] to the menu that shows preconfigured RROs that change the position
+ * and style of the System Bars.
+ * The module provides a [MenuItem.DropDown] and the reducer for the item.
+ */
+@Module
+internal class SystemBarPresetsModule {
+
+ @Provides
+ @RroSystemUiMenu
+ @IntoSet
+ fun provideSystemBarItem(overlayManager: OverlayManager): MenuItem =
+ systemBarPresetsDropDownItem(overlayManager)
+
+ @Provides
+ @IntoMap
+ @MenuActionKey(SelectSystemBarPresetAction::class)
+ fun provideSelectPresetsReducer(
+ overlayManager: OverlayManager,
+ @UIContext context: Context,
+ ): MenuActionReducer = SystemBarPresetsReducer(overlayManager, context)
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/systembarpresets/SystemBarPresetsReducer.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/systembarpresets/SystemBarPresetsReducer.kt
new file mode 100644
index 0000000..6a7e41e
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/systembarpresets/SystemBarPresetsReducer.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.systemui.systembarpresets
+
+import android.content.Context
+import android.content.om.OverlayManager
+import android.os.UserHandle
+import androidx.annotation.StringRes
+import com.android.car.customization.tool.domain.menu.Menu
+import com.android.car.customization.tool.domain.menu.MenuAction
+import com.android.car.customization.tool.domain.menu.MenuActionReducer
+import com.android.car.customization.tool.domain.menu.modifyDropDown
+
+internal data class SelectSystemBarPresetAction(
+ @StringRes val itemId: Int,
+ val packageToEnable: String?,
+ val packagesToDisable: List<String>,
+) : MenuAction
+
+internal class SystemBarPresetsReducer(
+ val overlayManager: OverlayManager,
+ val context: Context,
+) : MenuActionReducer {
+
+ override fun reduce(menu: Menu, action: MenuAction): Menu {
+ action as SelectSystemBarPresetAction
+
+ if (action.packageToEnable != null) {
+ overlayManager.setEnabled(
+ /*packageName =*/ action.packageToEnable,
+ /*enable =*/ true,
+ /*user =*/ UserHandle.SYSTEM
+ )
+ }
+ togglePersistentRRO(overlayManager, action.packageToEnable != null)
+
+ action.packagesToDisable.forEach { rroPackage ->
+ overlayManager.setEnabled(
+ /*packageName =*/ rroPackage,
+ /*enable =*/ false,
+ /*user =*/ UserHandle.SYSTEM
+ )
+ }
+
+ return menu.modifyDropDown(context, action.itemId, action)
+ }
+
+ private fun togglePersistentRRO(overlayManager: OverlayManager, enable: Boolean) {
+ val persistentRRO = "com.android.systemui.rro.persistent"
+ if (overlayManager.getOverlayInfo(persistentRRO, UserHandle.SYSTEM) != null) {
+ overlayManager.setEnabled(
+ /*packageName =*/ persistentRRO,
+ /*enable =*/ enable,
+ /*user =*/ UserHandle.SYSTEM
+ )
+ }
+ if (overlayManager.getOverlayInfo(persistentRRO, UserHandle.CURRENT) != null) {
+ overlayManager.setEnabled(
+ /*packageName =*/ persistentRRO,
+ /*enable =*/ enable,
+ /*user =*/ UserHandle.CURRENT
+ )
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/themepresets/ThemePresetsItem.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/themepresets/ThemePresetsItem.kt
new file mode 100644
index 0000000..b3fb6ee
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/themepresets/ThemePresetsItem.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.systemui.themepresets
+
+import android.content.om.OverlayManager
+import android.os.UserHandle
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.features.common.isValid
+
+internal data class ThemeRRO(
+ val systemPackages: List<String>,
+ val userPackages: List<String>,
+) {
+
+ fun filterInstalled(overlayManager: OverlayManager) = ThemeRRO(
+ systemPackages = systemPackages.filter { rroPackage ->
+ overlayManager.getOverlayInfo(rroPackage, UserHandle.SYSTEM)?.isValid() ?: false
+ },
+ userPackages = userPackages.filter { rroPackage ->
+ overlayManager.getOverlayInfo(rroPackage, UserHandle.CURRENT)?.isValid() ?: false
+ }
+ )
+
+ fun isActive(overlayManager: OverlayManager): Boolean {
+ return systemPackages.all { rroPackage ->
+ overlayManager.getOverlayInfo(rroPackage, UserHandle.SYSTEM)?.isEnabled ?: false
+ } && userPackages.all { rroPackage ->
+ overlayManager.getOverlayInfo(rroPackage, UserHandle.CURRENT)?.isEnabled ?: false
+ }
+ }
+}
+
+internal fun rroThemePresetsDropDownItem(overlayManager: OverlayManager): MenuItem.DropDown {
+ val textRes = R.string.menu_rro_systemui_theme
+
+ val dropDownItems = mutableListOf<MenuItem.DropDown.Item>()
+
+ val themes = mapOf(
+ "Pink" to ThemeRRO(
+ systemPackages = getSystemRroPackages(theme = "pink"),
+ userPackages = getUserRroPackages(theme = "pink")
+ ),
+ "Orange" to ThemeRRO(
+ systemPackages = getSystemRroPackages(theme = "orange"),
+ userPackages = getUserRroPackages(theme = "orange")
+ )
+ ).mapValues { (_, themeRros) -> themeRros.filterInstalled(overlayManager) }
+
+ themes
+ .forEach { (title, theme) ->
+ dropDownItems.add(
+ MenuItem.DropDown.Item(
+ title = title,
+ isActive = theme.isActive(overlayManager),
+ action = SelectThemePresetAction(
+ itemId = textRes,
+ themeToEnable = theme,
+ themesToDisable = themes.minus(title).values.toList()
+ )
+ )
+ )
+ }
+
+ val isDropDownEnabled = dropDownItems.isNotEmpty()
+ if (isDropDownEnabled) {
+ dropDownItems.add(
+ index = 0,
+ MenuItem.DropDown.Item(
+ title = "Default",
+ isActive = dropDownItems.none { it.isActive },
+ action = SelectThemePresetAction(
+ itemId = textRes,
+ // Default needs no package since it's there just to disable the others
+ themeToEnable = null,
+ themesToDisable = themes.values.toList()
+ )
+ )
+ )
+ }
+
+ return MenuItem.DropDown(
+ displayTextRes = textRes,
+ isEnabled = isDropDownEnabled,
+ items = dropDownItems
+ )
+}
+
+private fun getSystemRroPackages(theme: String): List<String> = listOf(
+ "com.android.systemui.googlecarui.theme.$theme.rro",
+ "android.googlecarui.theme.$theme.rro"
+)
+
+private fun getUserRroPackages(theme: String): List<String> = listOf(
+ "com.android.car.messenger.googlecarui.theme.$theme.rro",
+ "com.android.car.calendar.googlecarui.theme.$theme.rro",
+ "com.android.car.media.googlecarui.theme.$theme.rro",
+ "com.android.car.radio.googlecarui.theme.$theme.rro",
+ "com.android.htmlviewer.googlecarui.theme.$theme.rro",
+ "com.android.car.systemupdater.googlecarui.theme.$theme.rro",
+ "com.android.car.developeroptions.googlecarui.theme.$theme.rro",
+ "com.android.settings.intelligence.googlecarui.theme.$theme.rro",
+ "com.android.car.settings.googlecarui.theme.$theme.rro",
+ "com.android.car.home.googlecarui.theme.$theme.rro",
+ "com.android.car.dialer.googlecarui.theme.$theme.rro",
+ "com.android.car.notification.googlecarui.theme.$theme.rro",
+ "com.android.car.linkviewer.googlecarui.theme.$theme.rro",
+ "com.android.car.themeplayground.googlecarui.theme.$theme.rro",
+ "com.android.car.carlauncher.googlecarui.theme.$theme.rro",
+ "com.android.managedprovisioning.googlecarui.theme.$theme.rro",
+ "com.android.car.rotaryplayground.googlecarui.theme.$theme.rro",
+)
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/themepresets/ThemePresetsModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/themepresets/ThemePresetsModule.kt
new file mode 100644
index 0000000..e8e7a67
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/themepresets/ThemePresetsModule.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.systemui.themepresets
+
+import android.content.Context
+import android.content.om.OverlayManager
+import com.android.car.customization.tool.di.MenuActionKey
+import com.android.car.customization.tool.di.UIContext
+import com.android.car.customization.tool.domain.menu.MenuActionReducer
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.features.rro.systemui.submenu.RroSystemUiMenu
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+/**
+ * Theme Presets DropDown.
+ *
+ * Adds a [MenuItem.DropDown] to the menu that shows preconfigured RROs that change the theme of
+ * the system.
+ * The module provides a [MenuItem.DropDown] and the reducer for the item.
+ */
+@Module
+internal class ThemePresetsModule {
+
+ @Provides
+ @RroSystemUiMenu
+ @IntoSet
+ fun provideRROPresetSelection(overlayManager: OverlayManager): MenuItem =
+ rroThemePresetsDropDownItem(overlayManager)
+
+ @Provides
+ @IntoMap
+ @MenuActionKey(SelectThemePresetAction::class)
+ fun provideSelectPresetsReducer(
+ @UIContext context: Context,
+ overlayManager: OverlayManager,
+ ): MenuActionReducer = ThemePresetsReducer(context, overlayManager)
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/themepresets/ThemePresetsReducer.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/themepresets/ThemePresetsReducer.kt
new file mode 100644
index 0000000..9c1df76
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/systemui/themepresets/ThemePresetsReducer.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.systemui.themepresets
+
+import android.content.Context
+import android.content.om.OverlayManager
+import android.os.UserHandle
+import androidx.annotation.StringRes
+import com.android.car.customization.tool.di.UIContext
+import com.android.car.customization.tool.domain.menu.Menu
+import com.android.car.customization.tool.domain.menu.MenuAction
+import com.android.car.customization.tool.domain.menu.MenuActionReducer
+import com.android.car.customization.tool.domain.menu.modifyDropDown
+
+internal data class SelectThemePresetAction(
+ @StringRes val itemId: Int,
+ val themeToEnable: ThemeRRO?,
+ val themesToDisable: List<ThemeRRO>,
+) : MenuAction
+
+internal class ThemePresetsReducer(
+ @UIContext private val context: Context,
+ private val overlayManager: OverlayManager,
+) : MenuActionReducer {
+
+ override fun reduce(menu: Menu, action: MenuAction): Menu {
+ action as SelectThemePresetAction
+
+ if (action.themeToEnable != null) {
+ toggleRROs(action.themeToEnable.systemPackages, enable = true, UserHandle.SYSTEM)
+ toggleRROs(action.themeToEnable.userPackages, enable = true, UserHandle.CURRENT)
+ }
+
+ action.themesToDisable.forEach { theme ->
+ toggleRROs(theme.systemPackages, enable = false, UserHandle.SYSTEM)
+ toggleRROs(theme.userPackages, enable = false, UserHandle.CURRENT)
+ }
+
+ return menu.modifyDropDown(context, action.itemId, action)
+ }
+
+ private fun toggleRROs(rroPackageList: List<String>, enable: Boolean, user: UserHandle) {
+ rroPackageList.forEach { rroPackage ->
+ overlayManager.setEnabled(
+ /*packageName =*/ rroPackage,
+ /*enable =*/ enable,
+ /*user =*/ user
+ )
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/unbundled/apprro/UnbundledRroModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/unbundled/apprro/UnbundledRroModule.kt
new file mode 100644
index 0000000..f6a7ea1
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/unbundled/apprro/UnbundledRroModule.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.unbundled.apprro
+
+import android.content.om.OverlayManager
+import android.content.pm.PackageManager
+import androidx.annotation.StringRes
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.di.PanelReducerKey
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.domain.panel.OpenPanelAction
+import com.android.car.customization.tool.domain.panel.PanelActionReducer
+import com.android.car.customization.tool.features.rro.unbundled.submenu.RroUnbundledMenu
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+/**
+ * Unbundled Apps RRO List.
+ *
+ * Adds one [MenuItem.PanelLauncher] for each unbundled app (Dialer, Calendar, Media, Messenger) to
+ * the menu. Each item opens a Panel, where the user can view all of the RROs for that app and if
+ * they are dynamic RROs they can also toggle them.
+ * The Module provides also the [PanelActionReducer] for this feature.
+ */
+@Module
+internal class UnbundledRroModule {
+
+ @Provides
+ @RroUnbundledMenu
+ @IntoSet
+ fun provideDialerRROPanelLauncher(
+ packageManager: PackageManager,
+ ): MenuItem = createUnbundledPanelLauncher(
+ displayTextRes = R.string.menu_rro_unbundled_dialer,
+ unbundledPackage = "com.android.car.dialer",
+ packageManager,
+ )
+
+ @Provides
+ @RroUnbundledMenu
+ @IntoSet
+ fun provideMediaRroPanelLauncher(
+ packageManager: PackageManager,
+ ): MenuItem = createUnbundledPanelLauncher(
+ displayTextRes = R.string.menu_rro_unbundled_media,
+ unbundledPackage = "com.android.car.media",
+ packageManager,
+ )
+
+ @Provides
+ @RroUnbundledMenu
+ @IntoSet
+ fun provideMessengerRroPanelLauncher(
+ packageManager: PackageManager,
+ ): MenuItem = createUnbundledPanelLauncher(
+ displayTextRes = R.string.menu_rro_unbundled_messenger,
+ unbundledPackage = "com.android.car.messenger",
+ packageManager,
+ )
+
+ @Provides
+ @RroUnbundledMenu
+ @IntoSet
+ fun provideCalendarRroPanelLauncher(
+ packageManager: PackageManager,
+ ): MenuItem = createUnbundledPanelLauncher(
+ displayTextRes = R.string.menu_rro_unbundled_calendar,
+ unbundledPackage = "com.android.car.calendar",
+ packageManager,
+ )
+
+ @Provides
+ @IntoMap
+ @PanelReducerKey(UnbundledRroPanelReducer::class)
+ fun provideUnbundledRroPanel(
+ overlayManager: OverlayManager,
+ ): PanelActionReducer = UnbundledRroPanelReducer(overlayManager)
+}
+
+private fun createUnbundledPanelLauncher(
+ @StringRes displayTextRes: Int,
+ unbundledPackage: String,
+ packageManager: PackageManager,
+): MenuItem {
+ val isEnabled = try {
+ packageManager.getPackageInfo(unbundledPackage, /*flags=*/0)
+ true
+ } catch (e: PackageManager.NameNotFoundException) {
+ false
+ }
+ return MenuItem.PanelLauncher(
+ displayTextRes = displayTextRes,
+ isEnabled = isEnabled,
+ action = OpenPanelAction(
+ UnbundledRroPanelReducer::class,
+ mapOf(UnbundledRroPanelReducer.BUNDLE_UNBUNDLED_PACKAGE to unbundledPackage)
+ )
+ )
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/unbundled/apprro/UnbundledRroPanelReducer.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/unbundled/apprro/UnbundledRroPanelReducer.kt
new file mode 100644
index 0000000..04fb559
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/unbundled/apprro/UnbundledRroPanelReducer.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.unbundled.apprro
+
+import android.content.om.OverlayInfo
+import android.content.om.OverlayManager
+import android.os.UserHandle
+import com.android.car.customization.tool.domain.panel.Panel
+import com.android.car.customization.tool.domain.panel.PanelAction
+import com.android.car.customization.tool.domain.panel.PanelActionReducer
+import com.android.car.customization.tool.domain.panel.PanelItem
+import com.android.car.customization.tool.features.common.isValid
+
+data class UnbundledAppsRroToggleAction(
+ val rroPackage: String,
+ val newState: Boolean,
+) : PanelAction
+
+internal class UnbundledRroPanelReducer(
+ private val overlayManager: OverlayManager,
+) : PanelActionReducer {
+
+ override lateinit var bundle: Map<String, Any>
+
+ override fun build(): Panel {
+ val appPackage: String = (bundle[BUNDLE_UNBUNDLED_PACKAGE]
+ ?: throw IllegalArgumentException(
+ "The unbundled package has not been added to the action"
+ )) as String
+
+ val items = listOf(PanelItem.SectionTitle(appPackage))
+ .plus(
+ overlayManager.getOverlayInfosForTarget(
+ appPackage,
+ UserHandle.CURRENT
+ ).filter {
+ it.isValid()
+ }.map { overlayInfo ->
+ PanelItem.Switch(
+ text = overlayInfo.packageName,
+ errorText = if (!overlayInfo.isValid()) {
+ OverlayInfo.stateToString(overlayInfo.state)
+ } else {
+ null
+ },
+ isChecked = overlayInfo.isEnabled,
+ isEnabled = overlayInfo.isMutable && overlayInfo.isValid(),
+ action = UnbundledAppsRroToggleAction(
+ overlayInfo.packageName,
+ !overlayInfo.isEnabled
+ )
+ )
+ }
+ )
+
+ return Panel(items = items)
+ }
+
+ override fun reduce(
+ panel: Panel,
+ action: PanelAction,
+ ): Panel = when (action) {
+ is UnbundledAppsRroToggleAction -> toggleRRO(panel, action)
+ else -> throw NotImplementedError("Action $action not implemented for this Panel")
+ }
+
+ private fun toggleRRO(
+ panel: Panel,
+ action: UnbundledAppsRroToggleAction,
+ ): Panel {
+ overlayManager.setEnabled(action.rroPackage, action.newState, UserHandle.CURRENT)
+
+ return Panel(
+ items = panel.items.map { item ->
+ if (item is PanelItem.Switch && item.text == action.rroPackage) {
+ item.copy(
+ isChecked = action.newState,
+ action = action.copy(newState = !action.newState)
+ )
+ } else {
+ item
+ }
+ }
+ )
+ }
+
+ companion object {
+ const val BUNDLE_UNBUNDLED_PACKAGE = "unbundled_package"
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/unbundled/submenu/UnbundledMenuModule.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/unbundled/submenu/UnbundledMenuModule.kt
new file mode 100644
index 0000000..aa3226e
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/features/rro/unbundled/submenu/UnbundledMenuModule.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.features.rro.unbundled.submenu
+
+import android.content.Context
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.di.UIContext
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.domain.menu.toSubMenu
+import com.android.car.customization.tool.features.rro.submenu.RroMenu
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Qualifier
+
+/**
+ * Qualifier used to provide elements to the RRO - Unbundled Apps sub menu.
+ */
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+internal annotation class RroUnbundledMenu
+
+/**
+ * Provides the Menu element for the RRO - Unbundled menu.
+ */
+@Module
+internal class UnbundledMenuModule {
+
+ @Provides
+ @IntoSet
+ @RroMenu
+ fun provideUnbundledRROMenu(
+ @RroUnbundledMenu items: Set<@JvmSuppressWildcards MenuItem>,
+ @UIContext context: Context,
+ ): MenuItem = MenuItem.SubMenuNavigation(
+ displayTextRes = R.string.menu_rro_unbundled,
+ subMenu = items.toSubMenu(context)
+ )
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/CustomizationToolUI.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/CustomizationToolUI.kt
new file mode 100644
index 0000000..cc4bd7e
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/CustomizationToolUI.kt
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui
+
+import android.graphics.PixelFormat
+import android.util.Log
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewTreeObserver
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+import android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+import android.widget.ImageButton
+import android.widget.LinearLayout
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.Observer
+import com.android.car.customization.tool.domain.PageState
+import com.android.car.customization.tool.domain.ToggleUiAction
+import com.android.car.customization.tool.ui.menu.HorizontalMarginItemDecoration
+import com.android.car.customization.tool.ui.menu.MenuAdapter
+import com.android.car.customization.tool.ui.panel.header.PanelHeaderAdapter
+import com.android.car.customization.tool.ui.panel.items.PanelItemsAdapter
+import javax.inject.Inject
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Renders the UI of the tool.
+ */
+internal class CustomizationToolUI @Inject constructor(
+ private val windowManager: WindowManager,
+ layoutInflater: LayoutInflater,
+) : Observer<PageState> {
+
+ lateinit var handleAction: (action: Action) -> Unit
+
+ private val windowLayoutParams: WindowManager.LayoutParams = WindowManager.LayoutParams().apply {
+ width = WindowManager.LayoutParams.WRAP_CONTENT
+ height = WindowManager.LayoutParams.WRAP_CONTENT
+ type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY
+ format = PixelFormat.TRANSPARENT
+ flags = flags or FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCH_MODAL or FLAG_WATCH_OUTSIDE_TOUCH
+ gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
+ y = 0
+ x = 0
+ }
+ private val mMainLayout: MainLayoutView = layoutInflater.inflate(
+ R.layout.main_layout,
+ /*root=*/null
+ ) as MainLayoutView
+ private val mEntryPoint: ImageButton
+ private val mMenu: RecyclerView
+
+ private val mControlPanelContainer: LinearLayout
+ private val mControlPanelHeader: RecyclerView
+ private val mControlPanelItems: RecyclerView
+
+ private var usableWindowWidth: Int = calculateUsableWidth()
+
+ init {
+ setWindowMoveAnimation(false)
+ mMainLayout.setTouchListener { inside -> setKeyboardActive(inside) }
+ windowManager.addView(mMainLayout, windowLayoutParams)
+
+ mEntryPoint = mMainLayout.requireViewById<ImageButton>(R.id.entry_point).apply {
+ setOnClickListener {
+ handleAction(ToggleUiAction)
+ }
+ }
+
+ mMenu = mMainLayout.requireViewById<RecyclerView>(R.id.menu).apply {
+ layoutManager = LinearLayoutManager(
+ context,
+ LinearLayoutManager.HORIZONTAL,
+ /*reverseLayout=*/false
+ )
+ itemAnimator = null
+ addItemDecoration(
+ HorizontalMarginItemDecoration(
+ resources.getDimensionPixelSize(R.dimen.menu_items_horizontal_margin)
+ )
+ )
+ }
+
+ mControlPanelContainer = mMainLayout.requireViewById(R.id.control_panel_container)
+ mControlPanelHeader = mMainLayout
+ .requireViewById<RecyclerView>(R.id.control_panel_header)
+ .apply {
+ layoutManager = LinearLayoutManager(
+ context,
+ LinearLayoutManager.HORIZONTAL,
+ /*reverseLayout=*/ false
+ )
+ itemAnimator = null
+ }
+ mControlPanelItems = mMainLayout.requireViewById<RecyclerView>(R.id.control_panel_items).apply {
+ layoutManager = LinearLayoutManager(
+ context,
+ LinearLayoutManager.VERTICAL,
+ /*reverseLayout=*/false
+ )
+ itemAnimator = null
+ }
+
+ mMainLayout.setOnApplyWindowInsetsListener { _, insets ->
+ val newUsableWindowWidth = calculateUsableWidth()
+ if (newUsableWindowWidth != usableWindowWidth) {
+ usableWindowWidth = newUsableWindowWidth
+ updateToolWidth()
+ }
+ insets
+ }
+ }
+
+ override fun render(state: PageState) {
+ if (!state.isOpen) {
+ mMenu.visibility = View.GONE
+ mControlPanelContainer.visibility = View.GONE
+ } else {
+ mMenu.run {
+ if (adapter == null) adapter = MenuAdapter(handleAction)
+ (adapter as MenuAdapter).submitList(state.menu.currentParentNode.subMenu)
+ visibility = View.VISIBLE
+ }
+
+ if (mControlPanelItems.adapter == null) {
+ mControlPanelItems.adapter = PanelItemsAdapter(handleAction)
+ }
+ if (mControlPanelHeader.adapter == null) {
+ mControlPanelHeader.adapter = PanelHeaderAdapter(handleAction)
+ }
+ if (state.panel != null) {
+ (mControlPanelHeader.adapter as PanelHeaderAdapter).submitList(state.panel.headerItems)
+ (mControlPanelItems.adapter as PanelItemsAdapter).submitList(state.panel.items)
+ mControlPanelContainer.visibility = View.VISIBLE
+ } else {
+ (mControlPanelHeader.adapter as PanelHeaderAdapter).submitList(listOf())
+ (mControlPanelItems.adapter as PanelItemsAdapter).submitList(listOf())
+ mControlPanelContainer.visibility = View.GONE
+ }
+ }
+
+ waitForMeasureAndUpdateWidth()
+ }
+
+ /**
+ * A utility function to calculate the necessary width of the tool.
+ *
+ * On bigger screens WRAP_CONTENT doesn't behave correctly for dialogs because of a size limit
+ * defined in the config_prefDialogWidth property. For this reason
+ * the width of the tool is calculated manually based on the content displayed.
+ */
+ private fun waitForMeasureAndUpdateWidth() {
+ mMainLayout.viewTreeObserver.addOnGlobalLayoutListener(
+ object : ViewTreeObserver.OnGlobalLayoutListener {
+ override fun onGlobalLayout() {
+ mMainLayout.viewTreeObserver.removeOnGlobalLayoutListener(/*victim=*/this)
+ usableWindowWidth = calculateUsableWidth()
+ updateToolWidth()
+ }
+ }
+ )
+ }
+
+ private fun calculateUsableWidth(): Int {
+ val windowMetrics = windowManager.currentWindowMetrics
+ val insets = windowMetrics.windowInsets.getInsetsIgnoringVisibility(
+ /*typeMask=*/WindowInsets.Type.navigationBars() or WindowInsets.Type.displayCutout()
+ )
+ return windowMetrics.bounds.width() - insets.right - insets.left
+ }
+
+ private fun updateToolWidth() {
+ val windowMetrics = windowManager.currentWindowMetrics
+ mMainLayout.measure(
+ windowMetrics.bounds.width(),
+ windowMetrics.bounds.height()
+ )
+ val mainWidth = mMainLayout.measuredWidth
+
+ val panelMinWidth = if (mControlPanelContainer.visibility == View.VISIBLE) {
+ (usableWindowWidth * 0.8).toInt()
+ } else {
+ -1
+ }
+
+ windowLayoutParams.width = min(max(mainWidth, panelMinWidth), usableWindowWidth)
+ windowManager.updateViewLayout(mMainLayout, windowLayoutParams)
+ }
+
+ /**
+ * Accessing a PRIVATE_FLAG_NO_MOVE_ANIMATION to enable or disable window movement animation.
+ *
+ * Disabling the window animation prevents the UI from "shaking" when the window changes width,
+ * while enabling it allows the window to move smoothly while dragging the tool.
+ */
+ private fun setWindowMoveAnimation(enable: Boolean) {
+ try {
+ val privateFlagsField = windowLayoutParams.javaClass.getField("privateFlags")
+ val newValue = if (enable) {
+ (privateFlagsField.get(windowLayoutParams) as Int) and (1 shl 6).inv()
+ } else {
+ (privateFlagsField.get(windowLayoutParams) as Int) or (1 shl 6)
+ }
+ privateFlagsField.set(windowLayoutParams, newValue)
+ } catch (e: Exception) {
+ Log.e("CustomizationToolUi", "Setting window animation failed! \n $e")
+ }
+ }
+
+ /**
+ * Changing flags on [WindowManager.LayoutParams] based on clicks inside or outside the Window
+ * to enable / disable keyboard access in the tool.
+ */
+ private fun setKeyboardActive(active: Boolean) {
+ if (active && windowLayoutParams.flags and FLAG_NOT_FOCUSABLE != 0) {
+ windowLayoutParams.flags = windowLayoutParams.flags and FLAG_NOT_FOCUSABLE.inv()
+ windowManager.updateViewLayout(mMainLayout, windowLayoutParams)
+ } else if (!active && windowLayoutParams.flags and FLAG_NOT_FOCUSABLE == 0) {
+ windowLayoutParams.flags = windowLayoutParams.flags or FLAG_NOT_FOCUSABLE
+ windowManager.updateViewLayout(mMainLayout, windowLayoutParams)
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/MainLayoutView.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/MainLayoutView.kt
new file mode 100644
index 0000000..aaf1fa8
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/MainLayoutView.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.widget.LinearLayout
+
+/**
+ * Represents the main layout view of the tool. A [LinearLayout] with an additional touch listener
+ * that returns true or false when touches are triggered inside or outside the layout.
+ */
+class MainLayoutView : LinearLayout {
+
+ constructor(context: Context) : super(context)
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+ constructor(
+ context: Context,
+ attrs: AttributeSet,
+ defStyleAttr: Int
+ ) : super(context, attrs, defStyleAttr)
+
+ private var touchListener: ((inside: Boolean) -> Unit)? = null
+
+ fun setTouchListener(listener: (inside: Boolean) -> Unit) {
+ touchListener = listener
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ if (event.action == MotionEvent.ACTION_DOWN) touchListener?.invoke(/* inside = */true)
+ if (event.action == MotionEvent.ACTION_OUTSIDE) touchListener?.invoke(/* inside = */false)
+ return super.onTouchEvent(event)
+ }
+
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
+ if (event.action == MotionEvent.ACTION_DOWN) touchListener?.invoke(/* inside = */true)
+ if (event.action == MotionEvent.ACTION_OUTSIDE) touchListener?.invoke(/* inside = */false)
+ return super.onInterceptTouchEvent(event)
+ }
+
+}
\ No newline at end of file
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/DropDownItemViewHolder.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/DropDownItemViewHolder.kt
new file mode 100644
index 0000000..d144d3f
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/DropDownItemViewHolder.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.menu
+
+import android.view.View
+import android.widget.Button
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.menu.MenuItem
+
+internal class DropDownItemViewHolder(view: View) : MenuItemViewHolder(view) {
+
+ private val dropDownButton = itemView.requireViewById<Button>(R.id.menu_dropdown)
+
+ override fun bindTo(model: MenuItem, handleAction: (Action) -> Unit) {
+ model as MenuItem.DropDown
+
+ dropDownButton.run {
+ text = context.getText(model.displayTextRes)
+ isEnabled = model.isEnabled
+ setOnClickListener {
+ showDropDownPopupWindow(itemView, model.items, handleAction)
+ }
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/DropDownPopup.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/DropDownPopup.kt
new file mode 100644
index 0000000..bfd0608
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/DropDownPopup.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.menu
+
+import android.content.res.Resources
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.PopupWindow
+import android.widget.TextView
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.menu.MenuItem
+
+/**
+ * Opens a [PopupWindow] with a list of [MenuItem.DropDown.Item].
+ */
+internal fun showDropDownPopupWindow(
+ itemView: View,
+ items: List<MenuItem.DropDown.Item>,
+ handleAction: (Action) -> Unit,
+) {
+ val dropDownPopup = LayoutInflater.from(itemView.context).inflate(
+ R.layout.vh_menu_dropdown_popup,
+ /*root=*/null
+ )
+
+ val popupWindow = PopupWindow(
+ /*contentView=*/ dropDownPopup,
+ /*width=*/ ViewGroup.LayoutParams.WRAP_CONTENT,
+ /*height=*/ ViewGroup.LayoutParams.WRAP_CONTENT,
+ /*focusable=*/ true
+ )
+
+ dropDownPopup.requireViewById<RecyclerView>(R.id.dropdown_popup_list).run {
+ layoutManager = LinearLayoutManager(itemView.context)
+ adapter = DropDownAdapter(items, handleAction, popupWindow)
+ setHasFixedSize(true)
+ }
+
+ // Calculate the coordinates based on the button's location
+ val location = IntArray(2)
+ itemView.getLocationInWindow(location)
+ val margin = (5 / Resources.getSystem().displayMetrics.density).toInt()
+ val x = location[0]
+ val y = location[1] + itemView.height + margin
+
+ popupWindow.showAtLocation(itemView, Gravity.NO_GRAVITY, x, y)
+}
+
+/**
+ * Adapter for the item list inside a [MenuItem.DropDown].
+ */
+private class DropDownAdapter(
+ private val items: List<MenuItem.DropDown.Item>,
+ private val handleAction: (Action) -> Unit,
+ private val popupWindow: PopupWindow,
+) : RecyclerView.Adapter<DropDownAdapter.ViewHolder>() {
+ class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+
+ private val titleTextView = itemView.requireViewById<TextView>(R.id.dropdown_title)
+
+ fun bindTo(
+ model: MenuItem.DropDown.Item,
+ handleAction: (Action) -> Unit,
+ popupWindow: PopupWindow,
+ ) {
+ titleTextView.text = model.title
+ itemView.isSelected = model.isActive
+
+ if (!model.isActive) {
+ itemView.isSelected = false
+ itemView.setOnClickListener {
+ handleAction(model.action)
+ popupWindow.dismiss()
+ }
+ }
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
+ LayoutInflater.from(parent.context).inflate(
+ R.layout.vh_menu_dropdown_popup_item,
+ parent,
+ /*attachToRoot=*/false
+ )
+ )
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.bindTo(items[position], handleAction, popupWindow)
+ }
+
+ override fun getItemCount() = items.size
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/HorizontalMarginItemDecoration.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/HorizontalMarginItemDecoration.kt
new file mode 100644
index 0000000..b3e5308
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/HorizontalMarginItemDecoration.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.menu
+
+import android.graphics.Rect
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+ * Sets the margins between the items in the Menu RecyclerView.
+ */
+internal class HorizontalMarginItemDecoration(
+ private val spaceSize: Int,
+) : RecyclerView.ItemDecoration() {
+
+ override fun getItemOffsets(
+ outRect: Rect,
+ view: View,
+ parent: RecyclerView,
+ state: RecyclerView.State,
+ ) {
+ with(outRect) {
+ if (parent.getChildAdapterPosition(view) > 0) {
+ left = spaceSize
+ }
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/MenuAdapter.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/MenuAdapter.kt
new file mode 100644
index 0000000..86493f5
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/MenuAdapter.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.menu
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.menu.MenuItem
+
+/**
+ * Adapter for the Menu RecyclerView
+ */
+internal class MenuAdapter(
+ private val handleAction: (Action) -> Unit,
+) : ListAdapter<MenuItem, MenuItemViewHolder>(ItemCallback()) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MenuItemViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ return when (ViewType.from(viewType)) {
+ ViewType.SUBMENU_NAVIGATION -> {
+ SubMenuNavigationItemViewHolder(
+ inflater.inflate(
+ R.layout.vh_menu_navigation,
+ parent,
+ /*attachToRoot=*/false
+ )
+ )
+ }
+
+ ViewType.UP_NAVIGATION -> {
+ UpNavigationViewHolder(
+ inflater.inflate(
+ R.layout.vh_menu_up_navigation,
+ parent,
+ /*attachToRoot=*/false
+ )
+ )
+ }
+
+ ViewType.SWITCH -> {
+ SwitchItemViewHolder(
+ inflater.inflate(
+ R.layout.vh_menu_switch,
+ parent,
+ /*attachToRoot=*/false
+ )
+ )
+ }
+
+ ViewType.PANEL_LAUNCHER -> {
+ PanelLauncherItemViewHolder(
+ inflater.inflate(
+ R.layout.vh_menu_panel_launcher,
+ parent,
+ /*attachToRoot=*/false
+ )
+ )
+ }
+
+ ViewType.DROPDOWN -> DropDownItemViewHolder(
+ inflater.inflate(
+ R.layout.vh_menu_dropdown,
+ parent,
+ /*attachToRoot=*/false
+ )
+ )
+ }
+ }
+
+ override fun onBindViewHolder(viewHolder: MenuItemViewHolder, position: Int) {
+ viewHolder.bindTo(getItem(position), handleAction)
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return when (getItem(position)) {
+ is MenuItem.SubMenuNavigation -> ViewType.SUBMENU_NAVIGATION.ordinal
+ is MenuItem.UpNavigation -> ViewType.UP_NAVIGATION.ordinal
+ is MenuItem.Switch -> ViewType.SWITCH.ordinal
+ is MenuItem.PanelLauncher -> ViewType.PANEL_LAUNCHER.ordinal
+ is MenuItem.DropDown -> ViewType.DROPDOWN.ordinal
+ }
+ }
+
+ /**
+ * Defines which types of [MenuItem] are supported in this Adapter
+ */
+ enum class ViewType {
+ SUBMENU_NAVIGATION, UP_NAVIGATION, SWITCH, PANEL_LAUNCHER, DROPDOWN;
+
+ companion object {
+ fun from(index: Int): ViewType {
+ if (index >= values().size) {
+ throw IllegalArgumentException("Type not supported")
+ }
+ return values()[index]
+ }
+ }
+ }
+
+ class ItemCallback : DiffUtil.ItemCallback<MenuItem>() {
+
+ override fun areItemsTheSame(p0: MenuItem, p1: MenuItem): Boolean {
+ return p0.displayTextRes == p1.displayTextRes
+ }
+
+ override fun areContentsTheSame(p0: MenuItem, p1: MenuItem): Boolean {
+ return p0 == p1
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/MenuItemViewHolder.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/MenuItemViewHolder.kt
new file mode 100644
index 0000000..b98bb51
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/MenuItemViewHolder.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.menu
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.menu.MenuItem
+
+/**
+ * Base class for ViewHolders of the [MenuAdapter].
+ */
+internal abstract class MenuItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+
+ abstract fun bindTo(model: MenuItem, handleAction: (Action) -> Unit)
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/PanelLauncherItemViewHolder.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/PanelLauncherItemViewHolder.kt
new file mode 100644
index 0000000..2524c5f
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/PanelLauncherItemViewHolder.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.menu
+
+import android.view.View
+import android.widget.Button
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.menu.MenuItem
+
+internal class PanelLauncherItemViewHolder(view: View) : MenuItemViewHolder(view) {
+
+ private val panelLauncher = itemView.requireViewById<Button>(R.id.menu_panel_launcher)
+
+ override fun bindTo(model: MenuItem, handleAction: (Action) -> Unit) {
+ model as MenuItem.PanelLauncher
+
+ panelLauncher.run {
+ text = context.getText(model.displayTextRes)
+ isEnabled = model.isEnabled
+ setOnClickListener {
+ handleAction(model.action)
+ }
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/SubMenuNavigationItemViewHolder.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/SubMenuNavigationItemViewHolder.kt
new file mode 100644
index 0000000..02b3b81
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/SubMenuNavigationItemViewHolder.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.menu
+
+import android.view.View
+import android.widget.Button
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.menu.MenuItem
+
+internal class SubMenuNavigationItemViewHolder(view: View) : MenuItemViewHolder(view) {
+
+ private val navigationButton = itemView.requireViewById<Button>(R.id.menu_navigation)
+
+ override fun bindTo(model: MenuItem, handleAction: (Action) -> Unit) {
+ model as MenuItem.SubMenuNavigation
+
+ navigationButton.run {
+ text = context.getText(model.displayTextRes)
+ setOnClickListener {
+ handleAction(model.action)
+ }
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/SwitchItemViewHolder.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/SwitchItemViewHolder.kt
new file mode 100644
index 0000000..1e1460f
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/SwitchItemViewHolder.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.menu
+
+import android.view.View
+import android.widget.Switch
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.menu.MenuItem
+
+internal class SwitchItemViewHolder(view: View) : MenuItemViewHolder(view) {
+
+ private val switch = itemView.requireViewById<Switch>(R.id.menu_switch)
+
+ override fun bindTo(model: MenuItem, handleAction: (Action) -> Unit) {
+ model as MenuItem.Switch
+
+ switch.run {
+ text = context.getText(model.displayTextRes)
+ isChecked = model.isChecked
+ isEnabled = model.isEnabled
+ setOnClickListener {
+ // Preventing the switch to change visually before the state does
+ isChecked = !isChecked
+ handleAction(model.action)
+ }
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/UpNavigationViewHolder.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/UpNavigationViewHolder.kt
new file mode 100644
index 0000000..844ad48
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/menu/UpNavigationViewHolder.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.menu
+
+import android.view.View
+import android.widget.ImageButton
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.menu.MenuItem
+
+internal class UpNavigationViewHolder(view: View) : MenuItemViewHolder(view) {
+
+ private val navigationUpButton = itemView.requireViewById<ImageButton>(R.id.menu_up_navigation)
+
+ override fun bindTo(model: MenuItem, handleAction: (Action) -> Unit) {
+ model as MenuItem.UpNavigation
+
+ navigationUpButton.setOnClickListener {
+ handleAction(model.action)
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderAdapter.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderAdapter.kt
new file mode 100644
index 0000000..6760651
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderAdapter.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.panel.header
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.panel.PanelHeaderItem
+
+/**
+ * Adapter for the Panel RecyclerView.
+ */
+internal class PanelHeaderAdapter(
+ private val handleAction: (Action) -> Unit,
+) : ListAdapter<PanelHeaderItem, PanelHeaderItemViewHolder>(ItemCallback()) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PanelHeaderItemViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ return when (ViewType.from(viewType)) {
+ ViewType.HEADER_CLOSE -> {
+ PanelHeaderCloseViewHolder(
+ inflater.inflate(
+ R.layout.vh_panel_header_close,
+ parent,
+ /*attachToRoot=*/false
+ )
+ )
+ }
+
+ ViewType.HEADER_SEARCH -> {
+ PanelHeaderSearchBoxViewHolder(
+ inflater.inflate(
+ R.layout.vh_panel_header_search,
+ parent,
+ /*attachToRoot=*/false
+ )
+ )
+ }
+
+ ViewType.HEADER_DROPDOWN -> PanelHeaderDropDownViewHolder(
+ inflater.inflate(
+ R.layout.vh_panel_header_dropdown,
+ parent,
+ /*attachToRoot=*/false
+ )
+ )
+ }
+ }
+
+ override fun onBindViewHolder(viewHolder: PanelHeaderItemViewHolder, position: Int) {
+ viewHolder.bindTo(getItem(position), handleAction)
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return when (getItem(position)) {
+ is PanelHeaderItem.SearchBox -> ViewType.HEADER_SEARCH.ordinal
+ is PanelHeaderItem.CloseButton -> ViewType.HEADER_CLOSE.ordinal
+ is PanelHeaderItem.DropDown -> ViewType.HEADER_DROPDOWN.ordinal
+ }
+ }
+
+ /**
+ * Defines which types of panel items are supported by this Adapter.
+ */
+ enum class ViewType {
+ HEADER_SEARCH, HEADER_CLOSE, HEADER_DROPDOWN;
+
+ companion object {
+ fun from(index: Int): ViewType {
+ if (index >= values().size) {
+ throw IllegalArgumentException("Type not supported")
+ }
+ return values()[index]
+ }
+ }
+ }
+
+ class ItemCallback : DiffUtil.ItemCallback<PanelHeaderItem>() {
+
+ override fun areItemsTheSame(p0: PanelHeaderItem, p1: PanelHeaderItem): Boolean {
+ return p0::class == p1::class
+ }
+
+ override fun areContentsTheSame(p0: PanelHeaderItem, p1: PanelHeaderItem): Boolean {
+ return p0 == p1
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderCloseViewHolder.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderCloseViewHolder.kt
new file mode 100644
index 0000000..b0d203b
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderCloseViewHolder.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.panel.header
+
+import android.view.View
+import android.widget.ImageButton
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.panel.PanelHeaderItem
+
+internal class PanelHeaderCloseViewHolder(view: View) : PanelHeaderItemViewHolder(view) {
+
+ private val searchEditText = itemView.requireViewById<ImageButton>(R.id.control_panel_close)
+
+ override fun bindTo(model: PanelHeaderItem, handleAction: (Action) -> Unit) {
+ model as PanelHeaderItem.CloseButton
+ searchEditText.setOnClickListener {
+ handleAction(model.action)
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderDropDownViewHolder.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderDropDownViewHolder.kt
new file mode 100644
index 0000000..ccb81be
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderDropDownViewHolder.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.panel.header
+
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Spinner
+import android.widget.TextView
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.panel.PanelHeaderItem
+
+internal class PanelHeaderDropDownViewHolder(view: View) : PanelHeaderItemViewHolder(view) {
+
+ private val dropDownText = itemView.requireViewById<TextView>(R.id.panel_header_dropdown_text)
+ private val dropDown = itemView.requireViewById<Spinner>(R.id.panel_header_dropdown)
+
+ override fun bindTo(model: PanelHeaderItem, handleAction: (Action) -> Unit) {
+ model as PanelHeaderItem.DropDown
+
+ dropDownText.text = model.text
+ val adapter = ArrayAdapter(
+ dropDown.context,
+ R.layout.vh_panel_header_dropdown_item_selected,
+ model.items.map { it.text }
+ )
+ adapter.setDropDownViewResource(R.layout.vh_panel_header_dropdown_item)
+ dropDown.adapter = adapter
+ dropDown.setSelection(model.items.indexOfFirst { it.isActive })
+
+ dropDown.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ handleAction(model.items[position].action)
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {
+ }
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderItemViewHolder.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderItemViewHolder.kt
new file mode 100644
index 0000000..04d7d66
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderItemViewHolder.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.panel.header
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.panel.PanelHeaderItem
+
+/**
+ * Base class for ViewHolders in the [PanelHeaderAdapter].
+ */
+internal abstract class PanelHeaderItemViewHolder(view: View) : ViewHolder(view) {
+
+ abstract fun bindTo(model: PanelHeaderItem, handleAction: (Action) -> Unit)
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderSearchBoxViewHolder.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderSearchBoxViewHolder.kt
new file mode 100644
index 0000000..d2562f1
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/header/PanelHeaderSearchBoxViewHolder.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.panel.header
+
+import android.view.View
+import android.widget.EditText
+import androidx.core.widget.doAfterTextChanged
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.panel.PanelHeaderItem
+
+internal class PanelHeaderSearchBoxViewHolder(view: View) : PanelHeaderItemViewHolder(view) {
+
+ private val searchEditText = itemView.requireViewById<EditText>(R.id.panel_header_search)
+ private var runnable: Runnable = Runnable { }
+
+ override fun bindTo(model: PanelHeaderItem, handleAction: (Action) -> Unit) {
+ model as PanelHeaderItem.SearchBox
+ searchEditText.hint = model.searchHint
+ searchEditText.doAfterTextChanged { text ->
+ // Adding a manual "debounce" mechanism until Flow is added to the project
+ searchEditText.handler.removeCallbacks(runnable)
+ runnable = Runnable { handleAction(model.searchAction(text.toString())) }
+ searchEditText.handler.postDelayed(runnable, 300L)
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/items/PanelItemViewHolder.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/items/PanelItemViewHolder.kt
new file mode 100644
index 0000000..891cab4
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/items/PanelItemViewHolder.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.panel.items
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.panel.PanelItem
+
+/**
+ * Base class for ViewHolders in the [PanelItemsAdapter].
+ */
+internal abstract class PanelItemViewHolder(view: View) : ViewHolder(view) {
+
+ abstract fun bindTo(model: PanelItem, handleAction: (Action) -> Unit)
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/items/PanelItemsAdapter.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/items/PanelItemsAdapter.kt
new file mode 100644
index 0000000..e498797
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/items/PanelItemsAdapter.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.panel.items
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.panel.PanelItem
+
+/**
+ * Adapter for the Panel RecyclerView.
+ */
+internal class PanelItemsAdapter(
+ private val handleAction: (Action) -> Unit,
+) : ListAdapter<PanelItem, PanelItemViewHolder>(ItemCallback()) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PanelItemViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ return when (ViewType.from(viewType)) {
+ ViewType.SECTION_TITLE -> {
+ PanelSectionTitleItemViewHolder(
+ inflater.inflate(
+ R.layout.vh_panel_section_title,
+ parent,
+ /*attachToRoot=*/false
+ )
+ )
+ }
+
+ ViewType.SWITCH -> {
+ PanelSwitchViewHolder(
+ inflater.inflate(
+ R.layout.vh_panel_switch,
+ parent,
+ /*attachToRoot=*/false
+ )
+ )
+ }
+ }
+ }
+
+ override fun onBindViewHolder(viewHolder: PanelItemViewHolder, position: Int) {
+ viewHolder.bindTo(getItem(position), handleAction)
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return when (getItem(position)) {
+ is PanelItem.SectionTitle -> ViewType.SECTION_TITLE.ordinal
+ is PanelItem.Switch -> ViewType.SWITCH.ordinal
+ }
+ }
+
+ /**
+ * Defines which types of panel items are supported by this Adapter.
+ */
+ enum class ViewType {
+ SECTION_TITLE, SWITCH;
+
+ companion object {
+ fun from(index: Int): ViewType {
+ if (index >= values().size) {
+ throw IllegalArgumentException("Type not supported")
+ }
+ return values()[index]
+ }
+ }
+ }
+
+ class ItemCallback : DiffUtil.ItemCallback<PanelItem>() {
+
+ override fun areItemsTheSame(p0: PanelItem, p1: PanelItem): Boolean {
+ return p0::class == p1::class
+ }
+
+ override fun areContentsTheSame(p0: PanelItem, p1: PanelItem): Boolean {
+ return p0 == p1
+ }
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/items/PanelSectionTitleItemViewHolder.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/items/PanelSectionTitleItemViewHolder.kt
new file mode 100644
index 0000000..5ea3bc1
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/items/PanelSectionTitleItemViewHolder.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.panel.items
+
+import android.view.View
+import android.widget.TextView
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.panel.PanelItem
+
+internal class PanelSectionTitleItemViewHolder(view: View) : PanelItemViewHolder(view) {
+
+ private val textView = itemView.requireViewById<TextView>(R.id.panel_section_title)
+
+ override fun bindTo(model: PanelItem, handleAction: (Action) -> Unit) {
+ model as PanelItem.SectionTitle
+
+ textView.text = model.text
+ }
+}
diff --git a/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/items/PanelSwitchViewHolder.kt b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/items/PanelSwitchViewHolder.kt
new file mode 100644
index 0000000..ea388c6
--- /dev/null
+++ b/tests/CustomizationTool/src/com/android/car/customization/tool/ui/panel/items/PanelSwitchViewHolder.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.ui.panel.items
+
+import android.view.View
+import android.widget.Switch
+import android.widget.TextView
+import com.android.car.customization.tool.R
+import com.android.car.customization.tool.domain.Action
+import com.android.car.customization.tool.domain.panel.PanelItem
+
+internal class PanelSwitchViewHolder(view: View) : PanelItemViewHolder(view) {
+
+ private val textView = itemView.requireViewById<TextView>(R.id.panel_switch_text)
+ private val errorTextView = itemView.requireViewById<TextView>(R.id.panel_switch_error)
+ private val switch = itemView.requireViewById<Switch>(R.id.panel_switch_toggle)
+
+ override fun bindTo(model: PanelItem, handleAction: (Action) -> Unit) {
+ model as PanelItem.Switch
+
+ textView.text = model.text
+ if (model.errorText.isNullOrEmpty()) {
+ errorTextView.visibility = View.GONE
+ } else {
+ errorTextView.text = model.errorText
+ errorTextView.visibility = View.VISIBLE
+ }
+ switch.isChecked = model.isChecked
+ switch.isEnabled = model.isEnabled
+ switch.setOnClickListener {
+ handleAction(model.action)
+ }
+ }
+}
diff --git a/tests/CustomizationTool/tests/AndroidManifest.xml b/tests/CustomizationTool/tests/AndroidManifest.xml
new file mode 100644
index 0000000..ff00467
--- /dev/null
+++ b/tests/CustomizationTool/tests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.car.customization.tool.tests">
+
+ <application
+ android:debuggable="true"
+ android:testOnly="false">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Customization Tool Unit Tests"
+ android:targetPackage="com.android.car.customization.tool" />
+</manifest>
diff --git a/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/CustomizationToolStateMachineTest.kt b/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/CustomizationToolStateMachineTest.kt
new file mode 100644
index 0000000..2ac59b9
--- /dev/null
+++ b/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/CustomizationToolStateMachineTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain
+
+import com.android.car.customization.tool.domain.menu.Menu
+import com.android.car.customization.tool.domain.menu.MenuController
+import com.android.car.customization.tool.domain.menu.OpenSubMenuAction
+import com.android.car.customization.tool.domain.panel.OpenPanelAction
+import com.android.car.customization.tool.domain.panel.Panel
+import com.android.car.customization.tool.domain.panel.PanelController
+import javax.inject.Provider
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnitRunner
+
+@RunWith(MockitoJUnitRunner::class)
+internal class CustomizationToolStateMachineTest {
+
+ private lateinit var fakeObserver: Observer<PageState>
+
+ @Mock
+ private lateinit var mockMenuController: MenuController
+
+ @Before
+ fun setUp() {
+ fakeObserver = spy(
+ object : Observer<PageState> {
+ override fun render(state: PageState) {}
+ })
+ }
+
+ @Test
+ fun when_the_action_toggle_is_received_the_page_state_is_updated() {
+ `when`(mockMenuController.buildMenu()).thenReturn(Menu(rootNode, currentParentNode = rootNode))
+ val underTest = CustomizationToolStateMachine(mockMenuController, mock())
+
+ underTest.register(fakeObserver)
+
+ verify(fakeObserver, times(/*wantedNumberOfInvocations=*/1)).render(
+ PageState(
+ isOpen = false,
+ Menu(rootNode, rootNode),
+ panel = null
+ )
+ )
+ underTest.handleAction(ToggleUiAction)
+
+ verify(fakeObserver, times(/*wantedNumberOfInvocations=*/1)).render(
+ PageState(
+ isOpen = true,
+ Menu(rootNode, rootNode),
+ panel = null
+ )
+ )
+ verifyNoMoreInteractions(fakeObserver)
+ }
+
+ @Test
+ fun when_a_menu_action_is_received_then_it_is_forwarded_to_the_MenuController() {
+ val initialMenu = Menu(rootNode, rootNode)
+ `when`(mockMenuController.buildMenu()).thenReturn(initialMenu)
+ `when`(
+ mockMenuController.handleAction(
+ initialMenu,
+ OpenSubMenuAction(firstLevelSubNavigation.displayTextRes)
+ )
+ ).thenReturn(Menu(rootNode, firstLevelSubNavigation))
+
+ val underTest = CustomizationToolStateMachine(mockMenuController, mock())
+ underTest.register(fakeObserver)
+ underTest.handleAction(OpenSubMenuAction(firstLevelSubNavigation.displayTextRes))
+
+ // Verifying that the State Machine redirects the MenuAction to the MenuController
+ verify(mockMenuController, times(/*wantedNumberOfInvocations=*/1)).handleAction(
+ Menu(rootNode, rootNode),
+ OpenSubMenuAction(firstLevelSubNavigation.displayTextRes)
+ )
+
+ // Verifying that the result from the MenuController is actually used in the new PageState
+ verify(fakeObserver, times(/*wantedNumberOfInvocations=*/1)).render(
+ PageState(
+ isOpen = false,
+ Menu(rootNode, firstLevelSubNavigation),
+ panel = null
+ )
+ )
+ }
+
+ @Test
+ fun when_a_panel_action_is_received_then_it_is_forwarded_to_the_PanelController() {
+ val emptyPanelReducer = EmptyPanelReducer()
+ val spyPanelController =
+ spy(PanelController(mapOf(EmptyPanelReducer::class.java to Provider { emptyPanelReducer })))
+
+ val underTest = CustomizationToolStateMachine(mockMenuController, spyPanelController)
+ underTest.register(fakeObserver)
+
+ underTest.handleAction(OpenPanelAction(EmptyPanelReducer::class))
+
+ // Verifying that the State Machine redirects the PanelAction to the PanelController
+ verify(spyPanelController, times(/*wantedNumberOfInvocations=*/1)).handleAction(
+ panel = null,
+ OpenPanelAction(EmptyPanelReducer::class)
+ )
+
+ // Verifying that the result from the PanelController is actually used in the new PageState
+ verify(fakeObserver, times(/*wantedNumberOfInvocations=*/1)).render(
+ PageState(
+ isOpen = false,
+ Menu(rootNode, rootNode),
+ Panel(emptyList())
+ )
+ )
+ }
+
+ @Test
+ fun expect_exception_when_action_is_not_implemented_in_the_state_machine() {
+ val mockMenuController = MenuController({ rootNode }, mock(), mapOf())
+ val underTest = CustomizationToolStateMachine(mockMenuController, mock())
+
+ assertThrows(IllegalArgumentException::class.java) {
+ underTest.handleAction(FakeAction)
+ }
+ }
+}
diff --git a/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/TestData.kt b/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/TestData.kt
new file mode 100644
index 0000000..16f68cb
--- /dev/null
+++ b/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/TestData.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain
+
+import androidx.annotation.StringRes
+import com.android.car.customization.tool.domain.menu.Menu
+import com.android.car.customization.tool.domain.menu.MenuAction
+import com.android.car.customization.tool.domain.menu.MenuActionReducer
+import com.android.car.customization.tool.domain.menu.MenuItem
+import com.android.car.customization.tool.domain.menu.ToggleMenuAction
+import com.android.car.customization.tool.domain.panel.OpenPanelAction
+import com.android.car.customization.tool.domain.panel.Panel
+import com.android.car.customization.tool.domain.panel.PanelAction
+import com.android.car.customization.tool.domain.panel.PanelActionReducer
+
+internal object FakeAction : Action
+
+internal class FakeMenuAction : MenuAction
+
+internal data class FakeToggleAction(
+ @StringRes override val itemId: Int = 0,
+ override val newValue: Boolean = false,
+) : ToggleMenuAction {
+
+ override fun clone(newValue: Boolean): ToggleMenuAction = copy(newValue = newValue)
+}
+
+internal class FakeMenuActionReducer : MenuActionReducer {
+
+ override fun reduce(menu: Menu, action: MenuAction): Menu {
+ return Menu(rootNode = rootNode, currentParentNode = secondLevelSubNavigation)
+ }
+}
+
+// All of the necessary objects to create a simple menu tree. The structure is the following:
+// rootNode
+// / \
+// firstLevelSubNavigation firstLevelPanelLauncher
+// / | \
+// secondLevelSwitch1 secondLevelSwitch2 secondLevelSubNavigation
+// /
+// thirdLevelDropDown
+internal val dropDownItemA = MenuItem.DropDown.Item(
+ title = "Item1",
+ isActive = true,
+ action = FakeMenuAction()
+)
+internal val dropDownItemB = MenuItem.DropDown.Item(
+ title = "Item2",
+ isActive = false,
+ action = FakeMenuAction()
+)
+internal val thirdLevelDropDown = MenuItem.DropDown(
+ displayTextRes = 1,
+ isEnabled = true,
+ items = listOf(dropDownItemA, dropDownItemB)
+)
+internal val secondLevelSubNavigation = MenuItem.SubMenuNavigation(
+ displayTextRes = 2,
+ subMenu = listOf(thirdLevelDropDown)
+)
+internal val secondLevelSwitch1 = MenuItem.Switch(
+ displayTextRes = 3,
+ isEnabled = true,
+ isChecked = true,
+ FakeToggleAction()
+)
+internal val secondLevelSwitch2 = MenuItem.Switch(
+ displayTextRes = 4,
+ isEnabled = true,
+ isChecked = true,
+ FakeToggleAction()
+)
+internal val firstLevelPanelLauncher = MenuItem.PanelLauncher(
+ displayTextRes = 5,
+ isEnabled = true,
+ action = OpenPanelAction(PanelActionReducer::class)
+)
+internal val firstLevelSubNavigation = MenuItem.SubMenuNavigation(
+ displayTextRes = 6,
+ subMenu = listOf(secondLevelSubNavigation, secondLevelSwitch1, secondLevelSwitch2)
+)
+internal val rootNode = MenuItem.SubMenuNavigation(
+ displayTextRes = 0,
+ subMenu = listOf(firstLevelPanelLauncher, firstLevelSubNavigation)
+)
+
+internal class EmptyPanelReducer : PanelActionReducer {
+ override var bundle: Map<String, Any> = emptyMap()
+ override fun build(): Panel = Panel(items = emptyList())
+ override fun reduce(panel: Panel, action: PanelAction): Panel = Panel(items = emptyList())
+}
+
+/**
+ * A simple test function to find any node inside the menu tree
+ */
+internal fun MenuItem.SubMenuNavigation.findNode(
+ @StringRes nodeId: Int,
+): MenuItem? {
+ if (displayTextRes == nodeId) return this
+
+ subMenu.forEach { node ->
+ if (node.displayTextRes == nodeId) {
+ return node
+ } else if (node is MenuItem.SubMenuNavigation) {
+ val subNode = node.findNode(nodeId)
+ if (subNode?.displayTextRes == nodeId) {
+ return subNode
+ }
+ }
+ }
+ return null
+}
diff --git a/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/menu/MenuControllerTest.kt b/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/menu/MenuControllerTest.kt
new file mode 100644
index 0000000..521e077
--- /dev/null
+++ b/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/menu/MenuControllerTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain.menu
+
+import android.content.Context
+import android.content.res.Resources
+import com.android.car.customization.tool.domain.FakeMenuAction
+import com.android.car.customization.tool.domain.FakeMenuActionReducer
+import com.android.car.customization.tool.domain.firstLevelPanelLauncher
+import com.android.car.customization.tool.domain.firstLevelSubNavigation
+import com.android.car.customization.tool.domain.rootNode
+import com.android.car.customization.tool.domain.secondLevelSubNavigation
+import javax.inject.Provider
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnitRunner
+
+@RunWith(MockitoJUnitRunner::class)
+internal class MenuControllerTest {
+
+ @Mock
+ private lateinit var mockContext: Context
+
+ private val fakeRootProvider = Provider { rootNode }
+
+ private val firstLevelWithDuplicateIds = listOf(firstLevelPanelLauncher, firstLevelPanelLauncher)
+ private val rootNodeWithDuplicates = MenuItem.SubMenuNavigation(
+ displayTextRes = 0,
+ firstLevelWithDuplicateIds
+ )
+ private val fakeRootProviderWithDuplicates = Provider { rootNodeWithDuplicates }
+
+ @Test
+ fun build_menu_test() {
+ val underTest = MenuController(fakeRootProvider, mockContext, mapOf())
+ val menu = underTest.buildMenu()
+
+ // Verifying that given a Provider, the Menu is built correctly
+ assertEquals(Menu(rootNode, currentParentNode = rootNode), menu)
+ }
+
+ @Test
+ fun expect_exception_when_menu_has_duplicate_ids() {
+ val mockResources: Resources = mock()
+ `when`(mockResources.getResourceName(Mockito.anyInt())).thenReturn("mock")
+ `when`(mockContext.resources).thenReturn(mockResources)
+
+ val underTest = MenuController(fakeRootProviderWithDuplicates, mockContext, mapOf())
+
+ assertThrows(IllegalStateException::class.java) {
+ underTest.buildMenu()
+ }
+ }
+
+ @Test
+ fun when_opening_submenu_the_currentParent_is_updated_correctly() {
+ val expectedMenu = Menu(rootNode, currentParentNode = firstLevelSubNavigation)
+ val underTest = MenuController(fakeRootProvider, mockContext, mapOf())
+
+ val menuAfterAction = underTest.handleAction(
+ Menu(rootNode, rootNode),
+ OpenSubMenuAction(firstLevelSubNavigation.displayTextRes)
+ )
+
+ assertEquals(expectedMenu, menuAfterAction)
+ }
+
+ @Test
+ fun expect_exception_if_the_submenu_is_not_a_direct_child_of_the_current_parent() {
+ val underTest = MenuController(fakeRootProvider, mockContext, mapOf())
+
+ assertThrows(IllegalArgumentException::class.java) {
+ underTest.handleAction(
+ Menu(rootNode, currentParentNode = rootNode),
+ OpenSubMenuAction(secondLevelSubNavigation.displayTextRes)
+ )
+ }
+ }
+
+ @Test
+ fun when_navigating_up_then_the_currentParent_is_updated_correctly() {
+ val expectedMenu = Menu(rootNode, currentParentNode = rootNode)
+ val underTest = MenuController(fakeRootProvider, mockContext, mapOf())
+
+ val menuAfterAction =
+ underTest.handleAction(
+ Menu(rootNode, currentParentNode = firstLevelSubNavigation),
+ NavigateUpAction
+ )
+
+ assertEquals(expectedMenu, menuAfterAction)
+ }
+
+ @Test
+ fun when_the_menu_is_reloaded_then_the_menu_is_updated_and_the_currentParent_preserved() {
+ val underTest = MenuController(fakeRootProvider, mockContext, mapOf())
+ val expectedMenu = Menu(rootNode, currentParentNode = firstLevelSubNavigation)
+
+ val actualMenu =
+ underTest.handleAction(Menu(rootNode, firstLevelSubNavigation), ReloadMenuAction)
+
+ // When creating a new menu is very important that the currentParent value is also recalculated
+ // correctly otherwise the user will be moved to a different sub menu by accident
+ assertEquals(expectedMenu, actualMenu)
+ }
+
+ @Test
+ fun expect_exception_when_action_is_not_supported() {
+ val underTest = MenuController(fakeRootProvider, mockContext, mapOf())
+
+ assertThrows(IllegalArgumentException::class.java) {
+ underTest.handleAction(Menu(rootNode, currentParentNode = rootNode), FakeMenuAction())
+ }
+ }
+
+ @Test
+ fun when_an_additional_reducer_is_provided_the_menuController_uses_it() {
+ val fakeMenuActionReducer = spy(FakeMenuActionReducer())
+ val fakeAction = FakeMenuAction()
+ val underTest = MenuController(
+ fakeRootProvider,
+ mockContext,
+ mapOf(
+ FakeMenuAction::class.java to fakeMenuActionReducer
+ )
+ )
+
+ underTest.handleAction(Menu(rootNode, currentParentNode = rootNode), fakeAction)
+
+ // Testing that the "expandability" mechanism works, if a new reducer is provided to the controller
+ // then it can be used
+ verify(fakeMenuActionReducer, times(/*wantedNumberOfInvocations=*/1)).reduce(
+ Menu(rootNode, currentParentNode = rootNode),
+ fakeAction
+ )
+ }
+}
diff --git a/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/menu/MenuUtilsTest.kt b/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/menu/MenuUtilsTest.kt
new file mode 100644
index 0000000..17bb620
--- /dev/null
+++ b/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/menu/MenuUtilsTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain.menu
+
+import android.content.Context
+import com.android.car.customization.tool.domain.FakeToggleAction
+import com.android.car.customization.tool.domain.dropDownItemA
+import com.android.car.customization.tool.domain.dropDownItemB
+import com.android.car.customization.tool.domain.findNode
+import com.android.car.customization.tool.domain.firstLevelSubNavigation
+import com.android.car.customization.tool.domain.rootNode
+import com.android.car.customization.tool.domain.secondLevelSubNavigation
+import com.android.car.customization.tool.domain.secondLevelSwitch1
+import com.android.car.customization.tool.domain.thirdLevelDropDown
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnitRunner
+
+/**
+ * Tests to verify that the utility functions to interact with the tree structure of the tree
+ * work properly
+ */
+@RunWith(MockitoJUnitRunner::class)
+internal class MenuUtilsTest {
+
+ @Mock
+ private lateinit var mockContext: Context
+
+ @Test
+ fun parent_of_element_found_correctly() {
+ val firstLevelNode = rootNode.findParentOf(secondLevelSubNavigation)
+ val zeroLevelNode = rootNode.findParentOf(firstLevelSubNavigation)
+
+ assertEquals(firstLevelSubNavigation, firstLevelNode)
+ assertEquals(rootNode, zeroLevelNode)
+ }
+
+ @Test
+ fun child_subMenuNavigation_node_found_correctly() {
+ val childFromFirstLevel = rootNode
+ .findSubMenuNavigationNode(firstLevelSubNavigation.displayTextRes)
+ val childFromSecondLevel = rootNode
+ .findSubMenuNavigationNode(secondLevelSubNavigation.displayTextRes)
+ val notExistingChild = rootNode.findSubMenuNavigationNode(nodeId = 100)
+
+ assertEquals(firstLevelSubNavigation, childFromFirstLevel)
+ assertEquals(secondLevelSubNavigation, childFromSecondLevel)
+ assertNull(notExistingChild)
+ }
+
+ @Test
+ fun when_modifying_a_switch_the_value_is_updated_correctly() {
+ val expectedSwitch = secondLevelSwitch1.copy(
+ isChecked = true,
+ action = FakeToggleAction(newValue = true)
+ )
+ val startingMenu = Menu(rootNode, currentParentNode = rootNode)
+
+ val newMenu = startingMenu.modifySwitch(
+ mockContext,
+ secondLevelSwitch1.displayTextRes,
+ newState = true
+ )
+ val newSwitch = newMenu.rootNode.findNode(secondLevelSwitch1.displayTextRes)
+
+ assertEquals(expectedSwitch, newSwitch)
+ }
+
+ @Test
+ fun when_selecting_a_new_active_item_in_a_dropdown_the_menu_is_updated_correctly() {
+ val expectedDropDownItem1 = dropDownItemA.copy(isActive = false)
+ val expectedDropDownItem2 = dropDownItemB.copy(isActive = true)
+ val expectedDropDown = thirdLevelDropDown.copy(
+ items = listOf(expectedDropDownItem1, expectedDropDownItem2)
+ )
+
+ val startingMenu = Menu(rootNode, currentParentNode = rootNode)
+
+ val newMenu = startingMenu.modifyDropDown(
+ mockContext,
+ thirdLevelDropDown.displayTextRes,
+ dropDownItemB.action
+ )
+ val newDropDown = newMenu.rootNode.findNode(thirdLevelDropDown.displayTextRes)
+
+ assertEquals(expectedDropDown, newDropDown)
+ }
+}
diff --git a/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/panel/PanelControllerTest.kt b/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/panel/PanelControllerTest.kt
new file mode 100644
index 0000000..7047198
--- /dev/null
+++ b/tests/CustomizationTool/tests/src/com/android/car/customization/tool/domain/panel/PanelControllerTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.customization.tool.domain.panel
+
+import com.android.car.customization.tool.domain.EmptyPanelReducer
+import javax.inject.Provider
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.Mockito.times
+import org.mockito.junit.MockitoJUnitRunner
+
+@RunWith(MockitoJUnitRunner::class)
+internal class PanelControllerTest {
+
+ @Test
+ fun openPanel_action_opens_a_panel_and_the_extras_are_collected_correctly() {
+ val expectedPanel = Panel(emptyList())
+ val extras = mapOf("extra_key" to "extra_value")
+ val emptyPanelReducer = EmptyPanelReducer()
+ val underTest = PanelController(
+ mapOf(EmptyPanelReducer::class.java to Provider { emptyPanelReducer })
+ )
+
+ val actualPanel = underTest.handleAction(
+ panel = null,
+ OpenPanelAction(EmptyPanelReducer::class, extras)
+ )
+
+ assertEquals(expectedPanel, actualPanel)
+ assertEquals(extras, emptyPanelReducer.bundle)
+ }
+
+ @Test
+ fun when_the_panel_is_closed_the_value_for_the_state_is_null() {
+ val underTest = PanelController(mapOf())
+
+ assertNull(underTest.handleAction(Panel(emptyList()), ClosePanelAction))
+ }
+
+ @Test
+ fun when_the_panel_is_reloaded_the_reducer_builds_a_new_panel_from_scratch() {
+ val emptyPanelReducer = Mockito.spy(EmptyPanelReducer())
+ val underTest = PanelController(
+ mapOf(EmptyPanelReducer::class.java to Provider { emptyPanelReducer })
+ )
+
+ underTest.handleAction(panel = null, OpenPanelAction(EmptyPanelReducer::class))
+ underTest.handleAction(Panel(emptyList()), ReloadPanelAction)
+
+ Mockito.verify(emptyPanelReducer, times(/*wantedNumberOfInvocations=*/2)).build()
+ }
+}
diff --git a/tests/DiagnosticTools/Android.bp b/tests/DiagnosticTools/Android.bp
index 651542d..901dbdd 100644
--- a/tests/DiagnosticTools/Android.bp
+++ b/tests/DiagnosticTools/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/DiagnosticTools/tests/Android.bp b/tests/DiagnosticTools/tests/Android.bp
index adfc9ae..91ec5ce 100644
--- a/tests/DiagnosticTools/tests/Android.bp
+++ b/tests/DiagnosticTools/tests/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/EmbeddedKitchenSinkApp/Android.bp b/tests/EmbeddedKitchenSinkApp/Android.bp
index eeff187..9cb1e38 100644
--- a/tests/EmbeddedKitchenSinkApp/Android.bp
+++ b/tests/EmbeddedKitchenSinkApp/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -30,6 +31,7 @@
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
":cartelemetryservice-proto-srcs",
],
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index 20840d3..dab71de 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -47,8 +47,6 @@
<!-- use for CarServiceTest -->
<uses-permission android:name="android.car.permission.CAR_TEST_SERVICE"/>
<!-- use for CarServiceTest -->
- <uses-permission android:name="android.car.permission.CAR_VENDOR_EXTENSION"/>
- <!-- use for CarServiceTest -->
<uses-permission android:name="android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION"/>
<!-- use for AndroidCarApiTest -->
<uses-permission android:name="android.car.permission.CONTROL_CAR_APP_LAUNCH"/>
@@ -61,11 +59,15 @@
<!-- use for CarServiceTest -->
<uses-permission android:name="android.car.permission.USE_CAR_TELEMETRY_SERVICE"/>
<!-- Allow querying and writing to any property -->
+ <uses-permission android:name="android.car.permission.PRIVILEGED_CAR_INFO"/>
<uses-permission android:name="android.car.permission.READ_DRIVER_MONITORING_SETTINGS"/>
<uses-permission android:name="android.car.permission.CONTROL_DRIVER_MONITORING_SETTINGS"/>
<uses-permission android:name="android.car.permission.READ_DRIVER_MONITORING_STATES"/>
+ <uses-permission android:name="android.car.permission.CONTROL_CAR_ENERGY" />
<uses-permission android:name="android.car.permission.CAR_ENERGY_PORTS" />
- <uses-permission android:name="android.car.permission.PERMISSION_CONTROL_ENERGY_PORTS" />
+ <uses-permission android:name="android.car.permission.CONTROL_CAR_ENERGY_PORTS" />
+ <uses-permission android:name="android.car.permission.READ_CAR_INTERIOR_LIGHTS" />
+ <uses-permission android:name="android.car.permission.CONTROL_CAR_INTERIOR_LIGHTS" />
<uses-permission android:name="android.car.permission.CAR_EXTERIOR_LIGHTS" />
<uses-permission android:name="android.car.permission.CAR_TIRES" />
<uses-permission android:name="android.car.permission.CONTROL_CAR_DOORS" />
@@ -85,8 +87,8 @@
<uses-permission android:name="android.car.permission.READ_CAR_DISPLAY_UNITS" />
<uses-permission android:name="android.car.permission.CONTROL_CAR_DISPLAY_UNITS" />
<uses-permission android:name="android.car.permission.CAR_IDENTIFICATION" />
- <uses-permission android:name="android.car.permission.PERMISSION_ADJUST_RANGE_REMAINING" />
- <uses-permission android:name="android.car.permission.PERMISSION_CAR_ENGINE_DETAILED" />
+ <uses-permission android:name="android.car.permission.ADJUST_RANGE_REMAINING" />
+ <uses-permission android:name="android.car.permission.CAR_ENGINE_DETAILED" />
<uses-permission android:name="android.car.permission.CONTROL_STEERING_WHEEL" />
<uses-permission android:name="android.car.permission.READ_ADAS_SETTINGS" />
<uses-permission android:name="android.car.permission.CONTROL_ADAS_SETTINGS" />
@@ -245,6 +247,7 @@
<!-- To access Camera2 -->
<uses-permission android:name="android.permission.CAMERA"/>
+ <uses-permission android:name="android.permission.SYSTEM_CAMERA"/>
<uses-permission android:name="android.permission.CAMERA_HEADLESS_SYSTEM_USER"/>
<uses-permission android:name="android.permission.CAMERA_PRIVACY_ALLOWLIST"/>
@@ -333,6 +336,15 @@
</intent-filter>
</activity>
+ <activity android:name=".camera2.MultiCameraPreviewActivity"
+ android:label="@string/camera2_multi_camera_preview"
+ android:showForAllUsers="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ </intent-filter>
+ </activity>
+
<activity android:name=".cluster.FakeClusterNavigationActivity"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:launchMode="singleInstance"
@@ -443,6 +455,18 @@
</intent-filter>
</activity>
+ <activity
+ android:name=".cluster.ResponsiveClusterActivity"
+ android:launchMode="singleInstance"
+ android:theme="@style/KSDayNightTheme"
+ android:documentLaunchMode="always"
+ android:exported="true"
+ android:label="ResponsiveClusterActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ </intent-filter>
+ </activity>
+
<service android:name=".bluetooth.InCallServiceImpl"
android:exported="false"
android:permission="android.permission.BIND_INCALL_SERVICE">
diff --git a/tests/EmbeddedKitchenSinkApp/OWNERS b/tests/EmbeddedKitchenSinkApp/OWNERS
index bcff606..e07d1e0 100644
--- a/tests/EmbeddedKitchenSinkApp/OWNERS
+++ b/tests/EmbeddedKitchenSinkApp/OWNERS
@@ -1,6 +1,5 @@
# TLs
dnek@google.com
-igorr@google.com
# Audio
per-file src/com/google/android/car/kitchensink/AudioAutoStartActivity.java = oscarazu@google.com, ericjeong@google.com
@@ -14,13 +13,13 @@
per-file src/com/google/android/car/kitchensink/connectivity/* = salsavage@google.com, twasilczyk@google.com
# Cluster
-per-file src/com/google/android/car/kitchensink/cluster/* = ycheo@google.com
+per-file src/com/google/android/car/kitchensink/cluster/* = bkchoi@google.com
# OccupantZone
-per-file src/com/google/android/car/kitchensink/OccupantZoneStartActivity.java = ycheo@google.com, oscarazu@google.com, ericjeong@google.com
+per-file src/com/google/android/car/kitchensink/OccupantZoneStartActivity.java = xiangw@google.com, oscarazu@google.com, ericjeong@google.com
# PackageManager
-per-file src/com/google/android/car/kitchensink/packageinfo/* = ycheo@google.com
+per-file src/com/google/android/car/kitchensink/packageinfo/* = gargmayank@google.com, gauravbhola@google.com
# Power
per-file src/com/google/android/car/kitchensink/power/* = ericjeong@google.com
diff --git a/tests/EmbeddedKitchenSinkApp/res/drawable/progressbar_bg.xml b/tests/EmbeddedKitchenSinkApp/res/drawable/progressbar_bg.xml
new file mode 100644
index 0000000..7117220
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/drawable/progressbar_bg.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+<item android:id="@android:id/background">
+ <shape
+ android:innerRadiusRatio="3"
+ android:shape="ring"
+ android:thicknessRatio="20.0"
+ android:useLevel="false">
+ <solid android:color="#114D4D4D" />
+ </shape>
+</item>
+<item android:id="@android:id/progress">
+ <rotate
+ android:fromDegrees="270"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:toDegrees="270">
+ <shape
+ android:innerRadiusRatio="3"
+ android:thickness="12dp"
+ android:shape="ring"
+ android:thicknessRatio="30.0"
+ android:useLevel="true">
+ <solid android:color="@android:color/holo_green_dark" />
+ </shape>
+ </rotate>
+</item>
+
+</layer-list>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/activity_responsive_cluster.xml b/tests/EmbeddedKitchenSinkApp/res/layout/activity_responsive_cluster.xml
new file mode 100644
index 0000000..1818a80
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/activity_responsive_cluster.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/main"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".MainActivity">
+
+ <FrameLayout
+ android:id="@+id/root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_gravity="center"
+ >
+ <TextView
+ android:layout_width="wrap_content"
+
+ android:layout_height="wrap_content"
+ android:textSize="36sp"
+ android:text="Dynamic Driver UI" />
+
+ <ProgressBar
+ android:id="@+id/progressBar"
+ android:layout_gravity="center"
+ android:layout_width="150dp"
+ android:indeterminateOnly="false"
+ android:layout_height="150dp"
+ android:progressDrawable="@drawable/progressbar_bg"
+ android:max="100"
+ android:progress="1" />
+ </LinearLayout>
+
+
+ </FrameLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/camera2_details_list_item.xml b/tests/EmbeddedKitchenSinkApp/res/layout/camera2_details_list_item.xml
new file mode 100644
index 0000000..d4362a5
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/camera2_details_list_item.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/camera2_multi_camera_preview_activity.xml b/tests/EmbeddedKitchenSinkApp/res/layout/camera2_multi_camera_preview_activity.xml
new file mode 100644
index 0000000..50d0a62
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/camera2_multi_camera_preview_activity.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:baselineAligned="false">
+ <LinearLayout
+ android:id="@+id/preview_root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ android:orientation="vertical"
+ android:gravity="center_horizontal"
+ tools:ignore="UselessParent"> <!-- Rows of preview cells will be added here dynamically -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_horizontal"
+ style="?android:attr/buttonBarStyle">
+ <Button
+ android:id="@+id/start_preview_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/camera2_start_preview_button"
+ style="?android:attr/buttonBarButtonStyle"/>
+ <Button
+ android:id="@+id/start_recording_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/camera2_start_recording_button"
+ style="?android:attr/buttonBarButtonStyle"/>
+ <Button
+ android:id="@+id/stop_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/camera2_stop_session_button"
+ style="?android:attr/buttonBarButtonStyle"/>
+ <Button
+ android:id="@+id/quit_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/camera2_quit_button"
+ style="?android:attr/buttonBarButtonStyle"/>
+ </LinearLayout>
+ </LinearLayout>
+ <ListView
+ android:id="@+id/camera_details_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="5">
+ </ListView>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/camera2_preview_cell.xml b/tests/EmbeddedKitchenSinkApp/res/layout/camera2_preview_cell.xml
new file mode 100644
index 0000000..d83821a
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/camera2_preview_cell.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+ <SurfaceView
+ android:id="@+id/preview_surface"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="4">
+ <CheckBox
+ android:id="@+id/selection_checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="@string/camera2_selection_checkbox"/>
+ <Space
+ android:layout_width="80dp"
+ android:layout_height="match_parent"
+ android:gravity="center"/>
+ <Button
+ android:id="@+id/details_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="@string/camera2_details_button"/>
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/camera2_preview_empty_cell.xml b/tests/EmbeddedKitchenSinkApp/res/layout/camera2_preview_empty_cell.xml
new file mode 100644
index 0000000..ea1d66c
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/camera2_preview_empty_cell.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/camera2_preview_row.xml b/tests/EmbeddedKitchenSinkApp/res/layout/camera2_preview_row.xml
new file mode 100644
index 0000000..1030a8e
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/camera2_preview_row.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/preview_row_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center_horizontal">
+ <!-- Preview Cell layout to be added dynamically depending on number of cameras. -->
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/camera2_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/camera2_test.xml
index 1fb7939..3f7abd9 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/camera2_test.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/camera2_test.xml
@@ -26,4 +26,10 @@
android:layout_height="wrap_content"
android:text="@string/camera2_user0"
android:layout_gravity="center"/>
+ <Button
+ android:id="@+id/camera2_multi_camera_preview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/camera2_multi_camera_preview"
+ android:layout_gravity="center"/>
</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/customization_tool.xml b/tests/EmbeddedKitchenSinkApp/res/layout/customization_tool.xml
new file mode 100644
index 0000000..e40f5f8
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/customization_tool.xml
@@ -0,0 +1,31 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="40dp"
+ android:gravity="top|center"
+ android:orientation="vertical">
+
+ <Switch
+ android:id="@+id/customization_tool_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:text="@string/customization_tool_switch" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/fullscreen_activity.xml b/tests/EmbeddedKitchenSinkApp/res/layout/fullscreen_activity.xml
index 5c0957d..7b90742 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/fullscreen_activity.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/fullscreen_activity.xml
@@ -15,13 +15,39 @@
~ limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/show_dialog_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/car_padding_5"
+ android:text="@string/show_dialog"/>
+ <Button
+ android:id="@+id/show_car_ui_dialog_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/car_padding_5"
+ android:text="@string/show_car_ui_dialog"/>
+ <EditText
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/edit_text_hint"
+ android:layout_margin="@dimen/car_padding_5" />
+ </LinearLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:id="@+id/cancel_button"
+ android:layout_margin="@dimen/car_padding_5"
android:text="@string/cancel"/>
-</FrameLayout>
\ No newline at end of file
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_activity.xml b/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_activity.xml
index cdafbc1..acdd030 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_activity.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_activity.xml
@@ -17,6 +17,7 @@
<!-- We use this container to place kitchen app fragments. It insets the fragment contents -->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/property.xml b/tests/EmbeddedKitchenSinkApp/res/layout/property.xml
index 86fa54b..de2de0b 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/property.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/property.xml
@@ -14,107 +14,193 @@
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="horizontal" >
- <ListView
- android:id="@+id/lvPropertyList"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_width="0dp"
- android:scrollbars="vertical">
- </ListView>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_width="0dp"
- android:orientation="vertical" >
+ android:layout_width="match_parent"
+ android:orientation="vertical" >
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="0dp"
+ <LinearLayout
+ android:baselineAligned="false"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_width="match_parent"
+ android:orientation="horizontal" >
+ <Spinner
+ android:baselineAligned="false"
+ android:id="@+id/sPropertyId"
+ android:layout_height="wrap_content"
android:layout_weight="2"
- android:layout_width="match_parent"
- android:orientation="horizontal" >
- <Spinner
- android:id="@+id/sPropertyId"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_width="0dp" />
- <Button
- android:id="@+id/bGetProperty"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_width="0dp"
- android:text="@string/property_get"
- android:textSize="@dimen/propertyTextSize"/>
- <Button
- android:id="@+id/getPropertyAsync"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_width="0dp"
- android:text="@string/property_get_async"
- android:textSize="@dimen/propertyTextSize"/>
- <TextView
- android:id="@+id/tvGetPropertyValue"
- android:gravity="center"
- android:layout_height="wrap_content"
- android:layout_weight="3"
- android:layout_width="0dp"
- android:textSize="@dimen/propertyValueTextSize"/>
- </LinearLayout>
-
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="0dp"
+ android:layout_width="0dp" />
+ <Button
+ android:baselineAligned="false"
+ android:id="@+id/bGetProperty"
+ android:layout_height="wrap_content"
android:layout_weight="1"
- android:layout_width="match_parent"
- android:orientation="horizontal" >
- <Spinner
- android:id="@+id/sAreaId"
- android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:text="@string/property_get"
+ android:textSize="@dimen/propertyTextSize"/>
+ <Button
+ android:baselineAligned="false"
+ android:id="@+id/getPropertyAsync"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_width="0dp"
+ android:text="@string/property_get_async"
+ android:textSize="@dimen/propertyTextSize"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:baselineAligned="false"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_width="match_parent"
+ android:orientation="horizontal" >
+ <Spinner
+ android:baselineAligned="false"
+ android:id="@+id/sAreaId"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_width="0dp" />
+ <EditText
+ android:baselineAligned="false"
+ android:importantForAutofill="no"
+ android:hint="@string/set_value_hint"
+ android:id="@+id/etSetPropertyValue"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_width="0dp"
+ android:inputType="text" />
+ <Button
+ android:baselineAligned="false"
+ android:id="@+id/bSetProperty"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_width="0dp"
+ android:text="@string/property_set"
+ android:textSize="@dimen/propertyTextSize"/>
+ <Button
+ android:baselineAligned="false"
+ android:id="@+id/SetPropertyAsync"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_width="0dp"
+ android:text="@string/property_set_async"
+ android:textSize="@dimen/propertyTextSize"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:baselineAligned="false"
+ android:layout_height="0dp"
+ android:layout_weight="3"
+ android:layout_width="match_parent"
+ android:orientation="horizontal" >
+ <LinearLayout
+ android:baselineAligned="false"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:layout_width="0dp"
+ android:orientation="vertical" >
+ <LinearLayout
+ android:baselineAligned="false"
+ android:layout_height="0dp"
android:layout_weight="1"
- android:layout_width="0dp" />
- <EditText
- android:id="@+id/etSetPropertyValue"
- android:layout_height="wrap_content"
- android:layout_weight="2"
- android:layout_width="0dp"
- android:inputType="text" />
- <Button
- android:id="@+id/bSetProperty"
- android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:orientation="horizontal" >
+ <TextView
+ android:baselineAligned="false"
+ android:id="@+id/tvSubscriptionRate"
+ android:gravity="end"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_width="0dp"
+ android:text="@string/property_subscription_rate_hz"
+ android:textSize="@dimen/propertyTextSize"/>
+ <Spinner
+ android:baselineAligned="false"
+ android:id="@+id/sSubscriptionRate"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_width="0dp" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:baselineAligned="false"
+ android:layout_height="0dp"
android:layout_weight="1"
- android:layout_width="0dp"
- android:text="@string/property_set"
- android:textSize="@dimen/propertyTextSize"/>
- <Button
- android:id="@+id/SetPropertyAsync"
- android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:orientation="horizontal" >
+ <TextView
+ android:baselineAligned="false"
+ android:id="@+id/tvResolution"
+ android:gravity="end"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_width="0dp"
+ android:text="@string/property_resolution"
+ android:textSize="@dimen/propertyTextSize"/>
+ <Spinner
+ android:baselineAligned="false"
+ android:id="@+id/sResolution"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_width="0dp" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:baselineAligned="false"
+ android:layout_height="0dp"
android:layout_weight="1"
- android:layout_width="0dp"
- android:text="@string/property_set_async"
+ android:layout_width="match_parent"
+ android:orientation="horizontal" >
+ <TextView
+ android:baselineAligned="false"
+ android:id="@+id/tvVariableUpdateRate"
+ android:gravity="end"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_width="0dp"
+ android:text="@string/property_variable_update_rate"
+ android:textSize="@dimen/propertyTextSize"/>
+ <Spinner
+ android:baselineAligned="false"
+ android:id="@+id/sVariableUpdateRate"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_width="0dp" />
+ </LinearLayout>
+
+ <ToggleButton
+ android:clickable="false"
+ android:id="@+id/tbSubscribeButton"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_width="match_parent"
+ android:textOff="@string/subscribe"
+ android:textOn="@string/unsubscribe"
android:textSize="@dimen/propertyTextSize"/>
</LinearLayout>
-
<!-- Event Log -->
<ScrollView
+ android:baselineAligned="false"
android:id="@+id/svEventLog"
- android:layout_height="0dp"
- android:layout_weight="6"
- android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_width="0dp"
android:scrollbars="vertical">
<TextView
+ android:baselineAligned="false"
android:id="@+id/tvEventLog"
- android:gravity="left"
+ android:gravity="start"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:textSize="@dimen/propertyValueTextSize"/>
</ScrollView>
- <Button
- android:id="@+id/bClearLog"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:layout_width="match_parent"
- android:text="@string/property_clear"
- android:textSize="@dimen/propertyTextSize"/>
</LinearLayout>
+ <Button
+ android:baselineAligned="false"
+ android:id="@+id/bClearLog"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_width="match_parent"
+ android:text="@string/property_clear"
+ android:textSize="@dimen/propertyTextSize"/>
</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/property_list_item.xml b/tests/EmbeddedKitchenSinkApp/res/layout/property_list_item.xml
deleted file mode 100644
index e550d45..0000000
--- a/tests/EmbeddedKitchenSinkApp/res/layout/property_list_item.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <TextView
- android:id="@+id/tvPropertyName"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_alignParentLeft="true"
- android:paddingLeft="8dp"
- android:textSize="@dimen/propertyTextSize"/>
-
- <Spinner
- android:id="@+id/tbRegisterPropertySpinner"
- android:layout_alignParentRight="true"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="right"/>
-
-</RelativeLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 2d4926b..e934c18 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -261,10 +261,14 @@
<string name="property_clear" translatable="false">Clear Log</string>
<string name="property_get" translatable="false">Get</string>
<string name="property_get_async" translatable="false">Get Async</string>
- <string name="property_register" translatable="false">Register</string>
<string name="property_set" translatable="false">Set</string>
<string name="property_set_async" translatable="false">Set Async</string>
- <string name="property_unregister" translatable="false">Unregister</string>
+ <string name="property_subscription_rate_hz" translatable="false">Subscription Rate (Hz)</string>
+ <string name="property_resolution" translatable="false">Resolution</string>
+ <string name="property_variable_update_rate" translatable="false">Variable Update Rate</string>
+ <string name="set_value_hint" translatable="false">value</string>
+ <string name="subscribe" translatable="false">Subscribe</string>
+ <string name="unsubscribe" translatable="false">Unsubscribe</string>
<!-- radio test -->
<string name="radio_fm_am_tuner" translatable="false">FM/AM Tuner</string>
@@ -445,6 +449,12 @@
<!-- Fullscreen Activity Test -->
<string name="nav_to_full_screen" translatable="false">Navigate to Full Screen</string>
<string name="cancel" translatable="false">Cancel</string>
+ <string name="show_car_ui_dialog" translatable="false">Show Dialog from car_ui_lib</string>
+ <string name="car_ui_dialog_title" translatable="false">Dialog from car_ui_lib</string>
+ <string name="show_dialog" translatable="false">Show Dialog</string>
+ <string name="dialog_title" translatable="false">Dialog</string>
+ <string name="dismiss_btn_text" translatable="false">Dismiss</string>
+ <string name="edit_text_hint" translatable="false">Placeholder</string>
<!-- CarTelemetryService Test -->
<string name="gear_change_config" translatable="false">on_gear_change:</string>
@@ -528,5 +538,15 @@
<!-- Camera2 test -->
<string name="camera2_user0" translatable="false">Camera2 as User0</string>
+ <string name="camera2_multi_camera_preview" translatable="false">Camera2 Multi-camera Preview</string>
+ <string name="camera2_start_preview_button" translatable="false">Start Preview</string>
+ <string name="camera2_start_recording_button" translatable="false">Record</string>
+ <string name="camera2_stop_session_button" translatable="false">Stop Session</string>
+ <string name="camera2_quit_button" translatable="false">Quit</string>
+ <string name="camera2_details_button" translatable="false">Details</string>
+ <string name="camera2_selection_checkbox" translatable="false">Camera ID: %1$s</string>
+
+ <!-- Customization Switch -->
+ <string name="customization_tool_switch" translatable="false">Enable the Customization Tool</string>
</resources>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/styles.xml b/tests/EmbeddedKitchenSinkApp/res/values/styles.xml
index d857a5c..2cc7fff 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/styles.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/styles.xml
@@ -59,4 +59,12 @@
<item name="android:checkboxStyle">@style/CheckBoxStyle</item>
<item name="android:spinnerStyle">@style/SpinnerStyle</item>
</style>
+
+ <style name="KSDayNightTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
+ <item name="android:textSize">24sp</item>
+ <item name="android:buttonStyle">@style/ButtonStyle</item>
+ <item name="android:radioButtonStyle">@style/RadioButtonStyle</item>
+ <item name="android:checkboxStyle">@style/CheckBoxStyle</item>
+ <item name="android:spinnerStyle">@style/SpinnerStyle</item>
+ </style>
</resources>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSink2Activity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSink2Activity.java
index 4037053..36148ce 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSink2Activity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSink2Activity.java
@@ -31,8 +31,6 @@
import android.car.watchdog.CarWatchdogManager;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
@@ -72,20 +70,9 @@
private Fragment mLastFragment;
private int mNotificationId = 1000;
private static final int NO_INDEX = -1;
- private Car mCarApi;
- private CarHvacManager mHvacManager;
- private CarOccupantZoneManager mOccupantZoneManager;
- private CarPowerManager mPowerManager;
- private CarPropertyManager mPropertyManager;
- private CarSensorManager mSensorManager;
- private CarProjectionManager mCarProjectionManager;
- private CarTelemetryManager mCarTelemetryManager;
- private CarWatchdogManager mCarWatchdogManager;
- private CarPerformanceManager mCarPerformanceManager;
private static final String EMPTY_STRING = "";
private HighlightableAdapter mAdapter;
private final FragmentItemClickHandler mItemClickHandler = new FragmentItemClickHandler();
- private final Object mCarReady = new Object();
/**
* List of all the original menu items.
@@ -110,6 +97,63 @@
public static final String DUMP_ARG_FRAGMENT = "fragment";
public static final String DUMP_ARG_QUIET = "quiet";
+ private final KitchenSinkHelperImpl mKsHelperImpl = new KitchenSinkHelperImpl();
+
+ @Override
+ public Car getCar() {
+ return mKsHelperImpl.getCar();
+ }
+
+ @Override
+ public void requestRefreshManager(Runnable r, Handler h) {
+ mKsHelperImpl.requestRefreshManager(r, h);
+ }
+
+ @Override
+ public CarPropertyManager getPropertyManager() {
+ return mKsHelperImpl.getPropertyManager();
+ }
+
+ @Override
+ public CarHvacManager getHvacManager() {
+ return mKsHelperImpl.getHvacManager();
+ }
+
+ @Override
+ public CarOccupantZoneManager getOccupantZoneManager() {
+ return mKsHelperImpl.getOccupantZoneManager();
+ }
+
+ @Override
+ public CarPowerManager getPowerManager() {
+ return mKsHelperImpl.getPowerManager();
+ }
+
+ @Override
+ public CarSensorManager getSensorManager() {
+ return mKsHelperImpl.getSensorManager();
+ }
+
+ @Override
+ public CarProjectionManager getProjectionManager() {
+ return mKsHelperImpl.getProjectionManager();
+ }
+
+ @Override
+ public CarTelemetryManager getCarTelemetryManager() {
+ return mKsHelperImpl.getCarTelemetryManager();
+ }
+
+ @Override
+ public CarWatchdogManager getCarWatchdogManager() {
+ return mKsHelperImpl.getCarWatchdogManager();
+ }
+
+ @Override
+ public CarPerformanceManager getPerformanceManager() {
+ return mKsHelperImpl.getPerformanceManager();
+ }
+
@Override
public void onPointerCaptureChanged(boolean hasCapture) {
super.onPointerCaptureChanged(hasCapture);
@@ -164,10 +208,8 @@
mSharedPreferences = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE);
setContentView(R.layout.activity_2pane);
- // Connection to Car Service does not work for non-automotive yet.
- if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
- initCarApi();
- }
+
+ mKsHelperImpl.initCarApiIfAutomotive(this);
mRV = findViewById(R.id.list_pane);
mWrapper = findViewById(R.id.wrapper);
@@ -453,98 +495,6 @@
editor.apply();
}
-
- private void initCarApi() {
- if (mCarApi != null && mCarApi.isConnected()) {
- mCarApi.disconnect();
- mCarApi = null;
- }
- mCarApi = Car.createCar(this, null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
- (Car car, boolean ready) -> {
- if (ready) {
- synchronized (mCarReady) {
- mCarReady.notifyAll();
- }
- }
- });
- }
-
- @Override
- public Car getCar() {
- return mCarApi;
- }
-
- public CarHvacManager getHvacManager() {
- if (mHvacManager == null) {
- mHvacManager = (CarHvacManager) mCarApi.getCarManager(android.car.Car.HVAC_SERVICE);
- }
- return mHvacManager;
- }
-
- public CarOccupantZoneManager getOccupantZoneManager() {
- if (mOccupantZoneManager == null) {
- mOccupantZoneManager = (CarOccupantZoneManager) mCarApi.getCarManager(
- android.car.Car.CAR_OCCUPANT_ZONE_SERVICE);
- }
- return mOccupantZoneManager;
- }
-
- public CarPowerManager getPowerManager() {
- if (mPowerManager == null) {
- mPowerManager = (CarPowerManager) mCarApi.getCarManager(
- android.car.Car.POWER_SERVICE);
- }
- return mPowerManager;
- }
-
- public CarPropertyManager getPropertyManager() {
- if (mPropertyManager == null) {
- mPropertyManager = (CarPropertyManager) mCarApi.getCarManager(
- android.car.Car.PROPERTY_SERVICE);
- }
- return mPropertyManager;
- }
-
- public CarSensorManager getSensorManager() {
- if (mSensorManager == null) {
- mSensorManager = (CarSensorManager) mCarApi.getCarManager(
- android.car.Car.SENSOR_SERVICE);
- }
- return mSensorManager;
- }
-
- public CarProjectionManager getProjectionManager() {
- if (mCarProjectionManager == null) {
- mCarProjectionManager =
- (CarProjectionManager) mCarApi.getCarManager(Car.PROJECTION_SERVICE);
- }
- return mCarProjectionManager;
- }
-
- public CarTelemetryManager getCarTelemetryManager() {
- if (mCarTelemetryManager == null) {
- mCarTelemetryManager =
- (CarTelemetryManager) mCarApi.getCarManager(Car.CAR_TELEMETRY_SERVICE);
- }
- return mCarTelemetryManager;
- }
-
- public CarWatchdogManager getCarWatchdogManager() {
- if (mCarWatchdogManager == null) {
- mCarWatchdogManager =
- (CarWatchdogManager) mCarApi.getCarManager(Car.CAR_WATCHDOG_SERVICE);
- }
- return mCarWatchdogManager;
- }
-
- public CarPerformanceManager getPerformanceManager() {
- if (mCarPerformanceManager == null) {
- mCarPerformanceManager =
- (CarPerformanceManager) mCarApi.getCarManager(Car.CAR_PERFORMANCE_SERVICE);
- }
- return mCarPerformanceManager;
- }
-
/* Open any tab directly:
* adb shell am force-stop com.google.android.car.kitchensink
* adb shell am 'start -n com.google.android.car.kitchensink/.KitchenSink2Activity --es select
@@ -633,31 +583,6 @@
super.dump(prefix, fd, writer, args);
}
- @Override
- public void requestRefreshManager(final Runnable r, final Handler h) {
- final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... unused) {
- synchronized (mCarReady) {
- while (!mCarApi.isConnected()) {
- try {
- mCarReady.wait();
- } catch (InterruptedException e) {
- return null;
- }
- }
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void unused) {
- h.post(r);
- }
- };
- task.execute();
- }
-
private class FragmentItemClickHandler implements CarUiContentListItem.OnClickListener {
@Override
public void onClick(@NonNull CarUiContentListItem carUiContentListItem) {
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index 668b12e..e5d59ad 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -30,8 +30,6 @@
import android.car.watchdog.CarWatchdogManager;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemProperties;
@@ -71,6 +69,7 @@
import com.google.android.car.kitchensink.cluster.InstrumentClusterFragment;
import com.google.android.car.kitchensink.connectivity.ConnectivityFragment;
import com.google.android.car.kitchensink.cube.CubesTestFragment;
+import com.google.android.car.kitchensink.customizationtool.CustomizationToolFragment;
import com.google.android.car.kitchensink.diagnostic.DiagnosticTestFragment;
import com.google.android.car.kitchensink.display.DisplayInfoFragment;
import com.google.android.car.kitchensink.display.DisplayMirroringFragment;
@@ -141,11 +140,68 @@
private int mNotificationId = 1000;
private boolean mShowHeaderInfo;
+ private final KitchenSinkHelperImpl mKsHelperImpl = new KitchenSinkHelperImpl();
+
public static final String DUMP_ARG_CMD = "cmd";
public static final String DUMP_ARG_FRAGMENT = "fragment";
public static final String DUMP_ARG_QUIET = "quiet";
public static final String DUMP_ARG_REFRESH = "refresh";
+ @Override
+ public Car getCar() {
+ return mKsHelperImpl.getCar();
+ }
+
+ @Override
+ public void requestRefreshManager(Runnable r, Handler h) {
+ mKsHelperImpl.requestRefreshManager(r, h);
+ }
+
+ @Override
+ public CarPropertyManager getPropertyManager() {
+ return mKsHelperImpl.getPropertyManager();
+ }
+
+ @Override
+ public CarHvacManager getHvacManager() {
+ return mKsHelperImpl.getHvacManager();
+ }
+
+ @Override
+ public CarOccupantZoneManager getOccupantZoneManager() {
+ return mKsHelperImpl.getOccupantZoneManager();
+ }
+
+ @Override
+ public CarPowerManager getPowerManager() {
+ return mKsHelperImpl.getPowerManager();
+ }
+
+ @Override
+ public CarSensorManager getSensorManager() {
+ return mKsHelperImpl.getSensorManager();
+ }
+
+ @Override
+ public CarProjectionManager getProjectionManager() {
+ return mKsHelperImpl.getProjectionManager();
+ }
+
+ @Override
+ public CarTelemetryManager getCarTelemetryManager() {
+ return mKsHelperImpl.getCarTelemetryManager();
+ }
+
+ @Override
+ public CarWatchdogManager getCarWatchdogManager() {
+ return mKsHelperImpl.getCarWatchdogManager();
+ }
+
+ @Override
+ public CarPerformanceManager getPerformanceManager() {
+ return mKsHelperImpl.getPerformanceManager();
+ }
+
private interface ClickHandler {
void onClick();
}
@@ -264,6 +320,7 @@
new Pair<>("carboard", KeyboardTestFragment.class),
new Pair<>("connectivity", ConnectivityFragment.class),
new Pair<>("cubes test", CubesTestFragment.class),
+ new Pair<>("customization tool", CustomizationToolFragment.class),
new Pair<>("device policy", DevicePolicyFragment.class),
new Pair<>("diagnostic", DiagnosticTestFragment.class),
new Pair<>("display info", DisplayInfoFragment.class),
@@ -311,23 +368,6 @@
new Pair<>("Camera2", Camera2TestFragment.class),
new Pair<>(RadioTestFragment.FRAGMENT_NAME, RadioTestFragment.class));
- private Car mCarApi;
- private CarHvacManager mHvacManager;
- private CarOccupantZoneManager mOccupantZoneManager;
- private CarPowerManager mPowerManager;
- private CarPropertyManager mPropertyManager;
- private CarSensorManager mSensorManager;
- private CarProjectionManager mCarProjectionManager;
- private CarTelemetryManager mCarTelemetryManager;
- private CarWatchdogManager mCarWatchdogManager;
- private CarPerformanceManager mCarPerformanceManager;
- private Object mPropertyManagerReady = new Object();
-
- @Override
- public Car getCar() {
- return mCarApi;
- }
-
public KitchenSinkActivity() {
for (Pair<String, Class> entry : MENU_ENTRIES) {
mMenuEntries.add(new FragmentMenuEntry(entry.first, entry.second));
@@ -335,41 +375,6 @@
mMenuEntries.sort(Comparator.comparing(MenuEntry::getText));
}
- public CarHvacManager getHvacManager() {
- return mHvacManager;
- }
-
- public CarOccupantZoneManager getOccupantZoneManager() {
- return mOccupantZoneManager;
- }
-
- public CarPowerManager getPowerManager() {
- return mPowerManager;
- }
-
- public CarPropertyManager getPropertyManager() {
- return mPropertyManager;
- }
-
- public CarSensorManager getSensorManager() {
- return mSensorManager;
- }
-
- public CarProjectionManager getProjectionManager() {
- return mCarProjectionManager;
- }
-
- public CarTelemetryManager getCarTelemetryManager() {
- return mCarTelemetryManager;
- }
-
- public CarWatchdogManager getCarWatchdogManager() {
- return mCarWatchdogManager;
- }
-
- public CarPerformanceManager getPerformanceManager() {
- return mCarPerformanceManager;
- }
/* Open any tab directly:
* adb shell am force-stop com.google.android.car.kitchensink
@@ -391,7 +396,9 @@
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.i(TAG, "onNewIntent");
- if (intent.getCategories().contains(NotificationFragment.INTENT_CATEGORY_SELF_DISMISS)) {
+ if (intent.getCategories() != null
+ && intent.getCategories().contains(
+ NotificationFragment.INTENT_CATEGORY_SELF_DISMISS)) {
NotificationManager nm = this.getSystemService(NotificationManager.class);
nm.cancel(NotificationFragment.SELF_DISMISS_NOTIFICATION_ID);
}
@@ -416,10 +423,13 @@
super.onCreate(savedInstanceState);
setContentView(R.layout.kitchen_activity);
- // Connection to Car Service does not work for non-automotive yet.
- if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
- initCarApi();
- }
+ findViewById(R.id.root).setOnApplyWindowInsetsListener((v, insets) -> {
+ final android.graphics.Insets i = insets.getSystemWindowInsets();
+ v.setPadding(i.left, i.top, i.right, i.bottom);
+ return insets.inset(i).consumeSystemWindowInsets();
+ });
+
+ mKsHelperImpl.initCarApiIfAutomotive(this);
mKitchenContent = findViewById(R.id.kitchen_content);
@@ -505,19 +515,6 @@
startActivity(homeIntent);
}
- private void initCarApi() {
- if (mCarApi != null && mCarApi.isConnected()) {
- mCarApi.disconnect();
- mCarApi = null;
- }
- mCarApi = Car.createCar(this, null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
- (Car car, boolean ready) -> {
- if (ready) {
- initManagers(car);
- }
- });
- }
-
@Override
protected void onStart() {
super.onStart();
@@ -558,9 +555,7 @@
@Override
protected void onDestroy() {
- if (mCarApi != null) {
- mCarApi.disconnect();
- }
+ mKsHelperImpl.disconnect();
Log.i(TAG, "onDestroy");
super.onDestroy();
}
@@ -635,30 +630,6 @@
mLastFragment = fragment;
}
- private void initManagers(Car car) {
- synchronized (mPropertyManagerReady) {
- mHvacManager = (CarHvacManager) car.getCarManager(
- android.car.Car.HVAC_SERVICE);
- mOccupantZoneManager = (CarOccupantZoneManager) car.getCarManager(
- android.car.Car.CAR_OCCUPANT_ZONE_SERVICE);
- mPowerManager = (CarPowerManager) car.getCarManager(
- android.car.Car.POWER_SERVICE);
- mPropertyManager = (CarPropertyManager) car.getCarManager(
- android.car.Car.PROPERTY_SERVICE);
- mSensorManager = (CarSensorManager) car.getCarManager(
- android.car.Car.SENSOR_SERVICE);
- mCarProjectionManager =
- (CarProjectionManager) car.getCarManager(Car.PROJECTION_SERVICE);
- mCarTelemetryManager =
- (CarTelemetryManager) car.getCarManager(Car.CAR_TELEMETRY_SERVICE);
- mCarWatchdogManager =
- (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE);
- mCarPerformanceManager =
- (CarPerformanceManager) car.getCarManager(Car.CAR_PERFORMANCE_SERVICE);
- mPropertyManagerReady.notifyAll();
- }
- }
-
private void updateHeaderInfoVisibility() {
mShowHeaderInfo = getBooleanProperty(PROPERTY_SHOW_HEADER_INFO, false);
Log.i(TAG, "updateHeaderInfoVisibility(): showHeaderInfo=" + mShowHeaderInfo);
@@ -709,31 +680,6 @@
}
}
- // Use AsyncTask to refresh Car*Manager after car service connected
- public void requestRefreshManager(final Runnable r, final Handler h) {
- final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... unused) {
- synchronized (mPropertyManagerReady) {
- while (!mCarApi.isConnected()) {
- try {
- mPropertyManagerReady.wait();
- } catch (InterruptedException e) {
- return null;
- }
- }
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void unused) {
- h.post(r);
- }
- };
- task.execute();
- }
-
private static boolean getBooleanProperty(String prop, boolean defaultValue) {
String value = SystemProperties.get(prop);
Log.v(TAG, "getBooleanProperty(" + prop + "): got '" + value + "'");
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkHelper.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkHelper.java
index 5297f2e..a7b2d88 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkHelper.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkHelper.java
@@ -17,7 +17,15 @@
package com.google.android.car.kitchensink;
import android.car.Car;
+import android.car.CarOccupantZoneManager;
+import android.car.CarProjectionManager;
+import android.car.hardware.CarSensorManager;
+import android.car.hardware.hvac.CarHvacManager;
+import android.car.hardware.power.CarPowerManager;
import android.car.hardware.property.CarPropertyManager;
+import android.car.os.CarPerformanceManager;
+import android.car.telemetry.CarTelemetryManager;
+import android.car.watchdog.CarWatchdogManager;
import android.os.Handler;
public interface KitchenSinkHelper {
@@ -27,12 +35,57 @@
Car getCar();
/**
+ * Uses the handler to post the runnable to run after car service is connected or reconnected.
+ *
+ * This should be used to do initialization with the managers, e.g. get the property list from
+ * the property manager. If you cache your manager instance locally, you must use this to
+ * reset your local manager instance to the new instance returned via getXXXManager.
+ */
+ void requestRefreshManager(Runnable r, Handler h);
+
+ /**
* Gets the car property manager.
*/
CarPropertyManager getPropertyManager();
/**
- * Uses the handler to post the runnable to run after car service is connected.
+ * Gets the HVAC manager.
*/
- void requestRefreshManager(Runnable r, Handler h);
+ CarHvacManager getHvacManager();
+
+ /**
+ * Gets the occulant zone manager.
+ */
+ CarOccupantZoneManager getOccupantZoneManager();
+
+ /**
+ * Gets the power manager.
+ */
+ CarPowerManager getPowerManager();
+
+ /**
+ * Gets the sensor manager.
+ */
+ CarSensorManager getSensorManager();
+
+ /**
+ * Gets the projection manager.
+ */
+ CarProjectionManager getProjectionManager();
+
+ /**
+ * Gets the telemetry manager.
+ */
+ CarTelemetryManager getCarTelemetryManager();
+
+ /**
+ * Gets the car watchdog manager.
+ */
+ CarWatchdogManager getCarWatchdogManager();
+
+ /**
+ * Gets the performance manager.
+ */
+ CarPerformanceManager getPerformanceManager();
+
}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkHelperImpl.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkHelperImpl.java
new file mode 100644
index 0000000..97af8d7
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkHelperImpl.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink;
+
+import android.car.Car;
+import android.car.CarOccupantZoneManager;
+import android.car.CarProjectionManager;
+import android.car.hardware.CarSensorManager;
+import android.car.hardware.hvac.CarHvacManager;
+import android.car.hardware.power.CarPowerManager;
+import android.car.hardware.property.CarPropertyManager;
+import android.car.os.CarPerformanceManager;
+import android.car.telemetry.CarTelemetryManager;
+import android.car.watchdog.CarWatchdogManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class KitchenSinkHelperImpl implements KitchenSinkHelper {
+ private static final String TAG = "KitchenSinkHelperImpl";
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private Car mCarApi;
+ @GuardedBy("mLock")
+ private CarHvacManager mHvacManager;
+ @GuardedBy("mLock")
+ private CarOccupantZoneManager mOccupantZoneManager;
+ @GuardedBy("mLock")
+ private CarPowerManager mPowerManager;
+ @GuardedBy("mLock")
+ private CarPropertyManager mPropertyManager;
+ @GuardedBy("mLock")
+ private CarSensorManager mSensorManager;
+ @GuardedBy("mLock")
+ private CarProjectionManager mCarProjectionManager;
+ @GuardedBy("mLock")
+ private CarTelemetryManager mCarTelemetryManager;
+ @GuardedBy("mLock")
+ private CarWatchdogManager mCarWatchdogManager;
+ @GuardedBy("mLock")
+ private CarPerformanceManager mCarPerformanceManager;
+ @GuardedBy("mLock")
+ private boolean mCarReady = false;
+
+ private record HandlerRunnable(Handler h, Runnable r) {}
+
+ // A list of callbacks that would be posted to handlers every time car service is connected
+ // or reconnected.
+ @GuardedBy("mLock")
+ private final List<HandlerRunnable> mCarReadyCallbacks = new ArrayList<>();
+
+ @Override
+ public Car getCar() {
+ synchronized (mLock) {
+ return mCarApi;
+ }
+ }
+
+ @Override
+ public void requestRefreshManager(final Runnable r, final Handler h) {
+ synchronized (mLock) {
+ mCarReadyCallbacks.add(new HandlerRunnable(h, r));
+
+ if (mCarReady) {
+ // If car service is already ready, we already missed the initial callback so
+ // we invoke the runnable now.
+ h.post(r);
+ }
+ }
+ }
+
+ @Override
+ public CarPropertyManager getPropertyManager() {
+ synchronized (mLock) {
+ if (mPropertyManager == null) {
+ mPropertyManager = (CarPropertyManager) mCarApi.getCarManager(
+ android.car.Car.PROPERTY_SERVICE);
+ }
+ return mPropertyManager;
+ }
+ }
+
+ @Override
+ public CarHvacManager getHvacManager() {
+ synchronized (mLock) {
+ if (mHvacManager == null) {
+ mHvacManager = (CarHvacManager) mCarApi.getCarManager(android.car.Car.HVAC_SERVICE);
+ }
+ return mHvacManager;
+ }
+ }
+
+ @Override
+ public CarOccupantZoneManager getOccupantZoneManager() {
+ synchronized (mLock) {
+ if (mOccupantZoneManager == null) {
+ mOccupantZoneManager = (CarOccupantZoneManager) mCarApi.getCarManager(
+ android.car.Car.CAR_OCCUPANT_ZONE_SERVICE);
+ }
+ return mOccupantZoneManager;
+ }
+ }
+
+ @Override
+ public CarPowerManager getPowerManager() {
+ synchronized (mLock) {
+ if (mPowerManager == null) {
+ mPowerManager = (CarPowerManager) mCarApi.getCarManager(
+ android.car.Car.POWER_SERVICE);
+ }
+ return mPowerManager;
+ }
+ }
+
+ @Override
+ public CarSensorManager getSensorManager() {
+ synchronized (mLock) {
+ if (mSensorManager == null) {
+ mSensorManager = (CarSensorManager) mCarApi.getCarManager(
+ android.car.Car.SENSOR_SERVICE);
+ }
+ return mSensorManager;
+ }
+ }
+
+ @Override
+ public CarProjectionManager getProjectionManager() {
+ synchronized (mLock) {
+ if (mCarProjectionManager == null) {
+ mCarProjectionManager =
+ (CarProjectionManager) mCarApi.getCarManager(Car.PROJECTION_SERVICE);
+ }
+ return mCarProjectionManager;
+ }
+ }
+
+ @Override
+ public CarTelemetryManager getCarTelemetryManager() {
+ synchronized (mLock) {
+ if (mCarTelemetryManager == null) {
+ mCarTelemetryManager =
+ (CarTelemetryManager) mCarApi.getCarManager(Car.CAR_TELEMETRY_SERVICE);
+ }
+ return mCarTelemetryManager;
+ }
+ }
+
+ @Override
+ public CarWatchdogManager getCarWatchdogManager() {
+ synchronized (mLock) {
+ if (mCarWatchdogManager == null) {
+ mCarWatchdogManager =
+ (CarWatchdogManager) mCarApi.getCarManager(Car.CAR_WATCHDOG_SERVICE);
+ }
+ return mCarWatchdogManager;
+ }
+ }
+
+ @Override
+ public CarPerformanceManager getPerformanceManager() {
+ synchronized (mLock) {
+ if (mCarPerformanceManager == null) {
+ mCarPerformanceManager =
+ (CarPerformanceManager) mCarApi.getCarManager(Car.CAR_PERFORMANCE_SERVICE);
+ }
+ return mCarPerformanceManager;
+ }
+ }
+
+ /**
+ * Initialized Car API if in automotive. Must be called during onCreate.
+ */
+ public void initCarApiIfAutomotive(Context context) {
+ // Connection to Car Service does not work for non-automotive yet.
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ initCarApi(context);
+ }
+ }
+
+ /**
+ * Disconnects from car service. Must be called during onDestroy.
+ */
+ public void disconnect() {
+ synchronized (mLock) {
+ if (mCarApi != null) {
+ mCarApi.disconnect();
+ mCarApi = null;
+ }
+ }
+ }
+
+ private void initCarApi(Context context) {
+ synchronized (mLock) {
+ if (mCarApi != null && mCarApi.isConnected()) {
+ mCarApi.disconnect();
+ mCarApi = null;
+ }
+ }
+ Car.createCar(context, null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
+ (Car car, boolean ready) -> {
+ synchronized (mLock) {
+ mCarApi = car;
+ mCarReady = ready;
+ if (!mCarReady) {
+ return;
+ }
+
+ Log.d(TAG, "Car service connected");
+
+ mHvacManager = null;
+ mOccupantZoneManager = null;
+ mPowerManager = null;
+ mPropertyManager = null;
+ mSensorManager = null;
+ mCarProjectionManager = null;
+ mCarTelemetryManager = null;
+ mCarWatchdogManager = null;
+ mCarPerformanceManager = null;
+
+ for (int i = 0; i < mCarReadyCallbacks.size(); i++) {
+ var handlerRunnable = mCarReadyCallbacks.get(i);
+ handlerRunnable.h.post(handlerRunnable.r);
+ }
+ }
+ });
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkShellCommand.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkShellCommand.java
index aa079b1..cc1bcbc 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkShellCommand.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkShellCommand.java
@@ -32,6 +32,7 @@
import android.widget.Toast;
import com.google.android.car.kitchensink.drivemode.DriveModeSwitchController;
+import com.google.android.car.kitchensink.customizationtool.CustomizationToolController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -69,6 +70,7 @@
private static final String CMD_POST_NOTIFICATION = "post-notification";
private static final String CMD_POST_TOAST = "post-toast";
private static final String CMD_SET_DRIVE_MODE_SWITCH= "set-drive-mode-switch";
+ private static final String CMD_SET_CUSTOMIZATION_TOOL= "set-customization-tool";
private static final String ARG_VERBOSE = "-v";
private static final String ARG_VERBOSE_FULL = "--verbose";
@@ -125,6 +127,9 @@
case CMD_SET_DRIVE_MODE_SWITCH:
setDriveModeSwitch();
break;
+ case CMD_SET_CUSTOMIZATION_TOOL:
+ setCustomizationTool();
+ break;
default:
showHelp("Invalid command: %s", cmd);
}
@@ -156,6 +161,8 @@
"<MESSAGE>");
showCommandHelp("Enables / Disables the DriveMode Switch in the System UI.",
CMD_SET_DRIVE_MODE_SWITCH, "<true|false>");
+ showCommandHelp("Enables / Disables the Customization Tool service.",
+ CMD_SET_CUSTOMIZATION_TOOL, "<true|false>");
mWriter.decreaseIndent();
}
@@ -290,6 +297,14 @@
driveModeSwitchController.setDriveMode(value);
}
+ private void setCustomizationTool() {
+ boolean value = getNextBooleanArg();
+ CustomizationToolController customizationToolController = new CustomizationToolController(
+ mContext
+ );
+ customizationToolController.toggleCustomizationTool(value);
+ }
+
private void warnAboutAsyncCall() {
mWriter.printf("Command will be executed asynchronally; use `adb logcat %s *:s` for result"
+ "\n", TAG);
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
index 68ede65..9fabcb5 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
@@ -854,6 +854,11 @@
for (int audioZoneId: mCarAudioManager.getAudioZoneIds()) {
AudioDeviceInfo deviceInfo = mCarAudioManager
.getOutputDeviceForUsage(audioZoneId, USAGE_MEDIA);
+ if (deviceInfo == null) {
+ Log.i(TAG, "Audio device info for media in zone " + audioZoneId
+ + " is not available.");
+ continue;
+ }
CarAudioZoneDeviceInfo carAudioZoneDeviceInfo = new CarAudioZoneDeviceInfo();
carAudioZoneDeviceInfo.mDeviceInfo = deviceInfo;
carAudioZoneDeviceInfo.mAudioZoneId = audioZoneId;
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/camera2/Camera2TestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/camera2/Camera2TestFragment.java
index 36bb3a4..78b3b6c 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/camera2/Camera2TestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/camera2/Camera2TestFragment.java
@@ -42,6 +42,15 @@
getContext().startActivityAsUser(intent, UserHandle.SYSTEM);
}
});
+ Button multiCameraPreview = (Button) view.findViewById(R.id.camera2_multi_camera_preview);
+ multiCameraPreview.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(getContext(), MultiCameraPreviewActivity.class);
+ getContext().startActivity(intent);
+ }
+ });
+
return view;
}
}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/camera2/CameraPreviewManager.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/camera2/CameraPreviewManager.java
new file mode 100644
index 0000000..9d82314
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/camera2/CameraPreviewManager.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.camera2;
+
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
+import android.media.MediaRecorder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Size;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicLong;
+
+final class CameraPreviewManager {
+ private static final String TAG = CameraPreviewManager.class.getSimpleName();
+ private static final int MSG_SURFACE_READY = 1;
+ private static final int MSG_CAMERA_OPENED = 2;
+ private static final int MSG_SESSION_REQUESTED = 3;
+ private static final Size VIDEO_SIZE = new Size(/* width= */ 1280, /* height= */ 720);
+ private static final int VIDEO_FRAME_RATE = 30;
+ private static final int VIDEO_BIT_RATE = 10_000_000;
+ private final SessionStateListener mSessionStateListener;
+ private final CameraDeviceStateListener mCameraDeviceStateListener;
+ private final SessionCaptureListener mSessionCaptureListener;
+ private final String mCameraId;
+ private final SurfaceView mSurfaceView;
+ private final CameraManager mCameraManager;
+ private final Handler mSessionHandler;
+ private final Handler mConfigHandler;
+ private boolean mIsCameraConnected;
+ private boolean mIsSurfaceCreated;
+ private boolean mIsPreviewSessionRequested;
+ private boolean mIsPreviewSessionCreated;
+ private boolean mIsRecordingSessionRequested;
+ private boolean mIsRecordingSessionCreated;
+ private CameraDevice mCameraDevice;
+ private CaptureRequest mCaptureRequest;
+ private CameraCaptureSession mCaptureSession;
+ private MediaRecorder mRecorder;
+ private String mVideoFilePath;
+
+ /**
+ * CameraPreviewManager
+ * Class designed to create and manage a camera preview for a single camera device.
+ *
+ * @param cameraId Specifies for which camera this instance is managing the preview
+ * @param surfaceView The SurfaceView on which the preview will be shown
+ * @param cameraManager The system camera manager
+ */
+ CameraPreviewManager(
+ String cameraId, SurfaceView surfaceView,
+ CameraManager cameraManager, HandlerThread sessionHandlerThread) {
+ mCameraId = cameraId;
+ mSurfaceView = surfaceView;
+ mCameraManager = cameraManager;
+ mSurfaceView.getHolder().addCallback(new SurfacePreviewListener());
+
+ mCameraDeviceStateListener = new CameraDeviceStateListener();
+ mSessionStateListener = new SessionStateListener();
+ mSessionCaptureListener = new SessionCaptureListener();
+
+ mSessionHandler = new Handler(sessionHandlerThread.getLooper());
+ mConfigHandler = new Handler(Looper.getMainLooper(), new CameraSurfaceInitListener());
+ }
+
+ void openCamera() {
+ try {
+ Log.d(TAG, "Opening camera " + mCameraId);
+ mCameraManager.openCamera(mCameraId, mCameraDeviceStateListener, null);
+ } catch (CameraAccessException | IllegalStateException e) {
+ Log.e(TAG, "Failed to open camera " + mCameraId + ". Got:", e);
+ }
+ Log.d(TAG, "Initialize MediaRecorder.");
+ mRecorder = new MediaRecorder();
+ }
+
+ void closeCamera() {
+ if (mCameraDevice != null) {
+ mCameraDevice.close();
+ mCameraDevice = null;
+ mIsCameraConnected = false;
+ Log.d(TAG, "Closed camera " + mCameraId);
+ }
+ Log.d(TAG, "Release MediaRecorder.");
+ mRecorder.reset();
+ mRecorder.release();
+ }
+
+ void startPreviewSession() {
+ mIsPreviewSessionRequested = true;
+ mConfigHandler.sendEmptyMessage(MSG_SESSION_REQUESTED);
+ }
+
+ void startRecordingSession(String filePrefix) {
+ mIsRecordingSessionRequested = true;
+ mVideoFilePath = String.format("%s_id_%s.mp4", filePrefix, mCameraId);
+ Log.d(TAG, String.format(
+ "Video recording path for camera %s set to %s", mCameraId, mVideoFilePath));
+ setupMediaRecorder();
+ mConfigHandler.sendEmptyMessage(MSG_SESSION_REQUESTED);
+ }
+
+ private void setupMediaRecorder() {
+ mRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+ mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
+ mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+ mRecorder.setVideoSize(VIDEO_SIZE.getWidth(), VIDEO_SIZE.getHeight());
+ mRecorder.setVideoFrameRate(VIDEO_FRAME_RATE);
+ mRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE);
+ mRecorder.setOutputFile(mVideoFilePath);
+ try {
+ mRecorder.prepare();
+ } catch (IllegalStateException | IOException e) {
+ Log.e(TAG, "Unable to prepare media recorder with camera " + mCameraId, e);
+ }
+ }
+
+ void stopSession() {
+ if (mIsRecordingSessionRequested || mIsRecordingSessionCreated) {
+ Log.d(TAG, "Attempting to stop current recording session of camera " + mCameraId);
+ try {
+ mRecorder.stop();
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Attempting to stop recording that wasn't started, got: ", e);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Received no data during recording, got: ", e);
+ }
+ mRecorder.reset();
+ } else {
+ Log.d(TAG, "Attempting to stop current preview session of camera " + mCameraId);
+ }
+ try {
+ if (mCaptureSession != null) {
+ mCaptureSession.stopRepeating();
+ mCaptureSession.close();
+ mCaptureSession = null;
+ }
+ Log.d(TAG, "Stopped capture session " + mCameraId);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception caught while stopping camera session " + mCameraId, e);
+ }
+ mIsPreviewSessionRequested = false;
+ mIsPreviewSessionCreated = false;
+ mIsRecordingSessionRequested = false;
+ mIsRecordingSessionCreated = false;
+ }
+
+ long getFrameCountOfLastSession() {
+ return mSessionCaptureListener.getFrameCount();
+ }
+
+ final class SurfacePreviewListener implements SurfaceHolder.Callback {
+
+ @Override
+ public void surfaceCreated(@NonNull SurfaceHolder holder) {
+ Log.d(TAG, "Surface created");
+ mIsSurfaceCreated = true;
+ mConfigHandler.sendEmptyMessage(MSG_SURFACE_READY);
+ }
+
+ @Override
+ public void surfaceChanged(
+ @NonNull SurfaceHolder holder, int format, int width, int height) {
+ Log.d(TAG, "Surface Changed to: " + width + "x" + height);
+ }
+
+ @Override
+ public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
+ Log.d(TAG, "Surface destroyed");
+ mIsSurfaceCreated = false;
+ }
+ }
+
+ static final class SessionExecutor implements Executor {
+ private final Handler mExecutorHandler;
+
+ SessionExecutor(Handler handler) {
+ mExecutorHandler = handler;
+ }
+
+ @Override
+ public void execute(Runnable runCmd) {
+ mExecutorHandler.post(runCmd);
+ }
+ }
+
+ final class CameraDeviceStateListener extends CameraDevice.StateCallback {
+
+ @Override
+ public void onOpened(@NonNull CameraDevice camera) {
+ Log.d(TAG, "Camera Opened");
+ mCameraDevice = camera;
+ mIsCameraConnected = true;
+ mConfigHandler.sendEmptyMessage(MSG_CAMERA_OPENED);
+ }
+
+ @Override
+ public void onDisconnected(@NonNull CameraDevice camera) {
+ mIsCameraConnected = false;
+ }
+
+ @Override
+ public void onClosed(@NonNull CameraDevice camera) {
+ mIsCameraConnected = false;
+ }
+
+ @Override
+ public void onError(@NonNull CameraDevice camera, int error) {}
+ }
+
+ final class CameraSurfaceInitListener implements Handler.Callback {
+ @Override
+ public boolean handleMessage(@NonNull Message msg) {
+ switch(msg.what) {
+ case MSG_CAMERA_OPENED:
+ case MSG_SURFACE_READY:
+ case MSG_SESSION_REQUESTED:
+ if ((mCameraDevice != null) && mIsCameraConnected && mIsSurfaceCreated) {
+ // Camera and surfaces ready to start new capture session
+ if (mIsPreviewSessionRequested && !mIsPreviewSessionCreated) {
+ // Preview session requested but not created
+ Log.d(TAG, "All conditions satisfied, starting preview session.");
+ createPreviewSession();
+ } else if (mIsRecordingSessionRequested && !mIsRecordingSessionCreated) {
+ // Recording session requested but not created
+ Log.d(TAG, "All conditions satisfied, starting recording session.");
+ createRecordingSession();
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ private void createPreviewSession() {
+ try {
+ CaptureRequest.Builder captureRequestBuilder = mCameraDevice.createCaptureRequest(
+ CameraDevice.TEMPLATE_PREVIEW);
+ captureRequestBuilder.addTarget(mSurfaceView.getHolder().getSurface());
+ mCaptureRequest = captureRequestBuilder.build();
+
+ List<OutputConfiguration> outputs = new ArrayList<>();
+ outputs.add(new OutputConfiguration(mSurfaceView.getHolder().getSurface()));
+ SessionConfiguration sessionConfig = new SessionConfiguration(
+ SessionConfiguration.SESSION_REGULAR, outputs,
+ new SessionExecutor(mSessionHandler), mSessionStateListener);
+
+ sessionConfig.setSessionParameters(mCaptureRequest);
+ mCameraDevice.createCaptureSession(sessionConfig);
+ mIsPreviewSessionCreated = true;
+ Log.d(TAG, "Created preview capture session for camera " + mCameraId);
+ } catch (CameraAccessException | IllegalStateException e) {
+ Log.e(TAG, "Failed to create preview capture session with camera " + mCameraId, e);
+ }
+ }
+
+ private void createRecordingSession() {
+ try {
+
+ Log.d(TAG, String.format(
+ "Recorder for camera %s is valid? %b",
+ mCameraId, mRecorder.getSurface().isValid()));
+
+ CaptureRequest.Builder captureRequestBuilder = mCameraDevice.createCaptureRequest(
+ CameraDevice.TEMPLATE_RECORD);
+ captureRequestBuilder.addTarget(mSurfaceView.getHolder().getSurface());
+ captureRequestBuilder.addTarget(mRecorder.getSurface());
+ mCaptureRequest = captureRequestBuilder.build();
+
+ List<OutputConfiguration> outputs = new ArrayList<>();
+ outputs.add(new OutputConfiguration(mSurfaceView.getHolder().getSurface()));
+ outputs.add(new OutputConfiguration(mRecorder.getSurface()));
+ SessionConfiguration sessionConfig = new SessionConfiguration(
+ SessionConfiguration.SESSION_REGULAR, outputs,
+ new SessionExecutor(mSessionHandler), mSessionStateListener);
+
+ sessionConfig.setSessionParameters(mCaptureRequest);
+ mCameraDevice.createCaptureSession(sessionConfig);
+ mIsRecordingSessionCreated = true;
+ Log.d(TAG, "Created recording capture session for camera " + mCameraId);
+ } catch (CameraAccessException | IllegalStateException e) {
+ Log.e(TAG, "Failed to create recording capture session with camera " + mCameraId, e);
+ }
+ }
+
+
+ final class SessionStateListener extends CameraCaptureSession.StateCallback {
+ @Override
+ public void onConfigured(@NonNull CameraCaptureSession session) {
+ mCaptureSession = session;
+ try {
+ mSessionCaptureListener.resetFrameCount();
+ mCaptureSession.setRepeatingRequest(
+ mCaptureRequest, mSessionCaptureListener, mSessionHandler);
+ if (mIsPreviewSessionRequested) {
+ Log.d(TAG, "Successfully started recording session with camera " + mCameraId);
+ } else if (mIsRecordingSessionRequested) {
+ mRecorder.start();
+ Log.d(TAG, "Successfully started recording session with camera " + mCameraId);
+ }
+ Log.d(TAG, "Successfully started recording session with camera " + mCameraId);
+ } catch (CameraAccessException | IllegalStateException | IllegalArgumentException e) {
+ Log.e(TAG, "Failed to start camera preview with camera " + mCameraId, e);
+ }
+ }
+
+ @Override
+ public void onConfigureFailed(@NonNull CameraCaptureSession session) {
+ Log.e(TAG, "Failed to configure session with camera " + mCameraId);
+ }
+ }
+
+ static final class SessionCaptureListener extends CameraCaptureSession.CaptureCallback {
+ private final AtomicLong mFrameCount = new AtomicLong(0);
+
+ @Override
+ public void onCaptureCompleted(
+ @NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull TotalCaptureResult result) {
+ mFrameCount.incrementAndGet();
+ }
+
+ public void resetFrameCount() {
+ mFrameCount.set(0);
+ }
+
+ public long getFrameCount() {
+ return mFrameCount.get();
+ }
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/camera2/MultiCameraPreviewActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/camera2/MultiCameraPreviewActivity.java
new file mode 100644
index 0000000..274aba6
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/camera2/MultiCameraPreviewActivity.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.camera2;
+
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Debug.MemoryInfo;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+
+import androidx.fragment.app.FragmentActivity;
+
+import com.google.android.car.kitchensink.R;
+
+import java.lang.reflect.Array;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+
+public final class MultiCameraPreviewActivity extends FragmentActivity {
+ private static final String TAG = MultiCameraPreviewActivity.class.getSimpleName();
+ private final List<CameraPreviewManager> mCameraPreviewManagerList = new ArrayList<>();
+ private final List<SurfaceView> mPreviewSurfaceViewList = new ArrayList<>();
+ private final List<CheckBox> mSelectionCheckBoxList = new ArrayList<>();
+ private CameraManager mCameraManager;
+ private ListView mDetailsListView;
+ private String[] mCameraIds;
+ private HandlerThread mSessionHandlerThread;
+ private boolean mIsPreviewStarted;
+ private boolean mIsRecordingStarted;
+ private long mSessionStartTimeMs;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.camera2_multi_camera_preview_activity);
+
+ mDetailsListView = (ListView) findViewById(R.id.camera_details_list_view);
+
+ Button quitButton = (Button) findViewById(R.id.quit_button);
+ quitButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ finish();
+ }
+ });
+
+ Button startPreviewButton = (Button) findViewById(R.id.start_preview_button);
+ startPreviewButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startPreviews();
+ }
+ });
+
+ Button startRecordingButton = (Button) findViewById(R.id.start_recording_button);
+ startRecordingButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startRecording();
+ }
+ });
+
+ Button stopButton = (Button) findViewById(R.id.stop_button);
+ stopButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ stopSession();
+ }
+ });
+
+ mCameraManager = getSystemService(CameraManager.class);
+
+ mSessionHandlerThread = new HandlerThread(TAG + "_session_thread");
+ mSessionHandlerThread.start();
+
+ // Create a list of camera managers to handle each camera device and their capture sessions
+ try {
+ mCameraIds = mCameraManager.getCameraIdListNoLazy();
+ if (mCameraIds.length == 0) {
+ Log.w(TAG, "Camera service reported no cameras connected to device.");
+ return;
+ }
+ addPreviewCells(); // Adds preview cells with one SurfaceView for every camera
+ for (int camIdx = 0; camIdx < mCameraIds.length; camIdx++) {
+ Log.i(TAG, "Creating preview for camera with ID " + mCameraIds[camIdx]);
+ CameraPreviewManager cameraPreviewManager = new CameraPreviewManager(
+ mCameraIds[camIdx],
+ mPreviewSurfaceViewList.get(camIdx),
+ mCameraManager,
+ mSessionHandlerThread
+ );
+ cameraPreviewManager.openCamera();
+ mCameraPreviewManagerList.add(cameraPreviewManager);
+ }
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed to get camera ID list, got:", e);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to open camera, got:", e);
+ }
+ }
+
+ private void addPreviewCells() {
+ // Adds preview cells in a square-like grid
+ int numColumns = (int) Math.ceil(Math.sqrt(mCameraIds.length));
+ int numRows = -Math.floorDiv(-mCameraIds.length, numColumns); //==ceilDiv(nCameras, nCols)
+ int numPadded = numColumns * numRows - mCameraIds.length;
+ int numCellsLastRow = numColumns - numPadded;
+
+ LinearLayout previewRoot = (LinearLayout) findViewById(R.id.preview_root);
+
+ // Fill every row completely with preview cells except last row
+ for (int i = 0; i < numRows - 1; i++) {
+ LinearLayout previewRow = (LinearLayout) View.inflate(
+ this, R.layout.camera2_preview_row, null);
+ previewRoot.addView(previewRow, new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ 1f));
+ for (int j = 0; j < numColumns; j++) {
+ addPreviewSurfaceView(previewRow, i * numColumns + j);
+ }
+ }
+
+ // Add last row
+ LinearLayout lastPreviewRow = (LinearLayout) (LinearLayout) View.inflate(
+ this, R.layout.camera2_preview_row, null);
+ previewRoot.addView(lastPreviewRow, new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ 1f));
+ for (int j = 0; j < numCellsLastRow; j++) {
+ addPreviewSurfaceView(lastPreviewRow, (numRows - 1) * numColumns + j);
+ }
+ // Pad rest of the row with empty cells
+ for (int j = numCellsLastRow; j < numColumns; j++) {
+ LinearLayout emptyCell = (LinearLayout) View.inflate(
+ this, R.layout.camera2_preview_empty_cell, null);
+ lastPreviewRow.addView(emptyCell, new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ 1f));
+ }
+ }
+
+ private void addPreviewSurfaceView(ViewGroup parent, int camIdx) {
+ LinearLayout previewLayout = (LinearLayout) View.inflate(
+ this, R.layout.camera2_preview_cell, null);
+ parent.addView(previewLayout, new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ 1f
+ ));
+
+ Button detailsButton = (Button) previewLayout.findViewById(R.id.details_button);
+ detailsButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ viewCameraCharacteristics(camIdx);
+ }
+ });
+
+ CheckBox selectionCheckBox = (CheckBox) previewLayout.findViewById(R.id.selection_checkbox);
+ selectionCheckBox.setText(
+ getString(R.string.camera2_selection_checkbox, mCameraIds[camIdx]));
+ SurfaceView surfaceView = (SurfaceView) previewLayout.findViewById(R.id.preview_surface);
+
+ mSelectionCheckBoxList.add(selectionCheckBox);
+ mPreviewSurfaceViewList.add(surfaceView);
+ }
+
+ private void startPreviews() {
+ // Do nothing if a session has already started
+ if (mIsPreviewStarted || mIsRecordingStarted) {
+ Log.i(TAG, "Start preview button pressed when a session is already running.");
+ return;
+ }
+ // Freeze checkbox selections
+ for (CheckBox checkBox : mSelectionCheckBoxList) {
+ checkBox.setEnabled(false);
+ }
+ // Open Cameras and start previews
+ for (int i = 0; i < mCameraIds.length; i++) {
+ if (mSelectionCheckBoxList.get(i).isChecked()) {
+ mCameraPreviewManagerList.get(i).startPreviewSession();
+ }
+ }
+
+ // Set Start Time
+ mSessionStartTimeMs = SystemClock.elapsedRealtime();
+
+ // Set flag
+ mIsPreviewStarted = true;
+ }
+
+ private void startRecording() {
+ // Do nothing if a session has already started
+ if (mIsPreviewStarted || mIsRecordingStarted) {
+ Log.i(TAG, "Start recording button pressed when a session is already running.");
+ return;
+ }
+ // Freeze checkbox selections
+ for (CheckBox checkBox : mSelectionCheckBoxList) {
+ checkBox.setEnabled(false);
+ }
+
+ // Get file prefix from current time
+ String dateTimeString =
+ new SimpleDateFormat("yyyy-MM-dd_hh.mm.ss", Locale.US)
+ .format(Calendar.getInstance().getTime());
+ String filePathPrefix = String.format("%s/camera2_video_%s",
+ getExternalFilesDir(null), dateTimeString);
+
+ // Open Cameras and start recording
+ for (int i = 0; i < mCameraIds.length; i++) {
+ if (mSelectionCheckBoxList.get(i).isChecked()) {
+ mCameraPreviewManagerList.get(i).startRecordingSession(filePathPrefix);
+ }
+ }
+
+ // Set Start Time
+ mSessionStartTimeMs = SystemClock.elapsedRealtime();
+
+ // Set flag
+ mIsRecordingStarted = true;
+ }
+
+ private void stopSession() {
+ // Do nothing if no preview has been started
+ if (!mIsPreviewStarted && !mIsRecordingStarted) {
+ Log.i(TAG, "Stop button pressed when no session has been started.");
+ return;
+ }
+
+ // Unset flags
+ mIsPreviewStarted = false;
+ mIsRecordingStarted = false;
+
+ // Session memory usage
+ MemoryInfo sessionMemoryInfo = new MemoryInfo();
+ Debug.getMemoryInfo(sessionMemoryInfo);
+
+ // Get Frame Counts
+ Map<String, Long> frameCountMap = getSessionFrameCounts();
+
+ // Get End Time
+ long sessionDurationMs = SystemClock.elapsedRealtime() - mSessionStartTimeMs;
+
+ // View Session Metrics
+ List<String> sessionMetricsInfoText = getSessionMemoryInfoText(sessionMemoryInfo);
+ sessionMetricsInfoText.add("");
+ sessionMetricsInfoText.addAll(getSessionFpsInfoText(frameCountMap, sessionDurationMs));
+ mDetailsListView.setAdapter(new ArrayAdapter<String>(
+ this, R.layout.camera2_details_list_item, sessionMetricsInfoText));
+
+ // Stop camera sessions that have been started
+ for (int i = 0; i < mCameraIds.length; i++) {
+ if (mSelectionCheckBoxList.get(i).isChecked()) {
+ mCameraPreviewManagerList.get(i).stopSession();
+ }
+ }
+
+ // Un-freeze checkbox selections
+ for (CheckBox checkBox : mSelectionCheckBoxList) {
+ checkBox.setEnabled(true);
+ }
+ }
+
+ private Map<String, Long> getSessionFrameCounts() {
+ Map<String, Long> frameCountMap = new HashMap<>();
+ for (int i = 0; i < mCameraIds.length; i++) {
+ if (mSelectionCheckBoxList.get(i).isChecked()) {
+ frameCountMap.put(
+ mCameraIds[i],
+ mCameraPreviewManagerList.get(i).getFrameCountOfLastSession());
+ }
+ }
+ return frameCountMap;
+ }
+
+ private static List<String> getSessionFpsInfoText(
+ Map<String, Long> frameCountMap, Long sessionDurationMs) {
+ List<String> infoList = new ArrayList<>(List.of("SESSION FPS INFO (Hz)"));
+ for (Map.Entry<String, Long> entry : frameCountMap.entrySet()) {
+ String cameraId = entry.getKey();
+ long frameCount = entry.getValue();
+ infoList.add(String.format(
+ "Effective FPS of camera %s: %.2f",
+ cameraId,
+ (1000.0 * frameCount) / sessionDurationMs));
+ }
+ return infoList;
+ }
+
+ private List<String> getSessionMemoryInfoText(MemoryInfo memInfo) {
+ List<String> infoList = new ArrayList<>(List.of("SESSION MEMORY INFO (kB)"));
+ Map<String, String> memStats = memInfo.getMemoryStats();
+ for (Map.Entry<String, String> memStat : memStats.entrySet()) {
+ infoList.add(String.format("%s: %s", memStat.getKey(), memStat.getValue()));
+ }
+ return infoList;
+ }
+
+ private void viewCameraCharacteristics(int cameraIdx) {
+ List<String> detailsList = new ArrayList<>(Arrays.asList(
+ String.format("CAMERA %s INFO", mCameraIds[cameraIdx])));
+ detailsList.addAll(getCameraCharacteristics(cameraIdx));
+ mDetailsListView.setAdapter(new ArrayAdapter<String>(
+ this, R.layout.camera2_details_list_item, detailsList));
+ }
+
+ private List<String> getCameraCharacteristics(int cameraIdx) {
+ CameraCharacteristics characteristics;
+ try {
+ characteristics = mCameraManager.getCameraCharacteristics(mCameraIds[cameraIdx]);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, String.format("Camera %s disconnected while fetching characteristics.",
+ mCameraIds[cameraIdx]), e);
+ return Arrays.asList("Camera disconnected...");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, String.format("Attempting to fetch characteristics of unknown camera ID %s.",
+ mCameraIds[cameraIdx]), e);
+ return Arrays.asList("Invalid camera ID...");
+ }
+ List<CameraCharacteristics.Key<?>> allKeys = characteristics.getKeys();
+ List<String> detailsList = new ArrayList<>();
+ for (CameraCharacteristics.Key<?> key: allKeys) {
+ try {
+ Object val = characteristics.get(key);
+ if (val == null) {
+ detailsList.add(String.format("%s: (null)", key.getName()));
+ } else if (val.getClass().isArray()) {
+ Object[] valAsObjectArray = asObjectArray(val);
+ detailsList.add(String.format("%s: %s", key.getName(),
+ Arrays.deepToString(valAsObjectArray)));
+ } else {
+ detailsList.add(String.format("%s: %s", key.getName(), val));
+ }
+
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, String.format("Invalid key %s found in camera ID %s", key.getName(),
+ mCameraIds[cameraIdx]));
+ detailsList.add(String.format("%s: INVALID KEY", key.getName()));
+ }
+ }
+ return detailsList;
+ }
+
+ private static Object[] asObjectArray(Object array) {
+ int length = Array.getLength(array);
+ Object[] ret = new Object[length];
+ for (int i = 0; i < length; i++) {
+ ret[i] = Array.get(array, i);
+ }
+ return ret;
+ }
+
+ @Override
+ protected void onDestroy() {
+ for (CameraPreviewManager cameraPreviewManager : mCameraPreviewManagerList) {
+ cameraPreviewManager.closeCamera();
+ }
+ try {
+ if (mSessionHandlerThread != null) {
+ mSessionHandlerThread.quitSafely();
+ mSessionHandlerThread.join();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error while closing session thread.", e);
+ }
+ super.onDestroy();
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/ResponsiveClusterActivity.kt b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/ResponsiveClusterActivity.kt
new file mode 100644
index 0000000..08dd276
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/ResponsiveClusterActivity.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.cluster
+
+import android.os.Bundle
+import android.view.View
+import android.view.WindowInsets
+import android.widget.FrameLayout
+import android.widget.ProgressBar
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import com.google.android.car.kitchensink.R
+
+class ResponsiveClusterActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContentView(R.layout.activity_responsive_cluster)
+ val availableWidth = resources.displayMetrics.widthPixels
+ val bar = findViewById<ProgressBar>(R.id.progressBar)!!
+
+ window.decorView.findViewById<View>(R.id.root)!!
+ .setOnApplyWindowInsetsListener { v, insets ->
+ val systemBars = insets.getInsets(WindowInsets.Type.systemOverlays())
+ var params = v.layoutParams as FrameLayout.LayoutParams
+ params.setMargins(
+ systemBars.left,
+ systemBars.top,
+ systemBars.right,
+ systemBars.bottom
+ )
+ v.layoutParams = params
+ val widthAfterInsets = availableWidth - systemBars.right - systemBars.left
+ val progress = 100 * (widthAfterInsets.toFloat()) / availableWidth
+ bar.setProgress(progress.toInt())
+ insets
+ }
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/customizationtool/CustomizationToolController.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/customizationtool/CustomizationToolController.java
new file mode 100644
index 0000000..494a8c9
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/customizationtool/CustomizationToolController.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.customizationtool;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+
+public class CustomizationToolController {
+
+ private static final String PACKAGE = "com.android.car.customization.tool";
+ private static final String SERVICE = PACKAGE + '/' + PACKAGE + ".CustomizationToolService";
+
+ private final ContentResolver mContentResolver;
+
+ public CustomizationToolController(Context context) {
+ mContentResolver = context.getContentResolver();
+ }
+
+ boolean isCustomizationToolActive() {
+ String accessibilityServices = Settings.Secure.getString(
+ mContentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ if (accessibilityServices == null) {
+ return false;
+ }
+ String serviceStatus = Settings.Secure.getString(
+ mContentResolver, Settings.Secure.ACCESSIBILITY_ENABLED);
+ return accessibilityServices.contains(SERVICE) && "1".equals(serviceStatus);
+ }
+
+ public void toggleCustomizationTool(boolean newState) {
+ if (newState) {
+ Settings.Secure.putString(
+ mContentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, SERVICE);
+ Settings.Secure.putString(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_ENABLED, /* value = */"1");
+ } else {
+ String newAccessibilityServices = removeServiceFromList();
+ Settings.Secure.putString(mContentResolver,
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, newAccessibilityServices);
+ Settings.Secure.putString(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_ENABLED, /* value = */"0");
+ }
+ }
+
+ private String removeServiceFromList() {
+ String currentServices = Settings.Secure.getString(
+ mContentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ if (currentServices == null || currentServices.isEmpty()) {
+ return "";
+ }
+ return currentServices.replace(
+ SERVICE, /* replacement = */"").replace(/* target = */"::", /* replacement = */":");
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/customizationtool/CustomizationToolFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/customizationtool/CustomizationToolFragment.java
new file mode 100644
index 0000000..87661a9
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/customizationtool/CustomizationToolFragment.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.customizationtool;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Switch;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.google.android.car.kitchensink.R;
+
+/**
+ * Provides methods to enable or disable the Customization Tool service.
+ */
+public class CustomizationToolFragment extends Fragment {
+
+ private CustomizationToolController mCustomizationToolController;
+ private Switch mCustomizationToolSwitch;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.customization_tool, container, false);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ mCustomizationToolController = new CustomizationToolController(getContext());
+
+ mCustomizationToolSwitch = view.findViewById(R.id.customization_tool_switch);
+ mCustomizationToolSwitch.setOnClickListener(v ->
+ mCustomizationToolController.toggleCustomizationTool(
+ mCustomizationToolSwitch.isChecked()));
+ mCustomizationToolSwitch.setChecked(
+ mCustomizationToolController.isCustomizationToolActive());
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/display/SelfManagedVirtualDisplayView.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/display/SelfManagedVirtualDisplayView.java
index c822379..577a9a8 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/display/SelfManagedVirtualDisplayView.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/display/SelfManagedVirtualDisplayView.java
@@ -16,8 +16,6 @@
package com.google.android.car.kitchensink.display;
-import static android.widget.Toast.LENGTH_SHORT;
-
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
@@ -30,7 +28,6 @@
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
-import android.widget.Toast;
import com.google.android.car.kitchensink.R;
import com.google.android.car.kitchensink.users.UsersSpinner;
@@ -112,10 +109,11 @@
try {
boolean started = mContext.getSystemService(ActivityManager.class)
.startUserInBackgroundVisibleOnDisplay(user.id, mDisplayId);
- logAndToastMessage("%s user %d on display %d",
+ ToastUtils.logAndToastMessage(getContext(), "%s user %d on display %d",
(started ? "Started" : "Failed to start"), user.id, mDisplayId);
} catch (Exception e) {
- logAndToastError(e, "Error starting user %d on display %d", user.id, mDisplayId);
+ ToastUtils.logAndToastError(getContext(), e,
+ "Error starting user %d on display %d", user.id, mDisplayId);
}
}
@@ -145,9 +143,10 @@
Log.i(TAG, "Creating virtual display");
try {
createDisplay();
- logAndToastMessage("Created virtual display with id %d", mDisplayId);
+ ToastUtils.logAndToastMessage(getContext(),
+ "Created virtual display with id %d", mDisplayId);
} catch (Exception e) {
- logAndToastError(e, "Failed to create virtual display");
+ ToastUtils.logAndToastError(getContext(), e, "Failed to create virtual display");
}
}
@@ -166,9 +165,9 @@
Log.i(TAG, "Deleting display");
try {
deleteDisplay();
- logAndToastMessage("Virtual display deleted");
+ ToastUtils.logAndToastMessage(getContext(), "Virtual display deleted");
} catch (Exception e) {
- logAndToastError(e, "Failed to delete virtual display");
+ ToastUtils.logAndToastError(getContext(), e, "Failed to delete virtual display");
}
}
@@ -201,28 +200,12 @@
view.setVisibility(on ? View.VISIBLE : View.GONE);
}
- protected void logAndToastMessage(String format, Object...args) {
- String message = String.format(format, args);
- Log.i(TAG, message);
- Toast.makeText(getContext(), message, LENGTH_SHORT).show();
- }
-
- protected void printMessage(PrintWriter writer, String format, Object...args) {
+ protected void printMessage(PrintWriter writer, String format, Object... args) {
String message = String.format(format, args);
writer.printf("%s\n", message);
}
- protected void logAndToastError(Exception e, String format, Object...args) {
- String message = String.format(format, args);
- if (e != null) {
- Log.e(TAG, message, e);
- } else {
- Log.e(TAG, message);
- }
- Toast.makeText(getContext(), message, LENGTH_SHORT).show();
- }
-
- protected void printError(PrintWriter writer, Exception e, String format, Object...args) {
+ protected void printError(PrintWriter writer, Exception e, String format, Object... args) {
String message = String.format(format, args);
if (e != null) {
writer.printf("%s: %s\n", message, e);
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/display/ToastUtils.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/display/ToastUtils.java
new file mode 100644
index 0000000..e17c3a8
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/display/ToastUtils.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.display;
+
+import static android.widget.Toast.LENGTH_SHORT;
+
+import android.content.Context;
+import android.util.Log;
+import android.widget.Toast;
+
+final class ToastUtils {
+
+ private static final String TAG = ToastUtils.class.getSimpleName();
+
+ static void logAndToastMessage(Context context, String format, Object...args) {
+ String message = String.format(format, args);
+ Log.i(TAG, message);
+ Toast.makeText(context, message, LENGTH_SHORT).show();
+ }
+
+ static void logAndToastError(Context context, Exception e, String format, Object...args) {
+ String message = String.format(format, args);
+ if (e != null) {
+ Log.e(TAG, message, e);
+ } else {
+ Log.e(TAG, message);
+ }
+ Toast.makeText(context, message, LENGTH_SHORT).show();
+ }
+
+ private ToastUtils() {
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/display/VirtualDisplayFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/display/VirtualDisplayFragment.java
index e915695..b552da0 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/display/VirtualDisplayFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/display/VirtualDisplayFragment.java
@@ -15,8 +15,6 @@
*/
package com.google.android.car.kitchensink.display;
-import static android.widget.Toast.LENGTH_SHORT;
-
import static com.google.android.car.kitchensink.KitchenSinkActivity.DUMP_ARG_CMD;
import static com.google.android.car.kitchensink.KitchenSinkActivity.DUMP_ARG_FRAGMENT;
@@ -32,7 +30,6 @@
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.Spinner;
-import android.widget.Toast;
import androidx.fragment.app.Fragment;
@@ -51,8 +48,8 @@
* Example:
*
* <pre><code>
- adb shell 'am start -n com.google.android.car.kitchensink/.KitchenSinkActivity --es select "virtual display"'
- adb shell 'dumpsys activity com.google.android.car.kitchensink/.KitchenSinkActivity fragment "virtual display" cmd create'
+ * adb shell 'am start -n com.google.android.car.kitchensink/.KitchenSinkActivity --es select "virtual display"'
+ * adb shell 'dumpsys activity com.google.android.car.kitchensink/.KitchenSinkActivity fragment "virtual display" cmd create'
* </code></pre>
*/
public final class VirtualDisplayFragment extends Fragment {
@@ -119,6 +116,7 @@
long id) {
updateNumberDisplays(position + 1, /* force= */ false);
}
+
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
@@ -130,7 +128,7 @@
View view = inflater.inflate(R.layout.virtual_display, container, false);
- mDisplaysContainer = view.findViewById(R.id.displays_container);
+ mDisplaysContainer = view.findViewById(R.id.displays_container);
return view;
}
@@ -395,7 +393,7 @@
private void maximizeScreen() {
if (mMaximized) {
- logAndToastError(/* exception= */ null, "Already maximized");
+ ToastUtils.logAndToastError(getContext(), /* exception= */ null, "Already maximized");
return;
}
String msg1 = "Maximizing display. To minimize, run:";
@@ -403,8 +401,9 @@
String activity = KitchenSinkActivity.class.getSimpleName();
String msg2 = String.format("adb shell 'dumpsys activity %s/.%s %s \"%s\" %s %s'",
pkg, activity, DUMP_ARG_FRAGMENT, FRAGMENT_NAME, DUMP_ARG_CMD, CMD_MINIMIZE);
- logAndToastMessage(msg1);
- logAndToastMessage(msg2);
+ Context context = getContext();
+ ToastUtils.logAndToastMessage(context, msg1);
+ ToastUtils.logAndToastMessage(context, msg2);
minimizeOrMaximizeViews(/* maximize= */ true);
}
@@ -454,29 +453,13 @@
visibilityToString(view.getVisibility()));
}
- protected void logAndToastMessage(String format, Object...args) {
- String message = String.format(format, args);
- Log.i(TAG, message);
- Toast.makeText(getContext(), message, LENGTH_SHORT).show();
- }
-
- protected void printMessage(PrintWriter writer, String format, Object...args) {
+ protected void printMessage(PrintWriter writer, String format, Object... args) {
String message = String.format(format, args);
writer.printf("%s\n", message);
}
- protected void logAndToastError(Exception e, String format, Object...args) {
- String message = String.format(format, args);
- if (e != null) {
- Log.e(TAG, message, e);
- } else {
- Log.e(TAG, message);
- }
- Toast.makeText(getContext(), message, LENGTH_SHORT).show();
- }
-
protected void printError(PrintWriter writer, Exception exception,
- String format, Object...args) {
+ String format, Object... args) {
String message = String.format(format, args);
if (exception != null) {
writer.printf("%s: %s\n", message, exception);
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/insets/WindowInsetsTestActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/insets/WindowInsetsTestActivity.java
index 0afe369..bd694c0 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/insets/WindowInsetsTestActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/insets/WindowInsetsTestActivity.java
@@ -19,6 +19,8 @@
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.view.WindowInsets;
@@ -26,6 +28,8 @@
import androidx.annotation.Nullable;
+import com.android.car.ui.AlertDialogBuilder;
+
import com.google.android.car.kitchensink.R;
/**
@@ -37,9 +41,16 @@
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
hideSysUI();
-
setContentView(R.layout.fullscreen_activity);
+
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
findViewById(R.id.cancel_button).setOnClickListener(l -> finish());
+ findViewById(R.id.show_dialog_button).setOnClickListener(l -> showDialog());
+ findViewById(R.id.show_car_ui_dialog_button).setOnClickListener(l -> showCarUiDialog());
}
private void hideSysUI() {
@@ -48,4 +59,24 @@
windowInsetsController.hide(WindowInsets.Type.systemBars());
windowInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
+
+ private void showDialog() {
+ new AlertDialog.Builder(/* context= */ this)
+ .setTitle(R.string.dialog_title)
+ .setNeutralButton(R.string.dismiss_btn_text, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ })
+ .create()
+ .show();
+ }
+
+ private void showCarUiDialog() {
+ new AlertDialogBuilder(/* context= */ this)
+ .setTitle(R.string.car_ui_dialog_title)
+ .create()
+ .show();
+ }
}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyListAdapter.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyListAdapter.java
deleted file mode 100644
index 4aba0b2..0000000
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyListAdapter.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.car.kitchensink.property;
-
-import static java.lang.Integer.toHexString;
-
-import android.car.VehiclePropertyIds;
-import android.car.hardware.CarPropertyConfig;
-import android.car.hardware.CarPropertyValue;
-import android.car.hardware.property.AreaIdConfig;
-import android.car.hardware.property.CarPropertyManager;
-import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
-import android.car.hardware.property.Subscription;
-import android.content.Context;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.util.SparseLongArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
-import android.widget.ScrollView;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import com.google.android.car.kitchensink.R;
-
-import java.util.Arrays;
-import java.util.List;
-
-class PropertyListAdapter extends BaseAdapter implements ListAdapter {
- private static final float DEFAULT_RATE_HZ = 1f;
- private static final String TAG = "PropertyListAdapter";
- private static final String OFF = "Off";
- private static final String ON = "On";
- private static final String MAX_SAMPLE_RATE = "Maximum sample rate";
- private static final String AVG_SAMPLE_RATE = "Average sample rate";
- private static final String VUR = "VUR";
- private static final String[] DROP_MENU_FOR_CONTINUOUS =
- new String[]{OFF, MAX_SAMPLE_RATE, AVG_SAMPLE_RATE};
- private static final String[] DROP_MENU_FOR_CONTINUOUS_VUR =
- new String[]{OFF, VUR, MAX_SAMPLE_RATE, AVG_SAMPLE_RATE};
- private static final String[] DROP_MENU_FOR_ON_CHANGE = new String[]{OFF, ON};
- private final Context mContext;
- private final PropertyListEventListener mListener;
- private final CarPropertyManager mMgr;
- private final List<PropertyInfo> mPropInfo;
- private String[] mItems;
-
- PropertyListAdapter(List<PropertyInfo> propInfo, CarPropertyManager mgr, TextView eventLog,
- ScrollView svEventLog, Context context) {
- mContext = context;
- mListener = new PropertyListEventListener(eventLog, svEventLog);
- mMgr = mgr;
- mPropInfo = propInfo;
- }
-
- @Override
- public int getCount() {
- return mPropInfo.size();
- }
-
- @Override
- public Object getItem(int pos) {
- return mPropInfo.get(pos);
- }
-
- @Override
- public long getItemId(int pos) {
- return mPropInfo.get(pos).mPropId;
- }
-
- @Override
- public View getView(final int position, View convertView, ViewGroup parent) {
- View view = convertView;
- if (view == null) {
- LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- view = inflater.inflate(R.layout.property_list_item, null);
- }
-
- //Handle TextView and display string from your list
- TextView listItemText = (TextView) view.findViewById(R.id.tvPropertyName);
- listItemText.setText(mPropInfo.get(position).toString());
-
- Spinner dropdown = view.findViewById(R.id.tbRegisterPropertySpinner);
-
- CarPropertyConfig c = mPropInfo.get(position).mConfig;
- if (c.getChangeMode() == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
- if (isVurSupportedForAtLeastOneAreaId(c)) {
- mItems = DROP_MENU_FOR_CONTINUOUS_VUR;
- } else {
- mItems = DROP_MENU_FOR_CONTINUOUS;
- }
- } else {
- mItems = DROP_MENU_FOR_ON_CHANGE;
- }
- ArrayAdapter<String> adapter = new ArrayAdapter<String>(mContext,
- R.layout.custom_spinner_dropdown_item, mItems);
-
- adapter.setDropDownViewResource(R.layout.custom_spinner_dropdown_item);
- dropdown.setAdapter(adapter);
- dropdown.setSelection(0);
- dropdown.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long id) {
- String item = (String) adapterView.getItemAtPosition(pos);
- CarPropertyConfig c = mPropInfo.get(position).mConfig;
- int propId = c.getPropertyId();
- try {
- if (OFF.equals(item)) {
- mMgr.unsubscribePropertyEvents(propId, mListener);
- } else if (VUR.equals(item)) {
- // Default update rate is 1hz.
- mListener.addPropertySelectedSampleRate(propId, DEFAULT_RATE_HZ);
- mListener.updatePropertyStartTime(propId);
- mListener.resetEventCountForProperty(propId);
-
- // By default, VUR is on.
- mMgr.subscribePropertyEvents(propId, mListener);
- } else {
- float updateRateHz = 0;
- if (MAX_SAMPLE_RATE.equals(item)) {
- updateRateHz = c.getMaxSampleRate();
- } else if (AVG_SAMPLE_RATE.equals(item)) {
- updateRateHz = (c.getMaxSampleRate() + c.getMinSampleRate()) / 2;
- } else if (ON.equals(item)) {
- updateRateHz = DEFAULT_RATE_HZ;
- }
- mListener.addPropertySelectedSampleRate(propId, updateRateHz);
- mListener.updatePropertyStartTime(propId);
- mListener.resetEventCountForProperty(propId);
-
- mMgr.subscribePropertyEvents(List.of(
- new Subscription.Builder(propId).setUpdateRateHz(updateRateHz)
- .setVariableUpdateRateEnabled(false)
- .build()),
- /* callbackExecutor= */ null, mListener);
- }
- } catch (Exception e) {
- Log.e(TAG, "Unhandled exception: ", e);
- }
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> adapterView) {
- // do nothing.
- }
- });
- return view;
- }
-
- private boolean isVurSupportedForAtLeastOneAreaId(CarPropertyConfig carPropertyConfig) {
- List<AreaIdConfig<?>> areaIdConfigs = carPropertyConfig.getAreaIdConfigs();
- for (int i = 0; i < areaIdConfigs.size(); i++) {
- AreaIdConfig<?> areaIdConfig = areaIdConfigs.get(i);
- if (areaIdConfig.isVariableUpdateRateSupported()) {
- return true;
- }
- }
- return false;
- }
-
-
- private static class PropertyListEventListener implements CarPropertyEventCallback {
- private final ScrollView mScrollView;
- private final TextView mTvLogEvent;
- private final SparseArray<Float> mPropSampleRate = new SparseArray<>();
- private final SparseLongArray mStartTime = new SparseLongArray();
- private final SparseIntArray mNumEvents = new SparseIntArray();
-
- PropertyListEventListener(TextView logEvent, ScrollView scrollView) {
- mScrollView = scrollView;
- mTvLogEvent = logEvent;
- }
-
- void addPropertySelectedSampleRate(Integer propId, Float sampleRate) {
- mPropSampleRate.put(propId, sampleRate);
- }
-
- void updatePropertyStartTime(Integer propId) {
- mStartTime.put(propId, System.currentTimeMillis());
- }
-
- void resetEventCountForProperty(Integer propId) {
- mNumEvents.put(propId, 0);
- }
-
- @Override
- public void onChangeEvent(CarPropertyValue value) {
- int propId = value.getPropertyId();
- int areaId = value.getAreaId();
-
- mNumEvents.put(propId, mNumEvents.get(propId) + 1);
-
- String valueString = value.getValue().getClass().isArray()
- ? Arrays.toString((Object[]) value.getValue())
- : value.getValue().toString();
-
- mTvLogEvent.append(String.format("Event %1$s: time=%2$s propId=0x%3$s areaId=0x%4$s "
- + "name=%5$s status=%6$s value=%7$s", mNumEvents.get(propId),
- value.getTimestamp(), toHexString(propId), toHexString(areaId),
- VehiclePropertyIds.toString(propId), value.getStatus(), valueString));
- if (mPropSampleRate.contains(propId)) {
- mTvLogEvent.append(
- String.format(" selected sample rate=%1$s actual sample rate=%2$s\n",
- mPropSampleRate.get(propId),
- mNumEvents.get(propId) / (System.currentTimeMillis()
- - mStartTime.get(propId))));
- } else {
- mTvLogEvent.append("\n");
- }
- scrollToBottom();
- }
-
- @Override
- public void onErrorEvent(int propId, int areaId) {
- mTvLogEvent.append("Received error event propId=0x" + toHexString(propId)
- + ", areaId=0x" + toHexString(areaId));
- scrollToBottom();
- }
-
- private void scrollToBottom() {
- mScrollView.post(new Runnable() {
- public void run() {
- mScrollView.fullScroll(View.FOCUS_DOWN);
- //mScrollView.smoothScrollTo(0, mTextStatus.getBottom());
- }
- });
- }
-
- }
-}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java
index 2c82c73..b8875b2 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java
@@ -25,15 +25,22 @@
import android.car.Car;
import android.car.VehiclePropertyIds;
import android.car.VehiclePropertyType;
+import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyManager;
+import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
import android.car.hardware.property.CarPropertyManager.GetPropertyCallback;
import android.car.hardware.property.CarPropertyManager.GetPropertyRequest;
import android.car.hardware.property.CarPropertyManager.GetPropertyResult;
+import android.car.hardware.property.Subscription;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.util.SparseLongArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -42,19 +49,19 @@
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
-import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
+import android.widget.ToggleButton;
import androidx.fragment.app.Fragment;
import com.google.android.car.kitchensink.KitchenSinkHelper;
import com.google.android.car.kitchensink.R;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
@@ -67,18 +74,39 @@
Car.PERMISSION_ENERGY,
Car.PERMISSION_SPEED
};
+ private static final Float[] SUBSCRIPTION_RATES_HZ = new Float[]{
+ 0.0f,
+ 1.0f,
+ 2.0f,
+ 5.0f,
+ 10.0f,
+ 100.0f
+ };
+ private static final Float[] RESOLUTIONS = new Float[]{
+ 0.0f,
+ 0.1f,
+ 1.0f,
+ 10.0f
+ };
private Context mContext;
private KitchenSinkHelper mKitchenSinkHelper;
private CarPropertyManager mMgr;
private List<PropertyInfo> mPropInfo = null;
+ private Spinner mSubscriptionRateHz;
+ private Spinner mResolution;
+ private Spinner mVariableUpdateRate;
+ private ToggleButton mSubscribeButton;
private Spinner mAreaId;
private TextView mEventLog;
- private TextView mGetValue;
- private ListView mListView;
private Spinner mPropertyId;
private ScrollView mScrollView;
private EditText mSetValue;
+ private PropertyListEventListener mListener;
+ private final SparseIntArray mPropertySubscriptionRateHzSelection = new SparseIntArray();
+ private final SparseIntArray mPropertyResolutionSelection = new SparseIntArray();
+ private final SparseIntArray mPropertyVariableUpdateRateSelection = new SparseIntArray();
+ private final SparseBooleanArray mPropertyIsSubscribedSelection = new SparseBooleanArray();
private GetPropertyCallback mGetPropertyCallback = new GetPropertyCallback() {
@Override
public void onSuccess(@NonNull GetPropertyResult<?> getPropertyResult) {
@@ -127,8 +155,6 @@
Runnable r = () -> {
mMgr = mKitchenSinkHelper.getPropertyManager();
populateConfigList();
- mListView.setAdapter(new PropertyListAdapter(mPropInfo, mMgr, mEventLog, mScrollView,
- mContext));
// Configure dropdown menu for propertyId spinner
ArrayAdapter<PropertyInfo> adapter =
@@ -148,20 +174,25 @@
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.property, container, false);
// Get resource IDs
+ mSubscriptionRateHz = view.findViewById(R.id.sSubscriptionRate);
+ mResolution = view.findViewById(R.id.sResolution);
+ mVariableUpdateRate = view.findViewById(R.id.sVariableUpdateRate);
mAreaId = view.findViewById(R.id.sAreaId);
mEventLog = view.findViewById(R.id.tvEventLog);
- mGetValue = view.findViewById(R.id.tvGetPropertyValue);
- mListView = view.findViewById(R.id.lvPropertyList);
mPropertyId = view.findViewById(R.id.sPropertyId);
mScrollView = view.findViewById(R.id.svEventLog);
mSetValue = view.findViewById(R.id.etSetPropertyValue);
mContext = getActivity();
+ mListener = new PropertyListEventListener(mEventLog);
if (!(mContext instanceof KitchenSinkHelper)) {
throw new IllegalStateException(
"context does not implement " + KitchenSinkHelper.class.getSimpleName());
}
mKitchenSinkHelper = (KitchenSinkHelper) mContext;
+ mSubscribeButton = view.findViewById(R.id.tbSubscribeButton);
+ mSubscribeButton.setEnabled(false);
+
// Configure listeners for buttons
Button b = view.findViewById(R.id.bGetProperty);
b.setOnClickListener(v -> {
@@ -286,47 +317,208 @@
}
}
+ private void setEnabledSubscriptionScrollViews(boolean setEnabled) {
+ mSubscriptionRateHz.setEnabled(setEnabled);
+ mResolution.setEnabled(setEnabled);
+ mVariableUpdateRate.setEnabled(setEnabled);
+ }
+
// Spinner callbacks
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
PropertyInfo info = (PropertyInfo) parent.getItemAtPosition(pos);
+ int propertyId = info.mPropId;
int[] areaIds = info.mConfig.getAreaIds();
- List<String> areaString = new LinkedList<String>();
+ List<String> areaIdsString = new ArrayList<String>();
if (areaIds.length == 0) {
- areaString.add("0x0");
+ areaIdsString.add("0x0");
} else {
for (int areaId : areaIds) {
- areaString.add("0x" + toHexString(areaId));
+ areaIdsString.add("0x" + toHexString(areaId));
}
}
// Configure dropdown menu for propertyId spinner
- ArrayAdapter<String> adapter = new ArrayAdapter<String>(mContext,
- android.R.layout.simple_spinner_item, areaString);
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- mAreaId.setAdapter(adapter);
+ ArrayAdapter<String> areaIdAdapter = new ArrayAdapter<String>(mContext,
+ android.R.layout.simple_spinner_item, areaIdsString);
+ areaIdAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mAreaId.setAdapter(areaIdAdapter);
+
+ int changeMode = info.mConfig.getChangeMode();
+ List<String> subscriptionRateHzStrings = new ArrayList<String>();
+ subscriptionRateHzStrings.add("0 Hz");
+ List<String> resolutionStrings = new ArrayList<String>();
+ resolutionStrings.add("0");
+ List<String> vurStrings = new ArrayList<String>();
+ vurStrings.add("DISABLED");
+
+ if (changeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC) {
+ setEnabledSubscriptionScrollViews(false);
+ mSubscribeButton.setEnabled(false);
+ } else if (changeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE) {
+ setEnabledSubscriptionScrollViews(false);
+ mSubscribeButton.setEnabled(true);
+ } else if (changeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
+ setEnabledSubscriptionScrollViews(true);
+ mSubscribeButton.setEnabled(true);
+
+ float maxSubRate = info.mConfig.getMaxSampleRate();
+ subscriptionRateHzStrings.add("1 Hz");
+ if (maxSubRate >= 2.0) {
+ subscriptionRateHzStrings.add("2 Hz");
+ }
+ if (maxSubRate >= 5.0) {
+ subscriptionRateHzStrings.add("5 Hz");
+ }
+ if (maxSubRate >= 10.0) {
+ subscriptionRateHzStrings.add("10 Hz");
+ }
+ if (maxSubRate >= 100.0) {
+ subscriptionRateHzStrings.add("100 Hz");
+ }
+
+ resolutionStrings.add("0.1");
+ resolutionStrings.add("1");
+ resolutionStrings.add("10");
+
+ vurStrings.add("ENABLED");
+ }
+
+ if (mPropertySubscriptionRateHzSelection.get(propertyId, -1) == -1) {
+ mPropertySubscriptionRateHzSelection.put(propertyId, 0);
+ mPropertyResolutionSelection.put(propertyId, 0);
+ mPropertyVariableUpdateRateSelection.put(propertyId, 0);
+ mPropertyIsSubscribedSelection.put(propertyId, false);
+ }
+
+ ArrayAdapter<String> subscriptionRateHzAdapter = new ArrayAdapter<String>(mContext,
+ android.R.layout.simple_spinner_item, subscriptionRateHzStrings);
+ subscriptionRateHzAdapter.setDropDownViewResource(
+ android.R.layout.simple_spinner_dropdown_item);
+ mSubscriptionRateHz.setAdapter(subscriptionRateHzAdapter);
+ mSubscriptionRateHz.setSelection(mPropertySubscriptionRateHzSelection.get(propertyId));
+ mSubscriptionRateHz.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long id) {
+ mPropertySubscriptionRateHzSelection.put(info.mConfig.getPropertyId(), pos);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> adapterView) {
+ // do nothing.
+ }
+ });
+
+ ArrayAdapter<String> resolutionAdapter = new ArrayAdapter<String>(mContext,
+ android.R.layout.simple_spinner_item, resolutionStrings);
+ resolutionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mResolution.setAdapter(resolutionAdapter);
+ mResolution.setSelection(mPropertyResolutionSelection.get(propertyId));
+ mResolution.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long id) {
+ mPropertyResolutionSelection.put(info.mConfig.getPropertyId(), pos);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> adapterView) {
+ // do nothing.
+ }
+ });
+
+ ArrayAdapter<String> variableUpdateRateAdapter = new ArrayAdapter<String>(mContext,
+ android.R.layout.simple_spinner_item, vurStrings);
+ variableUpdateRateAdapter.setDropDownViewResource(
+ android.R.layout.simple_spinner_dropdown_item);
+ mVariableUpdateRate.setAdapter(variableUpdateRateAdapter);
+ mVariableUpdateRate.setSelection(mPropertyVariableUpdateRateSelection.get(propertyId));
+ mVariableUpdateRate.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long id) {
+ mPropertyVariableUpdateRateSelection.put(info.mConfig.getPropertyId(), pos);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> adapterView) {
+ // do nothing.
+ }
+ });
+
+ mSubscribeButton.setChecked(mPropertyIsSubscribedSelection.get(propertyId));
+ if (mSubscribeButton.isChecked()) {
+ setEnabledSubscriptionScrollViews(false);
+ }
+ mSubscribeButton.setOnClickListener(v -> {
+ Float subscriptionRateHz = SUBSCRIPTION_RATES_HZ[
+ mPropertySubscriptionRateHzSelection.get(propertyId)];
+ if (mSubscribeButton.isChecked()
+ && (changeMode != CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS
+ || subscriptionRateHz != 0.0)) {
+ mListener.addPropertySelectedSubscriptionRateHz(propertyId, subscriptionRateHz);
+ mListener.updatePropertyStartTime(propertyId);
+ mListener.resetEventCountForProperty(propertyId);
+
+ Float resolution = RESOLUTIONS[mPropertyResolutionSelection.get(propertyId)];
+ boolean variableUpdateRate =
+ mPropertyVariableUpdateRateSelection.get(propertyId) != 0;
+
+ try {
+ mMgr.subscribePropertyEvents(List.of(
+ new Subscription.Builder(propertyId)
+ .setUpdateRateHz(subscriptionRateHz)
+ .setResolution(resolution)
+ .setVariableUpdateRateEnabled(variableUpdateRate)
+ .build()),
+ /* callbackExecutor= */ null, mListener);
+ mPropertyIsSubscribedSelection.put(propertyId, true);
+ setEnabledSubscriptionScrollViews(false);
+ } catch (Exception e) {
+ Log.e(TAG, "Unhandled exception: ", e);
+ }
+ } else {
+ try {
+ mMgr.unsubscribePropertyEvents(propertyId, mListener);
+ mPropertyIsSubscribedSelection.put(propertyId, false);
+ setEnabledSubscriptionScrollViews(true);
+ } catch (Exception e) {
+ Log.e(TAG, "Unhandled exception: ", e);
+ }
+ }
+ });
}
public void onNothingSelected(AdapterView<?> parent) {
// Another interface callback
}
+ public void scrollEventLogsToBottom() {
+ mScrollView.post(new Runnable() {
+ public void run() {
+ mScrollView.fullScroll(View.FOCUS_DOWN);
+ //mListenerScrollView.smoothScrollTo(0, mTextStatus.getBottom());
+ }
+ });
+ }
+
private void setTextOnSuccess(int propId, long timestamp, Object value, int status) {
+ mEventLog.append("getProperty: ");
if (propId == VehiclePropertyIds.WHEEL_TICK) {
Object[] ticks = (Object[]) value;
- mGetValue.setText("Timestamp=" + timestamp
- + "\n[0]=" + (Long) ticks[0]
- + "\n[1]=" + (Long) ticks[1] + " [2]=" + (Long) ticks[2]
- + "\n[3]=" + (Long) ticks[3] + " [4]=" + (Long) ticks[4]);
+ mEventLog.append("ElapsedRealtimeNanos=" + timestamp
+ + " [0]=" + (Long) ticks[0]
+ + " [1]=" + (Long) ticks[1] + " [2]=" + (Long) ticks[2]
+ + " [3]=" + (Long) ticks[3] + " [4]=" + (Long) ticks[4]);
} else {
String valueString = value.getClass().isArray()
? Arrays.toString((Object[]) value)
: value.toString();
- mGetValue.setText("Timestamp=" + timestamp
- + "\nstatus=" + status
- + "\nvalue=" + valueString
- + "\nread=" + mMgr.getReadPermission(propId)
- + "\nwrite=" + mMgr.getWritePermission(propId));
+ mEventLog.append("ElapsedRealtimeNanos=" + timestamp
+ + " status=" + status
+ + " value=" + valueString
+ + " read=" + mMgr.getReadPermission(propId)
+ + " write=" + mMgr.getWritePermission(propId));
}
+ mEventLog.append("\n");
+ scrollEventLogsToBottom();
}
private <T> void callSetPropertiesAsync(int propId, int areaId, T request) {
@@ -335,4 +527,62 @@
/* cancellationSignal= */ null,
/* callbackExecutor= */ null, mSetPropertyCallback);
}
+
+ private class PropertyListEventListener implements CarPropertyEventCallback {
+ private final TextView mTvLogEvent;
+ private final SparseArray<Float> mPropSubscriptionRateHz = new SparseArray<>();
+ private final SparseLongArray mStartTime = new SparseLongArray();
+ private final SparseIntArray mNumEvents = new SparseIntArray();
+
+ PropertyListEventListener(TextView logEvent) {
+ mTvLogEvent = logEvent;
+ }
+
+ void addPropertySelectedSubscriptionRateHz(Integer propId, Float subscriptionRateHz) {
+ mPropSubscriptionRateHz.put(propId, subscriptionRateHz);
+ }
+
+ void updatePropertyStartTime(Integer propId) {
+ mStartTime.put(propId, System.currentTimeMillis());
+ }
+
+ void resetEventCountForProperty(Integer propId) {
+ mNumEvents.put(propId, 0);
+ }
+
+ @Override
+ public void onChangeEvent(CarPropertyValue value) {
+ int propId = value.getPropertyId();
+ int areaId = value.getAreaId();
+
+ mNumEvents.put(propId, mNumEvents.get(propId) + 1);
+
+ String valueString = value.getValue().getClass().isArray()
+ ? Arrays.toString((Object[]) value.getValue())
+ : value.getValue().toString();
+
+ mTvLogEvent.append(String.format("Event %1$s: elapsedRealtimeNanos=%2$s propId=0x%3$s "
+ + "areaId=0x%4$s name=%5$s status=%6$s value=%7$s", mNumEvents.get(propId),
+ value.getTimestamp(), toHexString(propId), toHexString(areaId),
+ VehiclePropertyIds.toString(propId), value.getStatus(), valueString));
+ if (mPropSubscriptionRateHz.contains(propId)) {
+ mTvLogEvent.append(
+ String.format(" selected subscription rate (Hz)=%1$s "
+ + "actual subscription rate (Hz)=%2$s\n",
+ mPropSubscriptionRateHz.get(propId),
+ mNumEvents.get(propId) * 1000.0f / (System.currentTimeMillis()
+ - mStartTime.get(propId))));
+ } else {
+ mTvLogEvent.append("\n");
+ }
+ scrollEventLogsToBottom();
+ }
+
+ @Override
+ public void onErrorEvent(int propId, int areaId) {
+ mTvLogEvent.append("Received error event propId=0x"
+ + VehiclePropertyIds.toString(propId) + ", areaId=0x" + toHexString(areaId));
+ scrollEventLogsToBottom();
+ }
+ }
}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/radio/RadioTunerTabAdapter.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/radio/RadioTunerTabAdapter.java
index 245d4b8..f76f24a 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/radio/RadioTunerTabAdapter.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/radio/RadioTunerTabAdapter.java
@@ -49,6 +49,11 @@
return mFragmentList.size();
}
+ @Override
+ public long getItemId(int position) {
+ return mFragmentTitleList.get(position).hashCode();
+ }
+
void addFragment(Fragment fragment, String title) {
if (findTitleIdx(title) != INDEX_TITLE_NOT_FOUND) {
Log.e(TAG, "Tuner title " + title + " already exists");
@@ -56,6 +61,7 @@
}
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
+ notifyItemInserted(mFragmentList.size() - 1);
}
void removeFragment(String title) {
@@ -66,6 +72,7 @@
}
mFragmentList.remove(titleIdx);
mFragmentTitleList.remove(titleIdx);
+ notifyItemRemoved(titleIdx);
}
CharSequence getPageTitle(int position) {
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/ProfileUserFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/ProfileUserFragment.java
index 8ccb5b6..8552f4d 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/ProfileUserFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/ProfileUserFragment.java
@@ -235,7 +235,7 @@
IActivityManager am = ActivityManager.getService();
Log.i(TAG, "stop user:" + userToUpdate);
try {
- am.stopUser(userToUpdate, /* force= */ false, /* callback= */ null);
+ am.stopUserWithCallback(userToUpdate, /* callback= */ null);
} catch (RemoteException e) {
setMessage(WARN_MESSAGE, "Cannot stop user", e);
return;
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/watchdog/CarWatchdogTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/watchdog/CarWatchdogTestFragment.java
index 8d21d04..0c5416b 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/watchdog/CarWatchdogTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/watchdog/CarWatchdogTestFragment.java
@@ -386,11 +386,15 @@
}
private long writeToFos(FileOutputStream fos, long remainingBytes) {
+ Runtime runtime = Runtime.getRuntime();
long totalBytesWritten = 0;
while (remainingBytes != 0) {
- int writeBytes =
- (int) Math.min(Integer.MAX_VALUE,
- Math.min(Runtime.getRuntime().freeMemory(), remainingBytes));
+ // The total available free memory can be calculated by adding the currently allocated
+ // memory that is free plus the total memory available to the process which hasn't been
+ // allocated yet.
+ long totalFreeMemory = runtime.maxMemory() - runtime.totalMemory()
+ + runtime.freeMemory();
+ int writeBytes = Math.toIntExact(Math.min(totalFreeMemory, remainingBytes));
try {
fos.write(new byte[writeBytes]);
} catch (InterruptedIOException e) {
diff --git a/tests/GarageModeTestApp/Android.bp b/tests/GarageModeTestApp/Android.bp
index 2cfd3ff..a53d758 100644
--- a/tests/GarageModeTestApp/Android.bp
+++ b/tests/GarageModeTestApp/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/car-helper-lib/Android.bp b/tests/LaunchOnPrivateDisplayTestApp/Android.bp
similarity index 65%
copy from car-helper-lib/Android.bp
copy to tests/LaunchOnPrivateDisplayTestApp/Android.bp
index a07a188..e0d946f 100644
--- a/car-helper-lib/Android.bp
+++ b/tests/LaunchOnPrivateDisplayTestApp/Android.bp
@@ -1,4 +1,5 @@
-// Copyright (C) 2022 The Android Open Source Project
+//
+// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,20 +12,25 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-
-// This library has common code which is used by other apps.
//
-// NOTE: This library should not be used within p/s/Car
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_library {
- name: "car-helper-lib",
- srcs: [
- "src/**/*.java",
- ],
+android_app {
+ name: "LaunchOnPrivateDisplayTestApp",
+
+ // Only compile source java files in this apk.
+ srcs: ["src/**/*.java"],
+
resource_dirs: ["res"],
- platform_apis: true,
+
+ sdk_version: "current",
+
+ enforce_uses_libs: false,
+ dex_preopt: {
+ enabled: false,
+ },
}
diff --git a/tests/LaunchOnPrivateDisplayTestApp/AndroidManifest.xml b/tests/LaunchOnPrivateDisplayTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..07c2c88
--- /dev/null
+++ b/tests/LaunchOnPrivateDisplayTestApp/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<!-- Declare the contents of this Android application. The namespace
+ attribute brings in the Android platform namespace, and the package
+ supplies a unique name for the application. When writing your
+ own application, the package name must be changed from "com.example.*"
+ to come from a domain that you own or have control over. -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.launchonprivatedisplay">
+
+ <uses-permission android:name="android.car.permission.ACCESS_PRIVATE_DISPLAY_ID" />
+
+ <application android:label="Launch on Private Display">
+ <activity android:name="MainActivity"
+ android:label="Launch on Private Display Main Activity 1"
+ android:launchMode="singleInstance"
+ android:theme="@android:style/Theme.NoTitleBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <activity android:name="ActivityForPrivateDisplay"
+ android:label="Launch on Private Display Main Activity 2"
+ android:launchMode="singleInstance"
+ android:exported="true">
+ </activity>
+
+ <meta-data
+ android:name="com.android.automotive"
+ android:resource="@xml/automotive_app_desc"/>
+ </application>
+</manifest>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_qc_entry_points_button.xml b/tests/LaunchOnPrivateDisplayTestApp/res/layout/launched_activity.xml
similarity index 60%
rename from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_qc_entry_points_button.xml
rename to tests/LaunchOnPrivateDisplayTestApp/res/layout/launched_activity.xml
index f473d10..e516744 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_qc_entry_points_button.xml
+++ b/tests/LaunchOnPrivateDisplayTestApp/res/layout/launched_activity.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -15,13 +15,18 @@
~ limitations under the License.
-->
-<FrameLayout
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/QuickControlEntryPointButton">
- <ImageView
- android:layout_width="@dimen/top_system_bar_qc_icon_drawing_size"
- android:layout_height="@dimen/top_system_bar_qc_icon_drawing_size"
- android:layout_gravity="center"
- android:tag="@string/qc_icon_tag"/>
-</FrameLayout>
+ android:id="@+id/root_view_2"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/text_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:text="Launched on a private display">
+ </TextView>
+
+</LinearLayout>
diff --git a/tests/LaunchOnPrivateDisplayTestApp/res/layout/main_activity.xml b/tests/LaunchOnPrivateDisplayTestApp/res/layout/main_activity.xml
new file mode 100644
index 0000000..7dad62c
--- /dev/null
+++ b/tests/LaunchOnPrivateDisplayTestApp/res/layout/main_activity.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/root_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <Spinner
+ android:layout_margin="100dp"
+ android:spinnerMode="dropdown"
+ android:id="@+id/spinner_view"
+ android:textSize="200sp"
+ android:layout_width="wrap_content"
+ android:layout_height="200dp"
+ android:dropDownHeight="wrap_content"/>
+
+ <Button
+ android:layout_margin="100dp"
+ android:id="@+id/button_view"
+ android:textSize="40sp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Launch on Selected Display">
+ </Button>
+
+</LinearLayout>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml b/tests/LaunchOnPrivateDisplayTestApp/res/values/config.xml
similarity index 61%
copy from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
copy to tests/LaunchOnPrivateDisplayTestApp/res/values/config.xml
index a6ac85e..9e1c990 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
+++ b/tests/LaunchOnPrivateDisplayTestApp/res/values/config.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
~ limitations under the License.
-->
-<ImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/statusbar_sensor_text_width"
- android:layout_height="@*android:dimen/status_bar_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:tag="@string/qc_icon_tag"/>
\ No newline at end of file
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="distant_display_names">
+ </string-array>
+
+ <string-array name="distant_display_unique_ids">
+ </string-array>
+</resources>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml b/tests/LaunchOnPrivateDisplayTestApp/res/xml/automotive_app_desc.xml
similarity index 61%
copy from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
copy to tests/LaunchOnPrivateDisplayTestApp/res/xml/automotive_app_desc.xml
index a6ac85e..c0204d5 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
+++ b/tests/LaunchOnPrivateDisplayTestApp/res/xml/automotive_app_desc.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" ?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,10 +15,8 @@
~ limitations under the License.
-->
-<ImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/statusbar_sensor_text_width"
- android:layout_height="@*android:dimen/status_bar_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:tag="@string/qc_icon_tag"/>
\ No newline at end of file
+<automotiveApp>
+ <!--Define the app as automotive type video since this app would launch on a private display.-->
+ <uses name="video"/>
+ <uses name="media"/>
+</automotiveApp>
\ No newline at end of file
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml b/tests/LaunchOnPrivateDisplayTestApp/res/xml/overlays.xml
similarity index 61%
copy from car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
copy to tests/LaunchOnPrivateDisplayTestApp/res/xml/overlays.xml
index a6ac85e..a2024f9 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/default_status_icon.xml
+++ b/tests/LaunchOnPrivateDisplayTestApp/res/xml/overlays.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -13,11 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<ImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/statusbar_sensor_text_width"
- android:layout_height="@*android:dimen/status_bar_height"
- android:layout_gravity="center"
- android:gravity="center"
- android:tag="@string/qc_icon_tag"/>
\ No newline at end of file
+<overlay>
+ <item target="array/distant_display_names" value="@array/distant_display_names" />
+ <item target="array/distant_display_unique_ids" value="@array/distant_display_unique_ids" />
+</overlay>
\ No newline at end of file
diff --git a/tests/LaunchOnPrivateDisplayTestApp/src/com/example/android/launchonprivatedisplay/ActivityForPrivateDisplay.java b/tests/LaunchOnPrivateDisplayTestApp/src/com/example/android/launchonprivatedisplay/ActivityForPrivateDisplay.java
new file mode 100644
index 0000000..220cc05
--- /dev/null
+++ b/tests/LaunchOnPrivateDisplayTestApp/src/com/example/android/launchonprivatedisplay/ActivityForPrivateDisplay.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.launchonprivatedisplay;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+
+/**
+ * Activity to be launched on a private display.
+ */
+public final class ActivityForPrivateDisplay extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.launched_activity);
+ }
+}
diff --git a/tests/LaunchOnPrivateDisplayTestApp/src/com/example/android/launchonprivatedisplay/MainActivity.java b/tests/LaunchOnPrivateDisplayTestApp/src/com/example/android/launchonprivatedisplay/MainActivity.java
new file mode 100644
index 0000000..91ac27d
--- /dev/null
+++ b/tests/LaunchOnPrivateDisplayTestApp/src/com/example/android/launchonprivatedisplay/MainActivity.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.launchonprivatedisplay;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+
+
+/**
+ * Activity which would launch an activity on the private display.
+ */
+public final class MainActivity extends Activity {
+
+ private static final String NAMESPACE_KEY = "com.android.car.app.private_display";
+ private static final String LAUNCH_ON_PRIVATE_DISPLAY =
+ NAMESPACE_KEY + ".launch_on_private_display";
+ private static final ComponentName LAUNCHED_ACTIVITY_COMPONENT_NAME = new ComponentName(
+ "com.example.android.launchonprivatedisplay",
+ "com.example.android.launchonprivatedisplay.ActivityForPrivateDisplay");
+ private Button mButton;
+ private Spinner mSpinner;
+ private String[] mDisplayUniqueIds;
+ private String[] mDisplayNames;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // TODO(b/344585656): Add an edit text to specify the activity name
+ // TODO(b/344585656): Improve spinner text size
+ setContentView(R.layout.main_activity);
+
+ mButton = findViewById(R.id.button_view);
+ mSpinner = findViewById(R.id.spinner_view);
+
+ mDisplayUniqueIds = getResources().getStringArray(R.array.distant_display_unique_ids);
+ mDisplayNames = getResources().getStringArray(R.array.distant_display_names);
+
+ createSpinnerView();
+
+ mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ // no-op
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // no-op
+ }
+ });
+
+ mButton.setOnClickListener(v -> {
+ int selectedItemPosition = mSpinner.getSelectedItemPosition();
+ String selectedOption = mDisplayUniqueIds[selectedItemPosition];
+
+ final Intent intent = new Intent();
+ intent.setComponent(LAUNCHED_ACTIVITY_COMPONENT_NAME);
+ Bundle bundle = new Bundle();
+ bundle.putString(LAUNCH_ON_PRIVATE_DISPLAY, selectedOption);
+ ActivityOptions options = ActivityOptions.makeBasic();
+ intent.putExtras(bundle);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent, options.toBundle());
+ });
+ }
+
+ private void createSpinnerView() {
+ ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
+ android.R.layout.simple_spinner_item, mDisplayNames);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mSpinner.setAdapter(adapter);
+ }
+}
diff --git a/tests/MultiDisplaySecondaryHomeTestLauncher/Android.bp b/tests/MultiDisplaySecondaryHomeTestLauncher/Android.bp
index 6d2b96d..ebd445a 100644
--- a/tests/MultiDisplaySecondaryHomeTestLauncher/Android.bp
+++ b/tests/MultiDisplaySecondaryHomeTestLauncher/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/MultiDisplayTest/Android.bp b/tests/MultiDisplayTest/Android.bp
index 996a302..c46fba8 100644
--- a/tests/MultiDisplayTest/Android.bp
+++ b/tests/MultiDisplayTest/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/MultiDisplayTest/AndroidManifest.xml b/tests/MultiDisplayTest/AndroidManifest.xml
index 0179cb6..93572d0 100644
--- a/tests/MultiDisplayTest/AndroidManifest.xml
+++ b/tests/MultiDisplayTest/AndroidManifest.xml
@@ -25,6 +25,7 @@
<!-- Used for OccupantConnectionFragment -->
<uses-permission android:name="android.car.permission.MANAGE_OCCUPANT_CONNECTION"/>
<uses-permission android:name="android.car.permission.MANAGE_REMOTE_DEVICE"/>
+ <uses-permission android:name="android.car.permission.CAR_POWER"/>
<application android:label="MD Test">
<activity android:name="MDTest"
diff --git a/tests/MultiDisplayTestHelloActivity/Android.bp b/tests/MultiDisplayTestHelloActivity/Android.bp
index c6d9a21..d911378 100644
--- a/tests/MultiDisplayTestHelloActivity/Android.bp
+++ b/tests/MultiDisplayTestHelloActivity/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/NetworkPreferenceApp/res/layout/manager.xml b/tests/NetworkPreferenceApp/res/layout/manager.xml
index ed62d0af..b3ea43e 100644
--- a/tests/NetworkPreferenceApp/res/layout/manager.xml
+++ b/tests/NetworkPreferenceApp/res/layout/manager.xml
@@ -103,7 +103,8 @@
android:id="@+id/OEMPaidAppsEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:inputType="textNoSuggestions"/>
+ android:inputType="textNoSuggestions|textMultiLine"
+ android:lines="2"/>
</LinearLayout>
<LinearLayout
style="@style/SectionContainer"
@@ -157,7 +158,8 @@
android:id="@+id/OEMPrivateOnlyAppsEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:inputType="textNoSuggestions"/>
+ android:inputType="textNoSuggestions|textMultiLine"
+ android:lines="2"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
diff --git a/tests/OccupantAwareness/OWNERS b/tests/OccupantAwareness/OWNERS
index b7a974d..a1296a9 100644
--- a/tests/OccupantAwareness/OWNERS
+++ b/tests/OccupantAwareness/OWNERS
@@ -1,3 +1,3 @@
-ycheo@google.com
+xiangw@google.com
oscarazu@google.com
ericjeong@google.com
diff --git a/tests/OemCarServiceTestApp/Android.bp b/tests/OemCarServiceTestApp/Android.bp
index 1d80fb5..4414529 100644
--- a/tests/OemCarServiceTestApp/Android.bp
+++ b/tests/OemCarServiceTestApp/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/SampleCustomInputService/OWNERS b/tests/SampleCustomInputService/OWNERS
index ac68eb0..aa7f074 100644
--- a/tests/SampleCustomInputService/OWNERS
+++ b/tests/SampleCustomInputService/OWNERS
@@ -1,2 +1 @@
-ycheo@google.com
kanant@google.com
diff --git a/tests/UserSwitchMonitorApp/Android.bp b/tests/UserSwitchMonitorApp/Android.bp
index 9ecf4a1..3953ad3 100644
--- a/tests/UserSwitchMonitorApp/Android.bp
+++ b/tests/UserSwitchMonitorApp/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/UxRestrictionsSample/Android.bp b/tests/UxRestrictionsSample/Android.bp
index bc9ba33..6a74edd 100644
--- a/tests/UxRestrictionsSample/Android.bp
+++ b/tests/UxRestrictionsSample/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/VendorServiceReferenceApp/Android.bp b/tests/VendorServiceReferenceApp/Android.bp
index 1bf9ecb..9cf774e 100644
--- a/tests/VendorServiceReferenceApp/Android.bp
+++ b/tests/VendorServiceReferenceApp/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/android_car_api_test/Android.bp b/tests/android_car_api_test/Android.bp
index b62caed..7954bbb 100644
--- a/tests/android_car_api_test/Android.bp
+++ b/tests/android_car_api_test/Android.bp
@@ -56,6 +56,7 @@
"compatibility-device-util-axt",
"platform-test-annotations",
"truth",
+ "Harrier",
],
libs: [
@@ -72,8 +73,7 @@
],
test_suites: [
- "general-tests",
+ "device-tests",
"automotive-tests",
- "automotive-general-tests",
],
}
diff --git a/tests/android_car_api_test/AndroidManifest.xml b/tests/android_car_api_test/AndroidManifest.xml
index 5866a1b..55292c2 100644
--- a/tests/android_car_api_test/AndroidManifest.xml
+++ b/tests/android_car_api_test/AndroidManifest.xml
@@ -95,6 +95,7 @@
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/>
<uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
<uses-permission android:name="android.permission.LOCATION_BYPASS" />
<!-- Use for sensor access in Sensors fragment. -->
@@ -135,6 +136,7 @@
<!-- needed for User fragment to lock user data -->
<uses-permission android:name="android.permission.STORAGE_INTERNAL"/>
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
+ <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<!-- use for vendor properties -->
diff --git a/tests/android_car_api_test/AndroidTest.xml b/tests/android_car_api_test/AndroidTest.xml
index f35ffba..b556d25 100644
--- a/tests/android_car_api_test/AndroidTest.xml
+++ b/tests/android_car_api_test/AndroidTest.xml
@@ -22,6 +22,7 @@
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<!-- Allow running this against a secondary user. -->
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user_on_secondary_display" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/android_car_api_test/OWNERS b/tests/android_car_api_test/OWNERS
index 407e4d9..792fdbf 100644
--- a/tests/android_car_api_test/OWNERS
+++ b/tests/android_car_api_test/OWNERS
@@ -1,19 +1,19 @@
# Bug component: 1316886
# ActivityManager
-per-file src/android/car/apitest/CarActivityManagerTest.java = ycheo@google.com
+per-file src/android/car/apitest/CarActivityManagerTest.java = gauravbhola@google.com
# AppFocus
-per-file src/android/car/apitest/CarAppFocusManagerTest.java = ycheo@google.com
+per-file src/android/car/apitest/CarAppFocusManagerTest.java = bkchoi@google.com
# Audio
per-file src/android/car/apitest/media/CarAudioManagerTest.java = oscarazu@google.com, ericjeong@google.com
# Navigation
-per-file src/android/car/apitest/CarNavigationManagerTest.java = ycheo@google.com
+per-file src/android/car/apitest/CarNavigationManagerTest.java = bkchoi@google.com
# PackageManager
-per-file src/android/car/apitest/CarPackageManagerTest.java = ycheo@google.com
+per-file src/android/car/apitest/CarPackageManagerTest.java = gauravbhola@google.com, gargmayank@google.com
# Property
per-file src/android/car/apitest/CarProperty* = ericjeong@google.com, tylertrephan@google.com, shanyu@google.com
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
index b1f4ed4..633b5c3 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
@@ -79,7 +79,7 @@
*/
private static final int SMALL_NAP_MS = 100;
- protected static final ReceiverTrackingContext sContext = new ReceiverTrackingContext(
+ private final ReceiverTrackingContext mContext = new ReceiverTrackingContext(
InstrumentationRegistry.getInstrumentation().getTargetContext());
private Car mCar;
@@ -123,8 +123,10 @@
@After
public final void checkReceiversUnregisters() {
- Collection<String> receivers = sContext.getReceiversInfo();
Log.d(TAG, "Checking if all receivers were unregistered.");
+ Collection<String> receivers = mContext.getReceiversInfo();
+ // Remove all registered receivers to prevent affecting future test cases.
+ mContext.clearReceivers();
assertWithMessage("Broadcast receivers that are not unregistered: %s", receivers)
.that(receivers).isEmpty();
@@ -135,7 +137,7 @@
}
protected final Context getContext() {
- return sContext;
+ return mContext;
}
@SuppressWarnings("TypeParameterUnusedInFormals") // error prone complains about returning <T>
@@ -173,14 +175,13 @@
}
}
- protected static void suspendToRamAndResume()
- throws Exception {
+ protected void suspendToRamAndResume() throws Exception {
Log.d(TAG, "Emulate suspend to RAM and resume");
try {
Log.d(TAG, "Disabling background users starting on garage mode");
runShellCommand("cmd car_service set-start-bg-users-on-garage-mode false");
- PowerManager powerManager = sContext.getSystemService(PowerManager.class);
+ PowerManager powerManager = mContext.getSystemService(PowerManager.class);
// clear log
runShellCommand("logcat -b all -c");
// We use a simulated suspend because physically suspended devices cannot be woken up by
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarDisplayCompatManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarDisplayCompatManagerTest.java
index 9b73adb..d425106 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarDisplayCompatManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarDisplayCompatManagerTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.car.Car;
import android.car.app.CarDisplayCompatManager;
import android.car.feature.Flags;
@@ -25,6 +27,7 @@
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -33,6 +36,12 @@
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private CarDisplayCompatManager mCarDisplayCompatManager;
+ @Before
+ public void setUp() throws Exception {
+ Car car = getCar();
+ assumeTrue(car.isFeatureEnabled(Car.CAR_DISPLAY_COMPAT_SERVICE));
+ }
+
@Test
@RequiresFlagsEnabled(Flags.FLAG_DISPLAY_COMPATIBILITY)
public void testGetCarDisplayCompatManager() {
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarPackageManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarPackageManagerTest.java
index 3d528f4..0469375 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarPackageManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarPackageManagerTest.java
@@ -73,7 +73,7 @@
@Test
public void testGetTargetCarMajorAndMinorVersion_set() throws Exception {
- String pkg = sContext.getPackageName();
+ String pkg = getContext().getPackageName();
CarVersion apiVersion = mCarPackageManager.getTargetCarVersion(pkg);
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarPropertyManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarPropertyManagerTest.java
index 87b7f73..398740c 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarPropertyManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarPropertyManagerTest.java
@@ -119,6 +119,7 @@
for (int i = 0; i < carPropertyConfigsListBeforeGenerating.size(); i++) {
resultSet.add(carPropertyConfigsListBeforeGenerating.get(i).getPropertyId());
}
+ boolean isDebugCommandSupported = false;
try {
for (int i = STARTING_TEST_CODES; i < END_TEST_CODES; i++) {
assertThat(i).isNotIn(resultSet);
@@ -135,14 +136,15 @@
for (int i = 0; i < carPropertyConfigsList.size(); i++) {
resultSet.add(carPropertyConfigsList.get(i).getPropertyId());
}
+ isDebugCommandSupported = result.equals("successfully generated vendor configs\n");
assumeTrue("successfully generated vendor configs, VHAL gen-test-vendor-configs "
+ "debug command is supported",
- result.equals("successfully generated vendor configs\n"));
+ isDebugCommandSupported);
for (int i = STARTING_TEST_CODES; i < END_TEST_CODES; i++) {
assertThat(i).isIn(resultSet);
}
} finally {
- restoreCarService();
+ restoreCarService(isDebugCommandSupported);
}
}
@@ -150,6 +152,7 @@
+ "(List, long, CancellationSignal, Executor, SetPropertyCallback)"})
@Test
public void testSetPropertiesAsyncWithLargeNumberRequests() throws Exception {
+ boolean isDebugCommandSupported = false;
try {
Log.d(TAG, "Stopping car service for test");
mTestManager.stopCarService(mToken);
@@ -157,9 +160,10 @@
"dumpsys car_service gen-test-vendor-configs");
Log.d(TAG, "Starting car service for test");
mTestManager.startCarService(mToken);
+ isDebugCommandSupported = result.equals("successfully generated vendor configs\n");
assumeTrue("successfully generated vendor configs, VHAL gen-test-vendor-configs "
+ "debug command is supported",
- result.equals("successfully generated vendor configs\n"));
+ isDebugCommandSupported);
Executor callbackExecutor = new HandlerExecutor(mHandler);
Set<Integer> setPropertyIds = new ArraySet<>();
List<CarPropertyManager.SetPropertyRequest<?>> setPropertyRequests = new ArrayList<>();
@@ -184,7 +188,7 @@
assertThat(callback.getErrorList().size()).isEqualTo(0);
} finally {
Log.d(TAG, "restoring car service");
- restoreCarService();
+ restoreCarService(isDebugCommandSupported);
}
}
@@ -192,16 +196,18 @@
+ "(List, long, CancellationSignal, Executor, GetPropertyCallback)"})
@Test
public void testGetPropertiesAsyncWithLargeNumberRequests() throws Exception {
+ boolean isDebugCommandSupported = false;
try {
Log.d(TAG, "Stopping car service for test");
mTestManager.stopCarService(mToken);
String result = CarApiTestBase.executeShellCommand(
"dumpsys car_service gen-test-vendor-configs");
+ isDebugCommandSupported = result.equals("successfully generated vendor configs\n");
Log.d(TAG, "Starting car service for test");
mTestManager.startCarService(mToken);
assumeTrue("successfully generated vendor configs, VHAL gen-test-vendor-configs "
+ "debug command is supported",
- result.equals("successfully generated vendor configs\n"));
+ isDebugCommandSupported);
Executor callbackExecutor = new HandlerExecutor(mHandler);
Set<Integer> getPropertyIds = new ArraySet<>();
@@ -226,17 +232,23 @@
assertThat(callback.getErrorList()).isEmpty();
} finally {
Log.d(TAG, "restoring car service");
- restoreCarService();
+ restoreCarService(isDebugCommandSupported);
}
}
- private void restoreCarService() throws Exception {
- Log.d(TAG, "Stopping car service for test");
- mTestManager.stopCarService(mToken);
- String result = CarApiTestBase.executeShellCommand(
- "dumpsys car_service restore-vendor-configs");
- assertThat(result.equals("successfully restored vendor configs\n")).isTrue();
- Log.d(TAG, "Starting car service for test");
- mTestManager.startCarService(mToken);
+ private void restoreCarService(boolean isDebugCommandSupported) throws Exception {
+ Binder restartCarService = new Binder("restart_car_service_after_test");
+ try {
+ Log.d(TAG, "Stopping car service for test");
+ mTestManager.stopCarService(restartCarService);
+ if (isDebugCommandSupported) {
+ String result = CarApiTestBase.executeShellCommand(
+ "dumpsys car_service restore-vendor-configs");
+ assertThat(result.equals("successfully restored vendor configs\n")).isTrue();
+ }
+ Log.d(TAG, "Starting car service for test");
+ } finally {
+ mTestManager.startCarService(restartCarService);
+ }
}
}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarTest.java b/tests/android_car_api_test/src/android/car/apitest/CarTest.java
index 50c6bfb..b94181c 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarTest.java
@@ -17,6 +17,7 @@
package android.car.apitest;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
@@ -25,11 +26,14 @@
import android.car.ICar;
import android.car.PlatformVersion;
import android.car.hardware.CarSensorManager;
+import android.car.test.CarTestManager;
import android.content.ComponentName;
import android.content.ServiceConnection;
+import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import org.junit.Test;
@@ -117,28 +121,28 @@
public void testApiVersion_deprecated() throws Exception {
int ApiVersionTooHigh = 1000000;
int MinorApiVersionTooHigh = 1000000;
- expectThat(Car.isApiVersionAtLeast(Car.API_VERSION_MAJOR_INT)).isTrue();
+ int apiVersionMajorInt = Car.getCarVersion().getMajorVersion();
+ int apiVersionMinorInt = Car.getCarVersion().getMinorVersion();
+ expectThat(Car.isApiVersionAtLeast(apiVersionMajorInt)).isTrue();
expectThat(Car.isApiVersionAtLeast(ApiVersionTooHigh)).isFalse();
- expectThat(Car.isApiVersionAtLeast(Car.API_VERSION_MAJOR_INT - 1,
- MinorApiVersionTooHigh)).isTrue();
- expectThat(Car.isApiVersionAtLeast(Car.API_VERSION_MAJOR_INT,
- Car.API_VERSION_MINOR_INT)).isTrue();
- expectThat(Car.isApiVersionAtLeast(Car.API_VERSION_MAJOR_INT,
- MinorApiVersionTooHigh)).isFalse();
+ expectThat(Car.isApiVersionAtLeast(apiVersionMajorInt - 1, MinorApiVersionTooHigh))
+ .isTrue();
+ expectThat(Car.isApiVersionAtLeast(apiVersionMajorInt, apiVersionMinorInt)).isTrue();
+ expectThat(Car.isApiVersionAtLeast(apiVersionMajorInt, MinorApiVersionTooHigh)).isFalse();
expectThat(Car.isApiVersionAtLeast(ApiVersionTooHigh, 0)).isFalse();
- expectThat(Car.isApiAndPlatformVersionAtLeast(Car.API_VERSION_MAJOR_INT,
- Build.VERSION.SDK_INT)).isTrue();
- expectThat(Car.isApiAndPlatformVersionAtLeast(Car.API_VERSION_MAJOR_INT,
- Car.API_VERSION_MINOR_INT, Build.VERSION.SDK_INT)).isTrue();
+ expectThat(Car.isApiAndPlatformVersionAtLeast(apiVersionMajorInt, Build.VERSION.SDK_INT))
+ .isTrue();
+ expectThat(Car.isApiAndPlatformVersionAtLeast(apiVersionMajorInt,
+ apiVersionMinorInt, Build.VERSION.SDK_INT)).isTrue();
// SDK + 1 only works for released platform.
if (CODENAME_REL.equals(Build.VERSION.CODENAME)) {
- expectThat(Car.isApiAndPlatformVersionAtLeast(Car.API_VERSION_MAJOR_INT,
+ expectThat(Car.isApiAndPlatformVersionAtLeast(apiVersionMajorInt,
Build.VERSION.SDK_INT + 1)).isFalse();
- expectThat(Car.isApiAndPlatformVersionAtLeast(Car.API_VERSION_MAJOR_INT,
- Car.API_VERSION_MINOR_INT, Build.VERSION.SDK_INT + 1)).isFalse();
+ expectThat(Car.isApiAndPlatformVersionAtLeast(apiVersionMajorInt,
+ apiVersionMinorInt, Build.VERSION.SDK_INT + 1)).isFalse();
}
}
@@ -162,4 +166,21 @@
: Build.VERSION_CODES.CUR_DEVELOPMENT);
assertThat(platformVersion.getMinorVersion()).isAtLeast(0);
}
+
+ // This test need to wait for car service release and initialization.
+ @Test
+ @LargeTest
+ public void testCarServiceReleaseReInit() throws Exception {
+ Car car = Car.createCar(mContext);
+
+ CarTestManager carTestManager = (CarTestManager) (car.getCarManager(Car.TEST_SERVICE));
+
+ assertWithMessage("Could not get service %s", Car.TEST_SERVICE).that(carTestManager)
+ .isNotNull();
+
+ Binder token = new Binder("testCarServiceReleaseReInit");
+ // Releaseing car service and re-initialize must not crash car service.
+ carTestManager.stopCarService(token);
+ carTestManager.startCarService(token);
+ }
}
diff --git a/tests/android_car_api_test/src/android/car/apitest/DeviceConfigTest.java b/tests/android_car_api_test/src/android/car/apitest/DeviceConfigTest.java
new file mode 100644
index 0000000..2bec63d
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/DeviceConfigTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import static android.provider.DeviceConfig.NAMESPACE_GAME_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+
+import android.util.Log;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.RequireRunNotOnVisibleBackgroundNonProfileUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnVisibleBackgroundNonProfileUser;
+import com.android.bedstead.harrier.annotations.RequireVisibleBackgroundUsers;
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+
+
+public final class DeviceConfigTest extends CarApiTestBase {
+
+ private static final String TAG = DeviceConfigTest.class.getSimpleName();
+
+ @Rule
+ @ClassRule
+ public static final DeviceState sDeviceState = new DeviceState();
+
+ public static final String KEY = "car_api_test_prop";
+ public static final String VALUE = "Set_Value_42";
+
+ @Before
+ @After
+ public void cleanUp() {
+ try {
+ DeviceConfig.deleteProperty(NAMESPACE_GAME_OVERLAY, KEY);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to clean up", e);
+ }
+ }
+
+ @Test
+ @ApiTest(apis = {"DeviceConfig.setProperty", "DeviceConfig.getProperty"})
+ @RequireVisibleBackgroundUsers(reason = "Device Config for only MUMD devices")
+ @RequireRunNotOnVisibleBackgroundNonProfileUser
+ public void testUpdateConfigForCurrentUser() throws Exception {
+ assertThat(DeviceConfig.setProperty(NAMESPACE_GAME_OVERLAY, KEY, VALUE,
+ /* makeDefault= */ false)).isTrue();
+ assertThat(DeviceConfig.getProperty(NAMESPACE_GAME_OVERLAY, KEY)).isEqualTo(VALUE);
+ }
+
+ @Test
+ @ApiTest(apis = {"Settings.Config.putString"})
+ @RequireVisibleBackgroundUsers(reason = "Device Config for only MUMD devices")
+ @RequireRunOnVisibleBackgroundNonProfileUser
+ public void testUpdateConfigForBackgroundUser() throws Exception {
+ assertThrows(SecurityException.class,
+ () -> Settings.Config.putString(NAMESPACE_GAME_OVERLAY, KEY, VALUE,
+ /* makeDefault= */ false));
+ }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/LocalePickerTest.java b/tests/android_car_api_test/src/android/car/apitest/LocalePickerTest.java
new file mode 100644
index 0000000..842e780
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/LocalePickerTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.LocaleManager;
+import android.os.LocaleList;
+import android.util.Log;
+
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.RequireRunNotOnVisibleBackgroundNonProfileUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnVisibleBackgroundNonProfileUser;
+import com.android.bedstead.harrier.annotations.RequireVisibleBackgroundUsers;
+import com.android.compatibility.common.util.ApiTest;
+import com.android.internal.app.LocalePicker;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+
+
+import java.util.Locale;
+import java.util.Objects;
+
+public final class LocalePickerTest extends CarApiTestBase {
+
+ private static final String TAG = LocalePickerTest.class.getSimpleName();
+
+ @Rule
+ @ClassRule
+ public static final DeviceState sDeviceState = new DeviceState();
+
+ private final LocaleManager mLocaleManager = getContext().getSystemService(LocaleManager.class);
+ LocaleList mPreviousLocaleList;
+
+ @Before
+ public void setUp() {
+ mPreviousLocaleList = mLocaleManager.getSystemLocales();
+ }
+
+ @After
+ public void cleanUp() {
+ try {
+ LocalePicker.updateLocales(mPreviousLocaleList);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to clean up", e);
+ }
+ }
+
+ @Test
+ @ApiTest(apis = {"android.internal.app.LocalePicker#updateLocale"})
+ @RequireVisibleBackgroundUsers(reason = "Locale test for only MUMD devices")
+ @RequireRunNotOnVisibleBackgroundNonProfileUser
+ public void testUpdateLocaleForCurrentUser() throws Exception {
+ Locale localeToSet = getLocaleToSetInTest();
+
+ LocalePicker.updateLocale(localeToSet);
+
+ assertSystemLocale(localeToSet);
+ }
+
+
+ @Test
+ @ApiTest(apis = {"android.internal.app.LocalePicker#updateLocale"})
+ @RequireVisibleBackgroundUsers(reason = "Locale test for only MUMD devices")
+ @RequireRunOnVisibleBackgroundNonProfileUser
+ public void testUpdateLocaleForVisibleBackgroundUserUser() throws Exception {
+ Locale localeToSet = getLocaleToSetInTest();
+
+ assertThrows(SecurityException.class, () -> LocalePicker.updateLocale(localeToSet));
+ }
+
+ private Locale getLocaleToSetInTest() {
+ if (Objects.equals(mPreviousLocaleList.get(0).getLanguage(), "en")) {
+ // If the current language is english, set the language to Hindi for testing.
+ return new Locale("hi", "IN");
+ } else {
+ // If the current language is not english, set the language to english for testing.
+ return new Locale("en", "US");
+ }
+ }
+
+ private void assertSystemLocale(Locale locale) {
+ Locale systemLocale = mLocaleManager.getSystemLocales().get(0);
+ assertThat(systemLocale.getLanguage()).isEqualTo(locale.getLanguage());
+ assertThat(systemLocale.getCountry()).isEqualTo(locale.getCountry());
+ }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/ReceiverTrackingContext.java b/tests/android_car_api_test/src/android/car/apitest/ReceiverTrackingContext.java
index 5a0e3b4..f05ee64 100644
--- a/tests/android_car_api_test/src/android/car/apitest/ReceiverTrackingContext.java
+++ b/tests/android_car_api_test/src/android/car/apitest/ReceiverTrackingContext.java
@@ -27,6 +27,9 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
import java.util.Collection;
/**
@@ -36,6 +39,8 @@
private static final String TAG = ReceiverTrackingContext.class.getSimpleName();
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
private final ArrayMap<BroadcastReceiver, String> mReceivers = new ArrayMap<>();
ReceiverTrackingContext(Context baseContext) {
@@ -126,7 +131,9 @@
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
Log.d(TAG, "unregisterReceiver(" + receiver + ") called.");
- mReceivers.remove(receiver);
+ synchronized (mLock) {
+ mReceivers.remove(receiver);
+ }
super.unregisterReceiver(receiver);
}
@@ -138,13 +145,26 @@
* receivers have been unregistered.</p>
*/
public Collection<String> getReceiversInfo() {
- return mReceivers.values();
+ synchronized (mLock) {
+ return new ArrayList<String>(mReceivers.values());
+ }
+ }
+
+ /**
+ * Clear all receivers.
+ */
+ public void clearReceivers() {
+ synchronized (mLock) {
+ mReceivers.clear();
+ }
}
private void addReceiver(BroadcastReceiver receiver, String methodPattern, Object... args) {
String info = String.format(methodPattern, args)
+ String.format(" called by %s", new Throwable().getStackTrace()[2]);
Log.d(TAG, info);
- mReceivers.put(receiver, info);
+ synchronized (mLock) {
+ mReceivers.put(receiver, info);
+ }
}
}
diff --git a/tests/android_car_api_test/src/android/car/apitest/VehiclePropertyIdsTest.java b/tests/android_car_api_test/src/android/car/apitest/VehiclePropertyIdsTest.java
index da1960a..74a7b68 100644
--- a/tests/android_car_api_test/src/android/car/apitest/VehiclePropertyIdsTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/VehiclePropertyIdsTest.java
@@ -45,9 +45,10 @@
// IDs that only exist in VHAL, not exposed by CarPropertyManager.
private static final List<String> MISSING_VEHICLE_PROPERTY_IDS = List.of(
- "EXTERNAL_CAR_TIME",
+ "CAMERA_SERVICE_CURRENT_STATE",
"DISABLED_OPTIONAL_FEATURES",
"EVS_SERVICE_REQUEST",
+ "EXTERNAL_CAR_TIME",
"HW_KEY_INPUT_V2",
"HW_MOTION_INPUT",
"HW_CUSTOM_INPUT",
diff --git a/tests/carservice_test/Android.bp b/tests/carservice_test/Android.bp
index 275387d..5c72f49 100644
--- a/tests/carservice_test/Android.bp
+++ b/tests/carservice_test/Android.bp
@@ -48,7 +48,7 @@
"android.car.testapi",
"androidx.test.ext.junit",
"androidx.test.rules",
- "android.frameworks.automotive.powerpolicy-V2-java",
+ "android.frameworks.automotive.powerpolicy-V3-java",
"android.hardware.automotive.vehicle-V2.0-java",
"car-service-test-static-lib",
"car-service-builtin-test-static-lib",
@@ -74,9 +74,8 @@
],
test_suites: [
- "general-tests",
+ "device-tests",
"automotive-tests",
- "automotive-general-tests",
],
// TODO(b/319708040): re-enable use_resource_processor
use_resource_processor: false,
diff --git a/tests/carservice_test/AndroidManifest.xml b/tests/carservice_test/AndroidManifest.xml
index ef29a18..6101a62 100644
--- a/tests/carservice_test/AndroidManifest.xml
+++ b/tests/carservice_test/AndroidManifest.xml
@@ -60,9 +60,6 @@
</intent-filter>
</service>
- <activity
- android:name="com.android.car.pm.CarPackageManagerServiceTest$NonDoNoHistoryActivity"
- android:noHistory="true"/>
<activity android:name="com.android.car.pm.CarPackageManagerServiceTest$NonDoActivity"
android:taskAffinity=""/>
<activity android:name="com.android.car.pm.CarPackageManagerServiceTest$DoActivity"
diff --git a/tests/carservice_test/OWNERS b/tests/carservice_test/OWNERS
index 3a005e7..be1ab55 100644
--- a/tests/carservice_test/OWNERS
+++ b/tests/carservice_test/OWNERS
@@ -1,23 +1,23 @@
# Bug component: 1316886
# AppFocus
-per-file src/com/android/car/AppFocusTest.java = ycheo@google.com
+per-file src/com/android/car/AppFocusTest.java = bkchoi@google.com
# Audio
per-file src/com/android/car/audio/* = oscarazu@google.com, ericjeong@google.com
# Cluster
-per-file src/com/android/car/cluster/* = ycheo@google.com
+per-file src/com/android/car/cluster/* = bkchoi@google.com
# Input
-per-file src/com/android/car/input/* = ycheo@google.com, kanant@google.com
+per-file src/com/android/car/input/* = kanant@google.com
# HAL
per-file src/com/android/car/hal/test/*MockedVehiclHal.java = ericjeong@google.com
# PackageManager
-per-file src/com/android/car/CarPackageManagerTest.java = ycheo@google.com
-per-file src/com/android/car/pm/* = ycheo@google.com
+per-file src/com/android/car/CarPackageManagerTest.java = gauravbhola@google.com, gargmayank@google.com
+per-file src/com/android/car/pm/* = gargmayank@google.com, gauravbhola@google.com
# Power
per-file src/com/android/car/garagemode/* = ericjeong@google.com
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_V3_missing_output_address.xml b/tests/carservice_test/res/raw/car_audio_configuration_V3_missing_output_address.xml
new file mode 100644
index 0000000..d59e4bd
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_V3_missing_output_address.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<carAudioConfiguration version="3">
+ <zones>
+ <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+ <zoneConfigs>
+ <zoneConfig name="primary zone config 1" isDefault="true">
+ <volumeGroups>
+ <group>
+ <device address="bus0_media_out">
+ <context context="music"/>
+ </device>
+ <device address="bus3_call_ring_out">
+ <context context="call_ring"/>
+ </device>
+ </group>
+ <group>
+ <device>
+ <context context="navigation"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ </zoneConfigs>
+ </zone>
+ </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_V4_missing_output_address.xml b/tests/carservice_test/res/raw/car_audio_configuration_V4_missing_output_address.xml
new file mode 100644
index 0000000..1d6b245
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_V4_missing_output_address.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<carAudioConfiguration version="4">
+ <zones>
+ <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+ <zoneConfigs>
+ <zoneConfig name="primary zone config 1" isDefault="true">
+ <volumeGroups>
+ <group name="media">
+ <device address="bus0_media_out">
+ <context context="music"/>
+ </device>
+ <device address="bus3_call_ring_out">
+ <context context="call_ring"/>
+ </device>
+ </group>
+ <group name="navigation">
+ <device>
+ <context context="navigation"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ <zoneConfig name="primary zone config BT media">
+ <volumeGroups>
+ <group name="media">
+ <device type="TYPE_BLUETOOTH_A2DP">
+ <context context="music"/>
+ </device>
+ </group>
+ <group name="call">
+ <device address="bus3_call_ring_out">
+ <context context="call_ring"/>
+ </device>
+ </group>
+ <group name="navigation">
+ <device address="bus1_navigation_out">
+ <context context="navigation"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ <zoneConfig name="primary zone config BT all">
+ <volumeGroups>
+ <group name="all">
+ <device type="TYPE_BLUETOOTH_A2DP">
+ <context context="music"/>
+ <context context="navigation"/>
+ <context context="voice_command"/>
+ <context context="call_ring"/>
+ <context context="call"/>
+ <context context="alarm"/>
+ <context context="system_sound"/>
+ <context context="notification"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ </zoneConfigs>
+ </zone>
+ </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_invalid_version.xml b/tests/carservice_test/res/raw/car_audio_configuration_invalid_version.xml
new file mode 100644
index 0000000..e640a3a
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_invalid_version.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<carAudioConfiguration version="0">
+ <zones>
+ <zone name="primary zone" isPrimary="true">
+ <volumeGroups>
+ <group>
+ <device address="bus0_media_out">
+ <context context="music"/>
+ </device>
+ <device address="bus3_call_ring_out">
+ <context context="call_ring"/>
+ </device>
+ </group>
+ <group>
+ <device address="bus1_navigation_out">
+ <context context="navigation"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zone>
+ </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_context_name.xml b/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_context_name.xml
new file mode 100644
index 0000000..4bf3456
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_using_core_routing_and_volume_invalid_context_name.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" ?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<carAudioConfiguration version="3">
+ <oemContexts>
+ <oemContext name="OEM_CONTEXT">
+ <audioAttributes>
+ <audioAttribute usage="AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE"
+ contentType="AUDIO_CONTENT_TYPE_SPEECH"
+ tags="oem=extension_1979" />
+ </audioAttributes>
+ </oemContext>
+ <oemContext name="MUSIC_CONTEXT">
+ <audioAttributes>
+ <audioAttribute usage="AUDIO_USAGE_MEDIA" contentType="AUDIO_CONTENT_TYPE_MUSIC" />
+ <usage value="AUDIO_USAGE_NOTIFICATION" />
+ <usage value="AUDIO_USAGE_NOTIFICATION_EVENT" />
+ <usage value="AUDIO_USAGE_ASSISTANCE_SONIFICATION" />
+ <usage value="AUDIO_USAGE_ALARM" />
+ <usage value="AUDIO_USAGE_VOICE_COMMUNICATION" />
+ <usage value="AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING" />
+ <usage value="AUDIO_USAGE_CALL_ASSISTANT" />
+ <usage value="AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE"/>
+ <usage value="AUDIO_USAGE_MEDIA" />
+ <usage value="AUDIO_USAGE_GAME" />
+ <usage value="AUDIO_USAGE_UNKNOWN"/>
+ <usage value="AUDIO_USAGE_ASSISTANT"/>
+ <usage value="AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY"/>
+ <usage value="AUDIO_USAGE_EMERGENCY" />
+ <usage value="AUDIO_USAGE_SAFETY" />
+ <usage value="AUDIO_USAGE_VEHICLE_STATUS" />
+ </audioAttributes>
+ </oemContext>
+ <oemContext name="NAV_CONTEXT">
+ <audioAttributes>
+ <audioAttribute usage="AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE"
+ contentType="AUDIO_CONTENT_TYPE_SPEECH" />
+ <usage value="AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE" />
+ </audioAttributes>
+ </oemContext>
+ <oemContext name="INVALID_CONTEXT">
+ <audioAttributes>
+ <usage value="AUDIO_USAGE_ANNOUNCEMENT" />
+ </audioAttributes>
+ </oemContext>
+ </oemContexts>
+ <zones>
+ <zone isPrimary="true" name="primary zone" audioZoneId="0" occupantZoneId="0">
+ <zoneConfigs>
+ <zoneConfig name="primary zone config 1" isDefault="true">
+ <volumeGroups>
+ <group name="MUSIC_GROUP">
+ <device address="bus0_media_out">
+ <context context="MUSIC_CONTEXT"/>
+ </device>
+ </group>
+ <group name="NAV_GROUP">
+ <device address="bus1_navigation_out">
+ <context context="NAV_CONTEXT"/>
+ </device>
+ </group>
+ <group name="OEM_GROUP">
+ <device address="bus3_call_ring_out">
+ <context context="OEM_CONTEXT"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ </zoneConfigs>
+ </zone>
+ </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_with_activation_volume_multiple_entries.xml b/tests/carservice_test/res/raw/car_audio_configuration_with_activation_volume_multiple_entries.xml
new file mode 100644
index 0000000..bebe03a
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_with_activation_volume_multiple_entries.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<carAudioConfiguration version="4">
+ <activationVolumeConfigs>
+ <activationVolumeConfig name="multi_entries_activation_volume_config">
+ <activationVolumeConfigEntry minActivationVolumePercentage="40"
+ maxActivationVolumePercentage="60" invocationType="onBoot" />
+ <activationVolumeConfigEntry minActivationVolumePercentage="40"
+ maxActivationVolumePercentage="80" invocationType="onSourceChanged" />
+ </activationVolumeConfig>
+ </activationVolumeConfigs>
+ <zones>
+ <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+ <zoneConfigs>
+ <zoneConfig name="primary_zone_config_1" isDefault="true">
+ <volumeGroups>
+ <group activationConfig="multi_entries_activation_volume_config">
+ <device address="bus0_media_out">
+ <context context="music"/>
+ </device>
+ <device address="bus3_call_ring_out">
+ <context context="call_ring"/>
+ </device>
+ </group>
+ <group>
+ <device address="bus1_navigation_out">
+ <context context="navigation"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ </zoneConfigs>
+ </zone>
+ </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_with_activation_volume_repeated_config_name.xml b/tests/carservice_test/res/raw/car_audio_configuration_with_activation_volume_repeated_config_name.xml
new file mode 100644
index 0000000..f97a9f6
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_with_activation_volume_repeated_config_name.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<carAudioConfiguration version="4">
+ <activationVolumeConfigs>
+ <activationVolumeConfig name="repeated_activation_volume_config">
+ <activationVolumeConfigEntry minActivationVolumePercentage="40"
+ maxActivationVolumePercentage="60" invocationType="onBoot" />
+ </activationVolumeConfig>
+ <activationVolumeConfig name="repeated_activation_volume_config">
+ <activationVolumeConfigEntry minActivationVolumePercentage="40"
+ maxActivationVolumePercentage="80" />
+ </activationVolumeConfig>
+ </activationVolumeConfigs>
+ <zones>
+ <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+ <zoneConfigs>
+ <zoneConfig name="primary_zone_config_1" isDefault="true">
+ <volumeGroups>
+ <group activationConfig="repeated_activation_volume_config">
+ <device address="bus0_media_out">
+ <context context="music"/>
+ </device>
+ <device address="bus3_call_ring_out">
+ <context context="call_ring"/>
+ </device>
+ </group>
+ <group>
+ <device address="bus1_navigation_out">
+ <context context="navigation"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ </zoneConfigs>
+ </zone>
+ </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_with_invalid_activation_volume_config_name.xml b/tests/carservice_test/res/raw/car_audio_configuration_with_invalid_activation_volume_config_name.xml
new file mode 100644
index 0000000..12ca284
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_with_invalid_activation_volume_config_name.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<carAudioConfiguration version="4">
+ <activationVolumeConfigs>
+ <activationVolumeConfig name="activation_volume_on_boot_config">
+ <activationVolumeConfigEntry minActivationVolumePercentage="40"
+ maxActivationVolumePercentage="60" invocationType="onBoot" />
+ </activationVolumeConfig>
+ </activationVolumeConfigs>
+ <zones>
+ <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+ <zoneConfigs>
+ <zoneConfig name="primary_zone_config_1" isDefault="true">
+ <volumeGroups>
+ <group activationConfig="activation_volume_invalid_config">
+ <device address="bus0_media_out">
+ <context context="music"/>
+ </device>
+ <device address="bus3_call_ring_out">
+ <context context="call_ring"/>
+ </device>
+ </group>
+ <group>
+ <device address="bus1_navigation_out">
+ <context context="navigation"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ </zoneConfigs>
+ </zone>
+ </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_with_invalid_activation_volume_type.xml b/tests/carservice_test/res/raw/car_audio_configuration_with_invalid_activation_volume_type.xml
new file mode 100644
index 0000000..af396fa
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_with_invalid_activation_volume_type.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<carAudioConfiguration version="4">
+ <activationVolumeConfigs>
+ <activationVolumeConfig name="invalid_type_activation_volume_config">
+ <activationVolumeConfigEntry minActivationVolumePercentage="40"
+ maxActivationVolumePercentage="60" invocationType="onPlayback" />
+ </activationVolumeConfig>
+ </activationVolumeConfigs>
+ <zones>
+ <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+ <zoneConfigs>
+ <zoneConfig name="primary_zone_config_1" isDefault="true">
+ <volumeGroups>
+ <group activationConfig="invalid_type_activation_volume_config">
+ <device address="bus0_media_out">
+ <context context="music"/>
+ </device>
+ <device address="bus3_call_ring_out">
+ <context context="call_ring"/>
+ </device>
+ </group>
+ <group>
+ <device address="bus1_navigation_out">
+ <context context="navigation"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ </zoneConfigs>
+ </zone>
+ </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_with_invalid_device_type.xml b/tests/carservice_test/res/raw/car_audio_configuration_with_invalid_device_type.xml
new file mode 100644
index 0000000..09226f0
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_with_invalid_device_type.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<carAudioConfiguration version="4">
+ <zones>
+ <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+ <zoneConfigs>
+ <zoneConfig name="primary zone config 1" isDefault="true">
+ <volumeGroups>
+ <group name="media">
+ <device address="bus0_media_out">
+ <context context="music"/>
+ </device>
+ <device address="bus3_call_ring_out">
+ <context context="call_ring"/>
+ </device>
+ </group>
+ <group name="navigation">
+ <device address="bus1_navigation_out">
+ <context context="navigation"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ <zoneConfig name="primary zone config with invalid device type">
+ <volumeGroups>
+ <group name="all">
+ <device type="TYPE_IP">
+ <context context="music"/>
+ <context context="navigation"/>
+ <context context="voice_command"/>
+ <context context="call_ring"/>
+ <context context="call"/>
+ <context context="alarm"/>
+ <context context="system_sound"/>
+ <context context="notification"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ </zoneConfigs>
+ </zone>
+ </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_with_min_greater_than_max_activation_volume.xml b/tests/carservice_test/res/raw/car_audio_configuration_with_min_greater_than_max_activation_volume.xml
index 0acd41c..e4ee8d8 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_with_min_greater_than_max_activation_volume.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_with_min_greater_than_max_activation_volume.xml
@@ -14,10 +14,16 @@
limitations under the License.
-->
<carAudioConfiguration version="4">
+ <activationVolumeConfigs>
+ <activationVolumeConfig name="invalid_activation_volume_config">
+ <activationVolumeConfigEntry minActivationVolumePercentage="50"
+ maxActivationVolumePercentage="49" />
+ </activationVolumeConfig>
+ </activationVolumeConfigs>
<zones>
<zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
<zoneConfigs>
- <zoneConfig name="primary zone config 1" isDefault="true">
+ <zoneConfig name="primary_zone_config_1" isDefault="true">
<volumeGroups>
<group>
<device address="bus0_media_out">
@@ -27,9 +33,8 @@
<context context="call_ring"/>
</device>
</group>
- <group minActivationVolumePercentage="50"
- maxActivationVolumePercentage="49">
- <device address="bus1_navigation_out">
+ <group activationConfig="invalid_activation_volume_config">
+ <device address="bus1_navigation_out" >
<context context="navigation"/>
<context context="emergency"/>
<context context="safety"/>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_with_min_max_activation_volume.xml b/tests/carservice_test/res/raw/car_audio_configuration_with_min_max_activation_volume.xml
index 3ff935b..7cb0115 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_with_min_max_activation_volume.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_with_min_max_activation_volume.xml
@@ -14,13 +14,18 @@
limitations under the License.
-->
<carAudioConfiguration version="4">
+ <activationVolumeConfigs>
+ <activationVolumeConfig name="activation_volume_on_boot_config">
+ <activationVolumeConfigEntry minActivationVolumePercentage="40"
+ maxActivationVolumePercentage="60" invocationType="onBoot" />
+ </activationVolumeConfig>
+ </activationVolumeConfigs>
<zones>
<zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
<zoneConfigs>
- <zoneConfig name="primary zone config 1" isDefault="true">
+ <zoneConfig name="primary_zone_config_1" isDefault="true">
<volumeGroups>
- <group minActivationVolumePercentage="40"
- maxActivationVolumePercentage="60">
+ <group activationConfig="activation_volume_on_boot_config">
<device address="bus0_media_out">
<context context="music"/>
</device>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_with_min_max_activation_volume_in_v3.xml b/tests/carservice_test/res/raw/car_audio_configuration_with_min_max_activation_volume_in_v3.xml
index 0097476..0793eb3 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_with_min_max_activation_volume_in_v3.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_with_min_max_activation_volume_in_v3.xml
@@ -14,10 +14,16 @@
limitations under the License.
-->
<carAudioConfiguration version="3">
+ <activationVolumeConfigs>
+ <activationVolumeConfig name="activation_volume_on_boot_config">
+ <activationVolumeConfigEntry minActivationVolumePercentage="10"
+ maxActivationVolumePercentage="90" />
+ </activationVolumeConfig>
+ </activationVolumeConfigs>
<zones>
<zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
<zoneConfigs>
- <zoneConfig name="primary zone config 1" isDefault="true">
+ <zoneConfig name="primary_zone_config_1" isDefault="true">
<volumeGroups>
<group>
<device address="bus0_media_out">
@@ -27,8 +33,7 @@
<context context="call_ring"/>
</device>
</group>
- <group minActivationVolumePercentage="10"
- maxActivationVolumePercentage="90">
+ <group activationConfig="activation_volume_on_boot_config">
<device address="bus1_navigation_out">
<context context="navigation"/>
<context context="emergency"/>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_with_min_max_activation_volume_out_of_range.xml b/tests/carservice_test/res/raw/car_audio_configuration_with_min_max_activation_volume_out_of_range.xml
index cd9d8e5..f3c1f8b 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_with_min_max_activation_volume_out_of_range.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_with_min_max_activation_volume_out_of_range.xml
@@ -14,10 +14,16 @@
limitations under the License.
-->
<carAudioConfiguration version="4">
+ <activationVolumeConfigs>
+ <activationVolumeConfig name="invalid_activation_volume_config">
+ <activationVolumeConfigEntry minActivationVolumePercentage="10"
+ maxActivationVolumePercentage="101" />
+ </activationVolumeConfig>
+ </activationVolumeConfigs>
<zones>
<zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
<zoneConfigs>
- <zoneConfig name="primary zone config 1" isDefault="true">
+ <zoneConfig name="primary_zone_config_1" isDefault="true">
<volumeGroups>
<group>
<device address="bus0_media_out">
@@ -27,8 +33,7 @@
<context context="call_ring"/>
</device>
</group>
- <group minActivationVolumePercentage="10"
- maxActivationVolumePercentage="101">
+ <group activationConfig="invalid_activation_volume_config">
<device address="bus1_navigation_out">
<context context="navigation"/>
<context context="emergency"/>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_without_activation_volume_config_name.xml b/tests/carservice_test/res/raw/car_audio_configuration_without_activation_volume_config_name.xml
new file mode 100644
index 0000000..b062056
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_without_activation_volume_config_name.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<carAudioConfiguration version="4">
+ <activationVolumeConfigs>
+ <activationVolumeConfig name="activation_volume_on_boot_config">
+ <activationVolumeConfigEntry minActivationVolumePercentage="40"
+ maxActivationVolumePercentage="60" invocationType="onBoot" />
+ </activationVolumeConfig>
+ <activationVolumeConfig>
+ <activationVolumeConfigEntry minActivationVolumePercentage="40"
+ maxActivationVolumePercentage="80" />
+ </activationVolumeConfig>
+ </activationVolumeConfigs>
+ <zones>
+ <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+ <zoneConfigs>
+ <zoneConfig name="primary_zone_config_1" isDefault="true">
+ <volumeGroups>
+ <group activationConfig="activation_volume_on_boot_config">
+ <device address="bus0_media_out">
+ <context context="music"/>
+ </device>
+ <device address="bus3_call_ring_out">
+ <context context="call_ring"/>
+ </device>
+ </group>
+ <group>
+ <device address="bus1_navigation_out">
+ <context context="navigation"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ </zoneConfigs>
+ </zone>
+ </zones>
+</carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_without_primary_zone.xml b/tests/carservice_test/res/raw/car_audio_configuration_without_primary_zone.xml
new file mode 100644
index 0000000..9055bba
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_without_primary_zone.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<carAudioConfiguration version="4">
+ <zones>
+ <zone name="rear seat zone" audioZoneId="1">
+ <zoneConfigs>
+ <zoneConfig name="rear seat zone config 1" isDefault="true">
+ <volumeGroups>
+ <group>
+ <device address="bus100_rear_seat">
+ <context context="music"/>
+ <context context="navigation"/>
+ <context context="voice_command"/>
+ <context context="call_ring"/>
+ <context context="call"/>
+ <context context="alarm"/>
+ <context context="system_sound"/>
+ <context context="notification"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ </zoneConfigs>
+ </zone>
+ </zones>
+</carAudioConfiguration>
+
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_without_zones.xml b/tests/carservice_test/res/raw/car_audio_configuration_without_zones.xml
new file mode 100644
index 0000000..2ded9c1
--- /dev/null
+++ b/tests/carservice_test/res/raw/car_audio_configuration_without_zones.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<carAudioConfiguration version="4">
+</carAudioConfiguration>
diff --git a/tests/CarLibTests/src/android/car/CarAppFocusManagerTest.java b/tests/carservice_test/src/com/android/car/CarAppFocusManagerTest.java
similarity index 72%
rename from tests/CarLibTests/src/android/car/CarAppFocusManagerTest.java
rename to tests/carservice_test/src/com/android/car/CarAppFocusManagerTest.java
index 1776b45..5abdfab 100644
--- a/tests/CarLibTests/src/android/car/CarAppFocusManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarAppFocusManagerTest.java
@@ -14,47 +14,45 @@
* limitations under the License.
*/
-package android.car;
+package com.android.car;
+
+import static android.car.CarAppFocusManager.OnAppFocusChangedListener;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
-import static org.robolectric.Shadows.shadowOf;
+import static org.mockito.Mockito.when;
-import android.app.Application;
+import android.car.Car;
+import android.car.CarAppFocusManager;
import android.car.CarAppFocusManager.OnAppFocusChangedListener;
import android.car.CarAppFocusManager.OnAppFocusOwnershipCallback;
-import android.car.testapi.CarAppFocusController;
-import android.car.testapi.FakeCar;
-import android.os.Looper;
+import android.content.Context;
-import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
-import org.junit.Before;
-import org.junit.Rule;
+import com.android.car.internal.StaticBinderInterface;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.internal.DoNotInstrument;
-import org.robolectric.shadows.ShadowBinder;
-@RunWith(RobolectricTestRunner.class)
-@DoNotInstrument
-public class CarAppFocusManagerTest {
- @Rule
- public MockitoRule rule = MockitoJUnit.rule();
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class CarAppFocusManagerTest extends MockedCarTestBase {
- private Application mContext;
-
- private FakeCar mFakeCar;
private CarAppFocusManager mCarAppFocusManager;
- private CarAppFocusController mCarAppFocusController;
- private Looper mAppFocusServiceLooper;
+ private AppFocusService mAppFocusService;
+
+ private static final int DEFAULT_FOREGROUND_ID = -1;
+
+ private int mForegroundPid = DEFAULT_FOREGROUND_ID;
+ private int mForegroundUid = DEFAULT_FOREGROUND_ID;
private static final int APP1_UID = 1041;
private static final int APP1_PID = 1043;
@@ -63,27 +61,41 @@
private static final int APP3_UID = 1111;
private static final int APP3_PID = 2222;
- @Mock OnAppFocusOwnershipCallback mApp1Callback;
- @Mock OnAppFocusChangedListener mApp1Listener;
- @Mock OnAppFocusOwnershipCallback mApp2Callback;
- @Mock OnAppFocusChangedListener mApp2Listener;
- @Mock OnAppFocusOwnershipCallback mApp3Callback;
- @Mock OnAppFocusChangedListener mApp3Listener;
+ private static final int DEFAULT_TIMEOUT_MS = 1000;
- @Before
- public void setUp() {
- ShadowBinder.reset();
- mContext = ApplicationProvider.getApplicationContext();
- mFakeCar = FakeCar.createFakeCar(mContext);
- mCarAppFocusManager =
- (CarAppFocusManager) mFakeCar.getCar().getCarManager(Car.APP_FOCUS_SERVICE);
- mCarAppFocusController = mFakeCar.getAppFocusController();
- mAppFocusServiceLooper = mCarAppFocusController.getLooper();
+ @Mock private Context mContext;
+ @Mock private StaticBinderInterface mMockBinder;
+ @Mock private OnAppFocusOwnershipCallback mApp1Callback;
+ @Mock private OnAppFocusChangedListener mApp1Listener;
+ @Mock private OnAppFocusOwnershipCallback mApp2Callback;
+ @Mock private OnAppFocusChangedListener mApp2Listener;
+ @Mock private OnAppFocusOwnershipCallback mApp3Callback;
+ @Mock private OnAppFocusChangedListener mApp3Listener;
+ @Mock private SystemActivityMonitoringService mSystemActivityMonitoringService;
+
+ @Override
+ protected void configureFakeSystemInterface() {
+ mAppFocusService = new AppFocusService(
+ mContext, mSystemActivityMonitoringService, mMockBinder);
+ setAppFocusService(mAppFocusService);
}
- private void flushDispatchHandler() {
- shadowOf(mAppFocusServiceLooper).runToEndOfTasks();
- shadowOf(Looper.getMainLooper()).idle();
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mCarAppFocusManager =
+ (CarAppFocusManager) getCar().getCarManager(Car.APP_FOCUS_SERVICE);
+ when(mSystemActivityMonitoringService.isInForeground(anyInt(), anyInt()))
+ .thenAnswer((inv) -> {
+ int pid = inv.getArgument(0);
+ int uid = inv.getArgument(1);
+ return (mForegroundPid == DEFAULT_FOREGROUND_ID || mForegroundPid == pid)
+ && (mForegroundUid == DEFAULT_FOREGROUND_ID || mForegroundUid == uid);
+ });
+ // Simulate an unprivileged app.
+ when(mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER))
+ .thenReturn(1);
}
@Test
@@ -102,9 +114,8 @@
public void requestNavFocus_noCurrentFocus_callbackIsRun() {
mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
mApp1Callback);
- flushDispatchHandler();
- verify(mApp1Callback)
+ verify(mApp1Callback, timeout(DEFAULT_TIMEOUT_MS))
.onAppFocusOwnershipGranted(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
}
@@ -129,16 +140,16 @@
}
private void setCallingApp(int uid, int pid) {
- ShadowBinder.setCallingUid(uid);
- ShadowBinder.setCallingPid(pid);
+ when(mMockBinder.getCallingUid()).thenReturn(uid);
+ when(mMockBinder.getCallingPid()).thenReturn(pid);
}
private void app2GainsFocus_app1BroughtToForeground() {
setCallingApp(APP2_UID, APP2_PID);
mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
mApp2Callback);
- mCarAppFocusController.setForegroundUid(APP1_UID);
- mCarAppFocusController.setForegroundPid(APP1_PID);
+ mForegroundUid = APP1_UID;
+ mForegroundPid = APP1_PID;
setCallingApp(APP2_UID, APP1_PID);
}
@@ -158,9 +169,8 @@
app2GainsFocus_app1BroughtToForeground();
mCarAppFocusManager
.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp1Callback);
- flushDispatchHandler();
- verify(mApp1Callback)
+ verify(mApp1Callback, timeout(DEFAULT_TIMEOUT_MS))
.onAppFocusOwnershipGranted(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
}
@@ -179,8 +189,8 @@
@Test
public void requestNavFocus_currentOwnerInForeground_requestFails() {
setCallingApp(APP2_UID, APP2_PID);
- mCarAppFocusController.setForegroundUid(APP2_UID);
- mCarAppFocusController.setForegroundPid(APP2_PID);
+ mForegroundUid = APP2_UID;
+ mForegroundPid = APP2_PID;
mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
mApp2Callback);
setCallingApp(APP1_UID, APP1_PID);
@@ -199,9 +209,8 @@
.addFocusListener(mApp1Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
mApp1Callback);
- flushDispatchHandler();
- verify(mApp1Listener)
+ verify(mApp1Listener, timeout(DEFAULT_TIMEOUT_MS))
.onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), anyBoolean());
}
@@ -213,9 +222,8 @@
setCallingApp(APP1_UID, APP1_PID);
mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
mApp1Callback);
- flushDispatchHandler();
- verify(mApp2Listener)
+ verify(mApp2Listener, timeout(DEFAULT_TIMEOUT_MS))
.onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(true));
}
@@ -227,9 +235,8 @@
setCallingApp(APP1_UID, APP1_PID);
mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
mApp1Callback);
- flushDispatchHandler();
- verify(mApp2Callback)
+ verify(mApp2Callback, timeout(DEFAULT_TIMEOUT_MS))
.onAppFocusOwnershipLost(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
}
@@ -242,9 +249,8 @@
mApp1Callback);
mCarAppFocusManager
.abandonAppFocus(mApp1Callback, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
- flushDispatchHandler();
- verify(mApp1Listener)
+ verify(mApp1Listener, timeout(DEFAULT_TIMEOUT_MS))
.onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(false));
}
@@ -258,9 +264,8 @@
mApp1Callback);
mCarAppFocusManager
.abandonAppFocus(mApp1Callback, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
- flushDispatchHandler();
- verify(mApp2Listener)
+ verify(mApp2Listener, timeout(DEFAULT_TIMEOUT_MS))
.onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(false));
}
@@ -277,13 +282,12 @@
.addFocusListener(mApp3Listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
mCarAppFocusManager
.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mApp3Callback);
- flushDispatchHandler();
- verify(mApp1Listener)
+ verify(mApp1Listener, timeout(DEFAULT_TIMEOUT_MS))
.onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(true));
- verify(mApp2Listener)
+ verify(mApp2Listener, timeout(DEFAULT_TIMEOUT_MS))
.onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(true));
- verify(mApp3Listener)
+ verify(mApp3Listener, timeout(DEFAULT_TIMEOUT_MS))
.onAppFocusChanged(eq(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION), eq(true));
}
}
diff --git a/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java b/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java
index 26a9696..6851e27 100644
--- a/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java
+++ b/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java
@@ -26,9 +26,10 @@
import android.car.drivingstate.CarDrivingStateManager;
import android.car.drivingstate.CarUxRestrictions;
import android.car.drivingstate.CarUxRestrictionsManager;
+import android.car.hardware.CarPropertyValue;
import android.hardware.automotive.vehicle.VehicleGear;
+import android.hardware.automotive.vehicle.VehicleIgnitionState;
import android.hardware.automotive.vehicle.VehicleProperty;
-import android.os.Build;
import android.os.SystemClock;
import android.util.Log;
@@ -44,10 +45,10 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class CarDrivingRestrictionsTest extends MockedCarTestBase {
- private static final String TAG = CarDrivingRestrictionsTest.class
- .getSimpleName();
+ private static final String TAG = CarDrivingRestrictionsTest.class.getSimpleName();
private CarDrivingStateManager mCarDrivingStateManager;
private CarUxRestrictionsManager mCarUxRManager;
+ private DrivingStateListener mDrivingStateListener;
// Currently set restrictions currently set in car_ux_restrictions_map.xml
private static final int UX_RESTRICTIONS_MOVING = CarUxRestrictions.UX_RESTRICTIONS_NO_DIALPAD
| CarUxRestrictions.UX_RESTRICTIONS_NO_FILTERING
@@ -73,6 +74,10 @@
.newBuilder(VehicleProperty.GEAR_SELECTION)
.addIntValues(0)
.build());
+ addAidlProperty(VehicleProperty.IGNITION_STATE, AidlVehiclePropValueBuilder
+ .newBuilder(VehicleProperty.IGNITION_STATE)
+ .addIntValues(0)
+ .build());
}
@Override
@@ -82,191 +87,253 @@
.getCarManager(Car.CAR_DRIVING_STATE_SERVICE);
mCarUxRManager = (CarUxRestrictionsManager) getCar()
.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
+ mDrivingStateListener = new DrivingStateListener();
+ mCarDrivingStateManager.registerListener(mDrivingStateListener);
+ mCarUxRManager.registerListener(mDrivingStateListener);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ mCarDrivingStateManager.unregisterListener();
+ mCarUxRManager.unregisterListener();
+ }
+
+ private void aidlInjectIntEvent(int propertyId, int value, int status) {
+ getAidlMockedVehicleHal().injectEvent(
+ AidlVehiclePropValueBuilder.newBuilder(propertyId)
+ .addIntValues(value)
+ .setTimestamp(SystemClock.elapsedRealtimeNanos())
+ .setStatus(status)
+ .build());
+ }
+
+ private void aidlInjectVehicleSpeedEvent(float value, int status) {
+ getAidlMockedVehicleHal().injectEvent(
+ AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.PERF_VEHICLE_SPEED)
+ .addFloatValues(value)
+ .setTimestamp(SystemClock.elapsedRealtimeNanos())
+ .setStatus(status)
+ .build());
+ }
+
+ private void aidlInjectParkingBrakeEvent(boolean value, int status) {
+ getAidlMockedVehicleHal().injectEvent(
+ AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.PARKING_BRAKE_ON)
+ .setBooleanValue(value)
+ .setTimestamp(SystemClock.elapsedRealtimeNanos())
+ .setStatus(status)
+ .build());
}
@Test
public void testDrivingStateChange() throws InterruptedException {
CarDrivingStateEvent drivingEvent;
CarUxRestrictions restrictions;
- DrivingStateListener listener = new DrivingStateListener();
- mCarDrivingStateManager.registerListener(listener);
- mCarUxRManager.registerListener(listener);
// With no gear value available, driving state should be unknown
- listener.reset();
+ mDrivingStateListener.reset();
// Test Parked state and corresponding restrictions based on car_ux_restrictions_map.xml
Log.d(TAG, "Injecting gear park");
- getAidlMockedVehicleHal().injectEvent(
- AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
- .addIntValues(VehicleGear.GEAR_PARK)
- .setTimestamp(SystemClock.elapsedRealtimeNanos())
- .build());
- drivingEvent = listener.waitForDrivingStateChange();
+ aidlInjectIntEvent(VehicleProperty.GEAR_SELECTION, VehicleGear.GEAR_PARK,
+ CarPropertyValue.STATUS_AVAILABLE);
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
assertNotNull(drivingEvent);
assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_PARKED);
Log.d(TAG, "Injecting speed 0");
- getAidlMockedVehicleHal().injectEvent(
- AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.PERF_VEHICLE_SPEED)
- .addFloatValues(0.0f)
- .setTimestamp(SystemClock.elapsedRealtimeNanos())
- .build());
+ aidlInjectVehicleSpeedEvent(0.0f, CarPropertyValue.STATUS_AVAILABLE);
- // Switch gear to drive. Driving state changes to Idling but the UX restrictions don't
+ // Switch gear to drive. Driving state changes to Idling but the UX restrictions don't
// change between parked and idling.
- listener.reset();
+ mDrivingStateListener.reset();
Log.d(TAG, "Injecting gear drive");
- getAidlMockedVehicleHal().injectEvent(
- AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
- .addIntValues(VehicleGear.GEAR_DRIVE)
- .setTimestamp(SystemClock.elapsedRealtimeNanos())
- .build());
- drivingEvent = listener.waitForDrivingStateChange();
+ aidlInjectIntEvent(VehicleProperty.GEAR_SELECTION, VehicleGear.GEAR_DRIVE,
+ CarPropertyValue.STATUS_AVAILABLE);
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
assertNotNull(drivingEvent);
assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_IDLING);
// Test Moving state and corresponding restrictions based on car_ux_restrictions_map.xml
- listener.reset();
+ mDrivingStateListener.reset();
Log.d(TAG, "Injecting speed 30");
- getAidlMockedVehicleHal().injectEvent(
- AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.PERF_VEHICLE_SPEED)
- .addFloatValues(30.0f)
- .setTimestamp(SystemClock.elapsedRealtimeNanos())
- .build());
- drivingEvent = listener.waitForDrivingStateChange();
+ aidlInjectVehicleSpeedEvent(30.0f, CarPropertyValue.STATUS_AVAILABLE);
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
assertNotNull(drivingEvent);
assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_MOVING);
- restrictions = listener.waitForUxRestrictionsChange(UX_RESTRICTIONS_MOVING);
+ restrictions = mDrivingStateListener.waitForUxRestrictionsChange(UX_RESTRICTIONS_MOVING);
assertNotNull(restrictions);
assertTrue(restrictions.isRequiresDistractionOptimization());
assertThat(restrictions.getActiveRestrictions()).isEqualTo(UX_RESTRICTIONS_MOVING);
// Test Idling state and corresponding restrictions based on car_ux_restrictions_map.xml
- listener.reset();
+ mDrivingStateListener.reset();
Log.d(TAG, "Injecting speed 0");
- getAidlMockedVehicleHal().injectEvent(
- AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.PERF_VEHICLE_SPEED)
- .addFloatValues(0.0f)
- .setTimestamp(SystemClock.elapsedRealtimeNanos())
- .build());
- drivingEvent = listener.waitForDrivingStateChange();
+ aidlInjectVehicleSpeedEvent(0.0f, CarPropertyValue.STATUS_AVAILABLE);
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
assertNotNull(drivingEvent);
assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_IDLING);
- restrictions = listener.waitForUxRestrictionsChange(UX_RESTRICTIONS_IDLE);
+ restrictions = mDrivingStateListener.waitForUxRestrictionsChange(UX_RESTRICTIONS_IDLE);
assertNotNull(restrictions);
assertTrue(restrictions.isRequiresDistractionOptimization());
assertThat(restrictions.getActiveRestrictions()).isEqualTo(UX_RESTRICTIONS_IDLE);
// Test Moving state and corresponding restrictions when driving in reverse.
Log.d(TAG, "Injecting gear reverse");
- getAidlMockedVehicleHal().injectEvent(
- AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
- .addIntValues(VehicleGear.GEAR_REVERSE)
- .setTimestamp(SystemClock.elapsedRealtimeNanos())
- .build());
+ aidlInjectIntEvent(VehicleProperty.GEAR_SELECTION, VehicleGear.GEAR_REVERSE,
+ CarPropertyValue.STATUS_AVAILABLE);
- listener.reset();
+ mDrivingStateListener.reset();
Log.d(TAG, "Injecting speed -10");
- getAidlMockedVehicleHal().injectEvent(
- AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.PERF_VEHICLE_SPEED)
- .addFloatValues(-10.0f)
- .setTimestamp(SystemClock.elapsedRealtimeNanos())
- .build());
- drivingEvent = listener.waitForDrivingStateChange();
+ aidlInjectVehicleSpeedEvent(-10.0f, CarPropertyValue.STATUS_AVAILABLE);
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
assertNotNull(drivingEvent);
assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_MOVING);
- restrictions = listener.waitForUxRestrictionsChange(UX_RESTRICTIONS_MOVING);
+ restrictions = mDrivingStateListener.waitForUxRestrictionsChange(UX_RESTRICTIONS_MOVING);
assertNotNull(restrictions);
assertTrue(restrictions.isRequiresDistractionOptimization());
assertThat(restrictions.getActiveRestrictions()).isEqualTo(UX_RESTRICTIONS_MOVING);
- // Apply Parking brake. Supported gears is not provided in this test and hence
+ // Apply Parking brake. Supported gears is not provided in this test and hence
// Automatic transmission should be assumed and hence parking brake state should not
// make a difference to the driving state.
- listener.reset();
+ mDrivingStateListener.reset();
Log.d(TAG, "Injecting parking brake on");
- getAidlMockedVehicleHal().injectEvent(
- AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.PARKING_BRAKE_ON)
- .setBooleanValue(true)
- .setTimestamp(SystemClock.elapsedRealtimeNanos())
- .build());
- drivingEvent = listener.waitForDrivingStateChange();
+ aidlInjectParkingBrakeEvent(true, CarPropertyValue.STATUS_AVAILABLE);
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
assertNull(drivingEvent);
-
- mCarDrivingStateManager.unregisterListener();
- mCarUxRManager.unregisterListener();
}
@Test
public void testDrivingStateChangeForMalformedInputs() throws InterruptedException {
CarDrivingStateEvent drivingEvent;
CarUxRestrictions restrictions;
- DrivingStateListener listener = new DrivingStateListener();
- mCarDrivingStateManager.registerListener(listener);
- mCarUxRManager.registerListener(listener);
// Start with gear = park and speed = 0 to begin with a known state.
- listener.reset();
+ mDrivingStateListener.reset();
Log.d(TAG, "Injecting gear park");
- getAidlMockedVehicleHal().injectEvent(
- AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
- .addIntValues(VehicleGear.GEAR_PARK)
- .setTimestamp(SystemClock.elapsedRealtimeNanos())
- .build());
- drivingEvent = listener.waitForDrivingStateChange();
+ aidlInjectIntEvent(VehicleProperty.GEAR_SELECTION, VehicleGear.GEAR_PARK,
+ CarPropertyValue.STATUS_AVAILABLE);
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
assertNotNull(drivingEvent);
assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_PARKED);
Log.d(TAG, "Injecting speed 0");
- getAidlMockedVehicleHal().injectEvent(
- AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.PERF_VEHICLE_SPEED)
- .addFloatValues(0.0f)
- .setTimestamp(SystemClock.elapsedRealtimeNanos())
- .build());
+ aidlInjectVehicleSpeedEvent(0.0f, CarPropertyValue.STATUS_AVAILABLE);
- // Inject an invalid gear. Since speed is still valid, idling will be the expected
+ // Inject an invalid gear. Since speed is still valid, idling will be the expected
// driving state
- listener.reset();
- Log.d(TAG, "Injecting gear -1");
- getAidlMockedVehicleHal().injectEvent(
- AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
- .addIntValues(-1)
- .setTimestamp(SystemClock.elapsedRealtimeNanos())
- .build());
- drivingEvent = listener.waitForDrivingStateChange();
- if (Build.IS_DEBUGGABLE) {
- // In userdebug build, payloadChecker in HAL drops the invalid event.
- assertNull(drivingEvent);
- } else {
- assertNotNull(drivingEvent);
- assertThat(drivingEvent.eventValue).isEqualTo(
- CarDrivingStateEvent.DRIVING_STATE_IDLING);
- }
+ mDrivingStateListener.reset();
+ Log.d(TAG, "Injecting gear -1 with status unavailable");
+ aidlInjectIntEvent(VehicleProperty.GEAR_SELECTION, -1, CarPropertyValue.STATUS_UNAVAILABLE);
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
+ assertNotNull(drivingEvent);
+ assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_IDLING);
+
// Now, send in an invalid speed value as well, now the driving state will be unknown and
// the UX restrictions will change to fully restricted.
- listener.reset();
- Log.d(TAG, "Injecting speed -1");
- getAidlMockedVehicleHal().injectEvent(
- AidlVehiclePropValueBuilder.newBuilder(VehicleProperty.PERF_VEHICLE_SPEED)
- .addFloatValues(-1.0f)
- .setTimestamp(SystemClock.elapsedRealtimeNanos())
- .build());
- drivingEvent = listener.waitForDrivingStateChange();
- if (Build.IS_DEBUGGABLE) {
- // In userdebug build, payloadChecker in HAL drops the invalid event.
- assertNull(drivingEvent);
- } else {
- assertNotNull(drivingEvent);
- assertThat(drivingEvent.eventValue).isEqualTo(
- CarDrivingStateEvent.DRIVING_STATE_UNKNOWN);
- restrictions = listener.waitForUxRestrictionsChange(
- CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
- assertNotNull(restrictions);
- assertTrue(restrictions.isRequiresDistractionOptimization());
- assertThat(restrictions.getActiveRestrictions())
- .isEqualTo(CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
- }
- mCarDrivingStateManager.unregisterListener();
- mCarUxRManager.unregisterListener();
+ mDrivingStateListener.reset();
+ Log.d(TAG, "Injecting speed -1 with status unavailable");
+ aidlInjectVehicleSpeedEvent(-1.0f, CarPropertyValue.STATUS_UNAVAILABLE);
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
+ assertNotNull(drivingEvent);
+ assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_UNKNOWN);
+ restrictions = mDrivingStateListener.waitForUxRestrictionsChange(
+ CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
+ assertNotNull(restrictions);
+ assertTrue(restrictions.isRequiresDistractionOptimization());
+ assertThat(restrictions.getActiveRestrictions())
+ .isEqualTo(CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
+ }
+
+ private CarDrivingStateEvent setupDrivingStateUnknown() throws InterruptedException {
+ CarDrivingStateEvent drivingEvent;
+ mDrivingStateListener.reset();
+
+ // Inject an invalid gear.
+ Log.d(TAG, "Injecting gear -1 with status unavailable");
+ aidlInjectIntEvent(VehicleProperty.GEAR_SELECTION, -1, CarPropertyValue.STATUS_UNAVAILABLE);
+
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
+ assertNull(drivingEvent);
+
+ // Now, send in an invalid speed value as well, now the driving state will be unknown.
+ mDrivingStateListener.reset();
+ Log.d(TAG, "Injecting speed -1 with status unavailable");
+ aidlInjectVehicleSpeedEvent(-1.0f, CarPropertyValue.STATUS_UNAVAILABLE);
+
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
+ assertNotNull(drivingEvent);
+ assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_UNKNOWN);
+ return drivingEvent;
+ }
+
+ @Test
+ public void testGearUnknown_speedUnknown_ignitionStateOff_drivingStateInferredAsParked()
+ throws InterruptedException {
+ setupDrivingStateUnknown();
+
+ mDrivingStateListener.reset();
+ Log.d(TAG, "Injecting ignition state off");
+ aidlInjectIntEvent(VehicleProperty.IGNITION_STATE, VehicleIgnitionState.OFF,
+ CarPropertyValue.STATUS_AVAILABLE);
+
+ CarDrivingStateEvent drivingEvent;
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
+ assertNotNull(drivingEvent);
+ assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_PARKED);
+ }
+
+ @Test
+ public void testGearUnknown_speedUnknown_ignitionStateAcc_drivingStateInferredAsParked()
+ throws InterruptedException {
+ setupDrivingStateUnknown();
+
+ mDrivingStateListener.reset();
+ Log.d(TAG, "Injecting ignition state acc");
+ aidlInjectIntEvent(VehicleProperty.IGNITION_STATE, VehicleIgnitionState.ACC,
+ CarPropertyValue.STATUS_AVAILABLE);
+
+ CarDrivingStateEvent drivingEvent;
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
+ assertNotNull(drivingEvent);
+ assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_PARKED);
+ }
+
+ @Test
+ public void testGearUnknown_speedUnknown_ignitionStateLock_drivingStateInferredAsParked()
+ throws InterruptedException {
+ setupDrivingStateUnknown();
+
+ mDrivingStateListener.reset();
+ Log.d(TAG, "Injecting ignition state lock");
+ aidlInjectIntEvent(VehicleProperty.IGNITION_STATE, VehicleIgnitionState.LOCK,
+ CarPropertyValue.STATUS_AVAILABLE);
+
+ CarDrivingStateEvent drivingEvent;
+ drivingEvent = mDrivingStateListener.waitForDrivingStateChange();
+ assertNotNull(drivingEvent);
+ assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_PARKED);
+ }
+
+ @Test
+ public void testGearUnknown_speedUnknown_ignitionStateOn_drivingStateInferredAsUnknown()
+ throws InterruptedException {
+ CarDrivingStateEvent drivingEvent;
+ drivingEvent = setupDrivingStateUnknown();
+ CarUxRestrictions restrictions;
+ restrictions = mDrivingStateListener.waitForUxRestrictionsChange(
+ CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
+
+ mDrivingStateListener.reset();
+ Log.d(TAG, "Injecting ignition state on");
+ aidlInjectIntEvent(VehicleProperty.IGNITION_STATE, VehicleIgnitionState.ON,
+ CarPropertyValue.STATUS_AVAILABLE);
+
+ assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_UNKNOWN);
+ assertNotNull(restrictions);
+ assertTrue(restrictions.isRequiresDistractionOptimization());
+ assertThat(restrictions.getActiveRestrictions())
+ .isEqualTo(CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
}
/**
@@ -320,7 +387,7 @@
synchronized (mUxRLock) {
while ((mLastRestrictions == null
|| mLastRestrictions.getActiveRestrictions() != expectedRestrictions)
- && (start + DEFAULT_WAIT_TIMEOUT_MS > SystemClock.elapsedRealtime())) {
+ && (start + DEFAULT_WAIT_TIMEOUT_MS > SystemClock.elapsedRealtime())) {
mUxRLock.wait(100L);
}
return mLastRestrictions;
diff --git a/tests/carservice_test/src/com/android/car/CarPropertyServiceTest.java b/tests/carservice_test/src/com/android/car/CarPropertyServiceTest.java
index fab8aa8..ed396d2 100644
--- a/tests/carservice_test/src/com/android/car/CarPropertyServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/CarPropertyServiceTest.java
@@ -255,20 +255,17 @@
AidlVehiclePropValueBuilder.newBuilder(TEST_SUBSCRIBE_PROP)
.addFloatValues(1.23f)
.setTimestamp(SystemClock.elapsedRealtimeNanos()).build());
+ // The first unsubscribe will throw exception, then the error goes away.
+ doThrow(new ServiceSpecificException(0)).doNothing()
+ .when(mMockPropertyHandler).onPropertyUnsubscribe(anyInt());
mService.registerListener(List.of(options), mockHandler);
verify(mMockPropertyHandler).onPropertySubscribe(eq(TEST_SUBSCRIBE_PROP), any(), eq(10f));
- doThrow(new ServiceSpecificException(0)).when(mMockPropertyHandler).onPropertyUnsubscribe(
- anyInt());
-
assertThrows(ServiceSpecificException.class, () ->
mService.unregisterListener(TEST_SUBSCRIBE_PROP, mockHandler));
- // Simulate the error goes away.
- doNothing().when(mMockPropertyHandler).onPropertyUnsubscribe(anyInt());
-
// Retry.
mService.unregisterListener(TEST_SUBSCRIBE_PROP, mockHandler);
diff --git a/tests/carservice_test/src/com/android/car/ICarImplTest.java b/tests/carservice_test/src/com/android/car/ICarImplTest.java
index 21111a8..436e450 100644
--- a/tests/carservice_test/src/com/android/car/ICarImplTest.java
+++ b/tests/carservice_test/src/com/android/car/ICarImplTest.java
@@ -16,15 +16,13 @@
package com.android.car;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
-
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -32,7 +30,6 @@
import android.car.Car;
import android.car.ICarResultReceiver;
import android.car.feature.Flags;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.content.Context;
import android.content.res.Resources;
import android.frameworks.automotive.powerpolicy.internal.ICarPowerPolicySystemNotification;
@@ -40,6 +37,7 @@
import android.os.HandlerThread;
import android.os.IInterface;
import android.os.Looper;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
@@ -48,7 +46,9 @@
import com.android.car.garagemode.GarageModeService;
import com.android.car.hal.HalPropValueBuilder;
+import com.android.car.hal.PowerHalService;
import com.android.car.internal.ICarServiceHelper;
+import com.android.car.internal.StaticBinderInterface;
import com.android.car.os.CarPerformanceService;
import com.android.car.remoteaccess.CarRemoteAccessService;
import com.android.car.systeminterface.ActivityManagerInterface;
@@ -89,7 +89,7 @@
* </ol>
*/
@RunWith(MockitoJUnitRunner.class)
-public final class ICarImplTest extends AbstractExtendedMockitoTestCase {
+public final class ICarImplTest {
private static final String TAG = ICarImplTest.class.getSimpleName();
@Mock private ActivityManagerInterface mMockActivityManagerInterface;
@@ -121,15 +121,6 @@
}
}
- public ICarImplTest() {
- super(ICarImpl.TAG, CarLog.TAG_SERVICE);
- }
-
- @Override
- protected void onSessionBuilder(CustomMockitoSessionBuilder builder) {
- builder.spyStatic(ICarImpl.class);
- }
-
/**
* Initialize all of the objects with the @Mock annotation.
*/
@@ -216,13 +207,26 @@
.setCarWatchdogService(mMockCarWatchdogService)
.setCarPerformanceService(mMockCarPerformanceService)
.setCarTelemetryService(mMockCarTelemetryService)
- .setCarRemoteAccessService(mMockCarRemoteAccessService)
+ .setCarRemoteAccessServiceConstructor((
+ Context context, SystemInterface systemInterface,
+ PowerHalService powerHalService
+ ) -> mMockCarRemoteAccessService)
.setGarageModeService(mMockGarageModeService)
.setPowerPolicyDaemon(powerPolicyDaemon)
.setDoPriorityInitInConstruction(false)
+ .setTestStaticBinder(new StaticBinderInterface() {
+ @Override
+ public int getCallingUid() {
+ return Process.SYSTEM_UID;
+ }
+
+ @Override
+ public int getCallingPid() {
+ return 0;
+ }
+ })
.build();
- doNothing().when(() -> ICarImpl.assertCallingFromSystemProcess());
carImpl.setSystemServerConnections(mICarServiceHelper, new CarServiceConnectedCallback());
carImpl.init();
Car mCar = new Car(mContext, carImpl, /* handler= */ null);
diff --git a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
index 4a750dc..386018e 100644
--- a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
@@ -17,7 +17,6 @@
import static android.content.pm.PackageManager.PERMISSION_DENIED;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -46,6 +45,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
+import android.os.Process;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -55,15 +55,16 @@
import androidx.test.annotation.UiThreadTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.car.ICarImpl.Builder.CarRemoteAccessServiceConstructor;
import com.android.car.garagemode.GarageModeService;
import com.android.car.hal.test.AidlMockedVehicleHal;
import com.android.car.hal.test.AidlVehiclePropConfigBuilder;
import com.android.car.hal.test.HidlMockedVehicleHal;
import com.android.car.hal.test.HidlVehiclePropConfigBuilder;
import com.android.car.internal.ICarServiceHelper;
+import com.android.car.internal.StaticBinderInterface;
import com.android.car.os.CarPerformanceService;
import com.android.car.power.CarPowerManagementService;
-import com.android.car.remoteaccess.CarRemoteAccessService;
import com.android.car.systeminterface.ActivityManagerInterface;
import com.android.car.systeminterface.DisplayInterface;
import com.android.car.systeminterface.IOInterface;
@@ -118,7 +119,8 @@
private CarTelemetryService mCarTelemetryService;
private CarWatchdogService mCarWatchdogService = mock(CarWatchdogService.class);
private CarPerformanceService mCarPerformanceService;
- private CarRemoteAccessService mCarRemoteAccessService;
+ private CarRemoteAccessServiceConstructor mCarRemoteAccessServiceConstructor;
+ private AppFocusService mAppFocusService;
private final CarUserService mCarUserService = mock(CarUserService.class);
private final MockIOInterface mMockIOInterface = new MockIOInterface();
@@ -217,12 +219,22 @@
}
/**
- * Set the CarRemoteAccessService to be used during the test.
+ * Set the consturctor to create a fake CarRemoteAccessService for the test.
*
* If not called, the real service would be used.
*/
- protected void setCarRemoteAccessService(CarRemoteAccessService service) {
- mCarRemoteAccessService = service;
+ protected void setCarRemoteAccessServiceConstructor(
+ CarRemoteAccessServiceConstructor constructor) {
+ mCarRemoteAccessServiceConstructor = constructor;
+ }
+
+ /**
+ * Set the AppFocusService to be used during the test.
+ *
+ * If not called, the real service would be used.
+ */
+ protected void setAppFocusService(AppFocusService service) {
+ mAppFocusService = service;
}
/**
@@ -293,7 +305,6 @@
protected MockitoSession createMockingSession() {
return mockitoSession()
.initMocks(this)
- .spyStatic(ICarImpl.class)
.strictness(Strictness.LENIENT)
.startMocking();
}
@@ -372,13 +383,24 @@
.setCarWatchdogService(mCarWatchdogService)
.setCarPerformanceService(mCarPerformanceService)
.setCarTelemetryService(mCarTelemetryService)
- .setCarRemoteAccessService(mCarRemoteAccessService)
+ .setCarRemoteAccessServiceConstructor(mCarRemoteAccessServiceConstructor)
+ .setAppFocusService(mAppFocusService)
.setGarageModeService(mGarageModeService)
.setPowerPolicyDaemon(powerPolicyDaemon)
.setDoPriorityInitInConstruction(false)
+ .setTestStaticBinder(new StaticBinderInterface() {
+ @Override
+ public int getCallingUid() {
+ return Process.SYSTEM_UID;
+ }
+
+ @Override
+ public int getCallingPid() {
+ return 0;
+ }
+ })
.build();
- doNothing().when(() -> ICarImpl.assertCallingFromSystemProcess());
carImpl.setSystemServerConnections(mICarServiceHelper,
new ICarImplTest.CarServiceConnectedCallback());
spyOnBeforeCarImplInit(carImpl);
@@ -407,7 +429,7 @@
mCarImpl.release();
mCarImpl = null;
}
- CarServiceUtils.finishAllHandlerTasks();
+ CarServiceUtils.quitHandlerThreads();
mMockIOInterface.tearDown();
mHidlMockedVehicleHal = null;
mAidlMockedVehicleHal = null;
diff --git a/tests/carservice_test/src/com/android/car/audio/AudioFocusInfoBuilder.java b/tests/carservice_test/src/com/android/car/audio/AudioFocusInfoBuilder.java
index 64b0d01..3758fe7 100644
--- a/tests/carservice_test/src/com/android/car/audio/AudioFocusInfoBuilder.java
+++ b/tests/carservice_test/src/com/android/car/audio/AudioFocusInfoBuilder.java
@@ -16,12 +16,17 @@
package com.android.car.audio;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public final class AudioFocusInfoBuilder {
private int mUsage;
private int mClientUid;
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioDynamicRoutingTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioDynamicRoutingTest.java
index 59d4a32..b80fffe 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioDynamicRoutingTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioDynamicRoutingTest.java
@@ -35,7 +35,6 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
-import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioPolicy;
@@ -75,13 +74,13 @@
TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(TEST_NAVIGATION_ATTRIBUTE);
@Mock
- private AudioManager mAudioManager;
+ private AudioManagerWrapper mAudioManager;
@Test
public void setupAudioDynamicRouting() {
AudioPolicy.Builder mockBuilder = Mockito.mock(AudioPolicy.Builder.class);
- SparseArray<CarAudioZone> zones = getTestCarAudioZones(/* withDefault= */ true,
- /* withSelected= */ true);
+ SparseArray<CarAudioZone> zones = getTestCarAudioZones(/* defaultConfigSelected= */ false,
+ /* secondaryConfigSelected= */ true);
CarAudioDynamicRouting.setupAudioDynamicRouting(TEST_CAR_AUDIO_CONTEXT, mAudioManager,
mockBuilder, zones);
@@ -156,10 +155,91 @@
}
@Test
+ public void setupAudioDynamicRouting_withDefaultConfigSelected() {
+ AudioPolicy.Builder mockBuilder = Mockito.mock(AudioPolicy.Builder.class);
+ SparseArray<CarAudioZone> zones = getTestCarAudioZones(/* defaultConfigSelected= */ true,
+ /* secondaryConfigSelected= */ false);
+
+ CarAudioDynamicRouting.setupAudioDynamicRouting(TEST_CAR_AUDIO_CONTEXT, mAudioManager,
+ mockBuilder, zones);
+
+ ArgumentCaptor<AudioMix> audioMixCaptor = ArgumentCaptor.forClass(AudioMix.class);
+ verify(mockBuilder, times(1)).addMix(audioMixCaptor.capture());
+ AudioMix audioMix = audioMixCaptor.getValue();
+ expectWithMessage("Music address registered with default config selected")
+ .that(audioMix.getRegistration()).isEqualTo(MUSIC_ADDRESS);
+ expectWithMessage("Contains Music device with default config selected")
+ .that(audioMix.isRoutedToDevice(AudioSystem.DEVICE_OUT_BUS, MUSIC_ADDRESS))
+ .isTrue();
+ expectWithMessage("Does not contain Nav device with default config selected")
+ .that(audioMix.isRoutedToDevice(AudioSystem.DEVICE_OUT_BUS, NAV_ADDRESS))
+ .isFalse();
+ expectWithMessage("Affected media usage with default config selected")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_MEDIA))
+ .isTrue();
+ expectWithMessage("Affected game usage with default config selected")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_GAME))
+ .isTrue();
+ expectWithMessage("Affected unknown usage with default config selected")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_UNKNOWN))
+ .isTrue();
+ expectWithMessage("Non-affected voice comm usage with default config selected")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION))
+ .isFalse();
+ expectWithMessage(
+ "Non-affected voice comm signalling usage with default config selected")
+ .that(audioMix.isAffectingUsage(
+ AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING))
+ .isFalse();
+ expectWithMessage("Non-affected alarm usage")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ALARM))
+ .isFalse();
+ expectWithMessage("Non-affected notification usage with default config selected")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_NOTIFICATION))
+ .isFalse();
+ expectWithMessage(
+ "Non-affected notification ringtone usage with default config selected")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE))
+ .isFalse();
+ expectWithMessage("Non-affected notification event usage with default config selected")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT))
+ .isFalse();
+ expectWithMessage(
+ "Non-affected assistance accessibility usage with default config selected")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY))
+ .isFalse();
+ expectWithMessage("Non-affected nav guidance usage")
+ .that(audioMix.isAffectingUsage(
+ AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE))
+ .isFalse();
+ expectWithMessage("Non-affected assistance sonification usage")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION))
+ .isFalse();
+ expectWithMessage("Non-affected assistant usage with default config selected")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ASSISTANT))
+ .isFalse();
+ expectWithMessage("Non-affected call assistant usage with default config selected")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_CALL_ASSISTANT))
+ .isFalse();
+ expectWithMessage("Non-affected emergency usage with default config selected")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_EMERGENCY))
+ .isFalse();
+ expectWithMessage("Non-affected safety usage with default config selected")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_SAFETY))
+ .isFalse();
+ expectWithMessage("Non-affected vehicle status usage")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_VEHICLE_STATUS))
+ .isFalse();
+ expectWithMessage("Non-affected announcement usage with default config selected")
+ .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ANNOUNCEMENT))
+ .isFalse();
+ }
+
+ @Test
public void setupAudioDynamicRouting_withoutSelectedConfig() {
AudioPolicy.Builder mockBuilder = Mockito.mock(AudioPolicy.Builder.class);
- SparseArray<CarAudioZone> zones = getTestCarAudioZones(/* withDefault= */ true,
- /* withSelected= */ false);
+ SparseArray<CarAudioZone> zones = getTestCarAudioZones(/* defaultConfigSelected= */ false,
+ /* secondaryConfigSelected= */ false);
IllegalStateException thrown = assertThrows(IllegalStateException.class,
() -> CarAudioDynamicRouting.setupAudioDynamicRouting(TEST_CAR_AUDIO_CONTEXT,
@@ -196,8 +276,8 @@
.isTrue();
}
- private SparseArray<CarAudioZone> getTestCarAudioZones(boolean withDefault,
- boolean withSelected) {
+ private SparseArray<CarAudioZone> getTestCarAudioZones(boolean defaultConfigSelected,
+ boolean secondaryConfigSelected) {
AudioDeviceInfo musicDeviceInfo = Mockito.mock(AudioDeviceInfo.class);
CarAudioDeviceInfo musicCarAudioDeviceInfo = getCarAudioDeviceInfo(MUSIC_ADDRESS,
/* canBeRoutedWithDynamicPolicyMix= */ true, musicDeviceInfo);
@@ -216,21 +296,22 @@
.build();
CarAudioZoneConfig defaultAudioZoneConfig =
new CarAudioZoneConfig.Builder("Default zone config 0",
- CarAudioManager.PRIMARY_AUDIO_ZONE, /* zoneConfigId= */ 0, withDefault)
- .addVolumeGroup(mockMusicGroup)
+ CarAudioManager.PRIMARY_AUDIO_ZONE, /* zoneConfigId= */ 0,
+ /* isDefault= */ true).addVolumeGroup(mockMusicGroup)
.addVolumeGroup(mockNavGroupRoutingOnMusic)
.build();
- CarAudioZoneConfig selectedAudioZoneConfig =
+ defaultAudioZoneConfig.setIsSelected(defaultConfigSelected);
+ CarAudioZoneConfig secondaryAudioZoneConfig =
new CarAudioZoneConfig.Builder("Selected zone config 0",
CarAudioManager.PRIMARY_AUDIO_ZONE, /* zoneConfigId= */ 1,
/* isDefault= */ false).addVolumeGroup(mockMusicGroup)
.addVolumeGroup(mockNavGroupRoutingOnMusic)
.build();
- selectedAudioZoneConfig.setIsSelected(withSelected);
+ secondaryAudioZoneConfig.setIsSelected(secondaryConfigSelected);
CarAudioZone carAudioZone = new CarAudioZone(TEST_CAR_AUDIO_CONTEXT, "Primary zone",
CarAudioManager.PRIMARY_AUDIO_ZONE);
carAudioZone.addZoneConfig(defaultAudioZoneConfig);
- carAudioZone.addZoneConfig(selectedAudioZoneConfig);
+ carAudioZone.addZoneConfig(secondaryAudioZoneConfig);
SparseArray<CarAudioZone> zones = new SparseArray<>();
zones.put(CarAudioManager.PRIMARY_AUDIO_ZONE, carAudioZone);
return zones;
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
index c185a87..fea38cf 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
@@ -29,6 +29,7 @@
import static com.android.car.audio.CarAudioService.CAR_DEFAULT_AUDIO_ATTRIBUTE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -39,12 +40,11 @@
import android.car.Car;
import android.car.feature.Flags;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.AbstractExpectableTestCase;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -53,6 +53,8 @@
import com.android.car.R;
import com.android.car.internal.util.LocalLog;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.google.common.collect.ImmutableList;
@@ -63,17 +65,18 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
+import java.util.MissingResourceException;
import java.util.stream.Collectors;
@RunWith(MockitoJUnitRunner.class)
-public class CarAudioZonesHelperTest extends AbstractExtendedMockitoTestCase {
- private static final String TAG = CarAudioZonesHelperTest.class.getSimpleName();
+public final class CarAudioZonesHelperTest extends AbstractExpectableTestCase {
private static final CarAudioContextInfo OEM_CONTEXT_INFO_MUSIC =
new CarAudioContextInfo(new AudioAttributes[] {
@@ -182,6 +185,13 @@
private static final String SECONDARY_ZONE_BACK_MICROPHONE_ADDRESS = "Built-In Back Mic";
private static final String SECONDARY_ZONE_BUS_1000_INPUT_ADDRESS = "bus_1000_input";
+ private static final int PRIMARY_ZONE_GROUP_0_ACTIVATION_VOLUME_TYPE =
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT;
+ private static final int PRIMARY_ZONE_DEFAULT_ACTIVATION_VOLUME_TYPE =
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT
+ | CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_SOURCE_CHANGED
+ | CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED;
+
private static final int PRIMARY_OCCUPANT_ID = 1;
private static final int SECONDARY_ZONE_ID = 2;
private List<CarAudioDeviceInfo> mCarAudioOutputDeviceInfos;
@@ -190,7 +200,7 @@
private Context mContext;
private CarAudioSettings mCarAudioSettings;
@Mock
- private AudioManager mAudioManager;
+ private AudioManagerWrapper mAudioManagerWrapper;
@Mock
private CarAudioDeviceInfo mTestCarMirrorDeviceOne;
@@ -201,21 +211,19 @@
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ private StaticMockitoSession mSession;
- public CarAudioZonesHelperTest() {
- super(TAG);
- }
-
- @Override
- protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
- session
- .spyStatic(AudioManager.class)
- .spyStatic(Car.class)
- .spyStatic(CoreAudioHelper.class);
- }
@Before
public void setUp() {
+ StaticMockitoSessionBuilder builder = mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(AudioManagerWrapper.class)
+ .spyStatic(Car.class)
+ .spyStatic(CoreAudioHelper.class);
+
+ mSession = builder.initMocks(this).startMocking();
+
setupAudioManagerMock();
mCarAudioOutputDeviceInfos = generateCarDeviceInfos();
@@ -231,6 +239,7 @@
if (mInputStream != null) {
mInputStream.close();
}
+ mSession.finishMocking();
}
private List<CarAudioDeviceInfo> generateCarDeviceInfos() {
@@ -284,7 +293,7 @@
@Test
public void loadAudioZones_parsesAllZones() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -300,9 +309,9 @@
public void loadAudioZones_versionTwoParsesAllZones() throws Exception {
try (InputStream versionTwoStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V2)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionTwoStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionTwoStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -318,9 +327,9 @@
public void loadAudioZones_versionOneParsesAllZones() throws Exception {
try (InputStream versionOneStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V1)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionOneStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionOneStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -333,7 +342,7 @@
@Test
public void loadAudioZones_parsesAudioZoneId() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -352,9 +361,9 @@
public void loadAudioZones_versionTwoParsesAudioZoneId() throws Exception {
try (InputStream versionTwoStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V2)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionTwoStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionTwoStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -370,7 +379,7 @@
@Test
public void loadAudioZones_parsesOccupantZoneId() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -393,9 +402,9 @@
public void loadAudioZones_versionTwoParsesOccupantZoneId() throws Exception {
try (InputStream versionTwoStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V2)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionTwoStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionTwoStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -415,7 +424,7 @@
@Test
public void loadAudioZones_parsesZoneName() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -433,9 +442,9 @@
public void loadAudioZones_versionTwoParsesZoneName() throws Exception {
try (InputStream versionTwoStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V2)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionTwoStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionTwoStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -450,7 +459,7 @@
@Test
public void loadAudioZones_parsesIsPrimary() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -467,9 +476,9 @@
public void loadAudioZones_versionTwoParsesIsPrimary() throws Exception {
try (InputStream versionTwoStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V2)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionTwoStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionTwoStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -485,7 +494,7 @@
@Test
public void loadAudioZones_parsesZoneConfigs() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -511,7 +520,7 @@
@Test
public void loadAudioZones_parsesDefaultZoneConfigs() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -530,7 +539,7 @@
@Test
public void loadAudioZones_parsesVolumeGroups() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -551,9 +560,9 @@
public void loadAudioZones_versionTwoParsesVolumeGroups() throws Exception {
try (InputStream versionTwoStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V2)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionTwoStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionTwoStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -568,7 +577,7 @@
@Test
public void loadAudioZones_parsesAddresses() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -593,9 +602,9 @@
public void loadAudioZones_versionTwoParsesAddresses() throws Exception {
try (InputStream versionTwoStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V2)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionTwoStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionTwoStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -612,7 +621,7 @@
@Test
public void loadAudioZones_parsesContexts() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -637,9 +646,9 @@
public void getCarAudioContext_withOEMContexts() throws Exception {
try (InputStream oemDefinedContextStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_using_oem_defined_context)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- oemDefinedContextStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, oemDefinedContextStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -662,9 +671,9 @@
public void loadAudioZones_versionTwoParsesContexts() throws Exception {
try (InputStream versionTwoStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V2)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionTwoStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionTwoStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -690,7 +699,7 @@
try (InputStream versionOneStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V1)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, versionOneStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -717,7 +726,7 @@
R.raw.car_audio_configuration_V1_with_non_legacy_contexts)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager,
+ new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, v1NonLegacyContextStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -737,7 +746,7 @@
try (InputStream missingAudioZoneIdStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_no_audio_zone_id_for_primary_zone)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager,
+ new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, missingAudioZoneIdStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -757,7 +766,7 @@
public void loadAudioZones_versionOneFailsOnAudioZoneId() throws Exception {
try (InputStream versionOneAudioZoneIdStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V1_with_audio_zone_id)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, versionOneAudioZoneIdStream, mCarAudioOutputDeviceInfos,
mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -775,7 +784,7 @@
try (InputStream versionOneOccupantIdStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V1_with_occupant_zone_id)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager,
+ new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, versionOneOccupantIdStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -791,7 +800,7 @@
@Test
public void loadAudioZones_primaryZoneHasInputDevices() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -809,9 +818,9 @@
public void loadAudioZones_versionTwoPrimaryZoneHasInputDevices() throws Exception {
try (InputStream versionTwoStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V2)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionTwoStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionTwoStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -826,7 +835,7 @@
@Test
public void loadAudioZones_primaryZoneHasMicrophoneDevice() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -845,9 +854,9 @@
public void loadAudioZones_versionTwoPrimaryZoneHasMicrophoneDevice() throws Exception {
try (InputStream versionTwoStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_V2)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionTwoStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionTwoStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -867,7 +876,7 @@
try (InputStream inputDevicesStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_with_input_devices)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager,
+ new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, inputDevicesStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -904,7 +913,7 @@
public void loadAudioZones_failsOnDuplicateOccupantZoneId() throws Exception {
try (InputStream duplicateOccupantZoneIdStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_duplicate_occupant_zone_id)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, duplicateOccupantZoneIdStream, mCarAudioOutputDeviceInfos,
mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -922,7 +931,7 @@
try (InputStream duplicateAudioZoneIdStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_duplicate_audio_zone_id)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager,
+ new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, duplicateAudioZoneIdStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -941,7 +950,7 @@
try (InputStream duplicateZoneConfigNameStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_duplicate_zone_config_name)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
duplicateZoneConfigNameStream, mCarAudioOutputDeviceInfos,
mInputAudioDeviceInfos, mServiceEventLogger,
/* useCarVolumeGroupMute= */ false, /* useCoreAudioVolume= */ false,
@@ -962,7 +971,7 @@
try (InputStream emptyZoneConfigNameStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_empty_zone_config_name)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
emptyZoneConfigNameStream, mCarAudioOutputDeviceInfos,
mInputAudioDeviceInfos, mServiceEventLogger,
/* useCarVolumeGroupMute= */ false, /* useCoreAudioVolume= */ false,
@@ -983,7 +992,7 @@
try (InputStream missingZoneConfigNameStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_missing_zone_config_name)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
missingZoneConfigNameStream, mCarAudioOutputDeviceInfos,
mInputAudioDeviceInfos, mServiceEventLogger,
/* useCarVolumeGroupMute= */ false, /* useCoreAudioVolume= */ false,
@@ -1004,7 +1013,7 @@
try (InputStream missingZoneConfigNameStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_primary_zone_with_multiple_configs)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
missingZoneConfigNameStream, mCarAudioOutputDeviceInfos,
mInputAudioDeviceInfos, mServiceEventLogger,
/* useCarVolumeGroupMute= */ false, /* useCoreAudioVolume= */ false,
@@ -1026,7 +1035,7 @@
try (InputStream inputDevicesStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_empty_input_device)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager,
+ new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, inputDevicesStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -1046,7 +1055,7 @@
try (InputStream nonNumericalStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_non_numerical_audio_zone_id)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager,
+ new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, nonNumericalStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -1065,7 +1074,7 @@
try (InputStream negativeAudioZoneIdStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_negative_audio_zone_id)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
negativeAudioZoneIdStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -1084,7 +1093,7 @@
try (InputStream inputDevicesStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_missing_address)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager,
+ new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, inputDevicesStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -1104,7 +1113,7 @@
try (InputStream nonNumericalStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_non_numerical_occupant_zone_id)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager,
+ new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, nonNumericalStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -1122,7 +1131,7 @@
public void loadAudioZones_failsOnNegativeOccupantZoneId() throws Exception {
try (InputStream negativeOccupantZoneIdStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_negative_occupant_zone_id)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings,
negativeOccupantZoneIdStream, mCarAudioOutputDeviceInfos,
mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -1141,7 +1150,7 @@
try (InputStream inputDevicesStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_non_existent_input_device)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager,
+ new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, inputDevicesStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -1161,7 +1170,7 @@
try (InputStream emptyOccupantZoneIdStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_empty_occupant_zone_id)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager,
+ new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, emptyOccupantZoneIdStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -1180,7 +1189,7 @@
try (InputStream nonZeroForPrimaryStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_primary_zone_with_non_zero_audio_zone_id)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
nonZeroForPrimaryStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -1198,7 +1207,7 @@
public void loadAudioZones_failsOnZeroAudioZoneIdForSecondary() throws Exception {
try (InputStream zeroZoneIdForSecondaryStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_non_primary_zone_with_primary_audio_zone_id)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings,
zeroZoneIdForSecondaryStream, mCarAudioOutputDeviceInfos,
mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -1217,7 +1226,7 @@
try (InputStream inputDevicesStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_repeat_input_device)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager,
+ new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, inputDevicesStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -1237,7 +1246,7 @@
try (InputStream outputDevicesStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_output_address_does_not_exist)) {
CarAudioZonesHelper cazh =
- new CarAudioZonesHelper(mAudioManager,
+ new CarAudioZonesHelper(mAudioManagerWrapper,
mCarAudioSettings, outputDevicesStream,
mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
@@ -1253,12 +1262,12 @@
}
@Test
- public void loadAudioZones_usingCoreAudioVersionThreeParsesAllZones() throws Exception {
+ public void loadAudioZones_usingCoreAudioAndVersionThree_parsesAllZones() throws Exception {
try (InputStream versionOneStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_using_core_routing_and_volume)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionOneStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionOneStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ true, /* useCoreAudioRouting= */ true,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1270,13 +1279,13 @@
}
@Test
- public void loadAudioZones_usingCoreAudioVersionThree_failsOnFirstInvalidAttributes()
+ public void loadAudioZones_usingCoreAudioAndVersionThree_failsOnFirstInvalidAttributes()
throws Exception {
try (InputStream versionOneStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_using_core_routing_and_volume_invalid_strategy)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionOneStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionOneStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ true, /* useCoreAudioRouting= */ true,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1294,13 +1303,13 @@
}
@Test
- public void loadAudioZones_usingCoreAudioVersionThree_failsOnInvalidAttributes()
+ public void loadAudioZones_usingCoreAudioAndVersionThree_failsOnInvalidAttributes()
throws Exception {
try (InputStream versionOneStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_using_core_routing_and_volume_invalid_strategy_2)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionOneStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionOneStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ true, /* useCoreAudioRouting= */ true,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1318,13 +1327,33 @@
}
@Test
+ public void loadAudioZones_usingCoreAudioAndVersionThree_failsOnInvalidContextName()
+ throws Exception {
+ try (InputStream versionOneStream = mContext.getResources().openRawResource(
+ R.raw.car_audio_configuration_using_core_routing_and_volume_invalid_context_name)) {
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionOneStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ /* useCoreAudioVolume= */ true, /* useCoreAudioRouting= */ true,
+ /* useFadeManagerConfiguration= */ false,
+ /* carAudioFadeConfigurationHelper= */ null);
+
+ IllegalArgumentException thrown =
+ assertThrows(IllegalArgumentException.class, () -> cazh.loadAudioZones());
+
+ expectWithMessage("Invalid context name exception").that(thrown)
+ .hasMessageThat().contains("Cannot find strategy id for context");
+ }
+ }
+
+ @Test
public void loadAudioZones_usingCoreAudioVersionThree_failsOnEmptyGroupName()
throws Exception {
try (InputStream versionOneStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_using_core_routing_and_volume_empty_group_name)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionOneStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionOneStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ true, /* useCoreAudioRouting= */ true,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1342,9 +1371,9 @@
throws Exception {
try (InputStream versionOneStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_using_core_routing_and_volume_invalid_order)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionOneStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionOneStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ true, /* useCoreAudioRouting= */ true,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1362,9 +1391,9 @@
throws Exception {
try (InputStream versionOneStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_using_core_routing_attr_valid_optional_fields)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionOneStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionOneStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ true, /* useCoreAudioRouting= */ true,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1380,9 +1409,9 @@
throws Exception {
try (InputStream versionOneStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_using_core_routing_attr_invalid_empty_fields)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionOneStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionOneStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ true, /* useCoreAudioRouting= */ true,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1397,7 +1426,7 @@
@Test
public void getMirrorDeviceInfos() throws Exception {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper, mCarAudioSettings,
mInputStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
@@ -1413,9 +1442,9 @@
public void getMirrorDeviceInfos_withOutMirroringDevices() throws Exception {
try (InputStream versionOneStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_without_mirroring)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionOneStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionOneStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1430,9 +1459,9 @@
public void loadAudioZones_failsOnMirroringDevicesInV2() throws Exception {
try (InputStream versionOneStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_with_mirroring_V2)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionOneStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionOneStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1449,9 +1478,9 @@
public void loadAudioZones_failsOnDuplicateMirroringDevices() throws Exception {
try (InputStream versionOneStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_duplicate_mirror_devices)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionOneStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionOneStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1469,9 +1498,9 @@
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
try (InputStream versionFourStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_with_dynamic_devices_for_primary_zone)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionFourStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1503,9 +1532,9 @@
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
try (InputStream versionFourStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_with_dynamic_devices_for_primary_zone_in_v3)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionFourStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1524,9 +1553,9 @@
mSetFlagsRule.disableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
try (InputStream versionFourStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_with_dynamic_devices_for_primary_zone)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionFourStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1551,9 +1580,9 @@
mSetFlagsRule.disableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
try (InputStream versionFourStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_with_min_max_activation_volume)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionFourStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1586,9 +1615,9 @@
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
try (InputStream versionFourStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_with_min_max_activation_volume)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionFourStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1603,12 +1632,18 @@
expectWithMessage("Primary zone volume group 0 max activation volume")
.that(volumeGroups[0].getMaxActivationGainIndex())
.isLessThan(volumeGroups[0].getMaxGainIndex());
+ expectWithMessage("Primary zone volume group 0 activation volume invocation types")
+ .that(volumeGroups[0].getActivationVolumeInvocationType())
+ .isEqualTo(PRIMARY_ZONE_GROUP_0_ACTIVATION_VOLUME_TYPE);
expectWithMessage("Primary zone volume group 1 min activation volume")
.that(volumeGroups[1].getMinActivationGainIndex())
.isEqualTo(volumeGroups[1].getMinGainIndex());
expectWithMessage("Primary zone volume group 1 max activation volume")
.that(volumeGroups[1].getMaxActivationGainIndex())
.isEqualTo(volumeGroups[1].getMaxGainIndex());
+ expectWithMessage("Primary zone volume group 1 activation volume invocation types")
+ .that(volumeGroups[1].getActivationVolumeInvocationType())
+ .isEqualTo(PRIMARY_ZONE_DEFAULT_ACTIVATION_VOLUME_TYPE);
}
}
@@ -1618,9 +1653,9 @@
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
try (InputStream versionFourStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_with_min_max_activation_volume_in_v3)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionFourStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1638,9 +1673,9 @@
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
try (InputStream versionFourStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_with_min_max_activation_volume_out_of_range)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionFourStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1658,9 +1693,9 @@
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
try (InputStream versionFourStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_with_min_greater_than_max_activation_volume)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionFourStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ false,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1674,14 +1709,120 @@
}
@Test
+ public void loadAudioZones_withInvalidMinMaxActivationVolumeActivationType_fails()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
+ try (InputStream versionFourStream = mContext.getResources().openRawResource(
+ R.raw.car_audio_configuration_with_invalid_activation_volume_type)) {
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ /* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
+ /* useFadeManagerConfiguration= */ false,
+ /* carAudioFadeConfigurationHelper= */ null);
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ cazh::loadAudioZones);
+
+ expectWithMessage("Invalid activation volume invocation type exception")
+ .that(thrown).hasMessageThat()
+ .contains("is invalid for group invocationType");
+ }
+ }
+
+ @Test
+ public void loadAudioZones_withMultipleActivationVolumeConfigEntriesInOneConfig_fails()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
+ try (InputStream versionFourStream = mContext.getResources().openRawResource(
+ R.raw.car_audio_configuration_with_activation_volume_multiple_entries)) {
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ /* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
+ /* useFadeManagerConfiguration= */ false,
+ /* carAudioFadeConfigurationHelper= */ null);
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ cazh::loadAudioZones);
+
+ expectWithMessage("Multiple activation volume entries in one config exception")
+ .that(thrown).hasMessageThat().contains("is not supported");
+ }
+ }
+
+ @Test
+ public void loadAudioZones_withRepeatedActivationVolumeConfigName_fails()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
+ try (InputStream versionFourStream = mContext.getResources().openRawResource(
+ R.raw.car_audio_configuration_with_activation_volume_repeated_config_name)) {
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ /* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
+ /* useFadeManagerConfiguration= */ false,
+ /* carAudioFadeConfigurationHelper= */ null);
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ cazh::loadAudioZones);
+
+ expectWithMessage("Repeated activation volume config name exception")
+ .that(thrown).hasMessageThat().contains("can not repeat");
+ }
+ }
+
+ @Test
+ public void loadAudioZones_withoutActivationVolumeConfigName_fails()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
+ try (InputStream versionFourStream = mContext.getResources().openRawResource(
+ R.raw.car_audio_configuration_without_activation_volume_config_name)) {
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ /* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
+ /* useFadeManagerConfiguration= */ false,
+ /* carAudioFadeConfigurationHelper= */ null);
+
+ NullPointerException thrown = assertThrows(NullPointerException.class,
+ cazh::loadAudioZones);
+
+ expectWithMessage("No activation volume config name exception")
+ .that(thrown).hasMessageThat().contains("must be present");
+ }
+ }
+
+ @Test
+ public void loadAudioZones_withInvalidActivationVolumeConfigName_fails()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
+ try (InputStream versionFourStream = mContext.getResources().openRawResource(
+ R.raw.car_audio_configuration_with_invalid_activation_volume_config_name)) {
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ /* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
+ /* useFadeManagerConfiguration= */ false,
+ /* carAudioFadeConfigurationHelper= */ null);
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ cazh::loadAudioZones);
+
+ expectWithMessage("Invalid activation volume config name exception")
+ .that(thrown).hasMessageThat().contains("does not exist");
+ }
+ }
+
+ @Test
public void loadAudioZones_applyFadeConfigs_forVersionThree_fails() throws Exception {
mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_FADE_MANAGER_CONFIGURATION);
try (InputStream versionFourStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_apply_fade_configs_in_v3)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionFourStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ true,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1701,9 +1842,9 @@
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_FADE_MANAGER_CONFIGURATION);
try (InputStream versionFourStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_apply_fade_configs)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionFourStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ true,
/* carAudioFadeConfigurationHelper= */ null);
@@ -1725,9 +1866,9 @@
R.raw.car_audio_fade_configuration);
try (InputStream versionFourStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_apply_fade_configs_with_empty_address)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionFourStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ true, fadeConfiguration);
@@ -1748,9 +1889,9 @@
R.raw.car_audio_fade_configuration);
try (InputStream versionFourStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_apply_fade_configs_with_neitherDefaultNorTransient)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionFourStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ true, fadeConfiguration);
@@ -1771,9 +1912,9 @@
R.raw.car_audio_fade_configuration);
try (InputStream versionFourStream = mContext.getResources().openRawResource(
R.raw.car_audio_configuration_apply_fade_configs_with_unavailable_config)) {
- CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManager, mCarAudioSettings,
- versionFourStream, mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos,
- mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, versionFourStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
/* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
/* useFadeManagerConfiguration= */ true, fadeConfiguration);
@@ -1785,6 +1926,160 @@
}
}
+ @Test
+ public void loadAudioZones_withUnsupportedVersion_fails() throws Exception {
+ try (InputStream v1NonLegacyContextStream = mContext.getResources().openRawResource(
+ R.raw.car_audio_configuration_invalid_version)) {
+
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, v1NonLegacyContextStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ /* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
+ /* useFadeManagerConfiguration= */ false,
+ /* carAudioFadeConfigurationHelper= */ null);
+
+ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ cazh::loadAudioZones);
+
+ expectWithMessage("Unsupported version exception").that(exception)
+ .hasMessageThat().contains("Latest Supported version");
+ }
+ }
+
+ @Test
+ public void loadAudioZones_withoutZones_fails() throws Exception {
+ try (InputStream v1NonLegacyContextStream = mContext.getResources().openRawResource(
+ R.raw.car_audio_configuration_without_zones)) {
+
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, v1NonLegacyContextStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ /* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
+ /* useFadeManagerConfiguration= */ false,
+ /* carAudioFadeConfigurationHelper= */ null);
+
+ MissingResourceException exception = assertThrows(MissingResourceException.class,
+ cazh::loadAudioZones);
+
+ expectWithMessage("Missing audio zones exception")
+ .that(exception).hasMessageThat().contains("is missing from configuration");
+ }
+ }
+
+ @Test
+ public void loadAudioZones_withoutPrimaryZone_fails() throws Exception {
+ try (InputStream v1NonLegacyContextStream = mContext.getResources().openRawResource(
+ R.raw.car_audio_configuration_without_primary_zone)) {
+
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, v1NonLegacyContextStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ /* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
+ /* useFadeManagerConfiguration= */ false,
+ /* carAudioFadeConfigurationHelper= */ null);
+
+ RuntimeException exception = assertThrows(RuntimeException.class, cazh::loadAudioZones);
+
+ expectWithMessage("Missing primary zone exception")
+ .that(exception).hasMessageThat().contains("Primary audio zone is required");
+ }
+ }
+
+ @Test
+ public void loadAudioZones_withoutOutputDeviceAddressInVersion3_fails() throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
+ try (InputStream v1NonLegacyContextStream = mContext.getResources().openRawResource(
+ R.raw.car_audio_configuration_V3_missing_output_address)) {
+
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, v1NonLegacyContextStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ /* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
+ /* useFadeManagerConfiguration= */ false,
+ /* carAudioFadeConfigurationHelper= */ null);
+
+ IllegalStateException exception = assertThrows(IllegalStateException.class,
+ cazh::loadAudioZones);
+
+ expectWithMessage("Missing output device address exception in version 3")
+ .that(exception).hasMessageThat()
+ .contains("Output device address must be specified");
+ }
+ }
+
+ @Test
+ public void loadAudioZones_withoutOutputDeviceAddressInVersion4AndWithDynamicSupport_fails()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
+ try (InputStream v1NonLegacyContextStream = mContext.getResources().openRawResource(
+ R.raw.car_audio_configuration_V4_missing_output_address)) {
+
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, v1NonLegacyContextStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ /* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
+ /* useFadeManagerConfiguration= */ false,
+ /* carAudioFadeConfigurationHelper= */ null);
+
+ IllegalStateException exception = assertThrows(IllegalStateException.class,
+ cazh::loadAudioZones);
+
+ expectWithMessage("Missing output device address exception in version 4 with dynamic"
+ + " support").that(exception).hasMessageThat()
+ .contains("does not belong to any configured output device.");
+ }
+ }
+
+ @Test
+ public void loadAudioZones_withInvalidInputDeviceTypeAndWithDynamicSupport_throws()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
+ try (InputStream v1NonLegacyContextStream = mContext.getResources().openRawResource(
+ R.raw.car_audio_configuration_with_invalid_device_type)) {
+
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, v1NonLegacyContextStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ /* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
+ /* useFadeManagerConfiguration= */ false,
+ /* carAudioFadeConfigurationHelper= */ null);
+
+ IllegalStateException exception = assertThrows(IllegalStateException.class,
+ cazh::loadAudioZones);
+
+ expectWithMessage("Invalid input device type exception with dynamic support")
+ .that(exception).hasMessageThat().contains("Output device type");
+ }
+ }
+
+ @Test
+ public void loadAudioZones_withInvalidInputDeviceTypeAndWithoutDynamicSupport()
+ throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
+ try (InputStream v1NonLegacyContextStream = mContext.getResources().openRawResource(
+ R.raw.car_audio_configuration_with_invalid_device_type)) {
+
+ CarAudioZonesHelper cazh = new CarAudioZonesHelper(mAudioManagerWrapper,
+ mCarAudioSettings, v1NonLegacyContextStream, mCarAudioOutputDeviceInfos,
+ mInputAudioDeviceInfos, mServiceEventLogger, /* useCarVolumeGroupMute= */ false,
+ /* useCoreAudioVolume= */ false, /* useCoreAudioRouting= */ false,
+ /* useFadeManagerConfiguration= */ false,
+ /* carAudioFadeConfigurationHelper= */ null);
+
+ SparseArray<CarAudioZone> zones = cazh.loadAudioZones();
+
+ expectWithMessage("Primary zone with invalid input device type"
+ + " and dynamic flag disabled").that(zones.size()).isEqualTo(1);
+ CarAudioZone zone = zones.get(0);
+ List<CarAudioZoneConfig> configs = zone.getAllCarAudioZoneConfigs();
+ expectWithMessage("Configurations for primary zone with invalid input device type"
+ + " and dynamic flag disabled").that(configs).hasSize(1);
+ CarAudioZoneConfig defaultConfig = configs.get(0);
+ expectWithMessage("Default configuration for zone with invalid input device type "
+ + " and dynamic devices disabled").that(defaultConfig.isDefault()).isTrue();
+ }
+ }
+
private CarAudioFadeConfigurationHelper getCarAudioFadeConfigurationHelper(int resource) {
try (InputStream inputStream = mContext.getResources().openRawResource(resource)) {
return new CarAudioFadeConfigurationHelper(inputStream);
@@ -1795,9 +2090,9 @@
private void setupAudioManagerMock() {
doReturn(CoreAudioRoutingUtils.getProductStrategies())
- .when(() -> AudioManager.getAudioProductStrategies());
+ .when(AudioManagerWrapper::getAudioProductStrategies);
doReturn(CoreAudioRoutingUtils.getVolumeGroups())
- .when(() -> AudioManager.getAudioVolumeGroups());
+ .when(AudioManagerWrapper::getAudioVolumeGroups);
doReturn(CoreAudioRoutingUtils.MUSIC_GROUP_ID)
.when(() -> CoreAudioHelper.getVolumeGroupIdForAudioAttributes(
diff --git a/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTestBase.java b/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTestBase.java
index 15a008a..11fa6cc 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTestBase.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTestBase.java
@@ -34,6 +34,8 @@
import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -47,11 +49,11 @@
import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.media.AudioFocusInfo;
-import android.media.AudioManager;
import android.media.audiopolicy.AudioPolicy;
import android.util.SparseArray;
import com.android.car.CarLocalServices;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.oem.CarOemAudioFocusProxyService;
import com.android.car.oem.CarOemProxyService;
@@ -171,7 +173,7 @@
USAGE_ANNOUNCEMENT, ANNOUNCEMENT_CLIENT_UID_2, ANNOUNCEMENT_CLIENT_ID_2);
@Mock
- protected AudioManager mMockAudioManager;
+ protected AudioManagerWrapper mMockAudioManager;
@Mock
protected AudioPolicy mAudioPolicy;
@Mock
@@ -292,6 +294,7 @@
.setAudioFocusEntry(entry).build();
}
+ @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public static final class AudioClientInfo {
private final int mUsage;
private final int mClientUid;
diff --git a/tests/carservice_test/src/com/android/car/audio/CoreAudioRoutingUtils.java b/tests/carservice_test/src/com/android/car/audio/CoreAudioRoutingUtils.java
index 32713ea..14ad0d3 100644
--- a/tests/carservice_test/src/com/android/car/audio/CoreAudioRoutingUtils.java
+++ b/tests/carservice_test/src/com/android/car/audio/CoreAudioRoutingUtils.java
@@ -16,6 +16,8 @@
package com.android.car.audio;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
import static com.google.common.collect.Sets.newHashSet;
import android.media.AudioAttributes;
@@ -24,9 +26,12 @@
import android.media.audiopolicy.AudioVolumeGroup;
import android.os.Parcel;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
import java.util.ArrayList;
import java.util.List;
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
final class CoreAudioRoutingUtils {
static final List<AudioVolumeGroup> VOLUME_GROUPS;
diff --git a/tests/carservice_test/src/com/android/car/pm/CarPackageManagerServiceTest.java b/tests/carservice_test/src/com/android/car/pm/CarPackageManagerServiceTest.java
index 8694216..2353de8 100644
--- a/tests/carservice_test/src/com/android/car/pm/CarPackageManagerServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/pm/CarPackageManagerServiceTest.java
@@ -46,6 +46,7 @@
import androidx.car.app.activity.CarAppActivity;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -102,6 +103,7 @@
for (TempActivity testingActivity : sTestingActivities) {
testingActivity.finishCompletely();
+ sTestingActivities.remove(testingActivity);
}
}
@@ -208,6 +210,7 @@
UI_TIMEOUT_MS)).isNotNull();
}
+ @FlakyTest(bugId = 338646912)
@Test
public void testBlockingActivity_DoLaunchesNonDo_nonDoIsKilled_noBlockingActivity()
throws Exception {
@@ -226,6 +229,23 @@
assertActivityLaunched(DoActivity.class.getSimpleName());
}
+ @FlakyTest(bugId = 338646912)
+ @Test
+ public void testBlockingActivity_DoLaunchesNonDo_DoIsKilled_isBlocked()
+ throws Exception {
+ startDoActivity(DoActivity.INTENT_EXTRA_LAUNCH_NONDO_NEW_TASK);
+ assertBlockingActivityFound();
+
+ for (TempActivity activity : sTestingActivities) {
+ if (activity instanceof DoActivity) {
+ activity.finishCompletely();
+ sTestingActivities.remove(activity);
+ }
+ }
+
+ assertBlockingActivityFound();
+ }
+
@Test
public void testBlockingActivity_nonDoFinishesOnCreate_noBlockingActivity()
throws Exception {
@@ -259,14 +279,6 @@
}
@Test
- public void testBlockingActivity_nonDoNoHistory_isBlocked() throws Exception {
- startActivity(toComponentName(getTestContext(), NonDoNoHistoryActivity.class));
-
- assertThat(mDevice.wait(Until.findObject(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)),
- UI_TIMEOUT_MS)).isNotNull();
- }
-
- @Test
public void testIsActivityBackedBySafeActivity_notMoving_nonDoActivity_returnsTrue()
throws Exception {
setDrivingStateParked();
@@ -443,9 +455,6 @@
}
}
- public static class NonDoNoHistoryActivity extends TempActivity {
- }
-
public static class DoActivity extends TempActivity {
public static final String INTENT_EXTRA_SHOW_DIALOG = "SHOW_DIALOG";
public static final String DIALOG_TITLE = "Title";
diff --git a/tests/carservice_test/src/com/android/car/remoteaccess/CarRemoteAccessManagerTest.java b/tests/carservice_test/src/com/android/car/remoteaccess/CarRemoteAccessManagerTest.java
index 15eb9a6..63ab6ce 100644
--- a/tests/carservice_test/src/com/android/car/remoteaccess/CarRemoteAccessManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/remoteaccess/CarRemoteAccessManagerTest.java
@@ -68,7 +68,9 @@
import com.android.car.ICarImpl;
import com.android.car.MockedCarTestBase;
+import com.android.car.hal.PowerHalService;
import com.android.car.hal.test.AidlMockedVehicleHal.VehicleHalPropertyHandler;
+import com.android.car.systeminterface.SystemInterface;
import com.android.car.systeminterface.SystemStateInterface;
import com.android.compatibility.common.util.PollingCheck;
import com.android.internal.annotations.GuardedBy;
@@ -322,13 +324,17 @@
@Override
protected void configureFakeSystemInterface() {
// We need to do this before ICarImpl.init and after fake system interface is set.
- CarRemoteAccessService service = new CarRemoteAccessService(getContext(),
- getFakeSystemInterface(), /* powerHalService= */ null, /* dep= */ null,
- /* remoteAccessHal= */ mRemoteAccessHal,
- /* remoteAccessStorage= */ null, TEST_ALLOWED_SYSTEM_UPTIME_IN_MS,
- /* inMemoryStorage= */ true);
- service.setAllowedTimeForRemoteTaskClientInitMs(TEST_REMOTE_TASK_CLIENT_INIT_MS);
- setCarRemoteAccessService(service);
+ setCarRemoteAccessServiceConstructor((Context context, SystemInterface systemInterface,
+ PowerHalService powerHalService) -> {
+ CarRemoteAccessService service = new CarRemoteAccessService(context,
+ systemInterface, powerHalService, /* dep= */ null,
+ /* remoteAccessHal= */ mRemoteAccessHal,
+ /* remoteAccessStorage= */ null, TEST_ALLOWED_SYSTEM_UPTIME_IN_MS,
+ /* inMemoryStorage= */ true);
+ service.setAllowedTimeForRemoteTaskClientInitMs(
+ TEST_REMOTE_TASK_CLIENT_INIT_MS);
+ return service;
+ });
}
@Override
diff --git a/tests/carservice_unit_test/Android.bp b/tests/carservice_unit_test/Android.bp
index a41f618..e6b0ba1 100644
--- a/tests/carservice_unit_test/Android.bp
+++ b/tests/carservice_unit_test/Android.bp
@@ -89,7 +89,9 @@
"compatibility-device-util-axt",
"frameworks-base-testutils",
"flag-junit",
+ "ravenwood-junit",
"mockito-target-extended",
+ "ravenwood-junit",
"SettingsLib",
"servicestests-utils",
"truth",
@@ -98,9 +100,8 @@
],
test_suites: [
- "general-tests",
+ "device-tests",
"automotive-tests",
- "automotive-general-tests",
],
// mockito-target-inline dependency
@@ -113,3 +114,39 @@
// TODO(b/319708040): re-enable use_resource_processor
use_resource_processor: false,
}
+
+android_ravenwood_test {
+ name: "CarServiceHostUnitTest",
+
+ srcs: [
+ "src/android/car/test/JUnitHelper.java",
+ "src/android/car/test/PermissionsCheckerRuleTest.java",
+ "src/com/android/car/CarPropertyServiceUnitTest.java",
+ "src/com/android/car/hal/VehicleHalTest.java",
+ "src/com/android/car/hal/fakevhal/FakeVhalConfigParserUnitTest.java",
+ "src/com/android/car/hal/fakevhal/FakeVehicleStubUnitTest.java",
+ "src/com/android/car/hal/property/PropertyHalServiceConfigsUnitTest.java",
+ // Don't depends on "com.android.car.test" because it uses
+ // "mockito-target-extended" which might not be the same mockito
+ // library used on host.
+ ":com.android.car.test.lib-srcs",
+ ],
+
+ static_libs: [
+ "android.car",
+ "android.car.testapi",
+ "android.car.test.utils",
+ "androidx.annotation_annotation",
+ "androidx.test.rules",
+ "car-service-test-static-lib",
+ ],
+
+ libs: [
+ "android.test.base",
+ "framework-annotations-lib",
+ ],
+
+ platform_apis: true,
+
+ auto_gen_config: true,
+}
diff --git a/tests/carservice_unit_test/OWNERS b/tests/carservice_unit_test/OWNERS
index 0fb9fcc..60bfebf 100644
--- a/tests/carservice_unit_test/OWNERS
+++ b/tests/carservice_unit_test/OWNERS
@@ -1,10 +1,10 @@
# Bug component: 1316886
# ActivityManager
-per-file src/com/android/car/am/* = ycheo@google.com
+per-file src/com/android/car/am/* = gauravbhola@google.com
# AppFocus
-per-file src/com/android/car/AppFocusServiceTest.java = ycheo@google.com
+per-file src/com/android/car/AppFocusServiceTest.java = bkchoi@google.com
# Audio
per-file src/com/android/car/audio/* = oscarazu@google.com, ericjeong@google.com
@@ -14,19 +14,19 @@
per-file src/com/android/car/bluetooth/* = salsavage@google.com, chengandrew@google.com
# Cluster
-per-file src/android/car/cluster/* = ycheo@google.com
-per-file src/com/android/car/hal/ClusterHalServiceTest.java = ycheo@google.com
+per-file src/android/car/cluster/* = bkchoi@google.com
+per-file src/com/android/car/hal/ClusterHalServiceTest.java = bkchoi@google.com
# Input
-per-file src/android/car/builtin/input/* = ycheo@google.com, kanant@google.com
-per-file src/com/android/car/CarInput*.java = ycheo@google.com, kanant@google.com
-per-file src/com/android/car/hal/InputHalServiceTest.java = ycheo@google.com, kanant@google.com
+per-file src/android/car/builtin/input/* = kanant@google.com
+per-file src/com/android/car/CarInput*.java = kanant@google.com
+per-file src/com/android/car/hal/InputHalServiceTest.java = kanant@google.com
# OccupantZone
-per-file src/com/android/car/CarOccupantZoneServiceTest.java = ycheo@google.com, oscarazu@google.com, ericjeong@google.com
+per-file src/com/android/car/CarOccupantZoneServiceTest.java = xiangw@google.com, oscarazu@google.com, ericjeong@google.com
# PackageManager
-per-file src/com/android/car/pm/* = ycheo@google.com
+per-file src/com/android/car/pm/* = gargmayank@google.com, gauravbhola@google.com
# Power
per-file src/com/android/car/garagemode/* = ericjeong@google.com
@@ -60,7 +60,7 @@
per-file src/com/android/car/watchdog/* = lakshmana@google.com, jahdiel@google.com
# View
-per-file src/android/car/builtin/view/* = ycheo@google.com
+per-file src/android/car/builtin/view/* = gauravbhola@google.com
# VHAL
per-file src/com/android/car/hal/fakevhal/*.java = ericjeong@google.com, tylertrephan@google.com, shanyu@google.com
diff --git a/tests/carservice_unit_test/res/raw/car_audio_configuration_using_activation_volumes.xml b/tests/carservice_unit_test/res/raw/car_audio_configuration_using_activation_volumes.xml
index f3d8e55..c7c22e0 100644
--- a/tests/carservice_unit_test/res/raw/car_audio_configuration_using_activation_volumes.xml
+++ b/tests/carservice_unit_test/res/raw/car_audio_configuration_using_activation_volumes.xml
@@ -14,13 +14,26 @@
limitations under the License.
-->
<carAudioConfiguration version="4">
+ <activationVolumeConfigs>
+ <activationVolumeConfig name="activation_volume_on_boot_config">
+ <activationVolumeConfigEntry minActivationVolumePercentage="30"
+ maxActivationVolumePercentage="70" invocationType="onBoot" />
+ </activationVolumeConfig>
+ <activationVolumeConfig name="activation_volume_on_source_changed_config">
+ <activationVolumeConfigEntry minActivationVolumePercentage="30"
+ maxActivationVolumePercentage="70" invocationType="onSourceChanged" />
+ </activationVolumeConfig>
+ <activationVolumeConfig name="activation_volume_on_playback_changed_config">
+ <activationVolumeConfigEntry minActivationVolumePercentage="30"
+ maxActivationVolumePercentage="70" />
+ </activationVolumeConfig>
+ </activationVolumeConfigs>
<zones>
- <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1">
+ <zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="0">
<zoneConfigs>
- <zoneConfig name="primary zone config 1" isDefault="true">
+ <zoneConfig name="primary_zone_config_1" isDefault="true">
<volumeGroups>
- <group minActivationVolumePercentage="30"
- maxActivationVolumePercentage="70">
+ <group activationConfig="activation_volume_on_boot_config">
<device address="media_bus_device">
<context context="music"/>
<context context="announcement"/>
@@ -29,8 +42,7 @@
<context context="notification"/>
</device>
</group>
- <group minActivationVolumePercentage="30"
- maxActivationVolumePercentage="70">
+ <group activationConfig="activation_volume_on_source_changed_config">
<device address="navigation_bus_device">
<context context="navigation"/>
</device>
@@ -38,8 +50,7 @@
<context context="voice_command"/>
</device>
</group>
- <group minActivationVolumePercentage="30"
- maxActivationVolumePercentage="70">
+ <group activationConfig="activation_volume_on_playback_changed_config">
<device address="call_bus_device">
<context context="call"/>
</device>
@@ -47,8 +58,7 @@
<context context="call_ring"/>
</device>
</group>
- <group minActivationVolumePercentage="30"
- maxActivationVolumePercentage="70">
+ <group activationConfig="activation_volume_on_playback_changed_config">
<device address="alarm_bus_device">
<context context="alarm"/>
</device>
@@ -63,5 +73,49 @@
</zoneConfig>
</zoneConfigs>
</zone>
+ <zone name="secondary zone" audioZoneId="1" occupantZoneId="1">
+ <zoneConfigs>
+ <zoneConfig name="secondary_zone_config_1" isDefault="true">
+ <volumeGroups>
+ <group activationConfig="activation_volume_on_boot_config">
+ <device address="secondary_zone_bus_100">
+ <context context="music"/>
+ <context context="navigation"/>
+ <context context="voice_command"/>
+ <context context="call_ring"/>
+ <context context="call"/>
+ <context context="alarm"/>
+ <context context="system_sound"/>
+ <context context="notification"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ <zoneConfig name="secondary_zone_config_2">
+ <volumeGroups>
+ <group activationConfig="activation_volume_on_boot_config">
+ <device address="secondary_zone_bus_200">
+ <context context="music"/>
+ <context context="navigation"/>
+ <context context="voice_command"/>
+ <context context="call_ring"/>
+ <context context="call"/>
+ <context context="alarm"/>
+ <context context="system_sound"/>
+ <context context="notification"/>
+ <context context="emergency"/>
+ <context context="safety"/>
+ <context context="vehicle_status"/>
+ <context context="announcement"/>
+ </device>
+ </group>
+ </volumeGroups>
+ </zoneConfig>
+ </zoneConfigs>
+ </zone>
</zones>
</carAudioConfiguration>
diff --git a/tests/carservice_unit_test/res/raw/car_hidden_apis.txt b/tests/carservice_unit_test/res/raw/car_hidden_apis.txt
index 332b454..4680547 100644
--- a/tests/carservice_unit_test/res/raw/car_hidden_apis.txt
+++ b/tests/carservice_unit_test/res/raw/car_hidden_apis.txt
@@ -28,8 +28,15 @@
android.car Car String PROPERTY_EMULATED_PLATFORM_VERSION_MINOR
android.car Car T handleRemoteExceptionFromCarService(RemoteException e, T returnValue)
android.car Car T handleRemoteExceptionFromCarService(Service service, RemoteException e, T returnValue)
+android.car Car record Deps(ServiceManager serviceManager, Process process, long carServiceBindRetryIntervalMs, long carServiceBindMaxRetry)
android.car Car void handleRemoteExceptionFromCarService(RemoteException e)
android.car Car void handleRemoteExceptionFromCarService(Service service, RemoteException e)
+android.car Car.CarBuilder Car createCar(Context context)
+android.car Car.CarBuilder Car createCar(Context context, Handler handler)
+android.car Car.CarBuilder Car createCar(Context context, Handler handler, long waitTimeoutMs, CarServiceLifecycleListener statusChangeListener)
+android.car Car.CarBuilder Car createCar(Context context, ServiceConnection serviceConnectionListener)
+android.car Car.CarBuilder Car createCar(Context context, ServiceConnection serviceConnectionListener, Handler handler)
+android.car Car.CarBuilder CarBuilder setFakeDeps(Deps deps)
android.car CarAppFocusManager int APP_FOCUS_MAX
android.car CarAppFocusManager int[] getActiveAppTypes()
android.car CarAppFocusManager void onCarDisconnected()
@@ -68,6 +75,7 @@
android.car CarOccupantZoneManager int DISPLAY_TYPE_AUXILIARY_3
android.car CarOccupantZoneManager int DISPLAY_TYPE_AUXILIARY_4
android.car CarOccupantZoneManager int DISPLAY_TYPE_AUXILIARY_5
+android.car CarOccupantZoneManager int DISPLAY_TYPE_DISPLAY_COMPATIBILITY
android.car CarOccupantZoneManager int OCCUPANT_TYPE_INVALID
android.car CarOccupantZoneManager void onCarDisconnected()
android.car CarOccupantZoneManager.OccupantZoneInfo int INVALID_ZONE_ID
@@ -259,8 +267,11 @@
android.car.content.pm CarPackageManager int ERROR_CODE_NO_PACKAGE
android.car.content.pm CarPackageManager void controlTemporaryActivityBlockingBypassingAsUser(String packageName, String activityClassName, boolean bypass, int userId)
android.car.content.pm CarPackageManager void onCarDisconnected()
+android.car.content.pm CarPackageManager void registerBlockingUiCommandListener(int displayId, Executor callbackExecutor, BlockingUiCommandListener listener)
android.car.content.pm CarPackageManager void restartTask(int taskId)
android.car.content.pm CarPackageManager void setEnableActivityBlocking(boolean enable)
+android.car.content.pm CarPackageManager void unregisterBlockingUiCommandListener(BlockingUiCommandListener listener)
+android.car.content.pm CarPackageManager.BlockingUiCommandListener void finishBlockingUi()
android.car.diagnostic CarDiagnosticEvent CarDiagnosticEvent checkFreezeFrame()
android.car.diagnostic CarDiagnosticEvent CarDiagnosticEvent checkLiveFrame()
android.car.diagnostic CarDiagnosticEvent CarDiagnosticEvent withVendorSensorsRemoved()
@@ -633,6 +644,8 @@
android.car.projection ProjectionOptions.Builder Builder setProjectionActivityOptions(ActivityOptions activityOptions)
android.car.projection ProjectionOptions.Builder Builder setUiMode(int uiMode)
android.car.projection ProjectionOptions.Builder ProjectionOptions build()
+android.car.remoteaccess CarRemoteAccessManager boolean isShutdownRequestSupported()
+android.car.remoteaccess CarRemoteAccessManager boolean isVehicleInUseSupported()
android.car.remoteaccess CarRemoteAccessManager void addServerlessRemoteTaskClient(String packageName, String clientId)
android.car.remoteaccess CarRemoteAccessManager void onCarDisconnected()
android.car.remoteaccess CarRemoteAccessManager void removeServerlessRemoteTaskClient(String packageName)
diff --git a/tests/carservice_unit_test/src/android/car/BinderInterfaceContainerTest.java b/tests/carservice_unit_test/src/android/car/BinderInterfaceContainerTest.java
index 6c7aa99..1302292 100644
--- a/tests/carservice_unit_test/src/android/car/BinderInterfaceContainerTest.java
+++ b/tests/carservice_unit_test/src/android/car/BinderInterfaceContainerTest.java
@@ -53,7 +53,7 @@
assertThat(container.getInterfaces()).hasSize(2);
}
- private final class TestBinderInterface extends android.os.Binder implements IInterface {
+ private static final class TestBinderInterface extends android.os.Binder implements IInterface {
@Override
public IBinder asBinder() {
return this;
diff --git a/tests/carservice_unit_test/src/android/car/CarTest.java b/tests/carservice_unit_test/src/android/car/CarTest.java
deleted file mode 100644
index 4e7ec32..0000000
--- a/tests/carservice_unit_test/src/android/car/CarTest.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.car;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.Nullable;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
-import android.car.test.util.ExceptionalFunction;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.car.CarServiceUtils;
-import com.android.car.internal.ICarServiceHelper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.invocation.InvocationOnMock;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.Consumer;
-
-/**
- * Unit test for Car API.
- */
-public final class CarTest extends AbstractExtendedMockitoTestCase {
-
- private static final String TAG = CarTest.class.getSimpleName();
- private static final String PKG_NAME = "Bond.James.Bond";
-
- @Mock
- private Context mContext;
-
- private int mGetServiceCallCount;
-
-
- // It is tricky to mock this. So create placeholder version instead.
- private static final class FakeService extends ICar.Stub {
-
- public ExceptionalFunction<String, CarVersion, RemoteException>
- getTargetCarApiVersionMocker;
-
- @Override
- public void setSystemServerConnections(ICarServiceHelper helper,
- ICarResultReceiver receiver) throws RemoteException {
- }
-
- @Override
- public boolean isFeatureEnabled(String featureName) {
- return false;
- }
-
- @Override
- public int enableFeature(String featureName) {
- return Car.FEATURE_REQUEST_SUCCESS;
- }
-
- @Override
- public int disableFeature(String featureName) {
- return Car.FEATURE_REQUEST_SUCCESS;
- }
-
- @Override
- public List<String> getAllEnabledFeatures() {
- return Collections.EMPTY_LIST;
- }
-
- @Override
- public List<String> getAllPendingDisabledFeatures() {
- return Collections.EMPTY_LIST;
- }
-
- @Override
- public List<String> getAllPendingEnabledFeatures() {
- return Collections.EMPTY_LIST;
- }
-
- @Override
- public String getCarManagerClassForFeature(String featureName) {
- return null;
- }
-
- @Override
- public android.os.IBinder getCarService(java.lang.String serviceName) {
- return null;
- }
-
- @Override
- public int getCarConnectionType() {
- return 0;
- }
- };
-
- private final FakeService mService = new FakeService();
-
-
- private final class LifecycleListener implements Car.CarServiceLifecycleListener {
- // Use thread safe one to prevent adding another lock for testing
- private CopyOnWriteArrayList<Pair<Car, Boolean>> mEvents = new CopyOnWriteArrayList<>();
-
- @Override
- public void onLifecycleChanged(Car car, boolean ready) {
- assertThat(Looper.getMainLooper()).isEqualTo(Looper.myLooper());
- mEvents.add(new Pair<>(car, ready));
- }
- }
-
- private final LifecycleListener mLifecycleListener = new LifecycleListener();
-
- @Before
- public void setUp() {
- when(mContext.getPackageName()).thenReturn(PKG_NAME);
- }
-
- @Override
- protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
- session.spyStatic(ServiceManager.class);
- }
-
- private void expectService(@Nullable IBinder service) {
- doReturn(service).when(
- () -> ServiceManager.getService(Car.CAR_SERVICE_BINDER_SERVICE_NAME));
- }
-
- private void expectBindService() {
- when(mContext.bindService(anyObject(), anyObject(), anyInt())).thenReturn(true);
- }
-
- private void returnServiceAfterNSereviceManagerCalls(int returnNonNullAfterThisCall) {
- doAnswer((InvocationOnMock invocation) -> {
- mGetServiceCallCount++;
- if (mGetServiceCallCount > returnNonNullAfterThisCall) {
- return mService;
- } else {
- return null;
- }
- }).when(() -> ServiceManager.getService(Car.CAR_SERVICE_BINDER_SERVICE_NAME));
- }
-
- private void assertServiceBoundOnce() {
- verify(mContext, times(1)).bindService(anyObject(), anyObject(), anyInt());
- }
-
- private void assertOneListenerCallAndClear(Car expectedCar, boolean ready) {
- assertThat(mLifecycleListener.mEvents).containsExactly(new Pair<>(expectedCar, ready));
- mLifecycleListener.mEvents.clear();
- }
-
- @Test
- public void testCreateCarSuccessWithCarServiceRunning() {
- expectService(mService);
- Car car = Car.createCar(mContext);
- assertThat(car).isNotNull();
- car.disconnect();
- }
-
- @Test
- public void testCreateCarReturnNull() {
- // car service is not running yet and bindService does not bring the service yet.
- // createCar should timeout and give up.
- expectService(null);
- assertThat(Car.createCar(mContext)).isNull();
- }
-
- @Test
- public void testCreateCarOkWhenCarServiceIsStarted() {
- returnServiceAfterNSereviceManagerCalls(10);
- // Car service is not running yet and binsService call should start it.
- expectBindService();
- Car car = Car.createCar(mContext);
- assertThat(car).isNotNull();
- assertServiceBoundOnce();
- car.disconnect();
- }
-
- @Test
- public void testCreateCarWithStatusChangeNoServiceConnectionWithCarServiceStarted() {
- returnServiceAfterNSereviceManagerCalls(10);
- expectBindService();
- Car car = Car.createCar(mContext, null,
- Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
- assertThat(car).isNotNull();
- assertServiceBoundOnce();
- waitForMainToBeComplete();
- assertOneListenerCallAndClear(car, true);
-
- // Just call these to guarantee that nothing crashes with these call.
- runOnMainSyncSafe(() -> {
- car.getServiceConnectionListener().onServiceConnected(new ComponentName("", ""),
- mService);
- car.getServiceConnectionListener().onServiceDisconnected(new ComponentName("", ""));
- });
- }
-
- @Test
- public void testCreateCarWithStatusChangeNoServiceHandleCarServiceRestart() {
- expectService(mService);
- expectBindService();
- Car car = Car.createCar(mContext, null,
- Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
- assertThat(car).isNotNull();
- assertServiceBoundOnce();
-
- // fake connection
- runOnMainSyncSafe(() ->
- car.getServiceConnectionListener().onServiceConnected(new ComponentName("", ""),
- mService));
- waitForMainToBeComplete();
- assertOneListenerCallAndClear(car, true);
-
- // fake crash
- runOnMainSyncSafe(() ->
- car.getServiceConnectionListener().onServiceDisconnected(
- new ComponentName("", "")));
- waitForMainToBeComplete();
- assertOneListenerCallAndClear(car, false);
-
-
- // fake restart
- runOnMainSyncSafe(() ->
- car.getServiceConnectionListener().onServiceConnected(new ComponentName("", ""),
- mService));
- waitForMainToBeComplete();
- assertOneListenerCallAndClear(car, true);
- }
-
- @Test
- public void testCreateCarWithStatusChangeDirectCallInsideMainForServiceAlreadyReady() {
- expectService(mService);
- expectBindService();
- runOnMainSyncSafe(() -> {
- Car car = Car.createCar(mContext, null,
- Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
- assertThat(car).isNotNull();
- verify(mContext, times(1)).bindService(anyObject(), anyObject(), anyInt());
- // mLifecycleListener should have been called as this is main thread.
- assertOneListenerCallAndClear(car, true);
- });
- }
-
- @Test
- public void testCreateCarWithStatusChangeDirectCallInsideMainForServiceReadyLater() {
- returnServiceAfterNSereviceManagerCalls(10);
- expectBindService();
- runOnMainSyncSafe(() -> {
- Car car = Car.createCar(mContext, null,
- Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mLifecycleListener);
- assertThat(car).isNotNull();
- assertServiceBoundOnce();
- assertOneListenerCallAndClear(car, true);
- });
- }
-
- @Test
- public void testDevelopmentVersion() {
- /*
- * TODO(b/242311601): Once U has its own branch
- * - Change DEVELOPMENT_PLATFORM_CODENAME and DEVELOPMENT_PLATFORM to null in U branch.
- * - Change DEVELOPMENT_PLATFORM_CODENAME and DEVELOPMENT_PLATFORM to V-release code in
- * master branch.
- * - Update the unit test testDevelopmentVersion for testing special handling of V version
- * in master branch.
- * - Remove the unit test testDevelopmentVersion from U branch as it no longer a development
- * branch and doesn't go through the code path of the special logic.
- */
- PlatformVersion.forMajorVersion(Integer.MAX_VALUE)
- .isAtLeast(PlatformVersion.VERSION_CODES.UPSIDE_DOWN_CAKE_0);
- }
-
- private void runOnMainSyncSafe(Runnable runnable) {
- if (Looper.getMainLooper() == Looper.myLooper()) {
- Log.d(TAG, "Running runnable directly on " + Thread.currentThread());
- runnable.run();
- } else {
- Log.d(TAG, "Running runnable using CarServiceUtils.runOnMainSync()");
- CarServiceUtils.runOnMainSync(runnable);
- }
- }
- private void waitForMainToBeComplete() {
- // dispatch placeholder runnable and confirm that it is done.
- runOnMainSyncSafe(() -> { });
- }
-
- private void onNewCar(Consumer<Car> action) throws Exception {
- expectService(mService);
-
- Car car = Car.createCar(mContext);
- try {
- assertThat(car).isNotNull();
- action.accept(car);
- } finally {
- car.disconnect();
- }
- }
-}
diff --git a/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java b/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java
index 4f894d2..242a27f 100644
--- a/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java
@@ -15,7 +15,7 @@
*/
package android.car.admin;
-import static android.car.testapi.CarMockitoHelper.mockHandleRemoteExceptionFromCarServiceWithDefaultValue;
+import static android.car.test.mock.CarMockitoHelper.mockHandleRemoteExceptionFromCarServiceWithDefaultValue;
import static com.google.common.truth.Truth.assertThat;
@@ -66,8 +66,7 @@
@Test
public void testRemoveUser_success() throws Exception {
- int status = UserRemovalResult.STATUS_SUCCESSFUL;
- mockRemoveUser(100, status);
+ mockRemoveUser(/* userId= */ 100);
RemoveUserResult result = mMgr.removeUser(UserHandle.of(100));
@@ -186,7 +185,7 @@
assertThrows(NullPointerException.class, () -> mMgr.stopUser(null));
}
- private void mockRemoveUser(@UserIdInt int userId, int status) throws Exception {
+ private void mockRemoveUser(@UserIdInt int userId) throws Exception {
doAnswer((invocation) -> {
@SuppressWarnings("unchecked")
ResultCallbackImpl<UserRemovalResult> resultResultCallbackImpl =
diff --git a/tests/carservice_unit_test/src/android/car/cluster/renderer/InstrumentClusterRenderingServiceTest.java b/tests/carservice_unit_test/src/android/car/cluster/renderer/InstrumentClusterRenderingServiceTest.java
index 574b8a6..af8490f 100644
--- a/tests/carservice_unit_test/src/android/car/cluster/renderer/InstrumentClusterRenderingServiceTest.java
+++ b/tests/carservice_unit_test/src/android/car/cluster/renderer/InstrumentClusterRenderingServiceTest.java
@@ -64,7 +64,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ServiceTestRule;
import com.google.common.collect.ImmutableList;
@@ -115,7 +115,8 @@
}
private void bindService(Intent intent) throws Exception {
- intent.setComponent(ComponentName.createRelative(InstrumentationRegistry.getContext(),
+ intent.setComponent(ComponentName.createRelative(InstrumentationRegistry
+ .getInstrumentation().getContext(),
TestableInstrumentClusterRenderingService.class.getName()));
IBinder binder = mServiceRule.bindService(intent);
diff --git a/tests/carservice_unit_test/src/android/car/media/CarVolumeGroupEventUnitTest.java b/tests/carservice_unit_test/src/android/car/media/CarVolumeGroupEventUnitTest.java
index 5e6bed1..8cd91bd 100644
--- a/tests/carservice_unit_test/src/android/car/media/CarVolumeGroupEventUnitTest.java
+++ b/tests/carservice_unit_test/src/android/car/media/CarVolumeGroupEventUnitTest.java
@@ -223,7 +223,7 @@
Parcel parcel = Parcel.obtain();
TEST_CAR_VOLUME_GROUP_EVENT.writeToParcel(parcel, TEST_PARCEL_FLAGS);
- parcel.setDataPosition(/* position= */ 0);
+ parcel.setDataPosition(/* pos= */ 0);
expectWithMessage("Car volume event write to and create from parcel")
.that(TEST_CAR_VOLUME_GROUP_EVENT)
diff --git a/tests/carservice_unit_test/src/android/car/navigation/CarNavigationInstrumentClusterTest.java b/tests/carservice_unit_test/src/android/car/navigation/CarNavigationInstrumentClusterTest.java
index f126b2f..ab2165a 100644
--- a/tests/carservice_unit_test/src/android/car/navigation/CarNavigationInstrumentClusterTest.java
+++ b/tests/carservice_unit_test/src/android/car/navigation/CarNavigationInstrumentClusterTest.java
@@ -30,9 +30,9 @@
@Test
public void testCopyConstructor_constructsAsExpected() {
CarNavigationInstrumentCluster carNavigationInstrumentCluster =
- CarNavigationInstrumentCluster.createCustomImageCluster(/* minIntervalMs= */ 100,
- /* imageWidth= */ 800, /* imageHeight= */ 480,
- /* imageColorDepthBits= */ 32);
+ CarNavigationInstrumentCluster.createCustomImageCluster(
+ /* minIntervalMillis= */ 100, /* imageWidth= */ 800,
+ /* imageHeight= */ 480, /* imageColorDepthBits= */ 32);
CarNavigationInstrumentCluster copy = new CarNavigationInstrumentCluster(
carNavigationInstrumentCluster);
@@ -55,9 +55,9 @@
@Test
public void testCreateFromParcel() {
CarNavigationInstrumentCluster carNavigationInstrumentCluster =
- CarNavigationInstrumentCluster.createCustomImageCluster(/* minIntervalMs= */ 100,
- /* imageWidth= */ 800, /* imageHeight= */ 480,
- /* imageColorDepthBits= */ 32);
+ CarNavigationInstrumentCluster.createCustomImageCluster(
+ /* minIntervalMillis= */ 100, /* imageWidth= */ 800,
+ /* imageHeight= */ 480, /* imageColorDepthBits= */ 32);
Parcel parcel = Parcel.obtain();
carNavigationInstrumentCluster.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
diff --git a/tests/carservice_unit_test/src/android/car/oem/OemCarVolumeChangeInfoUnitTest.java b/tests/carservice_unit_test/src/android/car/oem/OemCarVolumeChangeInfoUnitTest.java
index 4b7e7a3..4dd1877 100644
--- a/tests/carservice_unit_test/src/android/car/oem/OemCarVolumeChangeInfoUnitTest.java
+++ b/tests/carservice_unit_test/src/android/car/oem/OemCarVolumeChangeInfoUnitTest.java
@@ -77,7 +77,7 @@
new OemCarVolumeChangeInfo.Builder(/* volumeChanged= */ true).build();
info.writeToParcel(parcel, /* flags= */ 0);
- parcel.setDataPosition(/* position= */ 0);
+ parcel.setDataPosition(/* pos= */ 0);
expectWithMessage("Car volume change from parcel")
.that(OemCarVolumeChangeInfo.CREATOR.createFromParcel(parcel)).isEqualTo(info);
diff --git a/tests/carservice_unit_test/src/android/car/test/PermissionsCheckerRuleTest.java b/tests/carservice_unit_test/src/android/car/test/PermissionsCheckerRuleTest.java
index d7c7e48..df86bfc 100644
--- a/tests/carservice_unit_test/src/android/car/test/PermissionsCheckerRuleTest.java
+++ b/tests/carservice_unit_test/src/android/car/test/PermissionsCheckerRuleTest.java
@@ -18,37 +18,32 @@
import static android.car.test.JUnitHelper.newTestMethod;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.when;
-
-import android.car.test.JUnitHelper.SimpleStatement;
-
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.UiAutomation;
+import android.car.test.JUnitHelper.SimpleStatement;
import android.car.test.PermissionsCheckerRule.EnsureHasPermission;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
-import android.util.Log;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.Description;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-
+import org.mockito.junit.MockitoJUnitRunner;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Set;
-public final class PermissionsCheckerRuleTest extends AbstractExtendedMockitoTestCase {
+@RunWith(MockitoJUnitRunner.class)
+public final class PermissionsCheckerRuleTest {
private static final String TAG = PermissionsCheckerRuleTest.class.getSimpleName();
@@ -64,8 +59,6 @@
mRule = new PermissionsCheckerRule(mUiAutomation);
}
- // NOTE: no need to override onSessionBuilder() to spy on Log because superclass already does it
-
@Test
public void testNoAnnotation() throws Throwable {
Description testMethod = newTestMethod();
@@ -117,8 +110,6 @@
public void testEnsureHasPermission_permissionsAdoptedBefore() throws Throwable {
Description testMethod = newTestMethod(new EnsureHasPermissionAnnotation("To Kill"));
when(mUiAutomation.getAdoptedShellPermissions()).thenReturn(Set.of("Thou shalt not kill!"));
- ArgumentCaptor<String> logMessage = ArgumentCaptor.forClass(String.class);
- doReturn(666).when(() -> Log.w(eq(PermissionsCheckerRule.TAG), logMessage.capture()));
mRule.apply(mBaseStatement, testMethod).evaluate();
@@ -126,9 +117,6 @@
verify(mUiAutomation).adoptShellPermissionIdentity("To Kill");
verify(mUiAutomation).dropShellPermissionIdentity();
verify(mUiAutomation).adoptShellPermissionIdentity("Thou shalt not kill!");
-
- assertWithMessage("Log message").that(logMessage.getValue())
- .contains("Thou shalt not kill!");
}
@Test
diff --git a/tests/carservice_unit_test/src/android/car/util/concurrent/AndroidFutureTest.java b/tests/carservice_unit_test/src/android/car/util/concurrent/AndroidFutureTest.java
index 9838115..5935d3e 100644
--- a/tests/carservice_unit_test/src/android/car/util/concurrent/AndroidFutureTest.java
+++ b/tests/carservice_unit_test/src/android/car/util/concurrent/AndroidFutureTest.java
@@ -96,6 +96,7 @@
assertThat(mCompletedFuture.get()).isEqualTo(STRING_VALUE);
}
+ @SuppressWarnings("FutureReturnValueIgnored")
@Test
public void testWhenComplete_alreadyCompleted() throws Exception {
mCompletedFuture.whenComplete((obj, err) -> {
@@ -106,6 +107,7 @@
mLatch.await();
}
+ @SuppressWarnings("FutureReturnValueIgnored")
@Test
public void testWhenComplete_uncompleted() throws Exception {
mUncompletedFuture.whenComplete((obj, err) -> {
@@ -119,6 +121,7 @@
assertThat(mLatch.getCount()).isEqualTo(0);
}
+ @SuppressWarnings("FutureReturnValueIgnored")
@Test
public void testWhenComplete_completeExceptionally() throws Exception {
Exception origException = new UnsupportedOperationException(EXCEPTION_MESSAGE);
@@ -144,6 +147,7 @@
() -> mUncompletedFuture.whenCompleteAsync((o, e) -> {}, /* executor= */null));
}
+ @SuppressWarnings("FutureReturnValueIgnored")
@Test
public void testOrTimeout_completed() throws Exception {
mCompletedFuture.orTimeout(TIMEOUT_MS, MILLISECONDS);
@@ -151,6 +155,7 @@
assertThat(mCompletedFuture.get()).isEqualTo(STRING_VALUE);
}
+ @SuppressWarnings("FutureReturnValueIgnored")
@Test
public void testOrTimeout_uncompleted_timesOut() throws Exception {
mUncompletedFuture.orTimeout(TIMEOUT_MS, MILLISECONDS);
@@ -171,7 +176,7 @@
@Test
public void testSetTimeoutHandler_nullHandler() throws Exception {
assertThrows(NullPointerException.class,
- () -> mUncompletedFuture.setTimeoutHandler(/* handler= */null));
+ () -> mUncompletedFuture.setTimeoutHandler(/* h= */null));
}
@Test
diff --git a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
index 0d6575a..ede0496 100644
--- a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
@@ -46,7 +46,6 @@
import android.automotive.watchdog.internal.StateType;
import android.automotive.watchdog.internal.ThreadPolicyWithPriority;
import android.automotive.watchdog.internal.UserPackageIoUsageStats;
-import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -75,7 +74,7 @@
private static final int MAX_WAIT_TIME_MS = 3000;
@Mock CarWatchdogDaemonHelper.OnConnectionChangeListener mListener;
- @Mock private IBinder mBinder = new Binder();
+ @Mock private IBinder mBinder;
@Captor
private ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor;
@@ -372,7 +371,7 @@
}
// FakeCarWatchdog mimics ICarWatchdog daemon in local process.
- private final class FakeCarWatchdog extends ICarWatchdog.Default {
+ private static final class FakeCarWatchdog extends ICarWatchdog.Default {
private static final int UDC_INTERFACE_VERSION = 3;
private final ArrayList<ICarWatchdogServiceForSystem> mServices = new ArrayList<>();
@@ -406,7 +405,8 @@
}
}
- private final class ICarWatchdogServiceForSystemImpl extends ICarWatchdogServiceForSystem.Stub {
+ private static final class ICarWatchdogServiceForSystemImpl
+ extends ICarWatchdogServiceForSystem.Stub {
@Override
public void checkIfAlive(int sessionId, int timeout) {}
diff --git a/tests/carservice_unit_test/src/com/android/car/AbstractICarServiceHelperStub.java b/tests/carservice_unit_test/src/com/android/car/AbstractICarServiceHelperStub.java
index 66c2edf..e57a67c 100644
--- a/tests/carservice_unit_test/src/com/android/car/AbstractICarServiceHelperStub.java
+++ b/tests/carservice_unit_test/src/com/android/car/AbstractICarServiceHelperStub.java
@@ -125,8 +125,6 @@
@Override
public void setProcessProfile(int pid, int uid, @NonNull String profile) {
Log.d(TAG, "setProcessProfile(" + pid + "," + uid + "," + profile + ")");
-
- return;
}
@Override
diff --git a/tests/carservice_unit_test/src/com/android/car/AidlVehicleStubUnitTest.java b/tests/carservice_unit_test/src/com/android/car/AidlVehicleStubUnitTest.java
index ca55a69..34dfd58 100644
--- a/tests/carservice_unit_test/src/com/android/car/AidlVehicleStubUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/AidlVehicleStubUnitTest.java
@@ -97,8 +97,6 @@
private static final int TEST_AREA = 4;
private static final int TEST_STATUS = 5;
- private static final int VHAL_PROP_SUPPORTED_PROPERTY_IDS = 0x11410F48;
-
private static final HalPropValue HVAC_PROP_VALUE;
private static final HalPropValue TEST_PROP_VALUE;
@@ -304,7 +302,6 @@
LargeParcelable.reconstructStableAIDLParcelable(
requests, /*keepSharedMemory=*/false);
assertThat(requests.payloads.length).isEqualTo(1);
- GetValueRequest request = requests.payloads[0];
GetValueResults results = createGetValueResults(StatusCode.OK, requests.payloads);
diff --git a/tests/carservice_unit_test/src/com/android/car/AppFocusServiceTest.java b/tests/carservice_unit_test/src/com/android/car/AppFocusServiceTest.java
index db0ba22..d1c1c93 100644
--- a/tests/carservice_unit_test/src/com/android/car/AppFocusServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/AppFocusServiceTest.java
@@ -157,7 +157,8 @@
() -> mService.getAppTypeOwner(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
}
- private class AppFocusChangedListener implements CarAppFocusManager.OnAppFocusChangedListener {
+ private static final class AppFocusChangedListener
+ implements CarAppFocusManager.OnAppFocusChangedListener {
private final Semaphore mSemaphore = new Semaphore(0);
private int mLastAppType;
@@ -179,7 +180,7 @@
}
}
- private class AppFocusOwnershipCallback implements
+ private static final class AppFocusOwnershipCallback implements
CarAppFocusManager.OnAppFocusOwnershipCallback {
private final Semaphore mSemaphore = new Semaphore(0);
@@ -196,13 +197,5 @@
mGrantedAppTypes = mGrantedAppTypes | appType;
mSemaphore.release();
}
-
- public void waitForEvent() throws Exception {
- mSemaphore.tryAcquire(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- }
-
- public void resetWait() {
- mSemaphore.drainPermits();
- }
}
}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarFeatureControllerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/CarFeatureControllerUnitTest.java
new file mode 100644
index 0000000..999b955
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/CarFeatureControllerUnitTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.content.Context;
+import android.hardware.automotive.vehicle.VehicleProperty;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.hal.HalPropValue;
+import com.android.car.hal.HalPropValueBuilder;
+import com.android.car.hal.VehicleHal;
+import com.android.car.test.utils.TemporaryDirectory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+@SmallTest
+public final class CarFeatureControllerUnitTest extends AbstractExtendedMockitoTestCase {
+ private static final String TAG = CarFeatureControllerUnitTest.class.getSimpleName();
+ private static final String CAR_NAVIGATION_SERVICE_FEATURE = "car_navigation_service";
+
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private final HalPropValueBuilder mHalPropValueBuilder = new HalPropValueBuilder(
+ /* isAidl= */ true);
+ @Mock
+ private VehicleHal mMockHal;
+ private TemporaryDirectory mTestDir;
+
+ @Before
+ public void setUp() throws Exception {
+ mTestDir = new TemporaryDirectory(TAG);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mTestDir != null) {
+ try {
+ mTestDir.close();
+ } catch (Exception e) {
+ Log.w(TAG, "could not remove temporary directory", e);
+ }
+ }
+ }
+
+ @Test
+ public void testIsFeatureEnabled_enabled() {
+ HalPropValue disabledFeaturesResponse = mHalPropValueBuilder.build(
+ VehicleProperty.DISABLED_OPTIONAL_FEATURES, /* areaId= */ 0, /* value= */ "");
+ when(mMockHal.getIfSupportedOrFailForEarlyStage(
+ eq(VehicleProperty.DISABLED_OPTIONAL_FEATURES), anyInt())).thenReturn(
+ disabledFeaturesResponse);
+ CarFeatureController service =
+ new CarFeatureController(mContext, mTestDir.getDirectory(), mMockHal);
+
+ boolean isNavigationEnabled = service.isFeatureEnabled(CAR_NAVIGATION_SERVICE_FEATURE);
+
+ assertWithMessage("Navigation feature enabled status").that(isNavigationEnabled).isTrue();
+ }
+
+ @Test
+ public void testIsFeatureEnabled_disabled() {
+ HalPropValue disabledFeaturesResponse = mHalPropValueBuilder.build(
+ VehicleProperty.DISABLED_OPTIONAL_FEATURES, /* areaId= */ 0,
+ /* value= */ CAR_NAVIGATION_SERVICE_FEATURE);
+ when(mMockHal.getIfSupportedOrFailForEarlyStage(
+ eq(VehicleProperty.DISABLED_OPTIONAL_FEATURES), anyInt())).thenReturn(
+ disabledFeaturesResponse);
+ CarFeatureController service =
+ new CarFeatureController(mContext, mTestDir.getDirectory(), mMockHal);
+
+ boolean isNavigationEnabled = service.isFeatureEnabled(CAR_NAVIGATION_SERVICE_FEATURE);
+
+ assertWithMessage("Navigation feature enabled status").that(isNavigationEnabled).isFalse();
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarInputRotaryServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarInputRotaryServiceTest.java
index 63ddcf6..1aa5219 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarInputRotaryServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarInputRotaryServiceTest.java
@@ -35,7 +35,6 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Resources;
-import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -81,7 +80,6 @@
@Mock private InputHalService mInputHalService;
@Mock private TelecomManager mTelecomManager;
- @Mock private DisplayManager mDisplayManager;
@Mock private CarInputService.KeyEventListener mDefaultKeyEventMainListener;
@Mock private CarInputService.MotionEventListener mDefaultMotionEventMainListener;
@Mock private Supplier<String> mLastCallSupplier;
diff --git a/tests/carservice_unit_test/src/com/android/car/CarLogTest.java b/tests/carservice_unit_test/src/com/android/car/CarLogTest.java
index dd89967..21053a3 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarLogTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarLogTest.java
@@ -56,11 +56,11 @@
.isEqualTo((mPrefixExpected ? PREFIX : "") + mClass.getSimpleName());
}
- class CarKeywordInTheStart {}
- class KeywordInTheEndCar {}
- class KeywordInTheMiddleCarService {}
- class NoKeyword {}
- class NotExactKeywordCarrier {}
- class NotExactKeywordcar {}
- class NotExactKeywordcarService {}
+ private static final class CarKeywordInTheStart {}
+ private static final class KeywordInTheEndCar {}
+ private static final class KeywordInTheMiddleCarService {}
+ private static final class NoKeyword {}
+ private static final class NotExactKeywordCarrier {}
+ private static final class NotExactKeywordcar {}
+ private static final class NotExactKeywordcarService {}
}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java
index ec08fab..1e440a7 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarOccupantZoneServiceTest.java
@@ -1094,6 +1094,18 @@
CarOccupantZoneManager.DISPLAY_TYPE_HUD)).isEqualTo(Display.INVALID_DISPLAY);
assertThat(mManager.getDisplayIdForDriver(
CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY)).isEqualTo(Display.INVALID_DISPLAY);
+ assertThat(mManager.getDisplayIdForDriver(
+ CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY_2)).isEqualTo(
+ Display.INVALID_DISPLAY);
+ assertThat(mManager.getDisplayIdForDriver(
+ CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY_3)).isEqualTo(
+ Display.INVALID_DISPLAY);
+ assertThat(mManager.getDisplayIdForDriver(
+ CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY_4)).isEqualTo(
+ Display.INVALID_DISPLAY);
+ assertThat(mManager.getDisplayIdForDriver(
+ CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY_5)).isEqualTo(
+ Display.INVALID_DISPLAY);
}
@Test
@@ -1117,7 +1129,7 @@
mService.init();
int driverUser = mManager.getUserForOccupant(mZoneDriverLHD);
- assertThat(CURRENT_USER).isEqualTo(driverUser);
+ assertThat(driverUser).isEqualTo(CURRENT_USER);
assertThat(mManager.getUserForOccupant(mZoneFrontPassengerLHD)).isEqualTo(
CarOccupantZoneManager.INVALID_USER_ID);
@@ -1139,7 +1151,7 @@
mService.mUserLifecycleListener.onEvent(new UserLifecycleEvent(
CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, newUserId));
- assertThat(newUserId).isEqualTo(mManager.getUserForOccupant(mZoneDriverLHD));
+ assertThat(mManager.getUserForOccupant(mZoneDriverLHD)).isEqualTo(newUserId);
assertThat(mManager.getUserForOccupant(mZoneFrontPassengerLHD)).isEqualTo(
CarOccupantZoneManager.INVALID_USER_ID);
@@ -1218,7 +1230,7 @@
public void testGetSupportedInputTypes_driverZoneInfo() {
mService.init();
- assertThat(mService.getSupportedInputTypes(/* zoneId= */ 0,
+ assertThat(mService.getSupportedInputTypes(/* occupantZoneId= */ 0,
CarOccupantZoneManager.DISPLAY_TYPE_MAIN)).asList().containsExactly(CarInputManager
.INPUT_TYPE_DPAD_KEYS, CarInputManager.INPUT_TYPE_NAVIGATE_KEYS, CarInputManager
.INPUT_TYPE_ROTARY_NAVIGATION, CarInputManager.INPUT_TYPE_TOUCH_SCREEN);
diff --git a/tests/carservice_unit_test/src/com/android/car/CarPropertyManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/CarPropertyManagerUnitTest.java
deleted file mode 100644
index c521ea1..0000000
--- a/tests/carservice_unit_test/src/com/android/car/CarPropertyManagerUnitTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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;
-
-import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_SET;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.car.Car;
-import android.car.hardware.property.CarPropertyManager;
-import android.car.hardware.property.ICarProperty;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.os.Build;
-import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-
-import com.android.car.internal.property.CarPropertyErrorCodes;
-import com.android.car.internal.property.GetSetValueResult;
-import com.android.car.internal.property.GetSetValueResultList;
-import com.android.car.internal.property.IAsyncPropertyResultCallback;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * <p>This class contains unit tests for the {@link CarPropertyManager}.
- *
- * <p>Most test cases are already migrated to CarLibHostUnitTest. Host-side unit tests are
- * preferred. New tests should be added to CarLibHostUnitTest. This test class only contains
- * test cases that cannot be executed on host.
- */
-@RunWith(MockitoJUnitRunner.class)
-public final class CarPropertyManagerUnitTest {
- private final Handler mMainHandler = new Handler(Looper.getMainLooper());
-
- @Mock
- private Car mCar;
- @Mock
- private ApplicationInfo mApplicationInfo;
- @Mock
- private ICarProperty mICarProperty;
- @Mock
- private Context mContext;
- @Mock
- private CarPropertyManager.CarPropertyEventCallback mCarPropertyEventCallback;
- @Mock
- private CarPropertyManager.CarPropertyEventCallback mCarPropertyEventCallback2;
- @Mock
- private CarPropertyManager.GetPropertyCallback mGetPropertyCallback;
-
- private CarPropertyManager mCarPropertyManager;
-
- @Before
- public void setUp() throws RemoteException {
- when(mCar.getContext()).thenReturn(mContext);
- when(mCar.getEventHandler()).thenReturn(mMainHandler);
-
- mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
- when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
-
- mCarPropertyManager = new CarPropertyManager(mCar, mICarProperty);
- }
-
- @Test
- public void testGetPropertiesAsync_cancellationSignalCancelRequests() throws Exception {
- CarPropertyManager.GetPropertyRequest getPropertyRequest = createGetPropertyRequest();
- CancellationSignal cancellationSignal = new CancellationSignal();
- List<IAsyncPropertyResultCallback> callbackWrapper = new ArrayList<>();
- doAnswer((invocation) -> {
- Object[] args = invocation.getArguments();
- callbackWrapper.add((IAsyncPropertyResultCallback) args[1]);
- return null;
- }).when(mICarProperty).getPropertiesAsync(any(), any(), anyLong());
-
- mCarPropertyManager.getPropertiesAsync(List.of(getPropertyRequest), cancellationSignal,
- /* callbackExecutor= */ null, mGetPropertyCallback);
-
- // Cancel the pending request.
- cancellationSignal.cancel();
-
- verify(mICarProperty).cancelRequests(new int[]{0});
-
- // Call the manager callback after the request is already cancelled.
- GetSetValueResult getValueResult =
- GetSetValueResult.newErrorResult(0,
- new CarPropertyErrorCodes(
- CarPropertyManager.STATUS_ERROR_INTERNAL_ERROR,
- /* vendorErrorCode= */ 0,
- /* systemErrorCode= */ 0));
- assertThat(callbackWrapper.size()).isEqualTo(1);
- callbackWrapper.get(0).onGetValueResults(
- new GetSetValueResultList(List.of(getValueResult)));
-
- // No client callbacks should be called.
- verify(mGetPropertyCallback, never()).onFailure(any());
- verify(mGetPropertyCallback, never()).onSuccess(any());
- }
-
- private CarPropertyManager.GetPropertyRequest createGetPropertyRequest() {
- return mCarPropertyManager.generateGetPropertyRequest(HVAC_TEMPERATURE_SET, 0);
- }
-}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarPropertyServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/CarPropertyServiceUnitTest.java
index d508112..a0ffe85 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarPropertyServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarPropertyServiceUnitTest.java
@@ -18,7 +18,6 @@
import static android.car.hardware.property.CarPropertyManager.SENSOR_RATE_ONCHANGE;
-import static com.android.car.hal.PropertyHalServiceTest.createCarSubscriptionOption;
import static com.android.car.internal.property.CarPropertyHelper.SYNC_OP_LIMIT_TRY_AGAIN;
import static com.google.common.truth.Truth.assertThat;
@@ -26,6 +25,7 @@
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -53,6 +53,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.util.Log;
import android.util.SparseArray;
@@ -61,14 +62,16 @@
import com.android.car.internal.property.AsyncPropertyServiceRequestList;
import com.android.car.internal.property.CarSubscription;
import com.android.car.internal.property.IAsyncPropertyResultCallback;
+import com.android.car.logging.HistogramFactoryInterface;
+import com.android.modules.expresslog.Histogram;
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.junit.MockitoJUnitRunner;
+import org.mockito.MockitoAnnotations;
import java.time.Duration;
import java.util.List;
@@ -77,11 +80,15 @@
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-@RunWith(MockitoJUnitRunner.class)
public final class CarPropertyServiceUnitTest {
-
private static final String TAG = CarLog.tagFor(CarPropertyServiceUnitTest.class);
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProcessSystem()
+ .setProvideMainThread(true)
+ .build();
+
@Mock
private Context mContext;
@Mock
@@ -93,9 +100,9 @@
@Mock
private IAsyncPropertyResultCallback mAsyncPropertyResultCallback;
@Mock
- private CarPropertyConfig<?> mCarPropertyConfig;
- @Mock
private FeatureFlags mFeatureFlags;
+ @Mock
+ private HistogramFactoryInterface mHistogramFactory;
@Captor
private ArgumentCaptor<List<CarPropertyEvent>> mPropertyEventCaptor;
@@ -139,6 +146,7 @@
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
when(mICarPropertyEventListener.asBinder()).thenReturn(mIBinder);
@@ -284,8 +292,17 @@
when(mFeatureFlags.variableUpdateRate()).thenReturn(true);
when(mFeatureFlags.subscriptionWithResolution()).thenReturn(true);
- mService = new CarPropertyService(mContext, mHalService);
- mService.setFeatureFlags(mFeatureFlags);
+ when(mHistogramFactory.newUniformHistogram(any(), anyInt(), anyFloat(), anyFloat()))
+ .thenReturn(mock(Histogram.class));
+ when(mHistogramFactory.newScaledRangeHistogram(any(), anyInt(), anyInt(), anyFloat(),
+ anyFloat())).thenReturn(mock(Histogram.class));
+
+ mService = new CarPropertyService.Builder()
+ .setContext(mContext)
+ .setPropertyHalService(mHalService)
+ .setFeatureFlags(mFeatureFlags)
+ .setHistogramFactory(mHistogramFactory)
+ .build();
mService.init();
}
@@ -613,7 +630,7 @@
when(mHalService.getProperty(SPEED_ID, 0)).thenReturn(mValue);
// Register the first listener.
- mService.registerListener(SPEED_ID, /* rate= */ 10, mMockHandler1);
+ mService.registerListener(SPEED_ID, /* updateRateHz= */ 10, mMockHandler1);
// Wait until we get the on property change event for the initial value.
verify(mMockHandler1, timeout(5000)).onEvent(any());
@@ -626,7 +643,7 @@
clearInvocations(mHalService);
// Register the second listener.
- mService.registerListener(SPEED_ID, /* rate= */ 20, mMockHandler2);
+ mService.registerListener(SPEED_ID, /* updateRateHz= */ 20, mMockHandler2);
// Wait until we get the on property change event for the initial value.
verify(mMockHandler2, timeout(5000)).onEvent(any());
@@ -707,7 +724,8 @@
when(mHalService.getProperty(HVAC_TEMP, 0)).thenReturn(mValue);
// Register the first listener.
- mService.registerListener(HVAC_TEMP, /* rate= */ SENSOR_RATE_ONCHANGE, mMockHandler1);
+ mService.registerListener(HVAC_TEMP, /* updateRateHz= */ SENSOR_RATE_ONCHANGE,
+ mMockHandler1);
// Wait until we get the on property change event for the initial value.
verify(mMockHandler1, timeout(5000)).onEvent(any());
@@ -720,7 +738,8 @@
clearInvocations(mHalService);
// Register the second listener.
- mService.registerListener(HVAC_TEMP, /* rate= */ SENSOR_RATE_ONCHANGE, mMockHandler2);
+ mService.registerListener(HVAC_TEMP, /* updateRateHz= */ SENSOR_RATE_ONCHANGE,
+ mMockHandler2);
// Wait until we get the on property change event for the initial value.
verify(mMockHandler2, timeout(5000)).onEvent(any());
@@ -895,6 +914,9 @@
assertThrows(ServiceSpecificException.class, () ->
mService.registerListener(subscribeOptions, mockHandler));
+ // Finish the async get initial value task.
+ mService.finishHandlerTasks(/*timeoutInMs=*/ 1000);
+
// Simulate the error goes away.
clearInvocations(mHalService);
doNothing().when(mHalService).subscribeProperty(any());
@@ -1122,6 +1144,9 @@
assertThrows(ServiceSpecificException.class, () ->
mService.unregisterListener(SPEED_ID, mockHandler));
+ // Finish the async get initial value task.
+ mService.finishHandlerTasks(/*timeoutInMs=*/ 1000);
+
// Simulate the error goes away.
clearInvocations(mHalService);
doNothing().when(mHalService).unsubscribeProperty(anyInt());
@@ -1229,7 +1254,7 @@
when(mHalService.getProperty(HVAC_TEMP, 0)).thenReturn(value);
EventListener listener = new EventListener(mService);
- mService.registerListener(HVAC_TEMP, /* rate= */ SENSOR_RATE_ONCHANGE, listener);
+ mService.registerListener(HVAC_TEMP, /* updateRateHz= */ SENSOR_RATE_ONCHANGE, listener);
List<CarPropertyEvent> events = List.of(new CarPropertyEvent(0, value));
mService.onPropertyChange(events);
@@ -1422,7 +1447,8 @@
public void registerListener_throwsExceptionBecauseOfNullListener() {
assertThrows(NullPointerException.class,
() -> mService.registerListener(ON_CHANGE_READ_WRITE_PROPERTY_ID,
- CarPropertyManager.SENSOR_RATE_NORMAL, /* listener= */ null));
+ CarPropertyManager.SENSOR_RATE_NORMAL,
+ /* carPropertyEventListener= */ null));
}
@Test
@@ -1575,8 +1601,8 @@
@Test
public void unregisterListener_throwsExceptionBecauseOfNullListener() {
assertThrows(NullPointerException.class,
- () -> mService.unregisterListener(ON_CHANGE_READ_WRITE_PROPERTY_ID, /* listener= */
- null));
+ () -> mService.unregisterListener(ON_CHANGE_READ_WRITE_PROPERTY_ID,
+ /* iCarPropertyEventListener= */ null));
}
@Test
@@ -1675,4 +1701,30 @@
assertThat(mService.isSupportedAndHasWritePermissionOnly(CONTINUOUS_READ_ONLY_PROPERTY_ID))
.isFalse();
}
+
+ /** Creates a {@code CarSubscription} with Vur off. */
+ private static CarSubscription createCarSubscriptionOption(int propertyId,
+ int[] areaId, float updateRateHz) {
+ return createCarSubscriptionOption(propertyId, areaId, updateRateHz,
+ /* enableVur= */ false, /*resolution*/ 0.0f);
+ }
+
+ /** Creates a {@code CarSubscription}. */
+ private static CarSubscription createCarSubscriptionOption(int propertyId,
+ int[] areaId, float updateRateHz, boolean enableVur) {
+ return createCarSubscriptionOption(propertyId, areaId, updateRateHz,
+ enableVur, /*resolution*/ 0.0f);
+ }
+
+ /** Creates a {@code CarSubscription}. */
+ private static CarSubscription createCarSubscriptionOption(int propertyId,
+ int[] areaId, float updateRateHz, boolean enableVur, float resolution) {
+ CarSubscription options = new CarSubscription();
+ options.propertyId = propertyId;
+ options.areaIds = areaId;
+ options.updateRateHz = updateRateHz;
+ options.enableVariableUpdateRate = enableVur;
+ options.resolution = resolution;
+ return options;
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarRemoteDeviceManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/CarRemoteDeviceManagerUnitTest.java
index 89b9cdb..22e7ed1 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarRemoteDeviceManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarRemoteDeviceManagerUnitTest.java
@@ -83,7 +83,7 @@
@Test
public void testGetEndpointPackageInfoWithNullParameters_throwsException() {
assertThrows(NullPointerException.class,
- () -> mRemoteDeviceManager.getEndpointPackageInfo(/* receiverZone= */ null));
+ () -> mRemoteDeviceManager.getEndpointPackageInfo(/* occupantZone= */ null));
}
@Test
diff --git a/tests/carservice_unit_test/src/com/android/car/CarServiceUtilsTest.java b/tests/carservice_unit_test/src/com/android/car/CarServiceUtilsTest.java
index 8c7e80d..1f4f9f0 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarServiceUtilsTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarServiceUtilsTest.java
@@ -45,6 +45,7 @@
import android.hardware.automotive.vehicle.SubscribeOptions;
import android.os.Process;
import android.text.TextUtils;
+import android.util.ArraySet;
import com.android.car.util.TransitionLog;
@@ -55,6 +56,7 @@
import org.mockito.Mock;
import org.mockito.MockitoSession;
+import java.util.List;
import java.util.UUID;
public class CarServiceUtilsTest extends AbstractExtendedMockitoTestCase {
@@ -337,6 +339,26 @@
}
@Test
+ public void toIntArray() {
+ List<Integer> values = List.of(1, 2, 3);
+ ArraySet<Integer> set = new ArraySet<>(values);
+
+ expectWithMessage("Array converted from int array set")
+ .that(CarServiceUtils.toIntArray(set)).asList()
+ .containsExactlyElementsIn(values);
+ }
+
+ @Test
+ public void toIntArray_fails() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ CarServiceUtils.toIntArray(/* set= */ (ArraySet<Integer>) null)
+ );
+
+ expectWithMessage("Null int array set exception").that(thrown).hasMessageThat()
+ .contains("Int array set to converted to array must not be null");
+ }
+
+ @Test
public void asList() {
int[] values = {1, 2, 3};
diff --git a/tests/carservice_unit_test/src/com/android/car/CarUxRestrictionsConfigurationXmlParserTest.java b/tests/carservice_unit_test/src/com/android/car/CarUxRestrictionsConfigurationXmlParserTest.java
index 2daf2e8..f910185 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarUxRestrictionsConfigurationXmlParserTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarUxRestrictionsConfigurationXmlParserTest.java
@@ -199,7 +199,7 @@
CarUxRestrictionsConfigurationXmlParser.parse(
getContext(), R.xml.ux_restrictions_multiple_display_ports);
- assertEquals(configs.size(), 2);
+ assertThat(configs.size()).isEqualTo(2);
// 1 and 2 are specified in test xml.
Set<Integer> expected = new ArraySet<>();
diff --git a/tests/carservice_unit_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
index 0f58f41..37f4b3c 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
@@ -46,7 +46,6 @@
import android.car.drivingstate.CarDrivingStateEvent;
import android.car.drivingstate.CarUxRestrictions;
import android.car.drivingstate.CarUxRestrictionsConfiguration;
-import android.car.drivingstate.CarUxRestrictionsConfiguration.Builder;
import android.car.drivingstate.ICarDrivingStateChangeListener;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyEvent;
@@ -154,7 +153,7 @@
+ "\"restrictions\":511}],\"moving_restrictions\":[{\"req_opt\":true,"
+ "\"restrictions\":511}],\"unknown_restrictions\":[{\"req_opt\":true,"
+ "\"restrictions\":511}]}}]}";
- assertEquals(readFile(staged.toPath()), expectedConfig);
+ assertEquals(expectedConfig, readFile(staged.toPath()));
// Verify prod config file was not created.
assertFalse(new File(mTempSystemCarDir, CONFIG_FILENAME_PRODUCTION).exists());
}
@@ -179,20 +178,18 @@
CarUxRestrictions movingRestrictions = config.getUxRestrictions(DRIVING_STATE_MOVING,
MAX_SPEED);
assertTrue(movingRestrictions.isRequiresDistractionOptimization());
- assertEquals(movingRestrictions.getActiveRestrictions(),
- UX_RESTRICTIONS_FULLY_RESTRICTED);
+ assertEquals(UX_RESTRICTIONS_FULLY_RESTRICTED,
+ movingRestrictions.getActiveRestrictions());
CarUxRestrictions parkedRestrictions = config.getUxRestrictions(DRIVING_STATE_PARKED,
0f);
assertFalse(parkedRestrictions.isRequiresDistractionOptimization());
- assertEquals(parkedRestrictions.getActiveRestrictions(),
- UX_RESTRICTIONS_BASELINE);
+ assertEquals(UX_RESTRICTIONS_BASELINE, parkedRestrictions.getActiveRestrictions());
CarUxRestrictions idlingRestrictions = config.getUxRestrictions(DRIVING_STATE_IDLING,
0f);
assertFalse(idlingRestrictions.isRequiresDistractionOptimization());
- assertEquals(idlingRestrictions.getActiveRestrictions(),
- UX_RESTRICTIONS_BASELINE);
+ assertEquals(UX_RESTRICTIONS_BASELINE, idlingRestrictions.getActiveRestrictions());
}
}
@@ -214,7 +211,7 @@
CarUxRestrictionsConfiguration actual = mService.loadConfig().get(0);
- assertEquals(actual, expected);
+ assertEquals(expected, actual);
}
@Test
@@ -233,7 +230,7 @@
CarUxRestrictionsConfiguration actual = mService.loadConfig().get(0);
- CarUxRestrictionsConfiguration expectedConfig = new Builder()
+ CarUxRestrictionsConfiguration expectedConfig = new CarUxRestrictionsConfiguration.Builder()
.setPhysicalPort(1)
.setMaxContentDepth(2)
.setMaxCumulativeContentItems(20)
@@ -279,7 +276,7 @@
.setRestrictions(510)
.setMode(UX_RESTRICTION_MODE_PASSENGER))
.build();
- assertEquals(actual, expectedConfig);
+ assertEquals(expectedConfig, actual);
}
@Test
@@ -294,7 +291,7 @@
// Staged file should be moved as production.
assertFalse(staged.exists());
- assertEquals(actual, expected);
+ assertEquals(expected, actual);
}
@Test
@@ -309,7 +306,7 @@
// Staged file should be untouched.
assertTrue(staged.exists());
- assertEquals(actual, expected);
+ assertEquals(expected, actual);
}
@Test
@@ -324,7 +321,7 @@
// Staged file should be untouched.
assertTrue(staged.exists());
- assertEquals(actual, expected);
+ assertEquals(expected, actual);
}
@Test
@@ -588,7 +585,8 @@
}
private CarUxRestrictionsConfiguration createEmptyConfig(Integer port) {
- Builder builder = new Builder();
+ CarUxRestrictionsConfiguration.Builder builder =
+ new CarUxRestrictionsConfiguration.Builder();
if (port != null) {
builder.setPhysicalPort(port);
}
diff --git a/tests/carservice_unit_test/src/com/android/car/HidlVehicleStubUnitTest.java b/tests/carservice_unit_test/src/com/android/car/HidlVehicleStubUnitTest.java
index 38c4a96..a9c4ef0 100644
--- a/tests/carservice_unit_test/src/com/android/car/HidlVehicleStubUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/HidlVehicleStubUnitTest.java
@@ -100,7 +100,7 @@
private VehicleStub mHidlVehicleStub;
private AsyncGetSetRequest defaultVehicleStubAsyncRequest(HalPropValue value) {
- return new AsyncGetSetRequest(/* serviceRequestId=*/ 0, value, /* timeoutInMs= */ 1000);
+ return new AsyncGetSetRequest(/* serviceRequestId=*/ 0, value, /* timeoutUptimeMs= */ 1000);
}
@Before
@@ -153,7 +153,7 @@
doAnswer(inv -> {
getPropConfigsCallback callback = (getPropConfigsCallback) inv.getArgument(1);
- callback.onValues(StatusCode.INVALID_ARG, /* configs = */ null);
+ callback.onValues(StatusCode.INVALID_ARG, /* propConfigs = */ null);
return null;
}).when(mHidlVehicle).getPropConfigs(
eq(new ArrayList<>(Arrays.asList(VHAL_PROP_SUPPORTED_PROPERTY_IDS))), any());
@@ -278,7 +278,7 @@
doAnswer(inv -> {
getCallback callback = (getCallback) inv.getArgument(1);
- callback.onValues(StatusCode.INVALID_ARG, /* configs= */ null);
+ callback.onValues(StatusCode.INVALID_ARG, /* propValue= */ null);
return null;
}).when(mHidlVehicle).get(any(), any());
@@ -470,10 +470,10 @@
}).when(mHidlVehicle).get(any(), any());
HalPropValueBuilder builder = new HalPropValueBuilder(/* isAidl= */ false);
- HalPropValue newTestValue = builder.build(/* propId= */ 2, /* areaId= */ 0, TEST_VALUE);
+ HalPropValue newTestValue = builder.build(/* prop= */ 2, /* areaId= */ 0, TEST_VALUE);
AsyncGetSetRequest request0 = defaultVehicleStubAsyncRequest(TEST_PROP_VALUE);
AsyncGetSetRequest request1 = new AsyncGetSetRequest(
- /* serviceRequestId=*/ 1, newTestValue, /* timeoutInMs= */ 1000);
+ /* serviceRequestId=*/ 1, newTestValue, /* timeoutUptimeMs= */ 1000);
ArgumentCaptor<List<VehicleStub.GetVehicleStubAsyncResult>> argumentCaptor =
ArgumentCaptor.forClass(List.class);
diff --git a/tests/carservice_unit_test/src/com/android/car/OccupantAwarenessServiceTest.java b/tests/carservice_unit_test/src/com/android/car/OccupantAwarenessServiceTest.java
index 762610d..294f065 100644
--- a/tests/carservice_unit_test/src/com/android/car/OccupantAwarenessServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/OccupantAwarenessServiceTest.java
@@ -24,7 +24,6 @@
import android.hardware.automotive.occupant_awareness.IOccupantAwareness;
import android.hardware.automotive.occupant_awareness.IOccupantAwarenessClientCallback;
import android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus;
-import android.hardware.automotive.occupant_awareness.OccupantDetection;
import android.hardware.automotive.occupant_awareness.OccupantDetections;
import android.hardware.automotive.occupant_awareness.Role;
import android.os.RemoteException;
@@ -44,14 +43,12 @@
@RunWith(AndroidJUnit4.class)
@MediumTest
public final class OccupantAwarenessServiceTest {
- private static final int TIMESTAMP = 1234; // In milliseconds.
-
/**
* Mock implementation of {@link
* android.hardware.automotive.occupant_awareness.IOccupantAwareness} for testing the service
* and manager.
*/
- private class MockOasHal
+ private static final class MockOasHal
extends android.hardware.automotive.occupant_awareness.IOccupantAwareness.Stub {
private IOccupantAwarenessClientCallback mCallback;
private boolean mGraphIsRunning;
@@ -110,19 +107,6 @@
}
}
- /** Causes a status event to be generated with the specified detection event data. */
- public void fireDetectionEvent(OccupantAwarenessDetection detectionEvent)
- throws RemoteException {
- if (mCallback != null) {
- OccupantDetection detection = new OccupantDetection();
-
- OccupantDetections detections = new OccupantDetections();
- detections.timeStampMillis = TIMESTAMP;
- detections.detections = new OccupantDetection[] {detection};
- mCallback.onDetectionEvent(detections);
- }
- }
-
@Override
public int getInterfaceVersion() {
return this.VERSION;
diff --git a/tests/carservice_unit_test/src/com/android/car/SystemActivityMonitoringServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/SystemActivityMonitoringServiceUnitTest.java
index f18d2c9..29a7654 100644
--- a/tests/carservice_unit_test/src/com/android/car/SystemActivityMonitoringServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/SystemActivityMonitoringServiceUnitTest.java
@@ -91,18 +91,16 @@
@Test
public void testSetProcessGroupInFirstTry() throws Exception {
- setUpAssignPassengerActivityToFgGroup(/*enableResource=*/ true, /*hasDriverZone=*/ true,
- /*hasPassengerZones=*/ true);
+ setUpAssignPassengerActivityToFgGroup(/* enableResource= */ true, /* hasDriverZone= */ true,
+ /* hasPassengerZone= */ true);
mService.init();
- ProcessObserverCallback cb = verifyAndGetProcessObserverCallback();
-
mProcessGroupsForGet.add(ProcessHelper.THREAD_GROUP_TOP_APP);
int processPid = 1;
// appId does not matter here but better to avoid 0.
- int processUid = UserHandle.getUid(mNonCurrentUserId, /*appId=*/ 1);
- cb.onForegroundActivitiesChanged(processPid, processUid, /*foreground=*/ true);
+ int processUid = UserHandle.getUid(mNonCurrentUserId, /* appId= */ 1);
+ mService.handleFocusChanged(processPid, processUid);
verify(mICarServiceHelper).getProcessGroup(processPid);
verify(mICarServiceHelper).setProcessGroup(processPid, ProcessHelper.THREAD_GROUP_DEFAULT);
@@ -110,21 +108,19 @@
@Test
public void testSetProcessGroupInSecondTry() throws Exception {
- setUpAssignPassengerActivityToFgGroup(/*enableResource=*/ true, /*hasDriverZone=*/ true,
- /*hasPassengerZones=*/ true);
+ setUpAssignPassengerActivityToFgGroup(/* enableResource= */ true, /* hasDriverZone= */ true,
+ /* hasPassengerZone= */ true);
mService.init();
- ProcessObserverCallback cb = verifyAndGetProcessObserverCallback();
-
// 1st entry is not top app yet, so it will retry.
mProcessGroupsForGet.add(ProcessHelper.THREAD_GROUP_DEFAULT);
mProcessGroupsForGet.add(ProcessHelper.THREAD_GROUP_TOP_APP);
int processPid = 1;
// appId does not matter here but better to avoid 0.
- int processUid = UserHandle.getUid(mNonCurrentUserId, /*appId=*/ 1);
- cb.onForegroundActivitiesChanged(processPid, processUid, /*foreground=*/ true);
+ int processUid = UserHandle.getUid(mNonCurrentUserId, /* appId= */ 1);
+ mService.handleFocusChanged(processPid, processUid);
// Double the waiting time so that we have enough delay
waitForHandlerThreadToComplete(2 * PASSENGER_PROCESS_GROUP_SET_RETRY_TIMEOUT_MS);
@@ -136,19 +132,17 @@
@Test
public void testGetProcessGroupFailure() throws Exception {
- setUpAssignPassengerActivityToFgGroup(/*enableResource=*/ true, /*hasDriverZone=*/ true,
- /*hasPassengerZones=*/ true);
+ setUpAssignPassengerActivityToFgGroup(/* enableResource= */ true, /* hasDriverZone= */ true,
+ /* hasPassengerZone= */ true);
mService.init();
- ProcessObserverCallback cb = verifyAndGetProcessObserverCallback();
-
mThrowExceptionOnGetSetProcessGroup = true;
int processPid = 1;
// appId does not matter here but better to avoid 0.
- int processUid = UserHandle.getUid(mNonCurrentUserId, /*appId=*/ 1);
- cb.onForegroundActivitiesChanged(processPid, processUid, /*foreground=*/ true);
+ int processUid = UserHandle.getUid(mNonCurrentUserId, /* appId= */ 1);
+ mService.handleFocusChanged(processPid, processUid);
verify(mICarServiceHelper).getProcessGroup(processPid);
verify(mICarServiceHelper, never()).setProcessGroup(anyInt(), anyInt());
@@ -156,17 +150,15 @@
@Test
public void testIgnoreCurrentUser() throws Exception {
- setUpAssignPassengerActivityToFgGroup(/*enableResource=*/ true, /*hasDriverZone=*/ true,
- /*hasPassengerZones=*/ true);
+ setUpAssignPassengerActivityToFgGroup(/* enableResource= */ true, /* hasDriverZone= */ true,
+ /* hasPassengerZone= */ true);
mService.init();
- ProcessObserverCallback cb = verifyAndGetProcessObserverCallback();
-
int processPid = 1;
// appId does not matter here but better to avoid 0.
- int processUid = UserHandle.getUid(ActivityManager.getCurrentUser(), /*appId=*/ 1);
- cb.onForegroundActivitiesChanged(processPid, processUid, /*foreground=*/ true);
+ int processUid = UserHandle.getUid(ActivityManager.getCurrentUser(), /* appId= */ 1);
+ mService.handleFocusChanged(processPid, processUid);
verify(mICarServiceHelper, never()).getProcessGroup(processPid);
verify(mICarServiceHelper, never()).setProcessGroup(anyInt(), anyInt());
@@ -174,17 +166,15 @@
@Test
public void testIgnoreSystemUser() throws Exception {
- setUpAssignPassengerActivityToFgGroup(/*enableResource=*/ true, /*hasDriverZone=*/ true,
- /*hasPassengerZones=*/ true);
+ setUpAssignPassengerActivityToFgGroup(/* enableResource= */ true, /* hasDriverZone= */ true,
+ /* hasPassengerZone= */ true);
mService.init();
- ProcessObserverCallback cb = verifyAndGetProcessObserverCallback();
-
int processPid = 1;
// appId does not matter here but better to avoid 0.
- int processUid = UserHandle.getUid(UserHandle.USER_SYSTEM, /*appId=*/ 1);
- cb.onForegroundActivitiesChanged(processPid, processUid, /*foreground=*/ true);
+ int processUid = UserHandle.getUid(UserHandle.USER_SYSTEM, /* appId= */ 1);
+ mService.handleFocusChanged(processPid, processUid);
verify(mICarServiceHelper, never()).getProcessGroup(processPid);
verify(mICarServiceHelper, never()).setProcessGroup(anyInt(), anyInt());
@@ -192,26 +182,26 @@
@Test
public void testIgnoreWhenDisabledFromResource() throws Exception {
- doTestDisabledConfig(/*enableResource=*/ false, /*hasDriverZone=*/ true,
- /*hasPassengerZones=*/ true);
+ doTestDisabledConfig(/* enableResource= */ false, /* hasDriverZone= */ true,
+ /* hasPassengerZone= */ true);
}
@Test
public void testIgnoreWithoutDriverZone() throws Exception {
- doTestDisabledConfig(/*enableResource=*/ true, /*hasDriverZone=*/ false,
- /*hasPassengerZones=*/ true);
+ doTestDisabledConfig(/* enableResource= */ true, /* hasDriverZone= */ false,
+ /* hasPassengerZone= */ true);
}
@Test
public void testIgnoreWithoutPassengerZone() throws Exception {
- doTestDisabledConfig(/*enableResource=*/ true, /*hasDriverZone=*/ true,
- /*hasPassengerZones=*/ false);
+ doTestDisabledConfig(/* enableResource= */ true, /* hasDriverZone= */ true,
+ /* hasPassengerZone= */ false);
}
@Test
public void testRegisterProcessRunningStateCallback() {
- setUpAssignPassengerActivityToFgGroup(/*enableResource=*/ true, /*hasDriverZone=*/ true,
- /*hasPassengerZone=*/ true);
+ setUpAssignPassengerActivityToFgGroup(/* enableResource= */ true, /* hasDriverZone= */ true,
+ /* hasPassengerZone= */ true);
mService.init();
ProcessObserverCallback customCallback = mock(ProcessObserverCallback.class);
@@ -221,21 +211,21 @@
int processPid = 1;
// appId does not matter here but better to avoid 0.
- int processUid = UserHandle.getUid(ActivityManager.getCurrentUser(), /*appId=*/ 1);
+ int processUid = UserHandle.getUid(ActivityManager.getCurrentUser(), /* appId= */ 1);
- cb.onForegroundActivitiesChanged(processPid, processUid, /*foreground=*/ true);
+ cb.onForegroundActivitiesChanged(processPid, processUid, /* foregroundActivities= */ true);
// Double the waiting time so that we have enough delay
waitForHandlerThreadToComplete(2 * PASSENGER_PROCESS_GROUP_SET_RETRY_TIMEOUT_MS);
- verify(customCallback)
- .onForegroundActivitiesChanged(processPid, processUid, /*foreground=*/ true);
+ verify(customCallback).onForegroundActivitiesChanged(processPid, processUid,
+ /* foregroundActivities= */ true);
- cb.onForegroundActivitiesChanged(processPid, processUid, /*foreground=*/ false);
+ cb.onForegroundActivitiesChanged(processPid, processUid, /* foregroundActivities= */ false);
// Double the waiting time so that we have enough delay
waitForHandlerThreadToComplete(2 * PASSENGER_PROCESS_GROUP_SET_RETRY_TIMEOUT_MS);
- verify(customCallback)
- .onForegroundActivitiesChanged(processPid, processUid, /*foreground=*/ false);
+ verify(customCallback).onForegroundActivitiesChanged(processPid, processUid,
+ /* foregroundActivities= */ false);
cb.onProcessDied(processPid, processUid);
// Double the waiting time so that we have enough delay
@@ -246,8 +236,8 @@
@Test
public void testUnregisterProcessRunningStateCallback() {
- setUpAssignPassengerActivityToFgGroup(/*enableResource=*/ true, /*hasDriverZone=*/ true,
- /*hasPassengerZone=*/ true);
+ setUpAssignPassengerActivityToFgGroup(/* enableResource= */ true, /* hasDriverZone= */ true,
+ /* hasPassengerZone= */ true);
mService.init();
ProcessObserverCallback customCallback = mock(ProcessObserverCallback.class);
@@ -258,14 +248,14 @@
int processPid = 1;
// appId does not matter here but better to avoid 0.
- int processUid = UserHandle.getUid(ActivityManager.getCurrentUser(), /*appId=*/ 1);
+ int processUid = UserHandle.getUid(ActivityManager.getCurrentUser(), /* appId= */ 1);
- cb.onForegroundActivitiesChanged(processPid, processUid, /*foreground=*/ true);
+ cb.onForegroundActivitiesChanged(processPid, processUid, /* foregroundActivities= */ true);
// Double the waiting time so that we have enough delay
waitForHandlerThreadToComplete(2 * PASSENGER_PROCESS_GROUP_SET_RETRY_TIMEOUT_MS);
- verify(customCallback, never())
- .onForegroundActivitiesChanged(processPid, processUid, /*foreground=*/ true);
+ verify(customCallback, never()).onForegroundActivitiesChanged(processPid, processUid,
+ /* foregroundActivities= */ true);
}
private void setUpAssignPassengerActivityToFgGroup(boolean enableResource,
@@ -289,13 +279,11 @@
mService.init();
- ProcessObserverCallback cb = verifyAndGetProcessObserverCallback();
-
mProcessGroupsForGet.add(ProcessHelper.THREAD_GROUP_TOP_APP);
int processPid = 1;
// appId does not matter here but better to avoid 0.
- int processUid = UserHandle.getUid(mNonCurrentUserId, /*appId=*/ 1);
- cb.onForegroundActivitiesChanged(processPid, processUid, /*foreground=*/ true);
+ int processUid = UserHandle.getUid(mNonCurrentUserId, /* appId= */ 1);
+ mService.handleFocusChanged(processPid, processUid);
verify(mICarServiceHelper, never()).getProcessGroup(processPid);
verify(mICarServiceHelper, never()).setProcessGroup(processPid,
diff --git a/tests/carservice_unit_test/src/com/android/car/admin/CarDevicePolicyServiceTest.java b/tests/carservice_unit_test/src/com/android/car/admin/CarDevicePolicyServiceTest.java
index 0d84503..7f67f14 100644
--- a/tests/carservice_unit_test/src/com/android/car/admin/CarDevicePolicyServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/admin/CarDevicePolicyServiceTest.java
@@ -33,7 +33,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.car.SyncResultCallback;
@@ -49,10 +48,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.pm.UserInfo.UserInfoFlag;
import android.os.UserHandle;
-import android.os.UserManager;
import android.util.Log;
import com.android.car.BuiltinPackageDependency;
@@ -154,24 +150,20 @@
@Test
public void testCreateUser_ok_normalUser() {
- createUserOkTest(/* userInfoFlags=*/ 0, CarDevicePolicyManager.USER_TYPE_REGULAR,
- UserManager.USER_TYPE_FULL_SECONDARY);
+ createUserOkTest(CarDevicePolicyManager.USER_TYPE_REGULAR);
}
@Test
public void testCreateUser_ok_admin() {
- createUserOkTest(UserInfo.FLAG_ADMIN, CarDevicePolicyManager.USER_TYPE_ADMIN,
- UserManager.USER_TYPE_FULL_SECONDARY);
+ createUserOkTest(CarDevicePolicyManager.USER_TYPE_ADMIN);
}
@Test
public void testCreateUser_ok_guest() {
- createUserOkTest(/* userInfoFlags=*/ 0, CarDevicePolicyManager.USER_TYPE_GUEST,
- UserManager.USER_TYPE_FULL_GUEST);
+ createUserOkTest(CarDevicePolicyManager.USER_TYPE_GUEST);
}
- private void createUserOkTest(@UserInfoFlag int flags,
- @CarDevicePolicyManager.UserType int carDpmUserType, @NonNull String userType) {
+ private void createUserOkTest(@CarDevicePolicyManager.UserType int carDpmUserType) {
mService.createUser("name", carDpmUserType, mUserCreationResultCallbackImpl);
UserCreationRequest.Builder userCreationRequestBuilder =
new UserCreationRequest.Builder().setName("name");
diff --git a/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperValidImportanceTest.java b/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperValidImportanceTest.java
index 7997954..8c34644 100644
--- a/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperValidImportanceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperValidImportanceTest.java
@@ -107,7 +107,7 @@
NotificationChannel channel = captor.getValue();
assertWithMessage("channel id").that(channel.getId()).isEqualTo(mChannelId);
assertWithMessage("importance").that(channel.getImportance()).isEqualTo(mImportance);
- assertWithMessage("name").that(channel.getName()).isEqualTo(IMPORTANCE_NAME);
+ assertWithMessage("name").that(channel.getName().toString()).isEqualTo(IMPORTANCE_NAME);
}
@Parameterized.Parameters
diff --git a/tests/carservice_unit_test/src/com/android/car/am/CarActivityServiceTaskMonitorUnitTest.java b/tests/carservice_unit_test/src/com/android/car/am/CarActivityServiceTaskMonitorUnitTest.java
index fcf37be..a67b918 100644
--- a/tests/carservice_unit_test/src/com/android/car/am/CarActivityServiceTaskMonitorUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/am/CarActivityServiceTaskMonitorUnitTest.java
@@ -53,6 +53,7 @@
import androidx.test.filters.MediumTest;
+import com.android.compatibility.common.util.PollingCheck;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -88,7 +89,8 @@
private static final int SLEEP_MS = 50;
private static final long SHORT_MIRRORING_TOKEN_TIMEOUT_MS = 100;
- private static CopyOnWriteArrayList<Activity> sTestActivities = new CopyOnWriteArrayList<>();
+ private static final CopyOnWriteArrayList<TempActivity> sTestActivities =
+ new CopyOnWriteArrayList<>();
private CarActivityService mService;
@Mock
@@ -123,13 +125,15 @@
}
@After
- public void tearDown() {
+ public void tearDown() throws InterruptedException {
tearDownTaskOrganizer();
- for (Activity activity : sTestActivities) {
+ for (TempActivity activity : sTestActivities) {
activity.finish();
+ activity.waitForDestroyed();
}
+ sTestActivities.clear();
mService.unregisterTaskMonitor(mToken);
- // Any remaining ActivityLaunchListeners will be flushed in release().
+ // Any remaining ActivityListeners will be flushed in release().
mService.release();
mService = null;
}
@@ -175,51 +179,71 @@
}
@Test
- public void testActivityLaunch() throws Exception {
- startActivityAndAssertLaunched(mActivityA);
+ public void testActivityCameOnTop() throws Exception {
+ startActivityAndAssertCameOnTop(mActivityA);
- startActivityAndAssertLaunched(mActivityB);
+ startActivityAndAssertCameOnTop(mActivityB);
}
@Test
- public void testMultipleActivityLaunchListeners() throws Exception {
- FilteredLaunchListener listener1 = new FilteredLaunchListener(mActivityA);
- mService.registerActivityLaunchListener(listener1);
- FilteredLaunchListener listener2 = new FilteredLaunchListener(mActivityA);
- mService.registerActivityLaunchListener(listener2);
+ public void testActivityChangedInBackstackOnTaskInfoChanged() throws Exception {
+ FilteredListener listener = startActivityAndAssertCameOnTop(mActivityA);
- startActivity(mActivityA, Display.DEFAULT_DISPLAY);
+ // When some activity is launched from another, the baseIntent of the activity becomes
+ // the launching activity due to which an onActivityLaunched callback is received. The
+ // purpose of launching home here is that the baseIntent is not set and the correct
+ // onActivityChanged callback is received.
+ launchHomeScreenUsingIntent();
- listener2.assertTopTaskActivityLaunched();
- assertThat(listener1.mActivityLaunched.getCount()).isEqualTo(0);
+ listener.assertTopTaskActivityChangedInBackstack();
+ }
+
+ private void launchHomeScreenUsingIntent() {
+ Intent intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getTestContext().startActivity(intent);
}
@Test
- public void testUnregisterActivityLaunchListener() throws Exception {
- FilteredLaunchListener listener1 = new FilteredLaunchListener(mActivityA);
- mService.registerActivityLaunchListener(listener1);
- FilteredLaunchListener listener2 = new FilteredLaunchListener(mActivityA);
- mService.registerActivityLaunchListener(listener2);
- mService.unregisterActivityLaunchListener(listener1);
+ public void testMultipleActivityListeners() throws Exception {
+ FilteredListener listener1 = new FilteredListener(mActivityA);
+ mService.registerActivityListener(listener1);
+ FilteredListener listener2 = new FilteredListener(mActivityA);
+ mService.registerActivityListener(listener2);
startActivity(mActivityA, Display.DEFAULT_DISPLAY);
- listener2.assertTopTaskActivityLaunched();
- assertThat(listener1.mActivityLaunched.getCount()).isEqualTo(1);
+ listener2.assertTopTaskActivityCameOnTop();
+ assertThat(listener1.mActivityCameOnTop.getCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testUnregisterActivityListener() throws Exception {
+ FilteredListener listener1 = new FilteredListener(mActivityA);
+ mService.registerActivityListener(listener1);
+ FilteredListener listener2 = new FilteredListener(mActivityA);
+ mService.registerActivityListener(listener2);
+ mService.unregisterActivityListener(listener1);
+
+ startActivity(mActivityA, Display.DEFAULT_DISPLAY);
+
+ listener2.assertTopTaskActivityCameOnTop();
+ assertThat(listener1.mActivityCameOnTop.getCount()).isEqualTo(1);
}
@Test
public void testDeathRecipientIsSet() throws Exception {
- FilteredLaunchListener listenerA = new FilteredLaunchListener(mActivityA);
- mService.registerActivityLaunchListener(listenerA);
+ FilteredListener listenerA = new FilteredListener(mActivityA);
+ mService.registerActivityListener(listenerA);
verify(mToken).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
}
@Test
public void testBinderDied_cleansUpDeathRecipient() throws Exception {
- FilteredLaunchListener listenerA = new FilteredLaunchListener(mActivityA);
- mService.registerActivityLaunchListener(listenerA);
+ FilteredListener listenerA = new FilteredListener(mActivityA);
+ mService.registerActivityListener(listenerA);
verify(mToken).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
mDeathRecipientCaptor.getValue().binderDied();
@@ -229,8 +253,8 @@
startActivity(mActivityA);
// Starting a Activity shouldn't trigger the listener since the token is invalid.
- assertWithMessage("Shouldn't trigger the ActivityLaunched listener")
- .that(listenerA.waitForTopTaskActivityLaunched(NO_ACTIVITY_TIMEOUT_MS)).isFalse();
+ assertWithMessage("Shouldn't trigger the ActivityListener")
+ .that(listenerA.waitForTopTaskActivityCameOnTop(NO_ACTIVITY_TIMEOUT_MS)).isFalse();
}
@Test
@@ -238,26 +262,26 @@
Intent blockingIntent = new Intent().setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
blockingIntent.setComponent(mBlockingActivity);
- // start a black listed activity
- FilteredLaunchListener listenerDenyListed = startActivityAndAssertLaunched(mActivityC);
+ // start a deny listed activity
+ FilteredListener listenerDenyListed = startActivityAndAssertCameOnTop(mActivityC);
// Instead of start activity, invoke blockActivity.
- FilteredLaunchListener listenerBlocking = new FilteredLaunchListener(mBlockingActivity);
- mService.registerActivityLaunchListener(listenerBlocking);
+ FilteredListener listenerBlocking = new FilteredListener(mBlockingActivity);
+ mService.registerActivityListener(listenerBlocking);
mService.blockActivity(listenerDenyListed.mTopTask, blockingIntent);
- listenerBlocking.assertTopTaskActivityLaunched();
+ listenerBlocking.assertTopTaskActivityCameOnTop();
}
@Test
public void testRemovesFromTopTasks() throws Exception {
- FilteredLaunchListener listenerA = new FilteredLaunchListener(mActivityA);
- mService.registerActivityLaunchListener(listenerA);
+ FilteredListener listenerA = new FilteredListener(mActivityA);
+ mService.registerActivityListener(listenerA);
Activity launchedActivity = startActivity(mActivityA);
- listenerA.assertTopTaskActivityLaunched();
+ listenerA.assertTopTaskActivityCameOnTop();
assertTrue(topTasksHasComponent(mActivityA));
getInstrumentation().runOnMainSync(launchedActivity::finish);
- waitUntil(() -> !topTasksHasComponent(mActivityA));
+ listenerA.assertTopTaskActivityChangedInBackstack();
}
@Test
@@ -267,14 +291,14 @@
int virtualDisplayId = session.createDisplayWithDefaultDisplayMetricsAndWait(
getTestContext(), /* isPrivate= */ false).getDisplayId();
- startActivityAndAssertLaunched(mActivityA);
+ startActivityAndAssertCameOnTop(mActivityA);
assertTrue(topTasksHasComponent(mActivityA));
- startActivityAndAssertLaunched(mActivityB, virtualDisplayId);
+ startActivityAndAssertCameOnTop(mActivityB, virtualDisplayId);
assertTrue(topTasksHasComponent(mActivityB));
assertTrue(topTasksHasComponent(mActivityA));
- startActivityAndAssertLaunched(mActivityC, virtualDisplayId);
+ startActivityAndAssertCameOnTop(mActivityC, virtualDisplayId);
assertTrue(topTasksHasComponent(mActivityC));
assertFalse(topTasksHasComponent(mActivityB));
assertTrue(topTasksHasComponent(mActivityA));
@@ -283,17 +307,17 @@
@Test
public void testGetTopTasksOnDefaultDisplay() throws Exception {
- startActivityAndAssertLaunched(mActivityA);
+ startActivityAndAssertCameOnTop(mActivityA);
assertTrue(topTasksHasComponent(mActivityA));
- startActivityAndAssertLaunched(mActivityB);
+ startActivityAndAssertCameOnTop(mActivityB);
assertTrue(topTasksHasComponent(mActivityB));
assertFalse(topTasksHasComponent(mActivityA));
}
@Test
public void testGetTaskInfoForTopActivity() throws Exception {
- startActivityAndAssertLaunched(mActivityA);
+ startActivityAndAssertCameOnTop(mActivityA);
TaskInfo taskInfo = mService.getTaskInfoForTopActivity(mActivityA);
assertNotNull(taskInfo);
@@ -302,19 +326,19 @@
@Test
public void testRestartTask() throws Exception {
- startActivityAndAssertLaunched(mActivityA);
+ startActivityAndAssertCameOnTop(mActivityA);
- startActivityAndAssertLaunched(mActivityB);
+ startActivityAndAssertCameOnTop(mActivityB);
- FilteredLaunchListener listenerRestartA = new FilteredLaunchListener(mActivityA);
- mService.registerActivityLaunchListener(listenerRestartA);
+ FilteredListener listenerRestartA = new FilteredListener(mActivityA);
+ mService.registerActivityListener(listenerRestartA);
// ActivityA and ActivityB are in the same package, so ActivityA becomes the root task of
// ActivityB, so when we restarts ActivityB, it'll start ActivityA.
TaskInfo taskInfo = mService.getTaskInfoForTopActivity(mActivityB);
mService.restartTask(taskInfo.taskId);
- listenerRestartA.assertTopTaskActivityLaunched();
+ listenerRestartA.assertTopTaskActivityCameOnTop();
}
@Test
@@ -326,7 +350,7 @@
@Test
public void testCreateMirroredToken_returnsToken() throws Exception {
- FilteredLaunchListener listenerA = startActivityAndAssertLaunched(mActivityA);
+ FilteredListener listenerA = startActivityAndAssertCameOnTop(mActivityA);
IBinder token = mService.createTaskMirroringToken(listenerA.mTopTask.taskId);
assertThat(token).isNotNull();
@@ -351,7 +375,7 @@
@Test
public void testGetMirroredSurface_throwsExceptionForExpiredToken() throws Exception {
- FilteredLaunchListener listenerA = startActivityAndAssertLaunched(mActivityA);
+ FilteredListener listenerA = startActivityAndAssertCameOnTop(mActivityA);
IBinder token = mService.createTaskMirroringToken(listenerA.mTopTask.taskId);
Rect outBounds = new Rect();
@@ -364,21 +388,22 @@
@Test
public void testGetMirroredSurface_returnsNullForInvisibleToken() throws Exception {
- FilteredLaunchListener listenerA = startActivityAndAssertLaunched(mActivityA);
+ FilteredListener listenerA = startActivityAndAssertCameOnTop(mActivityA);
IBinder token = mService.createTaskMirroringToken(listenerA.mTopTask.taskId);
// Uses the Activity with the different taskAffinity to make the previous Task hidden.
- startActivityAndAssertLaunched(mBlockingActivity);
+ startActivityAndAssertCameOnTop(mBlockingActivity);
// Now the Surface of the token will be invisible.
Rect outBounds = new Rect();
- assertThat(mService.getMirroredSurface(token, outBounds)).isNull();
+ PollingCheck.waitFor(() -> mService.getMirroredSurface(token, outBounds) == null,
+ "The mirrored surface couldn't become invisible");
}
@Test
public void testGetMirroredSurface_returnsSurface() throws Exception {
- FilteredLaunchListener listenerA = startActivityAndAssertLaunched(mActivityA);
+ FilteredListener listenerA = startActivityAndAssertCameOnTop(mActivityA);
IBinder token = mService.createTaskMirroringToken(listenerA.mTopTask.taskId);
Rect outBounds = new Rect();
@@ -392,17 +417,17 @@
assertThat(mirror.isValid()).isTrue();
}
- private FilteredLaunchListener startActivityAndAssertLaunched(ComponentName activity)
+ private FilteredListener startActivityAndAssertCameOnTop(ComponentName activity)
throws InterruptedException {
- return startActivityAndAssertLaunched(activity, Display.DEFAULT_DISPLAY);
+ return startActivityAndAssertCameOnTop(activity, Display.DEFAULT_DISPLAY);
}
- private FilteredLaunchListener startActivityAndAssertLaunched(
+ private FilteredListener startActivityAndAssertCameOnTop(
ComponentName activity, int displayId) throws InterruptedException {
- FilteredLaunchListener listener = new FilteredLaunchListener(activity);
- mService.registerActivityLaunchListener(listener);
+ FilteredListener listener = new FilteredListener(activity);
+ mService.registerActivityListener(listener);
startActivity(activity, displayId);
- listener.assertTopTaskActivityLaunched();
+ listener.assertTopTaskActivityCameOnTop();
return listener;
}
@@ -426,11 +451,23 @@
/** Activity that closes itself after some timeout to clean up the screen. */
public static class TempActivity extends Activity {
+ private final CountDownLatch mDestroyed = new CountDownLatch(1);
+ private static final long QUIET_TIME_TO_BE_CONSIDERED_IDLE_STATE = 1000; // ms
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sTestActivities.add(this);
}
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mDestroyed.countDown();
+ }
+
+ private boolean waitForDestroyed() throws InterruptedException {
+ return mDestroyed.await(QUIET_TIME_TO_BE_CONSIDERED_IDLE_STATE, TimeUnit.MILLISECONDS);
+ }
}
public static class ActivityA extends TempActivity {}
@@ -471,47 +508,77 @@
return monitor.waitForActivityWithTimeout(ACTIVITY_TIMEOUT_MS);
}
- private class FilteredLaunchListener
- implements CarActivityService.ActivityLaunchListener {
-
+ private static final class FilteredListener implements CarActivityService.ActivityListener {
private final ComponentName mDesiredComponent;
- private final CountDownLatch mActivityLaunched = new CountDownLatch(1);
+ private final CountDownLatch mActivityCameOnTop = new CountDownLatch(1);
+ private final CountDownLatch mActivityChangedInBackstack = new CountDownLatch(1);
private TaskInfo mTopTask;
/**
- * Creates an instance of an
- * {@link com.android.car.am.CarActivityService.ActivityLaunchListener}
+ * Creates an instance of a {@link CarActivityService.ActivityListener}
* that filters based on the component name or does not filter if component name is null.
*/
- private FilteredLaunchListener(@NonNull ComponentName desiredComponent) {
+ private FilteredListener(@NonNull ComponentName desiredComponent) {
mDesiredComponent = desiredComponent;
}
@Override
- public void onActivityLaunch(TaskInfo topTask) {
- // Ignore activities outside of this test case
- if (!getTestContext().getPackageName().equals(topTask.topActivity.getPackageName())) {
- Log.d(TAG, "Component launched from other package: "
- + topTask.topActivity.getClassName());
+ public void onActivityCameOnTop(TaskInfo topTask) {
+ if (isActivityOutsideTestPackage(topTask)) {
return;
}
if (!topTask.topActivity.equals(mDesiredComponent)) {
- Log.d(TAG, String.format("Unexpected component: %s. Expected: %s",
- topTask.topActivity.getClassName(), mDesiredComponent));
+ Log.d(TAG,
+ String.format("onActivityCameOnTop#Unexpected component: %s. Expected: %s",
+ topTask.topActivity.getClassName(), mDesiredComponent));
return;
}
if (mTopTask == null) { // We are interested in the first one only.
mTopTask = topTask;
}
- mActivityLaunched.countDown();
+ mActivityCameOnTop.countDown();
}
- private void assertTopTaskActivityLaunched() throws InterruptedException {
- assertThat(waitForTopTaskActivityLaunched(DEFAULT_TIMEOUT_MS)).isTrue();
+ @Override
+ public void onActivityChangedInBackstack(TaskInfo taskInfo) {
+ if (isActivityOutsideTestPackage(taskInfo)) {
+ return;
+ }
+ if (!taskInfo.baseIntent.getComponent().equals(mDesiredComponent)) {
+ Log.d(TAG, String.format(
+ "onActivityChangedInBackstack#Unexpected component: %s. Expected: %s",
+ taskInfo.baseIntent.getComponent(), mDesiredComponent));
+ return;
+ }
+ mActivityChangedInBackstack.countDown();
}
- private boolean waitForTopTaskActivityLaunched(long timeoutMs) throws InterruptedException {
- return mActivityLaunched.await(timeoutMs, TimeUnit.MILLISECONDS);
+ private boolean isActivityOutsideTestPackage(TaskInfo taskInfo) {
+ if (taskInfo.topActivity != null && !getTestContext().getPackageName().equals(
+ taskInfo.topActivity.getPackageName())) {
+ Log.d(TAG, "Component launched from other package: "
+ + taskInfo.topActivity.getClassName());
+ return true;
+ }
+ return false;
+ }
+
+ private void assertTopTaskActivityCameOnTop() throws InterruptedException {
+ assertThat(waitForTopTaskActivityCameOnTop(DEFAULT_TIMEOUT_MS)).isTrue();
+ }
+
+ private boolean waitForTopTaskActivityCameOnTop(long timeoutMs)
+ throws InterruptedException {
+ return mActivityCameOnTop.await(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ private void assertTopTaskActivityChangedInBackstack() throws InterruptedException {
+ assertThat(waitForTopTaskActivityChangedInBackstack(DEFAULT_TIMEOUT_MS)).isTrue();
+ }
+
+ private boolean waitForTopTaskActivityChangedInBackstack(long timeoutMs)
+ throws InterruptedException {
+ return mActivityChangedInBackstack.await(timeoutMs, TimeUnit.MILLISECONDS);
}
}
}
diff --git a/tests/carservice_unit_test/src/com/android/car/am/FixedActivityServiceTest.java b/tests/carservice_unit_test/src/com/android/car/am/FixedActivityServiceTest.java
index b3e887c..7745e95 100644
--- a/tests/carservice_unit_test/src/com/android/car/am/FixedActivityServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/am/FixedActivityServiceTest.java
@@ -125,11 +125,11 @@
}
@After
- public void tearDown() {
+ public void tearDown() throws Exception {
if (mFixedActivityService != null) {
mFixedActivityService.release();
}
- CarServiceUtils.finishAllHandlerTasks();
+ CarServiceUtils.quitHandlerThreads();
}
@Test
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/AudioDeviceInfoBuilder.java b/tests/carservice_unit_test/src/com/android/car/audio/AudioDeviceInfoBuilder.java
index 5555439..a0f4e38 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/AudioDeviceInfoBuilder.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/AudioDeviceInfoBuilder.java
@@ -16,6 +16,8 @@
package com.android.car.audio;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -23,6 +25,9 @@
import android.media.AudioDevicePort;
import android.media.AudioGain;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public final class AudioDeviceInfoBuilder {
private String mAddressName;
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/AudioManagerWrapperTest.java b/tests/carservice_unit_test/src/com/android/car/audio/AudioManagerWrapperTest.java
new file mode 100644
index 0000000..d3744f5
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/AudioManagerWrapperTest.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.audio;
+
+import static android.media.AudioAttributes.USAGE_EMERGENCY;
+import static android.media.AudioAttributes.USAGE_MEDIA;
+import static android.media.AudioAttributes.USAGE_SAFETY;
+import static android.media.AudioManager.ADJUST_LOWER;
+import static android.media.AudioManager.AUDIOFOCUS_GAIN;
+import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+import static android.media.AudioManager.FLAG_FROM_KEY;
+import static android.media.AudioManager.FLAG_SHOW_UI;
+import static android.media.AudioManager.GET_DEVICES_OUTPUTS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.car.builtin.media.AudioManagerHelper;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFocusInfo;
+import android.media.AudioFocusRequest;
+import android.media.AudioManager;
+import android.media.AudioManager.AudioPlaybackCallback;
+import android.media.AudioManager.AudioServerStateCallback;
+import android.media.AudioPlaybackConfiguration;
+import android.media.FadeManagerConfiguration;
+import android.media.audiopolicy.AudioPolicy;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.media.audiopolicy.AudioVolumeGroup;
+import android.os.Handler;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.CarLog;
+
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public final class AudioManagerWrapperTest extends AbstractExtendedMockitoTestCase {
+
+ private static final String TAG = CarLog.TAG_AUDIO;
+ private static final int TEST_GROUP_ID = 4;
+ private static final int TEST_MIN_VOLUME_INDEX = 1;
+ private static final int TEST_MAX_VOLUME_INDEX = 9001;
+ private static final int TEST_VOLUME_INDEX = 8675309;
+ private static final boolean TEST_MUTED_STATE = true;
+ private static final int TEST_SET_INDEX = 13;
+ private static final int TEST_FLAGS = 0;
+ private static final String TEST_ADDRESS = "bus_to_nowhere";
+ private static final int TEST_GAIN = -640;
+ private static final boolean TEST_IS_OUTPUT = true;
+ private static final boolean TEST_GAIN_SET_STATE = true;
+
+ @Mock
+ private AudioManager mAudioManager;
+ @Mock
+ private AudioProductStrategy mTestStrategy;
+ @Mock
+ private AudioDeviceAttributes mTestDeviceAttributes;
+ @Mock
+ private AudioPolicy mAudioPolicy;
+
+ private AudioManagerWrapper mAudioManagerWrapper;
+ private AudioAttributes mTestAudioAttributes;
+
+ public AudioManagerWrapperTest() {
+ super(AudioManagerWrapperTest.TAG);
+ }
+
+ @Override
+ protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
+ session.spyStatic(AudioManager.class)
+ .spyStatic(AudioManagerHelper.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mTestAudioAttributes = CarAudioContext.getAudioAttributeFromUsage(USAGE_MEDIA);
+ when(mAudioManager.getMinVolumeIndexForAttributes(mTestAudioAttributes))
+ .thenReturn(TEST_MIN_VOLUME_INDEX);
+ when(mAudioManager.getMaxVolumeIndexForAttributes(mTestAudioAttributes))
+ .thenReturn(TEST_MAX_VOLUME_INDEX);
+ when(mAudioManager.getVolumeIndexForAttributes(mTestAudioAttributes))
+ .thenReturn(TEST_VOLUME_INDEX);
+ when(mAudioManager.getLastAudibleVolumeForVolumeGroup(TEST_GROUP_ID))
+ .thenReturn(TEST_VOLUME_INDEX);
+ doReturn(TEST_MUTED_STATE)
+ .when(() -> AudioManagerHelper.isVolumeGroupMuted(eq(mAudioManager),
+ eq(TEST_GROUP_ID)));
+ doReturn(TEST_GAIN_SET_STATE).when(() -> AudioManagerHelper
+ .setAudioDeviceGain(eq(mAudioManager), eq(TEST_ADDRESS),
+ eq(TEST_GAIN), eq(TEST_IS_OUTPUT)));
+ doReturn(TEST_MUTED_STATE)
+ .when(() -> AudioManagerHelper.isMasterMute(eq(mAudioManager)));
+
+ mAudioManagerWrapper = new AudioManagerWrapper(mAudioManager);
+ }
+
+ @Test
+ public void getMinVolumeIndexForAttributes() {
+ expectWithMessage("Min volume index")
+ .that(mAudioManagerWrapper.getMinVolumeIndexForAttributes(mTestAudioAttributes))
+ .isEqualTo(TEST_MIN_VOLUME_INDEX);
+ }
+
+ @Test
+ public void getMaxVolumeIndexForAttributes() {
+ expectWithMessage("Max volume index")
+ .that(mAudioManagerWrapper.getMaxVolumeIndexForAttributes(mTestAudioAttributes))
+ .isEqualTo(TEST_MAX_VOLUME_INDEX);
+ }
+
+ @Test
+ public void getVolumeIndexForAttributes() {
+ expectWithMessage("Volume index")
+ .that(mAudioManagerWrapper.getVolumeIndexForAttributes(mTestAudioAttributes))
+ .isEqualTo(TEST_VOLUME_INDEX);
+ }
+
+ @Test
+ public void isVolumeGroupMuted() {
+ expectWithMessage("Volume group muted state")
+ .that(mAudioManagerWrapper.isVolumeGroupMuted(TEST_GROUP_ID))
+ .isTrue();
+ }
+
+ @Test
+ public void getLastAudibleVolumeForVolumeGroup() {
+ expectWithMessage("Last audible volume")
+ .that(mAudioManagerWrapper.getLastAudibleVolumeForVolumeGroup(TEST_GROUP_ID))
+ .isEqualTo(TEST_VOLUME_INDEX);
+ }
+
+ @Test
+ public void setVolumeGroupVolumeIndex() {
+ mAudioManagerWrapper.setVolumeGroupVolumeIndex(TEST_GROUP_ID, TEST_SET_INDEX, TEST_FLAGS);
+
+ verify(mAudioManager).setVolumeGroupVolumeIndex(TEST_GROUP_ID, TEST_SET_INDEX, TEST_FLAGS);
+ }
+
+ @Test
+ public void adjustVolumeGroupVolume() {
+ mAudioManagerWrapper.adjustVolumeGroupVolume(TEST_GROUP_ID, ADJUST_LOWER, TEST_FLAGS);
+
+ verify(mAudioManager).adjustVolumeGroupVolume(TEST_GROUP_ID, ADJUST_LOWER, TEST_FLAGS);
+ }
+
+ @Test
+ public void setPreferredDeviceForStrategy() {
+ mAudioManagerWrapper.setPreferredDeviceForStrategy(mTestStrategy, mTestDeviceAttributes);
+
+ verify(mAudioManager).setPreferredDeviceForStrategy(mTestStrategy, mTestDeviceAttributes);
+ }
+
+ @Test
+ public void setAudioDeviceGain() {
+ expectWithMessage("Volume gain set state")
+ .that(mAudioManagerWrapper
+ .setAudioDeviceGain(TEST_ADDRESS, TEST_GAIN, TEST_IS_OUTPUT));
+ }
+
+ @Test
+ public void requestAudioFocus() {
+ AudioFocusRequest request = Mockito.mock(AudioFocusRequest.class);
+
+ mAudioManagerWrapper.requestAudioFocus(request);
+
+ verify(mAudioManager).requestAudioFocus(request);
+ }
+
+ @Test
+ public void abandonAudioFocusRequest() {
+ AudioFocusRequest request = Mockito.mock(AudioFocusRequest.class);
+
+ mAudioManagerWrapper.abandonAudioFocusRequest(request);
+
+ verify(mAudioManager).abandonAudioFocusRequest(request);
+ }
+
+ @Test
+ public void isMasterMuted() {
+ expectWithMessage("Master mute state").that(mAudioManagerWrapper.isMasterMuted())
+ .isEqualTo(TEST_MUTED_STATE);
+ }
+
+ @Test
+ public void setMasterMute() {
+ boolean mute = true;
+ int flags = FLAG_SHOW_UI;
+
+ mAudioManagerWrapper.setMasterMute(mute, flags);
+
+ verify(mAudioManager).setMasterMute(mute, flags);
+ }
+
+ @Test
+ public void setFocusRequestResult() {
+ AudioFocusInfo info = Mockito.mock(AudioFocusInfo.class);
+
+ mAudioManagerWrapper.setFocusRequestResult(info, AUDIOFOCUS_GAIN, mAudioPolicy);
+
+ verify(mAudioManager).setFocusRequestResult(info, AUDIOFOCUS_GAIN, mAudioPolicy);
+ }
+
+ @Test
+ public void dispatchAudioFocusChange() {
+ AudioFocusInfo info = Mockito.mock(AudioFocusInfo.class);
+ when(mAudioManagerWrapper.dispatchAudioFocusChange(any(), anyInt(), any()))
+ .thenReturn(AUDIOFOCUS_REQUEST_GRANTED);
+
+ int results = mAudioManagerWrapper.dispatchAudioFocusChange(info, AUDIOFOCUS_GAIN,
+ mAudioPolicy);
+
+ expectWithMessage("Audio focus dispatch results").that(results)
+ .isEqualTo(AUDIOFOCUS_REQUEST_GRANTED);
+ verify(mAudioManager).dispatchAudioFocusChange(info, AUDIOFOCUS_GAIN, mAudioPolicy);
+ }
+
+ @Test
+ public void dispatchAudioFocusChangeWithFade() {
+ AudioFocusInfo info = Mockito.mock(AudioFocusInfo.class);
+ List<AudioFocusInfo> activeInfos = List.of();
+ FadeManagerConfiguration config = Mockito.mock(FadeManagerConfiguration.class);
+ when(mAudioManagerWrapper.dispatchAudioFocusChangeWithFade(any(),
+ anyInt(), any(), any(), any())).thenReturn(AUDIOFOCUS_REQUEST_GRANTED);
+
+ int results = mAudioManagerWrapper.dispatchAudioFocusChangeWithFade(info, AUDIOFOCUS_GAIN,
+ mAudioPolicy, activeInfos, config);
+
+ expectWithMessage("Audio focus with fade dispatch results").that(results)
+ .isEqualTo(AUDIOFOCUS_REQUEST_GRANTED);
+ verify(mAudioManager).dispatchAudioFocusChangeWithFade(info, AUDIOFOCUS_GAIN, mAudioPolicy,
+ activeInfos, config);
+ }
+
+ @Test
+ public void registerVolumeGroupCallback() {
+ CoreAudioVolumeGroupCallback callback = Mockito.mock(CoreAudioVolumeGroupCallback.class);
+ Executor executor = Mockito.mock(Executor.class);
+
+ mAudioManagerWrapper.registerVolumeGroupCallback(executor, callback);
+
+ verify(mAudioManager).registerVolumeGroupCallback(executor, callback);
+ }
+
+ @Test
+ public void unregisterVolumeGroupCallback() {
+ CoreAudioVolumeGroupCallback callback = Mockito.mock(CoreAudioVolumeGroupCallback.class);
+
+ mAudioManagerWrapper.unregisterVolumeGroupCallback(callback);
+
+ verify(mAudioManager).unregisterVolumeGroupCallback(callback);
+ }
+
+ @Test
+ public void isAudioServerRunning() {
+ when(mAudioManager.isAudioServerRunning()).thenReturn(true);
+
+ expectWithMessage("Audio server state").that(mAudioManagerWrapper.isAudioServerRunning())
+ .isTrue();
+ }
+
+ @Test
+ public void setSupportedSystemUsages() {
+ int[] systemUsages = new int[]{USAGE_EMERGENCY, USAGE_SAFETY};
+
+ mAudioManagerWrapper.setSupportedSystemUsages(systemUsages);
+
+ verify(mAudioManager).setSupportedSystemUsages(systemUsages);
+ }
+
+ @Test
+ public void registerAudioDeviceCallback() {
+ AudioDeviceCallback callback = Mockito.mock(AudioDeviceCallback.class);
+ Handler handler = Mockito.mock(Handler.class);
+
+ mAudioManagerWrapper.registerAudioDeviceCallback(callback, handler);
+
+ verify(mAudioManager).registerAudioDeviceCallback(callback, handler);
+ }
+
+ @Test
+ public void unregisterAudioDeviceCallback() {
+ AudioDeviceCallback callback = Mockito.mock(AudioDeviceCallback.class);
+
+ mAudioManagerWrapper.unregisterAudioDeviceCallback(callback);
+
+ verify(mAudioManager).unregisterAudioDeviceCallback(callback);
+ }
+
+ @Test
+ public void setAudioServerStateCallback() {
+ AudioServerStateCallback callback = Mockito.mock(AudioServerStateCallback.class);
+ Executor executor = MoreExecutors.directExecutor();
+
+ mAudioManagerWrapper.setAudioServerStateCallback(executor, callback);
+
+ verify(mAudioManager).setAudioServerStateCallback(executor, callback);
+ }
+
+ @Test
+ public void clearAudioServerStateCallback() {
+ mAudioManagerWrapper.clearAudioServerStateCallback();
+
+ verify(mAudioManager).clearAudioServerStateCallback();
+ }
+
+ @Test
+ public void registerAudioPolicy() {
+ AudioPolicy policy = Mockito.mock(AudioPolicy.class);
+
+ mAudioManagerWrapper.registerAudioPolicy(policy);
+
+ verify(mAudioManager).registerAudioPolicy(policy);
+ }
+
+ @Test
+ public void unregisterAudioPolicy() {
+ AudioPolicy policy = Mockito.mock(AudioPolicy.class);
+
+ mAudioManagerWrapper.unregisterAudioPolicy(policy);
+
+ verify(mAudioManager).unregisterAudioPolicy(policy);
+ }
+
+ @Test
+ public void unregisterAudioPolicyAsync() {
+ AudioPolicy policy = Mockito.mock(AudioPolicy.class);
+
+ mAudioManagerWrapper.unregisterAudioPolicyAsync(policy);
+
+ verify(mAudioManager).unregisterAudioPolicyAsync(policy);
+ }
+
+ @Test
+ public void setStreamVolume() {
+ int stream = AudioManager.STREAM_ALARM;
+ int index = 100;
+ int flags = FLAG_FROM_KEY;
+
+ mAudioManagerWrapper.setStreamVolume(stream, index, flags);
+
+ verify(mAudioManager).setStreamVolume(stream, index, flags);
+ }
+
+ @Test
+ public void getStreamMaxVolume() {
+ int stream = AudioManager.STREAM_RING;
+ int index = 9001;
+ when(mAudioManager.getStreamMaxVolume(stream)).thenReturn(index);
+
+ expectWithMessage("Max ring stream volume").that(
+ mAudioManagerWrapper.getStreamMaxVolume(stream)).isEqualTo(index);
+ }
+
+ @Test
+ public void getStreamMinVolume() {
+ int stream = AudioManager.STREAM_SYSTEM;
+ int index = 1;
+ when(mAudioManager.getStreamMinVolume(stream)).thenReturn(index);
+
+ expectWithMessage("Min system stream volume").that(
+ mAudioManagerWrapper.getStreamMinVolume(stream)).isEqualTo(index);
+ }
+
+ @Test
+ public void getStreamVolume() {
+ int stream = AudioManager.STREAM_MUSIC;
+ int index = 8675309;
+ when(mAudioManager.getStreamVolume(stream)).thenReturn(index);
+
+ expectWithMessage("Min music stream volume").that(
+ mAudioManagerWrapper.getStreamVolume(stream)).isEqualTo(index);
+ }
+
+ @Test
+ public void setParameter() {
+ String parameter = "test_parameter:key";
+ mAudioManagerWrapper.setParameters(parameter);
+
+ verify(mAudioManager).setParameters(parameter);
+ }
+
+ @Test
+ public void getDevices() {
+ AudioDeviceInfo info1 = Mockito.mock(AudioDeviceInfo.class);
+ AudioDeviceInfo info2 = Mockito.mock(AudioDeviceInfo.class);
+ AudioDeviceInfo[] infos = new AudioDeviceInfo[]{info1, info2};
+ when(mAudioManager.getDevices(GET_DEVICES_OUTPUTS)).thenReturn(infos);
+
+ expectWithMessage("Output audio devices").that(
+ mAudioManagerWrapper.getDevices(GET_DEVICES_OUTPUTS)).isEqualTo(infos);
+ }
+
+ @Test
+ public void registerAudioPlaybackCallback() {
+ AudioPlaybackCallback callback = Mockito.mock(AudioPlaybackCallback.class);
+ Handler handler = Mockito.mock(Handler.class);
+
+ mAudioManagerWrapper.registerAudioPlaybackCallback(callback, handler);
+
+ verify(mAudioManager).registerAudioPlaybackCallback(callback, handler);
+ }
+
+ @Test
+ public void unregisterAudioPlaybackCallback() {
+ AudioPlaybackCallback callback = Mockito.mock(AudioPlaybackCallback.class);
+
+ mAudioManagerWrapper.unregisterAudioPlaybackCallback(callback);
+
+ verify(mAudioManager).unregisterAudioPlaybackCallback(callback);
+ }
+
+ @Test
+ public void getActivePlaybackConfigurations() {
+ AudioPlaybackConfiguration config1 = Mockito.mock(AudioPlaybackConfiguration.class);
+ AudioPlaybackConfiguration config2 = Mockito.mock(AudioPlaybackConfiguration.class);
+ List<AudioPlaybackConfiguration> configs = List.of(config1, config2);
+ when(mAudioManager.getActivePlaybackConfigurations()).thenReturn(configs);
+
+ List<AudioPlaybackConfiguration> activeConfigs =
+ mAudioManagerWrapper.getActivePlaybackConfigurations();
+
+ expectWithMessage("Active playback configs").that(activeConfigs)
+ .containsExactlyElementsIn(configs);
+ }
+
+ @Test
+ public void releaseAudioPatch() {
+ AudioManagerHelper.AudioPatchInfo info =
+ new AudioManagerHelper.AudioPatchInfo(/* sourceAddress= */ "input",
+ /* sinkAddress= */ "output", /* handleId= */ 10);
+ doReturn(true).when(() -> AudioManagerHelper.releaseAudioPatch(mAudioManager, info));
+
+ expectWithMessage("Release patch state").that(mAudioManagerWrapper.releaseAudioPatch(info))
+ .isTrue();
+ }
+
+ @Test
+ public void getAudioVolumeGroups() {
+ List<AudioVolumeGroup> groups = CoreAudioRoutingUtils.getVolumeGroups();
+ doReturn(groups).when(AudioManager::getAudioVolumeGroups);
+
+ expectWithMessage("Core volume groups")
+ .that(AudioManagerWrapper.getAudioVolumeGroups()).containsExactlyElementsIn(groups);
+ }
+
+ @Test
+ public void getAudioProductStrategies() {
+ List<AudioProductStrategy> strategies = CoreAudioRoutingUtils.getProductStrategies();
+ doReturn(strategies).when(AudioManager::getAudioProductStrategies);
+
+ expectWithMessage("Audio product strategies")
+ .that(AudioManagerWrapper.getAudioProductStrategies())
+ .containsExactlyElementsIn(strategies);
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/AudioPlaybackConfigurationBuilder.java b/tests/carservice_unit_test/src/com/android/car/audio/AudioPlaybackConfigurationBuilder.java
index 8aed2b7..33278f7 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/AudioPlaybackConfigurationBuilder.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/AudioPlaybackConfigurationBuilder.java
@@ -19,6 +19,8 @@
import static android.media.AudioAttributes.USAGE_MEDIA;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -26,10 +28,14 @@
import android.media.AudioDeviceInfo;
import android.media.AudioPlaybackConfiguration;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
final class AudioPlaybackConfigurationBuilder {
private @AudioAttributes.AttributeUsage int mUsage = USAGE_MEDIA;
private boolean mIsActive = true;
private String mDeviceAddress = "";
+ private int mClientUid = 0;
AudioPlaybackConfigurationBuilder setUsage(@AudioAttributes.AttributeUsage int usage) {
mUsage = usage;
@@ -46,6 +52,11 @@
return this;
}
+ AudioPlaybackConfigurationBuilder setClientUid(int clientUid) {
+ mClientUid = clientUid;
+ return this;
+ }
+
AudioPlaybackConfiguration build() {
AudioPlaybackConfiguration configuration = mock(AudioPlaybackConfiguration.class);
AudioAttributes attributes = new AudioAttributes.Builder().setUsage(mUsage).build();
@@ -53,6 +64,7 @@
when(configuration.getAudioAttributes()).thenReturn(attributes);
when(configuration.getAudioDeviceInfo()).thenReturn(outputDevice);
when(configuration.isActive()).thenReturn(mIsActive);
+ when(configuration.getClientUid()).thenReturn(mClientUid);
return configuration;
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarActivationVolumeConfigTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarActivationVolumeConfigTest.java
new file mode 100644
index 0000000..9286a76
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarActivationVolumeConfigTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.audio;
+
+import static org.junit.Assert.assertThrows;
+
+import android.car.test.AbstractExpectableTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class CarActivationVolumeConfigTest extends AbstractExpectableTestCase {
+
+ private static final int MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE = 10;
+ private static final int MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE = 90;
+ private static final int ACTIVATION_VOLUME_INVOCATION_TYPE =
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT;
+
+ private static final CarActivationVolumeConfig CAR_ACTIVATION_VOLUME_CONFIG =
+ new CarActivationVolumeConfig(ACTIVATION_VOLUME_INVOCATION_TYPE,
+ MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE, MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE);
+
+ @Test
+ public void construct_withMinActivationVolumePercentageHigherThanMax() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ () -> new CarActivationVolumeConfig(ACTIVATION_VOLUME_INVOCATION_TYPE,
+ MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE,
+ MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE));
+
+ expectWithMessage("Exception for min activation volume percentage higher than max")
+ .that(thrown).hasMessageThat().contains("can not be higher than");
+ }
+
+ @Test
+ public void getInvocationType() {
+ expectWithMessage("Activation volume invocation type")
+ .that(CAR_ACTIVATION_VOLUME_CONFIG.getInvocationType())
+ .isEqualTo(ACTIVATION_VOLUME_INVOCATION_TYPE);
+ }
+
+ @Test
+ public void getMinActivationVolumePercentage() {
+ expectWithMessage("Min activation volume percentage")
+ .that(CAR_ACTIVATION_VOLUME_CONFIG.getMinActivationVolumePercentage())
+ .isEqualTo(MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE);
+ }
+
+ @Test
+ public void getMaxActivationVolumePercentage() {
+ expectWithMessage("Max activation volume percentage")
+ .that(CAR_ACTIVATION_VOLUME_CONFIG.getMaxActivationVolumePercentage())
+ .isEqualTo(MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE);
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextInfoTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextInfoTest.java
index 3d30a4a..f54fb12 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextInfoTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextInfoTest.java
@@ -200,18 +200,30 @@
}
@Test
- public void equals_forDifferentAttributes() {
+ public void equals_forDifferentAttributesLength() {
CarAudioContextInfo info1 = new CarAudioContextInfo(TEST_ALL_AUDIO_ATTRIBUTES_ARRAY,
TEST_CONTEXT_NAME_MUSIC, TEST_CONTEXT_ID_100);
CarAudioContextInfo info = new CarAudioContextInfo(
new AudioAttributes[] {TEST_NAV_AUDIO_ATTRIBUTE, TEST_NAV_AUDIO_ATTRIBUTE},
TEST_CONTEXT_NAME_MUSIC, TEST_CONTEXT_ID_100);
- assertWithMessage("Car audio context info equality for different attributes")
+ assertWithMessage("Car audio context info equality for different-length attributes")
.that(info.equals(info1)).isFalse();
}
@Test
+ public void equals_forDifferentAttributesWithTheSameLength() {
+ CarAudioContextInfo info1 = new CarAudioContextInfo(TEST_AUDIO_ATTRIBUTES_ARRAY,
+ TEST_CONTEXT_NAME_MUSIC, TEST_CONTEXT_ID_100);
+ CarAudioContextInfo info2 = new CarAudioContextInfo(
+ new AudioAttributes[]{TEST_NOTIFICATION_AUDIO_ATTRIBUTE}, TEST_CONTEXT_NAME_MUSIC,
+ TEST_CONTEXT_ID_100);
+
+ assertWithMessage("Car audio context info equality for different attributes of"
+ + " the same length").that(info2.equals(info1)).isFalse();
+ }
+
+ @Test
public void hashCode_forSameInfo() {
CarAudioContextInfo info1 = new CarAudioContextInfo(TEST_ALL_AUDIO_ATTRIBUTES_ARRAY,
TEST_CONTEXT_NAME_MUSIC, TEST_CONTEXT_ID_100);
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
index 30fa5e5..ed8d90f 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
@@ -45,7 +45,6 @@
import android.car.builtin.media.AudioManagerHelper;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.media.AudioAttributes;
-import android.media.AudioManager;
import android.util.ArraySet;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -140,14 +139,14 @@
protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
session
.spyStatic(CoreAudioHelper.class)
- .spyStatic(AudioManager.class);
+ .spyStatic(AudioManagerWrapper.class);
}
void setupMock() {
doReturn(CoreAudioRoutingUtils.getProductStrategies())
- .when(() -> AudioManager.getAudioProductStrategies());
+ .when(AudioManagerWrapper::getAudioProductStrategies);
doReturn(CoreAudioRoutingUtils.getVolumeGroups())
- .when(() -> AudioManager.getAudioVolumeGroups());
+ .when(AudioManagerWrapper::getAudioVolumeGroups);
doReturn(CoreAudioRoutingUtils.MUSIC_STRATEGY_ID)
.when(() -> CoreAudioHelper.getStrategyForAudioAttributes(
@@ -970,4 +969,25 @@
.isEqualTo(TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(attributes));
}
}
+
+ @Test
+ public void getContextsInfo() {
+ List<CarAudioContextInfo> audioContextInfos = CoreAudioRoutingUtils
+ .getCarAudioContextInfos();
+ CarAudioContext carAudioContext = new CarAudioContext(audioContextInfos,
+ /* useCoreAudioRouting= */ true);
+
+ expectWithMessage("Context infos").that(carAudioContext.getContextsInfo())
+ .containsExactlyElementsIn(audioContextInfos);
+ }
+
+ @Test
+ public void getCarAudioContextId_forAudioAttributesWrapper() {
+ int contextId = 1;
+ CarAudioContext.AudioAttributesWrapper wrapper =
+ new CarAudioContext.AudioAttributesWrapper(TEST_MEDIA_ATTRIBUTE, contextId);
+
+ expectWithMessage("Car audio context Id").that(wrapper.getCarAudioContextId())
+ .isEqualTo(contextId);
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioDeviceInfoTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioDeviceInfoTest.java
index e731847..3939f84 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioDeviceInfoTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioDeviceInfoTest.java
@@ -16,13 +16,17 @@
package com.android.car.audio;
+import static android.media.AudioDeviceInfo.TYPE_BLE_BROADCAST;
import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
import static android.media.AudioDeviceInfo.TYPE_BUS;
import static android.media.AudioFormat.CHANNEL_OUT_MONO;
import static android.media.AudioFormat.CHANNEL_OUT_QUAD;
import static android.media.AudioFormat.CHANNEL_OUT_STEREO;
+import static android.media.AudioFormat.ENCODING_MPEGH_BL_L3;
import static android.media.AudioFormat.ENCODING_PCM_16BIT;
+import static android.media.AudioFormat.ENCODING_PCM_24BIT_PACKED;
+import static android.media.AudioFormat.ENCODING_PCM_32BIT;
import static com.android.car.audio.CarAudioDeviceInfo.DEFAULT_SAMPLE_RATE;
import static com.android.car.audio.GainBuilder.MAX_GAIN;
@@ -37,7 +41,6 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioGain;
-import android.media.AudioManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -53,12 +56,12 @@
private static final String TEST_ADDRESS = "test address";
@Mock
- private AudioManager mAudioManager;
+ private AudioManagerWrapper mAudioManagerWrapper;
@Test
public void setAudioDeviceInfo_requiresNonNullGain_forBusDevices() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioDeviceInfo audioDeviceInfo = mock(AudioDeviceInfo.class);
when(audioDeviceInfo.getPort()).thenReturn(null);
when(audioDeviceInfo.getType()).thenReturn(TYPE_BUS);
@@ -74,7 +77,7 @@
@Test
public void setAudioDeviceInfo_doesNotRequiresNonNullGain_forNonBusDevices() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BLUETOOTH_A2DP);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioDeviceInfo audioDeviceInfo = mock(AudioDeviceInfo.class);
when(audioDeviceInfo.getPort()).thenReturn(null);
when(audioDeviceInfo.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
@@ -88,7 +91,7 @@
@Test
public void setAudioDeviceInfo_requiresJointModeGain() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioGain gainWithChannelMode = new GainBuilder().setMode(AudioGain.MODE_CHANNELS).build();
AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
new AudioGain[]{gainWithChannelMode});
@@ -103,7 +106,7 @@
@Test
public void setAudioDeviceInfo_requiresMaxGainLargerThanMin() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioGain gainWithChannelMode = new GainBuilder().setMaxValue(10).setMinValue(20).build();
AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
new AudioGain[]{gainWithChannelMode});
@@ -118,7 +121,7 @@
@Test
public void setAudioDeviceInfo_requiresDefaultGainLargerThanMin() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioGain gainWithChannelMode = new GainBuilder().setDefaultValue(10).setMinValue(
20).build();
AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
@@ -134,7 +137,7 @@
@Test
public void setAudioDeviceInfo_requiresDefaultGainSmallerThanMax() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioGain gainWithChannelMode = new GainBuilder().setDefaultValue(15).setMaxValue(
10).build();
AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
@@ -150,7 +153,7 @@
@Test
public void setAudioDeviceInfo_requiresGainStepSizeFactorOfRange() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioGain gainWithChannelMode = new GainBuilder().setStepSize(7).build();
AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
new AudioGain[]{gainWithChannelMode});
@@ -165,7 +168,7 @@
@Test
public void setAudioDeviceInfo_requiresGainStepSizeFactorOfRangeToDefault() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioGain gainWithChannelMode = new GainBuilder().setStepSize(7).setMaxValue(98).build();
AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
new AudioGain[]{gainWithChannelMode});
@@ -181,7 +184,7 @@
@Test
public void isActive_beforeSettingAudioDevice() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
expectWithMessage("Default is active status").that(info.isActive())
.isFalse();
@@ -190,7 +193,7 @@
@Test
public void isActive_afterSettingDeviceInfo() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
info.setAudioDeviceInfo(getMockAudioDeviceInfo());
expectWithMessage("Is active status").that(info.isActive()).isTrue();
@@ -199,7 +202,7 @@
@Test
public void isActive_afterResettingAudioDeviceToNull_forNonBusDevices() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BLE_HEADSET);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
info.setAudioDeviceInfo(getMockAudioDeviceInfo(TYPE_BLE_HEADSET));
info.setAudioDeviceInfo(null);
@@ -210,7 +213,7 @@
@Test
public void isActive_afterResettingAudioDeviceToNull_forBusDevices() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
info.setAudioDeviceInfo(getMockAudioDeviceInfo(TYPE_BUS));
info.setAudioDeviceInfo(null);
@@ -224,10 +227,9 @@
AudioDeviceInfo deviceInfo = getMockAudioDeviceInfo();
int[] sampleRates = new int[]{48000, 96000, 16000, 8000};
when(deviceInfo.getSampleRates()).thenReturn(sampleRates);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, audioDevice);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, audioDevice);
info.setAudioDeviceInfo(deviceInfo);
-
int sampleRate = info.getSampleRate();
expectWithMessage("Sample rate").that(sampleRate).isEqualTo(96000);
@@ -236,7 +238,7 @@
@Test
public void getSampleRate_withNullSampleRate_returnsDefault() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
int sampleRate = info.getSampleRate();
@@ -246,15 +248,23 @@
@Test
public void getAddress_returnsValueFromDeviceInfo() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
expectWithMessage("Device Info Address").that(info.getAddress()).isEqualTo(TEST_ADDRESS);
}
@Test
+ public void getType() {
+ AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
+
+ expectWithMessage("Device info type").that(info.getType()).isEqualTo(TYPE_BUS);
+ }
+
+ @Test
public void getMaxGain_returnsValueFromDeviceInfo() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
info.setAudioDeviceInfo(getMockAudioDeviceInfo());
expectWithMessage("Device Info Max Gain")
@@ -264,7 +274,7 @@
@Test
public void getMinGain_returnsValueFromDeviceInfo() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
info.setAudioDeviceInfo(getMockAudioDeviceInfo());
expectWithMessage("Device Info Min Gain")
@@ -274,7 +284,7 @@
@Test
public void getDefaultGain_returnsValueFromDeviceInfo() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
info.setAudioDeviceInfo(getMockAudioDeviceInfo());
expectWithMessage("Device Info Default Gain").that(info.getDefaultGain())
@@ -284,7 +294,7 @@
@Test
public void getStepValue_returnsValueFromDeviceInfo() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
info.setAudioDeviceInfo(getMockAudioDeviceInfo());
expectWithMessage("Device Info Step Vale").that(info.getStepValue())
@@ -294,7 +304,7 @@
@Test
public void getChannelCount_withNoChannelMasks_returnsOne() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
info.setAudioDeviceInfo(getMockAudioDeviceInfo());
int channelCount = info.getChannelCount();
@@ -304,11 +314,11 @@
@Test
public void getChannelCount_withMultipleChannels_returnsHighestCount() {
- AudioDeviceAttributes audioDeviceAttribute = getMockAudioDeviceAttribute(TYPE_BUS);
+ AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
AudioDeviceInfo deviceInfo = getMockAudioDeviceInfo();
when(deviceInfo.getChannelMasks()).thenReturn(new int[]{CHANNEL_OUT_STEREO,
CHANNEL_OUT_QUAD, CHANNEL_OUT_MONO});
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, audioDeviceAttribute);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
info.setAudioDeviceInfo(deviceInfo);
int channelCount = info.getChannelCount();
@@ -319,7 +329,7 @@
@Test
public void getAudioDevice_returnsConstructorParameter() {
AudioDeviceAttributes audioDevice = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, audioDevice);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, audioDevice);
expectWithMessage("Device Info Audio Device Attributes")
.that(info.getAudioDevice()).isEqualTo(audioDevice);
@@ -328,16 +338,42 @@
@Test
public void getEncodingFormat_returnsPCM16() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
expectWithMessage("Device Info Audio Encoding Format")
.that(info.getEncodingFormat()).isEqualTo(ENCODING_PCM_16BIT);
}
@Test
+ public void getEncodingFormat_withMultipleLinearPcmEncodings_returnsMax() {
+ AudioDeviceAttributes audioDevice = getMockAudioDeviceAttribute(TYPE_BUS);
+ AudioDeviceInfo deviceInfo = getMockAudioDeviceInfo();
+ int[] encodings = {ENCODING_PCM_24BIT_PACKED, ENCODING_PCM_32BIT};
+ when(deviceInfo.getEncodings()).thenReturn(encodings);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, audioDevice);
+ info.setAudioDeviceInfo(deviceInfo);
+
+ expectWithMessage("Encoding format with linear PCM encoding").that(info.getEncodingFormat())
+ .isEqualTo(ENCODING_PCM_24BIT_PACKED);
+ }
+
+ @Test
+ public void getEncodingFormat_withoutLinearPcmEncodings_returnsDefault() {
+ AudioDeviceAttributes audioDevice = getMockAudioDeviceAttribute(TYPE_BUS);
+ AudioDeviceInfo deviceInfo = getMockAudioDeviceInfo();
+ int[] encodings = {ENCODING_MPEGH_BL_L3};
+ when(deviceInfo.getEncodings()).thenReturn(encodings);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, audioDevice);
+ info.setAudioDeviceInfo(deviceInfo);
+
+ expectWithMessage("Encoding format without linear PCM encoding")
+ .that(info.getEncodingFormat()).isEqualTo(ENCODING_PCM_16BIT);
+ }
+
+ @Test
public void defaultDynamicPolicyMix_enabled() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
expectWithMessage("Dynamic policy mix is enabled by default on Devices")
.that(info.canBeRoutedWithDynamicPolicyMix())
@@ -347,7 +383,7 @@
@Test
public void setGetCanBeRoutedWithDynamicPolicyMix() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
info.resetCanBeRoutedWithDynamicPolicyMix();
@@ -359,7 +395,7 @@
@Test
public void resetGetCanBeRoutedWithDynamicPolicyMix_isSticky() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
info.resetCanBeRoutedWithDynamicPolicyMix();
// Setting twice, no-op, reset is fused.
@@ -373,7 +409,7 @@
@Test
public void audioDevicesAdded_withSameDeviceType() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BLUETOOTH_A2DP);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(TYPE_BLUETOOTH_A2DP);
boolean updated = info.audioDevicesAdded(List.of(audioDeviceInfo));
@@ -387,7 +423,7 @@
@Test
public void audioDevicesAdded_withDifferentDeviceType() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BLUETOOTH_A2DP);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(TYPE_BLE_HEADSET);
boolean updated = info.audioDevicesAdded(List.of(audioDeviceInfo));
@@ -401,7 +437,7 @@
@Test
public void audioDevicesAdded_withNullDevices() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BLUETOOTH_A2DP);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(TYPE_BLUETOOTH_A2DP);
info.setAudioDeviceInfo(audioDeviceInfo);
@@ -413,9 +449,21 @@
}
@Test
+ public void audioDevicesAdded_withBluetoothDeviceForBusTypeDeviceInfo() {
+ AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(TYPE_BLUETOOTH_A2DP);
+
+ boolean updated = info.audioDevicesAdded(List.of(audioDeviceInfo));
+
+ expectWithMessage("Updated status of dynamic device with bus device type")
+ .that(updated).isFalse();
+ }
+
+ @Test
public void audioDevicesRemoved_withSameDeviceType() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BLUETOOTH_A2DP);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(TYPE_BLUETOOTH_A2DP);
info.setAudioDeviceInfo(audioDeviceInfo);
@@ -430,12 +478,14 @@
@Test
public void audioDevicesRemoved_withDifferentDeviceType() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BLUETOOTH_A2DP);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(TYPE_BLUETOOTH_A2DP);
info.setAudioDeviceInfo(audioDeviceInfo);
- AudioDeviceInfo removedAudioDevice = getMockAudioDeviceInfo(TYPE_BLE_HEADSET);
+ AudioDeviceInfo removedHeadsetAudioDevice = getMockAudioDeviceInfo(TYPE_BLE_HEADSET);
+ AudioDeviceInfo removedBroadcastAudioDevice = getMockAudioDeviceInfo(TYPE_BLE_BROADCAST);
- boolean updated = info.audioDevicesRemoved(List.of(removedAudioDevice));
+ boolean updated = info.audioDevicesRemoved(List.of(removedHeadsetAudioDevice,
+ removedBroadcastAudioDevice));
expectWithMessage("Updated status of remove dynamic device with different type")
.that(updated).isFalse();
@@ -446,7 +496,7 @@
@Test
public void audioDevicesRemoved_withNullDevices() {
AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BLUETOOTH_A2DP);
- CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManager, attributes);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(TYPE_BLUETOOTH_A2DP);
info.setAudioDeviceInfo(audioDeviceInfo);
@@ -457,6 +507,18 @@
.that(exception).hasMessageThat().contains("Audio devices");
}
+ @Test
+ public void audioDevicesRemoved_withBluetoothDeviceForBusTypeDeviceInfo() {
+ AudioDeviceAttributes attributes = getMockAudioDeviceAttribute(TYPE_BUS);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(mAudioManagerWrapper, attributes);
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(TYPE_BLUETOOTH_A2DP);
+
+ boolean updated = info.audioDevicesRemoved(List.of(audioDeviceInfo));
+
+ expectWithMessage("Updated status of remove dynamic device with bus device type")
+ .that(updated).isFalse();
+ }
+
private AudioDeviceInfo getMockAudioDeviceInfo() {
AudioGain mockGain = new GainBuilder().build();
return getMockAudioDeviceInfo(new AudioGain[]{mockGain});
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioFocusUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioFocusUnitTest.java
index 2bf100b..40a3b6a 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioFocusUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioFocusUnitTest.java
@@ -122,7 +122,7 @@
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Mock
- private AudioManager mMockAudioManager;
+ private AudioManagerWrapper mMockAudioManager;
@Mock
private PackageManager mMockPackageManager;
@Mock
@@ -401,7 +401,7 @@
mSetFlagsRule.enableFlags(FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
AudioAttributes mediaAttributes = new AudioAttributes.Builder()
.setUsage(USAGE_MEDIA).build();
- AudioAttributes assistantAttributes = new AudioAttributes.Builder()
+ AudioAttributes emergencyAttributes = new AudioAttributes.Builder()
.setSystemUsage(USAGE_EMERGENCY).build();
CarAudioFadeConfiguration cafcEnabled =
new CarAudioFadeConfiguration.Builder(TEST_FADE_MANAGER_CONFIG_ENABLED).build();
@@ -410,18 +410,19 @@
ArrayMap<AudioAttributes, CarAudioFadeConfiguration> attrToCarAudioFadeConfiguration =
new ArrayMap<>();
attrToCarAudioFadeConfiguration.put(mediaAttributes, cafcDisabled);
- attrToCarAudioFadeConfiguration.put(assistantAttributes, cafcEnabled);
- CarAudioFocus carAudioFocus = getCarAudioFocus(PRIMARY_AUDIO_ZONE, cafcDisabled,
- attrToCarAudioFadeConfiguration, getCarAudioFeaturesInfo(
- /* supportsFadeManager= */ true, /* supportsIsolatedFocus= */ false));
+ attrToCarAudioFadeConfiguration.put(emergencyAttributes, cafcEnabled);
+ CarAudioFocus carAudioFocus = getCarAudioFocus(PRIMARY_AUDIO_ZONE,
+ /* defaultCarAudioFadeConfig= */ null, attrToCarAudioFadeConfiguration,
+ getCarAudioFeaturesInfo(/* supportsFadeManager= */ true,
+ /* supportsIsolatedFocus= */ false));
AudioFocusInfo initialFocusInfo = requestFocusForMediaWithFirstClient(carAudioFocus);
- AudioFocusInfo concurrentFocusInfo = getConcurrentInfo(AUDIOFOCUS_GAIN);
+ AudioFocusInfo exclusiveFocusInfo = getExclusiveWithSystemUsageInfo();
- carAudioFocus.onAudioFocusRequest(concurrentFocusInfo, AUDIOFOCUS_REQUEST_GRANTED);
+ carAudioFocus.onAudioFocusRequest(exclusiveFocusInfo, AUDIOFOCUS_REQUEST_GRANTED);
verify(mMockAudioManager).dispatchAudioFocusChangeWithFade(initialFocusInfo,
- AudioManager.AUDIOFOCUS_LOSS, mAudioPolicy, List.of(concurrentFocusInfo),
- TEST_FADE_MANAGER_CONFIG_DISABLED);
+ AudioManager.AUDIOFOCUS_LOSS, mAudioPolicy, List.of(exclusiveFocusInfo),
+ TEST_FADE_MANAGER_CONFIG_ENABLED);
}
@Test
@@ -461,14 +462,15 @@
}
@Test
- public void onAudioFocusRequest_exclusiveRequest_holderLosesFocusWithFade() {
+ public void onAudioFocusRequest_exclusiveRequest_holderLosesFocus_withNullTransient() {
mSetFlagsRule.enableFlags(FLAG_CAR_AUDIO_FADE_MANAGER_CONFIGURATION);
mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
mSetFlagsRule.enableFlags(FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
- CarAudioFocus carAudioFocus = getCarAudioFocus(PRIMARY_AUDIO_ZONE,
- /* defaultCarAudioFadeConfig= */ null, /* transientCarAudioFadeConfigs= */ null,
- getCarAudioFeaturesInfo(/* supportsFadeManager= */ true,
- /* supportsIsolatedFocus= */ false));
+ CarAudioFadeConfiguration cafcEnabled =
+ new CarAudioFadeConfiguration.Builder(TEST_FADE_MANAGER_CONFIG_ENABLED).build();
+ CarAudioFocus carAudioFocus = getCarAudioFocus(PRIMARY_AUDIO_ZONE, cafcEnabled,
+ /* transientCarAudioFadeConfigs= */ null, getCarAudioFeaturesInfo(
+ /* supportsFadeManager= */ true, /* supportsIsolatedFocus= */ false));
AudioFocusInfo initialFocusInfo = requestFocusForMediaWithFirstClient(carAudioFocus);
AudioFocusInfo exclusiveRequestInfo = getExclusiveInfo(AUDIOFOCUS_GAIN);
@@ -479,6 +481,26 @@
}
@Test
+ public void onAudioFocusRequest_forSecondaryZone_exclusiveRequest_holderLosesFocusWithFade() {
+ mSetFlagsRule.enableFlags(FLAG_CAR_AUDIO_FADE_MANAGER_CONFIGURATION);
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ mSetFlagsRule.enableFlags(FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
+ CarAudioFadeConfiguration cafcEnabled =
+ new CarAudioFadeConfiguration.Builder(TEST_FADE_MANAGER_CONFIG_ENABLED).build();
+ CarAudioFocus carAudioFocus = getCarAudioFocus(TEST_SECONDARY_ZONE, cafcEnabled,
+ /* transientCarAudioFadeConfigs= */ null, getCarAudioFeaturesInfo(
+ /* supportsFadeManager= */ true, /* supportsIsolatedFocus= */ false));
+ AudioFocusInfo initialFocusInfo = requestFocusForMediaWithFirstClient(carAudioFocus);
+ AudioFocusInfo exclusiveRequestInfo = getExclusiveInfo(AUDIOFOCUS_GAIN);
+
+ carAudioFocus.onAudioFocusRequest(exclusiveRequestInfo, AUDIOFOCUS_REQUEST_GRANTED);
+
+ verify(mMockAudioManager).dispatchAudioFocusChangeWithFade(initialFocusInfo,
+ AudioManager.AUDIOFOCUS_LOSS, mAudioPolicy, List.of(exclusiveRequestInfo),
+ TEST_FADE_MANAGER_CONFIG_ENABLED);
+ }
+
+ @Test
public void onAudioFocusRequest_exclusiveRequestMayDuck_holderLosesFocusTransiently() {
CarAudioFocus carAudioFocus = getCarAudioFocus();
AudioFocusInfo initialFocusInfo = requestFocusForMediaWithFirstClient(carAudioFocus);
@@ -1786,19 +1808,24 @@
AudioFocusEntry emergencyEntry = new AudioFocusEntry.Builder(exclusiveSystemUsageInfo,
TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(emergencyAudioAttributes),
TEST_VOLUME_GROUP, AUDIOFOCUS_GAIN).build();
- CarAudioFadeConfiguration carAudioFadeConfiguration =
+ CarAudioFadeConfiguration cafcDisabled =
new CarAudioFadeConfiguration.Builder(TEST_FADE_MANAGER_CONFIG_DISABLED).build();
ArrayMap<AudioAttributes, CarAudioFadeConfiguration> attrToCarAudioFadeConfigMap =
new ArrayMap<>();
- attrToCarAudioFadeConfigMap.put(mediaAudioAttribute, carAudioFadeConfiguration);
+ attrToCarAudioFadeConfigMap.put(mediaAudioAttribute, cafcDisabled);
OemCarAudioFocusResult systemUsageResults = getAudioFocusResults(emergencyEntry,
AUDIOFOCUS_REQUEST_GRANTED, List.of(mediaEntry),
/* blockedEntries= */ List.of(), attrToCarAudioFadeConfigMap);
when(mMockAudioFocusProxyService.evaluateAudioFocusRequest(any()))
.thenReturn(mediaResults)
.thenReturn(systemUsageResults);
+ CarAudioFadeConfiguration cafcEnabled =
+ new CarAudioFadeConfiguration.Builder(TEST_FADE_MANAGER_CONFIG_ENABLED).build();
+ ArrayMap<AudioAttributes, CarAudioFadeConfiguration> attrToCarAudioFadeConfigMapXml =
+ new ArrayMap<>();
+ attrToCarAudioFadeConfigMapXml.put(emergencyAudioAttributes, cafcEnabled);
CarAudioFocus carAudioFocus = getCarAudioFocus(PRIMARY_AUDIO_ZONE,
- /* defaultCarAudioFadeConfig= */ null, /* transientCarAudioFadeConfigs= */ null,
+ /* defaultCarAudioFadeConfig= */ null, attrToCarAudioFadeConfigMapXml,
getCarAudioFeaturesInfo(/* supportsFadeManager= */ true,
/* supportsIsolatedFocus= */ false));
carAudioFocus.onAudioFocusRequest(initialFocusInfo, AUDIOFOCUS_REQUEST_GRANTED);
@@ -1815,8 +1842,8 @@
mSetFlagsRule.enableFlags(FLAG_CAR_AUDIO_FADE_MANAGER_CONFIGURATION);
mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
mSetFlagsRule.enableFlags(FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
- AudioAttributes notificationAttributes = new AudioAttributes.Builder()
- .setUsage(USAGE_NOTIFICATION).build();
+ AudioAttributes ringtoneAttributes = new AudioAttributes.Builder()
+ .setUsage(USAGE_NOTIFICATION_RINGTONE).build();
AudioAttributes vehicleStatusAttributes = new AudioAttributes.Builder()
.setSystemUsage(USAGE_VEHICLE_STATUS).build();
CarAudioFadeConfiguration cafcEnabled =
@@ -1825,7 +1852,7 @@
new CarAudioFadeConfiguration.Builder(TEST_FADE_MANAGER_CONFIG_DISABLED).build();
ArrayMap<AudioAttributes, CarAudioFadeConfiguration> attrToCarAudioFadeConfiguration =
new ArrayMap<>();
- attrToCarAudioFadeConfiguration.put(notificationAttributes, cafcEnabled);
+ attrToCarAudioFadeConfiguration.put(ringtoneAttributes, cafcEnabled);
attrToCarAudioFadeConfiguration.put(vehicleStatusAttributes, cafcDisabled);
CarAudioFocus carAudioFocus = getCarAudioFocus(PRIMARY_AUDIO_ZONE,
/* defaultCarAudioFadeConfig= */ null, attrToCarAudioFadeConfiguration,
@@ -1844,7 +1871,7 @@
verify(mMockAudioManager).dispatchAudioFocusChangeWithFade(secondConcurrentRequest,
AUDIOFOCUS_LOSS, mAudioPolicy, List.of(firstConcurrentRequest, exclusiveRequest),
- TEST_FADE_MANAGER_CONFIG_DISABLED);
+ TEST_FADE_MANAGER_CONFIG_ENABLED);
verify(mMockAudioManager).dispatchAudioFocusChangeWithFade(firstConcurrentRequest,
AUDIOFOCUS_LOSS, mAudioPolicy, List.of(exclusiveRequest),
TEST_FADE_MANAGER_CONFIG_ENABLED);
@@ -1857,15 +1884,6 @@
return captor.getValue();
}
- private AudioFocusEntry captureOemServiceAudioFocusEntry() {
- ArgumentCaptor<OemCarAudioFocusEvaluationRequest> captor = ArgumentCaptor
- .forClass(OemCarAudioFocusEvaluationRequest.class);
- verify(mMockAudioFocusProxyService).evaluateAudioFocusRequest(captor.capture());
- OemCarAudioFocusEvaluationRequest request = captor.getValue();
- AudioFocusEntry entry = request.getAudioFocusRequest();
- return entry;
- }
-
private OemCarAudioFocusResult getAudioFocusResults(AudioFocusEntry entry, int results,
List<AudioFocusEntry> lostEntries, List<AudioFocusEntry> blockedEntries,
Map<AudioAttributes, CarAudioFadeConfiguration> attrToCarAudioFadeConfigMap) {
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioGainConfigInfoTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioGainConfigInfoTest.java
index 326809f..635e946 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioGainConfigInfoTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioGainConfigInfoTest.java
@@ -105,7 +105,6 @@
CarAudioGainConfigInfo carGainInfo1 = new CarAudioGainConfigInfo(gainInfo);
CarAudioGainConfigInfo carGainInfo2 = new CarAudioGainConfigInfo(gainInfo);
- assertWithMessage("Audio Gain Configs").that(carGainInfo1 == carGainInfo2).isFalse();
assertWithMessage("Audio Gain Configs").that(carGainInfo1.equals(carGainInfo2)).isTrue();
assertWithMessage("Audio Gain Configs").that(carGainInfo1).isEqualTo(carGainInfo2);
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioParserUtilsTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioParserUtilsTest.java
new file mode 100644
index 0000000..1116a56
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioParserUtilsTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.audio;
+
+import static org.junit.Assert.assertThrows;
+
+import android.car.test.AbstractExpectableTestCase;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class CarAudioParserUtilsTest extends AbstractExpectableTestCase {
+
+ @Test
+ public void parsePositiveLongAttributeWithInvalidLongString() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ () -> CarAudioParserUtils.parsePositiveLongAttribute(/* attribute*/ "testAttribute",
+ /* longString= */ "5.0"));
+
+ expectWithMessage("Parsing invalid long string exception").that(thrown).hasMessageThat()
+ .contains("must be a positive long");
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPlaybackCallbackTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPlaybackCallbackTest.java
index cb91268..6c003b2 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPlaybackCallbackTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPlaybackCallbackTest.java
@@ -32,6 +32,7 @@
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.media.AudioAttributes;
import android.media.AudioPlaybackConfiguration;
+import android.util.Pair;
import android.util.SparseArray;
import com.google.common.collect.ImmutableList;
@@ -60,6 +61,8 @@
private static final int NEGATIVE_KEY_EVENT_TIMEOUT_MS = -KEY_EVENT_TIMEOUT_MS;
private static final String PRIMARY_MEDIA_ADDRESS = "music_bus0";
private static final String SECONDARY_MEDIA_ADDRESS = "music_bus1";
+ private static final int PLAYBACK_UID_1 = 10101;
+ private static final int PLAYBACK_UID_2 = 10102;
private static final AudioAttributes TEST_MEDIA_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build();
@@ -81,7 +84,7 @@
@Mock
private CarAudioPlaybackMonitor mCarAudioPlaybackMonitor;
@Captor
- private ArgumentCaptor<List<AudioAttributes>> mAudioAttributesCaptor;
+ private ArgumentCaptor<List<Pair<AudioAttributes, Integer>>> mAudioAttributesCaptor;
@Before
public void setUp() {
@@ -143,6 +146,7 @@
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_MEDIA)
.setDeviceAddress(PRIMARY_MEDIA_ADDRESS)
+ .setClientUid(PLAYBACK_UID_1)
.build()
);
@@ -158,7 +162,8 @@
expectWithMessage("Secondary zone active attributes")
.that(secondaryZoneActiveAttributes)
.isEmpty();
- verifyActivationPlaybacks(List.of(TEST_MEDIA_AUDIO_ATTRIBUTE), PRIMARY_ZONE_ID);
+ verifyActivationPlaybacks(List.of(new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_1)),
+ PRIMARY_ZONE_ID);
verify(mCarAudioPlaybackMonitor, never()).onActiveAudioPlaybackAttributesAdded(any(),
eq(SECONDARY_ZONE_ID));
}
@@ -192,10 +197,12 @@
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_MEDIA)
.setDeviceAddress(PRIMARY_MEDIA_ADDRESS)
+ .setClientUid(PLAYBACK_UID_1)
.build(),
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_MEDIA)
.setDeviceAddress(SECONDARY_MEDIA_ADDRESS)
+ .setClientUid(PLAYBACK_UID_2)
.build()
);
@@ -211,8 +218,10 @@
expectWithMessage("Secondary zone active attributes")
.that(secondaryZoneActiveAttributes)
.containsExactly(TEST_MEDIA_AUDIO_ATTRIBUTE);
- verifyActivationPlaybacks(List.of(TEST_MEDIA_AUDIO_ATTRIBUTE), PRIMARY_ZONE_ID);
- verifyActivationPlaybacks(List.of(TEST_MEDIA_AUDIO_ATTRIBUTE), SECONDARY_ZONE_ID);
+ verifyActivationPlaybacks(List.of(new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_1)),
+ PRIMARY_ZONE_ID);
+ verifyActivationPlaybacks(List.of(new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_2)),
+ SECONDARY_ZONE_ID);
}
@Test
@@ -221,11 +230,13 @@
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_MEDIA)
.setDeviceAddress(PRIMARY_MEDIA_ADDRESS)
+ .setClientUid(PLAYBACK_UID_1)
.build(),
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_MEDIA)
.setDeviceAddress(SECONDARY_MEDIA_ADDRESS)
.setInactive()
+ .setClientUid(PLAYBACK_UID_2)
.build()
);
@@ -241,7 +252,8 @@
expectWithMessage("Secondary zone active attributes")
.that(secondaryZoneActiveAttributes)
.isEmpty();
- verifyActivationPlaybacks(List.of(TEST_MEDIA_AUDIO_ATTRIBUTE), PRIMARY_ZONE_ID);
+ verifyActivationPlaybacks(List.of(new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_1)),
+ PRIMARY_ZONE_ID);
verify(mCarAudioPlaybackMonitor, never()).onActiveAudioPlaybackAttributesAdded(any(),
eq(SECONDARY_ZONE_ID));
}
@@ -252,10 +264,12 @@
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_MEDIA)
.setDeviceAddress(PRIMARY_MEDIA_ADDRESS)
+ .setClientUid(PLAYBACK_UID_1)
.build(),
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_MEDIA)
.setDeviceAddress(SECONDARY_MEDIA_ADDRESS)
+ .setClientUid(PLAYBACK_UID_2)
.build()
);
List<AudioPlaybackConfiguration> configurationsChanged = ImmutableList.of(
@@ -263,11 +277,13 @@
.setUsage(USAGE_MEDIA)
.setDeviceAddress(PRIMARY_MEDIA_ADDRESS)
.setInactive()
+ .setClientUid(PLAYBACK_UID_1)
.build(),
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_MEDIA)
.setDeviceAddress(SECONDARY_MEDIA_ADDRESS)
.setInactive()
+ .setClientUid(PLAYBACK_UID_2)
.build()
);
mCallback.onPlaybackConfigChanged(configurations);
@@ -285,8 +301,10 @@
expectWithMessage("Secondary zone active attributes")
.that(secondaryZoneActiveAttributes)
.containsExactly(TEST_MEDIA_AUDIO_ATTRIBUTE);
- verifyActivationPlaybacks(List.of(TEST_MEDIA_AUDIO_ATTRIBUTE), PRIMARY_ZONE_ID);
- verifyActivationPlaybacks(List.of(TEST_MEDIA_AUDIO_ATTRIBUTE), SECONDARY_ZONE_ID);
+ verifyActivationPlaybacks(List.of(new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_1)),
+ PRIMARY_ZONE_ID);
+ verifyActivationPlaybacks(List.of(new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_2)),
+ SECONDARY_ZONE_ID);
}
@Test
@@ -295,10 +313,12 @@
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_MEDIA)
.setDeviceAddress(PRIMARY_MEDIA_ADDRESS)
+ .setClientUid(PLAYBACK_UID_1)
.build(),
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_MEDIA)
.setDeviceAddress(SECONDARY_MEDIA_ADDRESS)
+ .setClientUid(PLAYBACK_UID_2)
.build()
);
List<AudioPlaybackConfiguration> configurationsChanged = ImmutableList.of(
@@ -306,11 +326,13 @@
.setUsage(USAGE_MEDIA)
.setDeviceAddress(PRIMARY_MEDIA_ADDRESS)
.setInactive()
+ .setClientUid(PLAYBACK_UID_1)
.build(),
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_MEDIA)
.setDeviceAddress(SECONDARY_MEDIA_ADDRESS)
.setInactive()
+ .setClientUid(PLAYBACK_UID_2)
.build()
);
mCallback.onPlaybackConfigChanged(configurations);
@@ -328,8 +350,10 @@
expectWithMessage("Secondary zone active attributes")
.that(secondaryZoneActiveAttributes)
.isEmpty();
- verifyActivationPlaybacks(List.of(TEST_MEDIA_AUDIO_ATTRIBUTE), PRIMARY_ZONE_ID);
- verifyActivationPlaybacks(List.of(TEST_MEDIA_AUDIO_ATTRIBUTE), SECONDARY_ZONE_ID);
+ verifyActivationPlaybacks(List.of(new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_1)),
+ PRIMARY_ZONE_ID);
+ verifyActivationPlaybacks(List.of(new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_2)),
+ SECONDARY_ZONE_ID);
}
@Test
@@ -402,12 +426,13 @@
.build();
}
- private void verifyActivationPlaybacks(List<AudioAttributes> newlyActiveAudioAttributes,
+ private void verifyActivationPlaybacks(List<Pair<AudioAttributes, Integer>>
+ newlyActiveAudioAttributesWithUid,
int zoneId) {
verify(mCarAudioPlaybackMonitor).onActiveAudioPlaybackAttributesAdded(
mAudioAttributesCaptor.capture(), eq(zoneId));
- assertWithMessage("Audio attributes for newly active playbacks")
+ assertWithMessage("Audio attributes with uid for newly active playbacks")
.that(mAudioAttributesCaptor.getValue())
- .containsExactlyElementsIn(newlyActiveAudioAttributes);
+ .containsExactlyElementsIn(newlyActiveAudioAttributesWithUid);
}
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPlaybackMonitorTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPlaybackMonitorTest.java
index fd00307..55fb7ed 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPlaybackMonitorTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPlaybackMonitorTest.java
@@ -18,15 +18,24 @@
import static android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
import static android.media.AudioAttributes.USAGE_MEDIA;
+import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import android.car.test.AbstractExpectableTestCase;
import android.media.AudioAttributes;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.Pair;
+import android.util.SparseArray;
import org.junit.Before;
import org.junit.Test;
@@ -43,39 +52,94 @@
public final class CarAudioPlaybackMonitorTest extends AbstractExpectableTestCase {
private static final int PRIMARY_ZONE_ID = 0;
+ private static final int SECONDARY_ZONE_ID = 1;
+ private static final int DEFAULT_ZONE_CONFIG_ID = 0;
+ private static final int DEFAULT_MEDIA_GROUP_ID = 0;
+ private static final int DEFAULT_NAVIGATION_GROUP_ID = 1;
+ private static final int DEFAULT_RINGTONE_GROUP_ID = 2;
+ private static final int PLAYBACK_UID_1 = 10101;
+ private static final int PLAYBACK_UID_2 = 10102;
private static final AudioAttributes TEST_MEDIA_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build();
private static final AudioAttributes TEST_NAVIGATION_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).build();
+ private static final AudioAttributes TEST_NOTIFICATION_RINGTONE_AUDIO_ATTRIBUTE =
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build();
+ private static final AudioAttributes TEST_VOICE_COMMUNICATION_AUDIO_ATTRIBUTE =
+ new AudioAttributes.Builder().setUsage(USAGE_VOICE_COMMUNICATION).build();
+ private CarAudioZone mPrimaryZone;
+ private CarAudioZone mSecondaryZone;
+ private SparseArray<CarAudioZone> mCarAudioZones = new SparseArray<>();
private CarAudioPlaybackMonitor mCarAudioPlaybackMonitor;
@Mock
+ private CarVolumeGroup mMockPrimaryZoneMediaGroup;
+ @Mock
+ private CarVolumeGroup mMockPrimaryZoneNavGroup;
+ @Mock
+ private CarVolumeGroup mMockPrimaryZoneRingtoneGroup;
+ @Mock
+ private CarVolumeGroup mMockSecondaryZoneMediaGroup;
+ @Mock
+ private CarVolumeGroup mMockSecondaryZoneNavGroup;
+ @Mock
private CarAudioService mMockCarAudioService;
+ @Mock
+ private TelephonyManager mMockTelephonyManager;
@Captor
- private ArgumentCaptor<List<AudioAttributes>> mAudioAttributesCaptor;
+ private ArgumentCaptor<List<CarAudioPlaybackMonitor.ActivationInfo>> mActivationInfoCaptor;
@Before
public void setup() {
- mCarAudioPlaybackMonitor = new CarAudioPlaybackMonitor(mMockCarAudioService);
+ mPrimaryZone = generatePrimaryZone();
+ mSecondaryZone = generateSecondaryZone();
+ mCarAudioZones.put(PRIMARY_ZONE_ID, mPrimaryZone);
+ mCarAudioZones.put(SECONDARY_ZONE_ID, mSecondaryZone);
+ mCarAudioPlaybackMonitor = new CarAudioPlaybackMonitor(mMockCarAudioService,
+ mCarAudioZones, mMockTelephonyManager);
}
@Test
public void construct_withNullCarAudioService_fails() {
NullPointerException thrown = assertThrows(NullPointerException.class,
- () -> new CarAudioPlaybackMonitor(/* carAudioService= */ null));
+ () -> new CarAudioPlaybackMonitor(/* carAudioService= */ null,
+ mCarAudioZones, mMockTelephonyManager));
- expectWithMessage("Car playback monitor construction exception")
- .that(thrown).hasMessageThat().contains("Car audio service can not be null");
+ expectWithMessage("Car playback monitor construction exception with null "
+ + "car audio service").that(thrown).hasMessageThat()
+ .contains("Car audio service can not be null");
+ }
+
+ @Test
+ public void construct_withNullCarAudioZones_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class,
+ () -> new CarAudioPlaybackMonitor(mMockCarAudioService,
+ /* carAudioZones= */ null, mMockTelephonyManager));
+
+ expectWithMessage("Car playback monitor construction exception with null "
+ + "car audio zones").that(thrown).hasMessageThat()
+ .contains("Car audio zones can not be null");
+ }
+
+ @Test
+ public void construct_withNullTelephonyManager_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class,
+ () -> new CarAudioPlaybackMonitor(mMockCarAudioService,
+ mCarAudioZones, /* telephonyManager= */ null));
+
+ expectWithMessage("Car playback monitor construction exception with null "
+ + "telephony manager").that(thrown).hasMessageThat()
+ .contains("Telephony manager can not be null");
}
@Test
public void onActiveAudioPlaybackAttributesAdded_withNullAttributesList() {
mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(
- /* newActivePlaybackAttributes= */ null, PRIMARY_ZONE_ID);
+ /* newActivePlaybackAttributesWithUid= */ null, PRIMARY_ZONE_ID);
- verify(mMockCarAudioService, never()).handleActivationVolumeWithAudioAttributes(any(),
- eq(PRIMARY_ZONE_ID));
+ verify(mMockCarAudioService, never()).handleActivationVolumeWithActivationInfos(any(),
+ eq(PRIMARY_ZONE_ID), eq(DEFAULT_ZONE_CONFIG_ID));
}
@Test
@@ -83,19 +147,230 @@
mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(Collections.emptyList(),
PRIMARY_ZONE_ID);
- verify(mMockCarAudioService, never()).handleActivationVolumeWithAudioAttributes(any(),
- eq(PRIMARY_ZONE_ID));
+ verify(mMockCarAudioService, never()).handleActivationVolumeWithActivationInfos(any(),
+ eq(PRIMARY_ZONE_ID), eq(DEFAULT_ZONE_CONFIG_ID));
}
@Test
- public void onActiveAudioPlaybackAttributesAdded() {
+ public void onActiveAudioPlaybackAttributesAdded_forPlaybacksInZoneForFirstTime() {
mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(List.of(
- TEST_MEDIA_AUDIO_ATTRIBUTE, TEST_NAVIGATION_AUDIO_ATTRIBUTE), PRIMARY_ZONE_ID);
+ new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_1),
+ new Pair<>(TEST_NAVIGATION_AUDIO_ATTRIBUTE, PLAYBACK_UID_2)), PRIMARY_ZONE_ID);
- verify(mMockCarAudioService).handleActivationVolumeWithAudioAttributes(
- mAudioAttributesCaptor.capture(), eq(PRIMARY_ZONE_ID));
- expectWithMessage("Audio attributes for playback configuration")
- .that(mAudioAttributesCaptor.getValue())
- .containsExactly(TEST_MEDIA_AUDIO_ATTRIBUTE, TEST_NAVIGATION_AUDIO_ATTRIBUTE);
+ verify(mMockCarAudioService).handleActivationVolumeWithActivationInfos(
+ mActivationInfoCaptor.capture(), eq(PRIMARY_ZONE_ID), eq(DEFAULT_ZONE_CONFIG_ID));
+ expectWithMessage("Activation infos for playback configuration")
+ .that(mActivationInfoCaptor.getValue())
+ .containsExactly(new CarAudioPlaybackMonitor.ActivationInfo(DEFAULT_MEDIA_GROUP_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT),
+ new CarAudioPlaybackMonitor.ActivationInfo(DEFAULT_NAVIGATION_GROUP_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT));
+ }
+
+ @Test
+ public void onActiveAudioPlaybackAttributesAdded_forAudioAttributesOfFirstTime() {
+ mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(List.of(
+ new Pair<>(TEST_NAVIGATION_AUDIO_ATTRIBUTE, PLAYBACK_UID_1)), PRIMARY_ZONE_ID);
+ reset(mMockCarAudioService);
+
+ mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(List.of(
+ new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_2)), PRIMARY_ZONE_ID);
+
+ verify(mMockCarAudioService).handleActivationVolumeWithActivationInfos(
+ mActivationInfoCaptor.capture(), eq(PRIMARY_ZONE_ID), eq(DEFAULT_ZONE_CONFIG_ID));
+ expectWithMessage("Activation infos of playback configuration for the first-time media"
+ + " attributes").that(mActivationInfoCaptor.getValue())
+ .containsExactly(new CarAudioPlaybackMonitor.ActivationInfo(
+ DEFAULT_MEDIA_GROUP_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT));
+ }
+
+ @Test
+ public void onActiveAudioPlaybackAttributesAdded_withSourceChanged() {
+ mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(List.of(
+ new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_1)), PRIMARY_ZONE_ID);
+ reset(mMockCarAudioService);
+
+ mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(List.of(
+ new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_2)), PRIMARY_ZONE_ID);
+
+ verify(mMockCarAudioService).handleActivationVolumeWithActivationInfos(
+ mActivationInfoCaptor.capture(), eq(PRIMARY_ZONE_ID), eq(DEFAULT_ZONE_CONFIG_ID));
+ expectWithMessage("Activation infos of playback configuration with playback source"
+ + " changed").that(mActivationInfoCaptor.getValue())
+ .containsExactly(new CarAudioPlaybackMonitor.ActivationInfo(DEFAULT_MEDIA_GROUP_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_SOURCE_CHANGED));
+ }
+
+ @Test
+ public void onActiveAudioPlaybackAttributesAdded_withPlaybackChanged() {
+ mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(List.of(
+ new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_1)), PRIMARY_ZONE_ID);
+ reset(mMockCarAudioService);
+
+ mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(List.of(
+ new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_1)), PRIMARY_ZONE_ID);
+
+ verify(mMockCarAudioService).handleActivationVolumeWithActivationInfos(
+ mActivationInfoCaptor.capture(), eq(PRIMARY_ZONE_ID), eq(DEFAULT_ZONE_CONFIG_ID));
+ expectWithMessage("Activation infos of playback configuration with playback changed")
+ .that(mActivationInfoCaptor.getValue())
+ .containsExactly(new CarAudioPlaybackMonitor.ActivationInfo(DEFAULT_MEDIA_GROUP_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED));
+ }
+
+ @Test
+ public void onActiveAudioPlaybackAttributesAdded_withNoVolumeGroupFound() {
+ AudioAttributes ringtoneAudioAttribute =
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build();
+
+ mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(List.of(
+ new Pair<>(ringtoneAudioAttribute, PLAYBACK_UID_1)), SECONDARY_ZONE_ID);
+
+ verify(mMockCarAudioService, never()).handleActivationVolumeWithActivationInfos(any(),
+ anyInt(), anyInt());
+ }
+
+ @Test
+ public void onActiveAudioPlaybackAttributesAdded_afterActivationTypesForZonesReset() {
+ mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(List.of(
+ new Pair<>(TEST_NAVIGATION_AUDIO_ATTRIBUTE, PLAYBACK_UID_1)), PRIMARY_ZONE_ID);
+ reset(mMockCarAudioService);
+ mCarAudioPlaybackMonitor.resetActivationTypesForZone(PRIMARY_ZONE_ID);
+
+ mCarAudioPlaybackMonitor.onActiveAudioPlaybackAttributesAdded(List.of(
+ new Pair<>(TEST_NAVIGATION_AUDIO_ATTRIBUTE, PLAYBACK_UID_2)), PRIMARY_ZONE_ID);
+
+ verify(mMockCarAudioService).handleActivationVolumeWithActivationInfos(
+ mActivationInfoCaptor.capture(), eq(PRIMARY_ZONE_ID), eq(DEFAULT_ZONE_CONFIG_ID));
+ expectWithMessage("Activation infos of playback configuration after "
+ + "activation types of zones reset").that(mActivationInfoCaptor.getValue())
+ .containsExactly(new CarAudioPlaybackMonitor.ActivationInfo(
+ DEFAULT_NAVIGATION_GROUP_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT));
+ }
+
+ @Test
+ public void onCallStateChanged_withRingingState() {
+ TelephonyCallback.CallStateListener carCallStateListener = getCallStateListener();
+
+ carCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING);
+
+ verify(mMockCarAudioService).handleActivationVolumeWithActivationInfos(
+ mActivationInfoCaptor.capture(), eq(PRIMARY_ZONE_ID), eq(DEFAULT_ZONE_CONFIG_ID));
+ expectWithMessage("Activation infos of playback with telephony ringing state")
+ .that(mActivationInfoCaptor.getValue())
+ .containsExactly(new CarAudioPlaybackMonitor.ActivationInfo(
+ DEFAULT_RINGTONE_GROUP_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT));
+ }
+
+ @Test
+ public void onCallStateChanged_withOffHookState() {
+ TelephonyCallback.CallStateListener carCallStateListener = getCallStateListener();
+
+ carCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+
+ verify(mMockCarAudioService).handleActivationVolumeWithActivationInfos(
+ mActivationInfoCaptor.capture(), eq(PRIMARY_ZONE_ID), eq(DEFAULT_ZONE_CONFIG_ID));
+ expectWithMessage("Activation infos of playback with telephony off-hook state")
+ .that(mActivationInfoCaptor.getValue())
+ .containsExactly(new CarAudioPlaybackMonitor.ActivationInfo(
+ DEFAULT_RINGTONE_GROUP_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT));
+ }
+
+ @Test
+ public void onCallStateChanged_withIdleState() {
+ TelephonyCallback.CallStateListener carCallStateListener = getCallStateListener();
+ carCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_IDLE);
+
+ verify(mMockCarAudioService, never()).handleActivationVolumeWithActivationInfos(
+ any(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void equals_withActivationInfoWithDifferentTypeObject() {
+ CarAudioPlaybackMonitor.ActivationInfo activationInfo = new CarAudioPlaybackMonitor
+ .ActivationInfo(DEFAULT_MEDIA_GROUP_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT);
+
+ expectWithMessage("Activation info").that(activationInfo).isNotEqualTo(mPrimaryZone);
+ }
+
+ @Test
+ public void equals_withDifferentActivationInfo() {
+ CarAudioPlaybackMonitor.ActivationInfo activationInfo1 = new CarAudioPlaybackMonitor
+ .ActivationInfo(DEFAULT_MEDIA_GROUP_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT);
+ CarAudioPlaybackMonitor.ActivationInfo activationInfo2 = new CarAudioPlaybackMonitor
+ .ActivationInfo(DEFAULT_MEDIA_GROUP_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED);
+
+ expectWithMessage("Activation info with different invocation types")
+ .that(activationInfo1).isNotEqualTo(activationInfo2);
+ }
+
+ @Test
+ public void hashCode_forActivationInfo() {
+ CarAudioPlaybackMonitor.ActivationInfo activationInfo1 = new CarAudioPlaybackMonitor
+ .ActivationInfo(DEFAULT_MEDIA_GROUP_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT);
+ CarAudioPlaybackMonitor.ActivationInfo activationInfo2 = new CarAudioPlaybackMonitor
+ .ActivationInfo(DEFAULT_MEDIA_GROUP_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT);
+
+ expectWithMessage("Hash code of activation info").that(activationInfo1.hashCode())
+ .isEqualTo(activationInfo2.hashCode());
+ }
+
+ private CarAudioZone generatePrimaryZone() {
+ doReturn(DEFAULT_MEDIA_GROUP_ID).when(mMockPrimaryZoneMediaGroup).getId();
+ doReturn(DEFAULT_NAVIGATION_GROUP_ID).when(mMockPrimaryZoneNavGroup).getId();
+ doReturn(DEFAULT_RINGTONE_GROUP_ID).when(mMockPrimaryZoneRingtoneGroup).getId();
+ doReturn(false).when(mMockPrimaryZoneMediaGroup).hasAudioAttributes(any());
+ doReturn(false).when(mMockPrimaryZoneNavGroup).hasAudioAttributes(any());
+ doReturn(false).when(mMockPrimaryZoneRingtoneGroup).hasAudioAttributes(any());
+ doReturn(true).when(mMockPrimaryZoneMediaGroup)
+ .hasAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE);
+ doReturn(true).when(mMockPrimaryZoneNavGroup)
+ .hasAudioAttributes(TEST_NAVIGATION_AUDIO_ATTRIBUTE);
+ doReturn(true).when(mMockPrimaryZoneRingtoneGroup)
+ .hasAudioAttributes(TEST_NOTIFICATION_RINGTONE_AUDIO_ATTRIBUTE);
+ doReturn(true).when(mMockPrimaryZoneRingtoneGroup)
+ .hasAudioAttributes(TEST_VOICE_COMMUNICATION_AUDIO_ATTRIBUTE);
+ CarAudioZoneConfig primaryCarAudioZoneConfig =
+ new CarAudioZoneConfig.Builder("Primary zone config 0", PRIMARY_ZONE_ID,
+ DEFAULT_ZONE_CONFIG_ID, /* isDefault= */ true)
+ .addVolumeGroup(mMockPrimaryZoneMediaGroup)
+ .addVolumeGroup(mMockPrimaryZoneNavGroup)
+ .addVolumeGroup(mMockPrimaryZoneRingtoneGroup).build();
+ return new TestCarAudioZoneBuilder("Primary zone", PRIMARY_ZONE_ID)
+ .addCarAudioZoneConfig(primaryCarAudioZoneConfig).build();
+ }
+
+ private CarAudioZone generateSecondaryZone() {
+ doReturn(DEFAULT_MEDIA_GROUP_ID).when(mMockSecondaryZoneMediaGroup).getId();
+ doReturn(DEFAULT_NAVIGATION_GROUP_ID).when(mMockSecondaryZoneNavGroup).getId();
+ doReturn(false).when(mMockSecondaryZoneMediaGroup).hasAudioAttributes(any());
+ doReturn(false).when(mMockSecondaryZoneNavGroup).hasAudioAttributes(any());
+ doReturn(true).when(mMockSecondaryZoneMediaGroup)
+ .hasAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE);
+ doReturn(true).when(mMockSecondaryZoneNavGroup)
+ .hasAudioAttributes(TEST_NAVIGATION_AUDIO_ATTRIBUTE);
+ CarAudioZoneConfig secondaryCarAudioZoneConfig =
+ new CarAudioZoneConfig.Builder("Secondary zone config 0", SECONDARY_ZONE_ID,
+ DEFAULT_ZONE_CONFIG_ID, /* isDefault= */ true)
+ .addVolumeGroup(mMockSecondaryZoneMediaGroup)
+ .addVolumeGroup(mMockSecondaryZoneNavGroup).build();
+ return new TestCarAudioZoneBuilder("Secondary zone", SECONDARY_ZONE_ID)
+ .addCarAudioZoneConfig(secondaryCarAudioZoneConfig).build();
+ }
+
+ private TelephonyCallback.CallStateListener getCallStateListener() {
+ ArgumentCaptor<TelephonyCallback> captor =
+ ArgumentCaptor.forClass(TelephonyCallback.class);
+ verify(mMockTelephonyManager).registerTelephonyCallback(any(), captor.capture());
+ return (TelephonyCallback.CallStateListener) captor.getValue();
}
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPolicyVolumeCallbackTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPolicyVolumeCallbackTest.java
index ef06710..b2100fb 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPolicyVolumeCallbackTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPolicyVolumeCallbackTest.java
@@ -16,6 +16,7 @@
package com.android.car.audio;
+import static android.car.media.CarAudioManager.INVALID_VOLUME_GROUP_ID;
import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
import static android.media.AudioManager.ADJUST_LOWER;
import static android.media.AudioManager.ADJUST_MUTE;
@@ -40,7 +41,6 @@
import android.car.media.CarVolumeGroupInfo;
import android.car.oem.OemCarVolumeChangeInfo;
-import android.media.AudioManager;
import android.media.audiopolicy.AudioPolicy;
import com.android.car.CarLocalServices;
@@ -78,7 +78,7 @@
@Mock
private CarVolumeInfoWrapper mMockVolumeInfoWrapper;
@Mock
- private AudioManager mMockAudioManager;
+ private AudioManagerWrapper mAudioManagerWrapper;
@Mock
private AudioPolicy.Builder mMockBuilder;
@@ -101,7 +101,7 @@
@Before
public void setUp() {
mCarAudioPolicyVolumeCallback =
- new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mMockAudioManager,
+ new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mAudioManagerWrapper,
mMockVolumeInfoWrapper, false);
when(mMockVolumeInfoWrapper.getSuggestedAudioContextForZone(PRIMARY_AUDIO_ZONE))
.thenReturn(VOICE_COMMAND);
@@ -128,7 +128,7 @@
@Test
public void createCarAudioPolicyVolumeCallback_withNullCarAudioCallback_fails() {
NullPointerException thrown = assertThrows(NullPointerException.class, () ->
- new CarAudioPolicyVolumeCallback(/* volumeCallback = */ null, mMockAudioManager,
+ new CarAudioPolicyVolumeCallback(/* volumeCallback = */ null, mAudioManagerWrapper,
mMockVolumeInfoWrapper, /* useCarVolumeGroupMuting = */ false));
assertWithMessage("Car audio policy volume callback constructor")
@@ -148,7 +148,7 @@
@Test
public void createCarAudioPolicyVolumeCallback_withNullCarVolumeInfo_fails() {
NullPointerException thrown = assertThrows(NullPointerException.class, () ->
- new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mMockAudioManager,
+ new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mAudioManagerWrapper,
/* carVolumeInfo = */ null, /* useCarVolumeGroupMuting = */ false));
assertWithMessage("Car audio policy volume callback constructor")
@@ -160,8 +160,8 @@
NullPointerException thrown = assertThrows(NullPointerException.class, () ->
CarAudioPolicyVolumeCallback.addVolumeCallbackToPolicy(
/* policyBuilder = */ null, new CarAudioPolicyVolumeCallback(
- mVolumeCallbackInternal, mMockAudioManager, mMockVolumeInfoWrapper,
- /* useCarVolumeGroupMuting = */ false)));
+ mVolumeCallbackInternal, mAudioManagerWrapper,
+ mMockVolumeInfoWrapper, /* useCarVolumeGroupMuting = */ false)));
assertWithMessage("Add volume callback to policy")
.that(thrown).hasMessageThat().contains("AudioPolicy.Builder cannot be null");
@@ -170,7 +170,7 @@
@Test
public void addVolumeCallbackToPolicy_registersVolumePolicy() {
CarAudioPolicyVolumeCallback.addVolumeCallbackToPolicy(mMockBuilder,
- new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mMockAudioManager,
+ new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mAudioManagerWrapper,
mMockVolumeInfoWrapper, /* useCarVolumeGroupMuting = */ false));
verify(mMockBuilder).setAudioPolicyVolumeCallback(any());
@@ -222,7 +222,7 @@
setGroupVolumeMute(true);
CarAudioPolicyVolumeCallback callback =
- new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mMockAudioManager,
+ new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mAudioManagerWrapper,
mMockVolumeInfoWrapper, true);
@@ -233,12 +233,54 @@
}
@Test
+ public void onVolumeAdjustment_withAdjustRaiseForInvalidGroup() {
+ when(mMockVolumeInfoWrapper.getVolumeGroupIdForAudioZone(PRIMARY_AUDIO_ZONE))
+ .thenReturn(INVALID_VOLUME_GROUP_ID);
+ CarAudioPolicyVolumeCallback callback =
+ new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mAudioManagerWrapper,
+ mMockVolumeInfoWrapper, /* useCarVolumeGroupMuting= */ true);
+
+ callback.onVolumeAdjustment(ADJUST_RAISE);
+
+ verify(mVolumeCallbackInternal, never())
+ .onGroupVolumeChange(anyInt(), anyInt(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void onVolumeAdjustment_withAdjustLowerForInvalidGroup() {
+ when(mMockVolumeInfoWrapper.getVolumeGroupIdForAudioZone(PRIMARY_AUDIO_ZONE))
+ .thenReturn(INVALID_VOLUME_GROUP_ID);
+ CarAudioPolicyVolumeCallback callback =
+ new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mAudioManagerWrapper,
+ mMockVolumeInfoWrapper, /* useCarVolumeGroupMuting= */ true);
+
+ callback.onVolumeAdjustment(ADJUST_LOWER);
+
+ verify(mVolumeCallbackInternal, never())
+ .onGroupVolumeChange(anyInt(), anyInt(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void onVolumeAdjustment_withAdjustMuteForInvalidGroup() {
+ when(mMockVolumeInfoWrapper.getVolumeGroupIdForAudioZone(PRIMARY_AUDIO_ZONE))
+ .thenReturn(INVALID_VOLUME_GROUP_ID);
+ CarAudioPolicyVolumeCallback callback =
+ new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mAudioManagerWrapper,
+ mMockVolumeInfoWrapper, /* useCarVolumeGroupMuting= */ true);
+
+ callback.onVolumeAdjustment(ADJUST_MUTE);
+
+ verify(mVolumeCallbackInternal, never())
+ .onMuteChange(anyBoolean(), anyInt(), anyInt(), anyInt());
+ }
+
+ @Test
public void onVolumeAdjustment_withAdjustLower_whileMuted_setsGroupVolumeToMin() {
setGroupVolume(TEST_MAX_VOLUME);
setGroupVolumeMute(true);
CarAudioPolicyVolumeCallback callback =
- new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mMockAudioManager,
+ new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mAudioManagerWrapper,
mMockVolumeInfoWrapper, true);
callback.onVolumeAdjustment(ADJUST_LOWER);
@@ -275,7 +317,7 @@
@Test
public void onVolumeAdjustment_withToggleMute_whileMuted_unMutesMasterVolume() {
- when(mMockAudioManager.isMasterMute()).thenReturn(true);
+ when(mAudioManagerWrapper.isMasterMuted()).thenReturn(true);
mCarAudioPolicyVolumeCallback.onVolumeAdjustment(ADJUST_TOGGLE_MUTE);
@@ -285,7 +327,7 @@
@Test
public void onVolumeAdjustment_withToggleMute_whileUnMuted_mutesMasterVolume() {
- when(mMockAudioManager.isMasterMute()).thenReturn(false);
+ when(mAudioManagerWrapper.isMasterMuted()).thenReturn(false);
mCarAudioPolicyVolumeCallback.onVolumeAdjustment(ADJUST_TOGGLE_MUTE);
@@ -296,7 +338,7 @@
@Test
public void onVolumeAdjustment_forGroupMute_withAdjustMute_mutesVolumeGroup() {
CarAudioPolicyVolumeCallback callback =
- new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mMockAudioManager,
+ new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mAudioManagerWrapper,
mMockVolumeInfoWrapper, true);
callback.onVolumeAdjustment(ADJUST_MUTE);
@@ -310,7 +352,7 @@
setGroupVolumeMute(true);
CarAudioPolicyVolumeCallback callback =
- new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mMockAudioManager,
+ new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mAudioManagerWrapper,
mMockVolumeInfoWrapper, true);
callback.onVolumeAdjustment(ADJUST_TOGGLE_MUTE);
@@ -324,7 +366,7 @@
setGroupVolumeMute(false);
CarAudioPolicyVolumeCallback callback =
- new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mMockAudioManager,
+ new CarAudioPolicyVolumeCallback(mVolumeCallbackInternal, mAudioManagerWrapper,
mMockVolumeInfoWrapper, true);
callback.onVolumeAdjustment(ADJUST_UNMUTE);
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioServerStateCallbackTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioServerStateCallbackTest.java
index c4f574c..5ea376c 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioServerStateCallbackTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioServerStateCallbackTest.java
@@ -53,7 +53,7 @@
public void onAudioServerDown() {
mAudioServerStateCallback.onAudioServerDown();
- verify(mMockCarAudioService).setAudioEnabled(false);
+ verify(mMockCarAudioService).releaseAudioCallbacks(/* isAudioServerDown= */ true);
}
@Test
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioServiceUnitTest.java
index 0705c5d..9986499 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioServiceUnitTest.java
@@ -85,6 +85,7 @@
import static android.view.KeyEvent.KEYCODE_VOLUME_MUTE;
import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
+import static com.android.car.R.bool.audioEnableVolumeKeyEventsToDynamicDevices;
import static com.android.car.R.bool.audioPersistMasterMuteState;
import static com.android.car.R.bool.audioUseCarVolumeGroupEvent;
import static com.android.car.R.bool.audioUseCarVolumeGroupMuting;
@@ -134,7 +135,6 @@
import android.car.media.CarVolumeGroupInfo;
import android.car.media.IAudioZoneConfigurationsChangeCallback;
import android.car.media.IAudioZonesMirrorStatusCallback;
-import android.car.media.ICarVolumeEventCallback;
import android.car.media.IMediaAudioRequestStatusCallback;
import android.car.media.IPrimaryZoneMediaAudioRequestCallback;
import android.car.media.ISwitchAudioZoneConfigCallback;
@@ -142,6 +142,7 @@
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.car.test.mocks.MockSettings;
import android.car.test.util.TemporaryFile;
+import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -172,6 +173,8 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -179,6 +182,8 @@
import android.os.UserHandle;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.NoSuchPropertyException;
@@ -187,8 +192,10 @@
import androidx.test.core.app.ApplicationProvider;
import com.android.car.CarInputService;
+import com.android.car.CarInputService.KeyEventListener;
import com.android.car.CarLocalServices;
import com.android.car.CarOccupantZoneService;
+import com.android.car.CarServiceUtils;
import com.android.car.R;
import com.android.car.audio.hal.AudioControlFactory;
import com.android.car.audio.hal.AudioControlWrapper;
@@ -202,6 +209,7 @@
import com.android.car.oem.CarOemAudioFocusProxyService;
import com.android.car.oem.CarOemAudioVolumeProxyService;
import com.android.car.oem.CarOemProxyService;
+import com.android.car.power.CarPowerManagementService;
import org.junit.After;
import org.junit.Before;
@@ -214,6 +222,7 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -221,7 +230,7 @@
public final class CarAudioServiceUnitTest extends AbstractExtendedMockitoTestCase {
private static final String TAG = CarAudioServiceUnitTest.class.getSimpleName();
private static final long TEST_CALLBACK_TIMEOUT_MS = 100;
- private static final long TEST_ZONE_CONFIG_CALLBACK_TIMEOUT_MS = 350;
+ private static final long TEST_ZONE_CONFIG_CALLBACK_TIMEOUT_MS = 500;
private static final int VOLUME_KEY_EVENT_TIMEOUT_MS = 3000;
private static final int AUDIO_CONTEXT_PRIORITY_LIST_VERSION_ONE = 1;
private static final int AUDIO_CONTEXT_PRIORITY_LIST_VERSION_TWO = 2;
@@ -293,6 +302,8 @@
private static final int TEST_GAIN_DEFAULT_VALUE = -2000;
private static final int TEST_GAIN_STEP_VALUE = 2;
+ private static final int TEST_PLAYBACK_UID = 10101;
+
private static final CarOccupantZoneManager.OccupantZoneInfo TEST_DRIVER_OCCUPANT =
getOccupantInfo(TEST_DRIVER_OCCUPANT_ZONE_ID,
CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER,
@@ -421,17 +432,28 @@
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, AUDIOFOCUS_NONE, /* flags= */ 0,
Build.VERSION.SDK_INT);
+ private static final int AUDIO_SERVICE_POLICY_REGISTRATIONS = 3;
+ private static final int AUDIO_SERVICE_POLICY_REGISTRATIONS_WITH_FADE_MANAGER = 4;
+ private static final int AUDIO_SERVICE_CALLBACKS_REGISTRATION = 1;
+
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+
@Mock
private Context mMockContext;
@Mock
+ private TelephonyManager mMockTelephonyManagerWithoutSubscriptionId;
+ @Mock
private TelephonyManager mMockTelephonyManager;
@Mock
- private AudioManager mAudioManager;
+ private AudioManagerWrapper mAudioManager;
@Mock
private Resources mMockResources;
@Mock
private ContentResolver mMockContentResolver;
@Mock
+ private AttributionSource mMockAttributionSource;
+ @Mock
IBinder mBinder;
@Mock
IBinder mVolumeCallbackBinder;
@@ -453,6 +475,8 @@
private CarVolumeCallbackHandler mCarVolumeCallbackHandler;
@Mock
private CarInputService mMockCarInputService;
+ @Mock
+ private CarPowerManagementService mMockPowerService;
// Not used directly, but sets proper mockStatic() expectations on Settings
@SuppressWarnings("UnusedVariable")
@@ -464,6 +488,8 @@
private boolean mUseCarVolumeGroupMuting = true;
private boolean mUseCarVolumeGroupEvents = true;
private boolean mUseMinMaxActivationVolume = true;
+ private boolean mEnableVolumeKeyEventsToDynamicDevices = false;
+
private TemporaryFile mTempCarAudioConfigFile;
private TemporaryFile mTempCarAudioFadeConfigFile;
@@ -509,7 +535,8 @@
protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
mMockSettings = new MockSettings(session);
session
- .spyStatic(AudioManager.class)
+ .spyStatic(SubscriptionManager.class)
+ .spyStatic(AudioManagerWrapper.class)
.spyStatic(AudioManagerHelper.class)
.spyStatic(AudioControlWrapperAidl.class)
.spyStatic(CoreAudioHelper.class)
@@ -521,6 +548,8 @@
@Before
public void setUp() throws Exception {
+ mHandlerThread = CarServiceUtils.getHandlerThread(CarAudioService.class.getSimpleName());
+ mHandler = new Handler(mHandlerThread.getLooper());
mContext = ApplicationProvider.getApplicationContext();
mockCarGetPlatformVersion(UPSIDE_DOWN_CAKE_0);
@@ -546,6 +575,7 @@
}
CarLocalServices.removeServiceForTest(CarOemProxyService.class);
CarLocalServices.removeServiceForTest(CarOccupantZoneService.class);
+ CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
}
private void setUpAudioControlHAL() {
@@ -562,15 +592,18 @@
when(mAudioControlWrapperAidl.supportsFeature(
AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_MODULE_CALLBACK)).thenReturn(true);
doReturn(mAudioControlWrapperAidl)
- .when(() -> AudioControlFactory.newAudioControl());
+ .when(AudioControlFactory::newAudioControl);
}
private void setUpService() throws Exception {
- when(mMockContext.getSystemService(Context.TELEPHONY_SERVICE))
+ doReturn(0).when(() -> SubscriptionManager.getDefaultDataSubscriptionId());
+ when(mMockContext.getSystemService(TelephonyManager.class))
+ .thenReturn(mMockTelephonyManagerWithoutSubscriptionId);
+ when(mMockTelephonyManagerWithoutSubscriptionId.createForSubscriptionId(anyInt()))
.thenReturn(mMockTelephonyManager);
- when(mMockContext.getSystemService(Context.AUDIO_SERVICE))
- .thenReturn(mAudioManager);
+
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockContext.getAttributionSource()).thenReturn(mMockAttributionSource);
doReturn(true)
.when(() -> AudioManagerHelper
.setAudioDeviceGain(any(), any(), anyInt(), anyBoolean()));
@@ -579,7 +612,6 @@
when(mMockOccupantZoneService.getUserForOccupant(TEST_DRIVER_OCCUPANT_ZONE_ID))
.thenReturn(TEST_DRIVER_USER_ID);
- when(mMockOccupantZoneService.getDriverUserId()).thenReturn(TEST_DRIVER_USER_ID);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(TEST_DRIVER_OCCUPANT_ZONE_ID))
.thenReturn(PRIMARY_AUDIO_ZONE);
when(mMockOccupantZoneService.getOccupantZoneForUser(UserHandle.of(TEST_DRIVER_USER_ID)))
@@ -621,10 +653,15 @@
when(mMockOccupantZoneService.getOccupantForAudioZoneId(TEST_REAR_ROW_3_ZONE_ID))
.thenReturn(TEST_REAR_ROW_3_PASSENGER_OCCUPANT);
+ // Initially set occupant zone service at uninitialized
+ when(mMockOccupantZoneService.getDriverUserId()).thenReturn(UserHandle.USER_SYSTEM);
+
CarLocalServices.removeServiceForTest(CarOccupantZoneService.class);
CarLocalServices.addService(CarOccupantZoneService.class, mMockOccupantZoneService);
CarLocalServices.removeServiceForTest(CarInputService.class);
CarLocalServices.addService(CarInputService.class, mMockCarInputService);
+ CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
+ CarLocalServices.addService(CarPowerManagementService.class, mMockPowerService);
CarLocalServices.removeServiceForTest(CarOemProxyService.class);
CarLocalServices.addService(CarOemProxyService.class, mMockCarOemProxyService);
@@ -716,7 +753,6 @@
.thenReturn(outputDevices);
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
.thenReturn(inputDevices);
- when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
when(mAudioManager.registerAudioPolicy(any())).thenAnswer(invocation -> {
AudioPolicy policy = (AudioPolicy) invocation.getArguments()[0];
@@ -727,6 +763,9 @@
? SUCCESS : mAudioPolicyRegistrationStatus.get(mRegistrationCount++);
});
+ when(mAudioManager.isAudioServerRunning()).thenReturn(true);
+
+ // Needed by audio policy when setting UID device affinity
IBinder mockBinder = mock(IBinder.class);
when(mockBinder.queryLocalInterface(any())).thenReturn(mMockAudioService);
doReturn(mockBinder).when(() -> ServiceManager.getService(Context.AUDIO_SERVICE));
@@ -749,6 +788,23 @@
when(mMockResources.getInteger(audioVolumeAdjustmentContextsVersion))
.thenReturn(AUDIO_CONTEXT_PRIORITY_LIST_VERSION_ONE);
when(mMockResources.getBoolean(audioPersistMasterMuteState)).thenReturn(mPersistMasterMute);
+ enableVolumeKeyEventsToDynamicDevices(mEnableVolumeKeyEventsToDynamicDevices);
+ }
+
+ private void enableVolumeKeyEventsToDynamicDevices(boolean enableVolumeKeyEvents) {
+ when(mMockResources.getBoolean(audioEnableVolumeKeyEventsToDynamicDevices))
+ .thenReturn(enableVolumeKeyEvents);
+ }
+
+ @Test
+ public void constructor_withValidContext() {
+ AudioManager manager = mock(AudioManager.class);
+ when(mMockContext.getSystemService(AudioManager.class)).thenReturn(manager);
+
+ new CarAudioService(mMockContext);
+
+ verify(mMockContext).getSystemService(AudioManager.class);
+ verify(mMockContext).getSystemService(TelephonyManager.class);
}
@Test
@@ -756,7 +812,7 @@
NullPointerException thrown =
assertThrows(NullPointerException.class, () -> new CarAudioService(null));
- expectWithMessage("Car Audio Service Construction")
+ expectWithMessage("Car Audio Service Construction Exception")
.that(thrown).hasMessageThat().contains("Context");
}
@@ -765,6 +821,7 @@
NullPointerException thrown =
assertThrows(NullPointerException.class,
() -> new CarAudioService(/* context= */null,
+ /* audioManagerWrapper= */ null,
/* audioConfigurationPath= */ null,
/* carVolumeCallbackHandler= */ null,
/* audioFadeConfigurationPath= */ null));
@@ -874,6 +931,7 @@
@Test
public void init_initializesAudioServiceCallbacks_withDynamicDevices() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
CarAudioService service = setUpAudioServiceWithDynamicDevices();
service.init();
@@ -884,6 +942,7 @@
@Test
public void init_withDynamicDevices() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
CarAudioService audioServiceWithDynamicDevices = setUpAudioServiceWithDynamicDevices();
audioServiceWithDynamicDevices.init();
@@ -902,6 +961,18 @@
}
@Test
+ public void init_withAudioServerDown() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
+ when(mAudioManager.isAudioServerRunning()).thenReturn(false);
+ CarAudioService service = setUpAudioServiceWithDynamicDevices();
+
+ service.init();
+
+ verify(mAudioManager).setAudioServerStateCallback(any(), any());
+ verify(mAudioManager, never()).registerAudioDeviceCallback(any(), any());
+ }
+
+ @Test
public void release_releasesAudioServiceCallbacks() throws Exception {
mSetFlagsRule.disableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
CarAudioService service = setUpAudioService();
@@ -910,6 +981,7 @@
verify(mAudioManager, never()).unregisterAudioDeviceCallback(any());
verify(mAudioManager).clearAudioServerStateCallback();
+ verify(mAudioControlWrapperAidl).clearModuleChangeCallback();
}
@Test
@@ -921,6 +993,18 @@
verify(mAudioManager).unregisterAudioDeviceCallback(any());
verify(mAudioManager).clearAudioServerStateCallback();
+ verify(mAudioControlWrapperAidl).clearModuleChangeCallback();
+ }
+
+ @Test
+ public void release_withoutModuleChangeCallback() throws Exception {
+ when(mAudioControlWrapperAidl.supportsFeature(
+ AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_MODULE_CALLBACK)).thenReturn(false);
+ CarAudioService service = setUpAudioService();
+
+ service.release();
+
+ verify(mAudioControlWrapperAidl, never()).clearModuleChangeCallback();
}
@Test
@@ -2060,6 +2144,56 @@
}
@Test
+ public void init_forUserAlreadySetup_callsInternalConfigChange() throws Exception {
+ when(mMockOccupantZoneService.getDriverUserId()).thenReturn(TEST_DRIVER_USER_ID);
+ when(mMockOccupantZoneService.getUserForOccupant(TEST_DRIVER_OCCUPANT_ZONE_ID))
+ .thenReturn(TEST_DRIVER_USER_ID);
+ when(mMockOccupantZoneService.getUserForOccupant(TEST_REAR_RIGHT_OCCUPANT_ZONE_ID))
+ .thenReturn(TEST_REAR_RIGHT_USER_ID);
+ CarAudioService service = setUpAudioServiceWithoutInit();
+
+ service.init();
+
+ waitForInternalCallback();
+ expectWithMessage("User ID for primary zone for user available at init")
+ .that(service.getUserIdForZone(PRIMARY_AUDIO_ZONE))
+ .isEqualTo(TEST_DRIVER_USER_ID);
+ expectWithMessage("User ID secondary zone for user available at init")
+ .that(service.getUserIdForZone(TEST_REAR_RIGHT_ZONE_ID))
+ .isEqualTo(TEST_REAR_RIGHT_USER_ID);
+ }
+
+ @Test
+ public void init_withAudioModuleCallbackFeatureDisabled() throws Exception {
+ when(mAudioControlWrapperAidl.supportsFeature(
+ AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_MODULE_CALLBACK)).thenReturn(false);
+
+ setUpAudioService();
+
+ verify(mAudioControlWrapperAidl, never()).setModuleChangeCallback(any());
+ }
+
+ @Test
+ public void init_withAudioFocusFeatureDisabled() throws Exception {
+ when(mAudioControlWrapperAidl.supportsFeature(
+ AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_FOCUS)).thenReturn(false);
+
+ setUpAudioService();
+
+ verify(mAudioControlWrapperAidl, never()).registerFocusListener(any());
+ }
+
+ @Test
+ public void init_withAudioGainCallbackFeatureDisabled() throws Exception {
+ when(mAudioControlWrapperAidl.supportsFeature(
+ AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_GAIN_CALLBACK)).thenReturn(false);
+
+ setUpAudioService();
+
+ verify(mAudioControlWrapperAidl, never()).registerAudioGainCallback(any());
+ }
+
+ @Test
public void serviceDied_registersAudioGainCallback() throws Exception {
setUpAudioService();
ArgumentCaptor<AudioControlDeathRecipient> captor =
@@ -2074,6 +2208,22 @@
}
@Test
+ public void serviceDied_withNullAudioGainCallback() throws Exception {
+ when(mAudioControlWrapperAidl.supportsFeature(
+ AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_GAIN_CALLBACK)).thenReturn(false);
+ setUpAudioService();
+ ArgumentCaptor<AudioControlDeathRecipient> captor =
+ ArgumentCaptor.forClass(AudioControlDeathRecipient.class);
+ verify(mAudioControlWrapperAidl).linkToDeath(captor.capture());
+ AudioControlDeathRecipient runnable = captor.getValue();
+ reset(mAudioControlWrapperAidl);
+
+ runnable.serviceDied();
+
+ verify(mAudioControlWrapperAidl, never()).registerAudioGainCallback(any());
+ }
+
+ @Test
public void serviceDied_registersFocusListener() throws Exception {
setUpAudioService();
ArgumentCaptor<AudioControlDeathRecipient> captor =
@@ -2088,6 +2238,39 @@
}
@Test
+ public void serviceDied_withAudioServerNotRunning() throws Exception {
+ setUpAudioService();
+ ArgumentCaptor<AudioControlDeathRecipient> captor =
+ ArgumentCaptor.forClass(AudioControlDeathRecipient.class);
+ verify(mAudioControlWrapperAidl).linkToDeath(captor.capture());
+ AudioControlDeathRecipient runnable = captor.getValue();
+ reset(mAudioControlWrapperAidl);
+ when(mAudioManager.isAudioServerRunning()).thenReturn(false);
+
+ runnable.serviceDied();
+
+ verify(mAudioControlWrapperAidl, never()).registerAudioGainCallback(any());
+ verify(mAudioControlWrapperAidl, never()).registerFocusListener(any());
+ }
+
+ @Test
+ public void serviceDied_withAudioServerDown() throws Exception {
+ CarAudioService service = setUpAudioService();
+ ArgumentCaptor<AudioControlDeathRecipient> captor =
+ ArgumentCaptor.forClass(AudioControlDeathRecipient.class);
+ verify(mAudioControlWrapperAidl).linkToDeath(captor.capture());
+ AudioControlDeathRecipient runnable = captor.getValue();
+ reset(mAudioControlWrapperAidl);
+ service.releaseAudioCallbacks(/* isAudioServerDown= */ true);
+
+ runnable.serviceDied();
+
+ verify(mAudioControlWrapperAidl, never()).registerAudioGainCallback(any());
+ verify(mAudioControlWrapperAidl, never()).registerFocusListener(any());
+ verify(mAudioControlWrapperAidl, never()).setModuleChangeCallback(any());
+ }
+
+ @Test
public void serviceDied_setsModuleChangeCallback() throws Exception {
setUpAudioService();
ArgumentCaptor<AudioControlDeathRecipient> captor =
@@ -2102,6 +2285,22 @@
}
@Test
+ public void serviceDied_withNullModuleChangeCallback() throws Exception {
+ when(mAudioControlWrapperAidl.supportsFeature(
+ AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_MODULE_CALLBACK)).thenReturn(false);
+ setUpAudioService();
+ ArgumentCaptor<AudioControlDeathRecipient> captor =
+ ArgumentCaptor.forClass(AudioControlDeathRecipient.class);
+ verify(mAudioControlWrapperAidl).linkToDeath(captor.capture());
+ AudioControlDeathRecipient runnable = captor.getValue();
+ reset(mAudioControlWrapperAidl);
+
+ runnable.serviceDied();
+
+ verify(mAudioControlWrapperAidl, never()).setModuleChangeCallback(any());
+ }
+
+ @Test
public void getVolumeGroupIdForAudioContext_forPrimaryGroup() throws Exception {
CarAudioService service = setUpAudioService();
@@ -2187,10 +2386,83 @@
public void onAudioServerDown_forCarAudioServiceCallback() throws Exception {
setUpAudioService();
AudioServerStateCallback callback = getAudioServerStateCallback();
+ AudioDeviceCallback deviceCallback = captureAudioDeviceCallback();
+ AudioPlaybackCallback playbackCallback = getCarAudioPlaybackCallback();
+ ICarOccupantZoneCallback occupantZoneCallback = getOccupantZoneCallback();
+ KeyEventListener keyInputListener = getAudioKeyEventListener();
callback.onAudioServerDown();
- verify(mAudioControlWrapperAidl).onDevicesToMuteChange(any());
+ verify(mAudioControlWrapperAidl, never()).onDevicesToMuteChange(any());
+ // Routing policy is not unregistered on audio server going down
+ verify(mAudioManager, times(AUDIO_SERVICE_POLICY_REGISTRATIONS - 1))
+ .unregisterAudioPolicy(any());
+ verify(mAudioManager).unregisterAudioPlaybackCallback(playbackCallback);
+ verify(mAudioControlWrapperAidl).unregisterFocusListener();
+ verify(mAudioManager, never()).unregisterVolumeGroupCallback(any());
+ verify(mMockPowerService).removePowerPolicyListener(any());
+ verify(mMockTelephonyManager).unregisterTelephonyCallback(any());
+ verify(mAudioManager).unregisterAudioDeviceCallback(deviceCallback);
+ verify(mAudioControlWrapperAidl).clearModuleChangeCallback();
+ verify(mMockOccupantZoneService).unregisterCallback(occupantZoneCallback);
+ verify(mMockCarInputService).unregisterKeyEventListener(keyInputListener);
+ verify(mAudioControlWrapperAidl, never()).unlinkToDeath();
+ }
+
+ @Test
+ public void onAudioServerDown_forCarAudioServiceCallback_withFadeManagerEnabled()
+ throws Exception {
+ setUpCarAudioServiceWithFadeManagerEnabled();
+ AudioServerStateCallback callback = getAudioServerStateCallback();
+ AudioDeviceCallback deviceCallback = captureAudioDeviceCallback();
+ AudioPlaybackCallback playbackCallback = getCarAudioPlaybackCallback();
+ ICarOccupantZoneCallback occupantZoneCallback = getOccupantZoneCallback();
+ KeyEventListener keyInputListener = getAudioKeyEventListener();
+
+ callback.onAudioServerDown();
+
+ verify(mAudioControlWrapperAidl, never()).onDevicesToMuteChange(any());
+ // Routing policy is not unregistered on audio server going down
+ verify(mAudioManager, times(AUDIO_SERVICE_POLICY_REGISTRATIONS_WITH_FADE_MANAGER - 1))
+ .unregisterAudioPolicy(any());
+ verify(mAudioManager).unregisterAudioPlaybackCallback(playbackCallback);
+ verify(mAudioControlWrapperAidl).unregisterFocusListener();
+ verify(mAudioManager, never()).unregisterVolumeGroupCallback(any());
+ verify(mMockPowerService).removePowerPolicyListener(any());
+ verify(mMockTelephonyManager).unregisterTelephonyCallback(any());
+ verify(mAudioManager).unregisterAudioDeviceCallback(deviceCallback);
+ verify(mAudioControlWrapperAidl).clearModuleChangeCallback();
+ verify(mMockOccupantZoneService).unregisterCallback(occupantZoneCallback);
+ verify(mMockCarInputService).unregisterKeyEventListener(keyInputListener);
+ verify(mAudioControlWrapperAidl, never()).unlinkToDeath();
+ }
+
+ @Test
+ public void onAudioServerDown_forCarAudioServiceCallback_withCoreVolumeAndRouting()
+ throws Exception {
+ setUpCarAudioServiceUsingCoreAudioRoutingAndVolume();
+ AudioServerStateCallback callback = getAudioServerStateCallback();
+ AudioDeviceCallback deviceCallback = captureAudioDeviceCallback();
+ AudioPlaybackCallback playbackCallback = getCarAudioPlaybackCallback();
+ ICarOccupantZoneCallback occupantZoneCallback = getOccupantZoneCallback();
+ KeyEventListener keyInputListener = getAudioKeyEventListener();
+
+ callback.onAudioServerDown();
+
+ verify(mAudioControlWrapperAidl, never()).onDevicesToMuteChange(any());
+ // Routing policy is not unregistered on audio server going down
+ verify(mAudioManager, times(AUDIO_SERVICE_POLICY_REGISTRATIONS - 1))
+ .unregisterAudioPolicy(any());
+ verify(mAudioManager).unregisterAudioPlaybackCallback(playbackCallback);
+ verify(mAudioControlWrapperAidl).unregisterFocusListener();
+ verify(mAudioManager).unregisterVolumeGroupCallback(any());
+ verify(mMockPowerService).removePowerPolicyListener(any());
+ verify(mMockTelephonyManager).unregisterTelephonyCallback(any());
+ verify(mAudioManager).unregisterAudioDeviceCallback(deviceCallback);
+ verify(mAudioControlWrapperAidl).clearModuleChangeCallback();
+ verify(mMockOccupantZoneService).unregisterCallback(occupantZoneCallback);
+ verify(mMockCarInputService).unregisterKeyEventListener(keyInputListener);
+ verify(mAudioControlWrapperAidl, never()).unlinkToDeath();
}
@Test
@@ -2201,10 +2473,134 @@
callback.onAudioServerUp();
+ waitForInternalCallback();
expectWithMessage("Re-initialized Car Audio Service Zones")
.that(service.getAudioZoneIds()).asList()
.containsExactly(PRIMARY_AUDIO_ZONE, TEST_REAR_LEFT_ZONE_ID,
TEST_REAR_RIGHT_ZONE_ID, TEST_FRONT_ZONE_ID, TEST_REAR_ROW_3_ZONE_ID);
+ // Each callback should register twice the registration from init for each required callback
+ verify(mAudioManager, times(2 * AUDIO_SERVICE_POLICY_REGISTRATIONS))
+ .registerAudioPolicy(any());
+ verify(mAudioManager, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerAudioPlaybackCallback(any(), any());
+ verify(mAudioControlWrapperAidl, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerFocusListener(any());
+ verify(mAudioManager, never()).registerVolumeGroupCallback(any(), any());
+ verify(mMockPowerService, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .addPowerPolicyListener(any(), any());
+ verify(mAudioManager, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerAudioDeviceCallback(any(), any());
+ verify(mAudioControlWrapperAidl, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .setModuleChangeCallback(any());
+ verify(mMockOccupantZoneService, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerCallback(any());
+ verify(mMockCarInputService, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerKeyEventListener(any(), any());
+ verify(mAudioControlWrapperAidl, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .linkToDeath(any());
+ }
+
+ @Test
+ public void onAudioServerUp_forCarAudioServiceCallback_withFadeManagerEnabled()
+ throws Exception {
+ CarAudioService service = setUpCarAudioServiceWithFadeManagerEnabled();
+ AudioServerStateCallback callback = getAudioServerStateCallback();
+ callback.onAudioServerDown();
+
+ callback.onAudioServerUp();
+
+ expectWithMessage("Re-initialized Car Audio Service Zones")
+ .that(service.getAudioZoneIds()).asList()
+ .containsExactly(PRIMARY_AUDIO_ZONE, TEST_REAR_LEFT_ZONE_ID,
+ TEST_REAR_RIGHT_ZONE_ID, TEST_FRONT_ZONE_ID, TEST_REAR_ROW_3_ZONE_ID);
+ // Each callback should register twice the registration from init for each required callback
+ verify(mAudioManager, times(2 * AUDIO_SERVICE_POLICY_REGISTRATIONS_WITH_FADE_MANAGER))
+ .registerAudioPolicy(any());
+ verify(mAudioManager, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerAudioPlaybackCallback(any(), any());
+ verify(mAudioControlWrapperAidl, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerFocusListener(any());
+ verify(mAudioManager, never()).registerVolumeGroupCallback(any(), any());
+ verify(mMockPowerService, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .addPowerPolicyListener(any(), any());
+ verify(mAudioManager, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerAudioDeviceCallback(any(), any());
+ verify(mAudioControlWrapperAidl, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .setModuleChangeCallback(any());
+ verify(mMockOccupantZoneService, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerCallback(any());
+ verify(mMockCarInputService, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerKeyEventListener(any(), any());
+ verify(mAudioControlWrapperAidl, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .linkToDeath(any());
+ }
+
+
+ @Test
+ public void onAudioServerUp_forCarAudioServiceCallback_withCoreVolumeAndRouting()
+ throws Exception {
+ CarAudioService service = setUpCarAudioServiceUsingCoreAudioRoutingAndVolume();
+ AudioServerStateCallback callback = getAudioServerStateCallback();
+ callback.onAudioServerDown();
+
+ callback.onAudioServerUp();
+
+ expectWithMessage("Re-initialized Car Audio Service Zones")
+ .that(service.getAudioZoneIds()).asList()
+ .containsExactly(PRIMARY_AUDIO_ZONE);
+ // Each callback should register twice the registration from init for each required callback
+ verify(mAudioManager, times(2 * AUDIO_SERVICE_POLICY_REGISTRATIONS))
+ .registerAudioPolicy(any());
+ verify(mAudioManager, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerAudioPlaybackCallback(any(), any());
+ verify(mAudioControlWrapperAidl, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerFocusListener(any());
+ verify(mAudioManager, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerVolumeGroupCallback(any(), any());
+ verify(mMockPowerService, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .addPowerPolicyListener(any(), any());
+ verify(mAudioManager, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerAudioDeviceCallback(any(), any());
+ verify(mAudioControlWrapperAidl, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .setModuleChangeCallback(any());
+ verify(mMockOccupantZoneService, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerCallback(any());
+ verify(mMockCarInputService, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .registerKeyEventListener(any(), any());
+ verify(mAudioControlWrapperAidl, times(2 * AUDIO_SERVICE_CALLBACKS_REGISTRATION))
+ .linkToDeath(any());
+ }
+
+ @Test
+ public void onAudioServerUp_forUserIdAssignments() throws Exception {
+ CarAudioService service = setUpAudioService();
+ when(mMockOccupantZoneService.getDriverUserId()).thenReturn(TEST_DRIVER_USER_ID);
+ when(mMockOccupantZoneService.getUserForOccupant(TEST_DRIVER_OCCUPANT_ZONE_ID))
+ .thenReturn(TEST_DRIVER_USER_ID);
+ AudioServerStateCallback callback = getAudioServerStateCallback();
+ callback.onAudioServerDown();
+
+ callback.onAudioServerUp();
+
+ waitForInternalCallback();
+ expectWithMessage("Re-initialized Car Audio Service Zones")
+ .that(service.getAudioZoneIds()).asList()
+ .containsExactly(PRIMARY_AUDIO_ZONE, TEST_REAR_LEFT_ZONE_ID,
+ TEST_REAR_RIGHT_ZONE_ID, TEST_FRONT_ZONE_ID, TEST_REAR_ROW_3_ZONE_ID);
+ expectWithMessage("Primary user id after server recovery")
+ .that(service.getUserIdForZone(PRIMARY_AUDIO_ZONE)).isEqualTo(TEST_DRIVER_USER_ID);
+ expectWithMessage("Rear left user id after server recovery")
+ .that(service.getUserIdForZone(TEST_REAR_LEFT_ZONE_ID))
+ .isEqualTo(TEST_REAR_LEFT_USER_ID);
+ expectWithMessage("Rear right user id after server recovery")
+ .that(service.getUserIdForZone(TEST_REAR_RIGHT_ZONE_ID))
+ .isEqualTo(TEST_REAR_RIGHT_USER_ID);
+ expectWithMessage("Rear front user id after server recovery")
+ .that(service.getUserIdForZone(TEST_FRONT_ZONE_ID))
+ .isEqualTo(TEST_FRONT_PASSENGER_USER_ID);
+ expectWithMessage("Rear row 3 user id after server recovery")
+ .that(service.getUserIdForZone(TEST_REAR_ROW_3_ZONE_ID))
+ .isEqualTo(TEST_REAR_ROW_3_PASSENGER_USER_ID);
}
@Test
@@ -2361,7 +2757,7 @@
@Test
public void getVolumeGroupInfosForZone_forOEMConfiguration() throws Exception {
setUpTempFileForAudioConfiguration(R.raw.car_audio_configuration_using_oem_defined_context);
- CarAudioService nonDynamicAudioService = new CarAudioService(mMockContext,
+ CarAudioService nonDynamicAudioService = new CarAudioService(mMockContext, mAudioManager,
mTempCarAudioConfigFile.getFile().getAbsolutePath(), mCarVolumeCallbackHandler,
/* audioFadeConfigurationPath= */ null);
nonDynamicAudioService.init();
@@ -2449,6 +2845,15 @@
}
@Test
+ public void getVolumeGroupInfo_withLegacyMode() throws Exception {
+ CarAudioService service = setUpAudioServiceWithoutDynamicRouting();
+
+ expectWithMessage("Volume group info in legacy mode")
+ .that(service.getVolumeGroupInfo(PRIMARY_OCCUPANT_ZONE, TEST_PRIMARY_ZONE_GROUP_0))
+ .isNull();
+ }
+
+ @Test
public void registerPrimaryZoneMediaAudioRequestCallbackListener_withNullCallback_fails()
throws Exception {
CarAudioService service = setUpAudioService();
@@ -3087,7 +3492,7 @@
CarAudioService service = setUpAudioService();
int volumeBefore = service.getGroupVolume(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0);
- CarInputService.KeyEventListener listener = getAudioKeyEventListener();
+ KeyEventListener listener = getAudioKeyEventListener();
when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
.thenReturn(PRIMARY_OCCUPANT_ZONE);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(PRIMARY_OCCUPANT_ZONE))
@@ -3106,7 +3511,7 @@
CarAudioService service = setUpAudioService();
int volumeBefore = service.getGroupVolume(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0);
- CarInputService.KeyEventListener listener = getAudioKeyEventListener();
+ KeyEventListener listener = getAudioKeyEventListener();
when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
.thenReturn(PRIMARY_OCCUPANT_ZONE);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(PRIMARY_OCCUPANT_ZONE))
@@ -3125,7 +3530,7 @@
CarAudioService service = setUpAudioService();
int volumeBefore = service.getGroupVolume(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0);
- CarInputService.KeyEventListener listener = getAudioKeyEventListener();
+ KeyEventListener listener = getAudioKeyEventListener();
when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
.thenReturn(PRIMARY_OCCUPANT_ZONE);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(PRIMARY_OCCUPANT_ZONE))
@@ -3141,11 +3546,116 @@
}
@Test
+ public void onKeyEvent_forDynamicDevKeyEventEnabledForDefaultConfigForZoneWithDynamicDevices()
+ throws Exception {
+ enableVolumeKeyEventsToDynamicDevices(/* enableVolumeKeyEvents= */ true);
+ CarAudioService service = setUpAudioServiceWithDynamicDevices();
+ service.init();
+ assignOccupantToAudioZones();
+ int volumeBefore = service.getGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0);
+ KeyEventListener listener = getAudioKeyEventListener();
+ when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
+ .thenReturn(PRIMARY_OCCUPANT_ZONE);
+ when(mMockOccupantZoneService.getAudioZoneIdForOccupant(PRIMARY_OCCUPANT_ZONE))
+ .thenReturn(PRIMARY_AUDIO_ZONE);
+ KeyEvent actionDownKeyEvent = new KeyEvent(ACTION_DOWN, KEYCODE_VOLUME_UP);
+ KeyEvent actionUpKeyEvent = new KeyEvent(ACTION_UP, KEYCODE_VOLUME_UP);
+ listener.onKeyEvent(actionDownKeyEvent, TEST_DISPLAY_TYPE, TEST_SEAT);
+
+ listener.onKeyEvent(actionUpKeyEvent, TEST_DISPLAY_TYPE, TEST_SEAT);
+
+ expectWithMessage("Volume group volume after volume up in primary zone in primary group "
+ + "for volume group without dynamic devices")
+ .that(service.getGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0))
+ .isEqualTo(volumeBefore + 1);
+ }
+
+ @Test
+ public void
+ onKeyEvent_forDynamicDevKeyEventEnabledForDynamicDeviceConfigForZoneWithDynamicDevices()
+ throws Exception {
+ enableVolumeKeyEventsToDynamicDevices(/* enableVolumeKeyEvents= */ true);
+ CarAudioService service = setUpAudioServiceWithDynamicDevices();
+ service.init();
+ assignOccupantToAudioZones();
+ SwitchAudioZoneConfigCallbackImpl callback = new SwitchAudioZoneConfigCallbackImpl();
+ TestAudioZoneConfigurationsChangeCallback configCallback =
+ getRegisteredZoneConfigCallback(service);
+ AudioDeviceCallback deviceCallback = captureAudioDeviceCallback();
+ deviceCallback.onAudioDevicesAdded(new AudioDeviceInfo[]{mBTAudioDeviceInfo});
+ configCallback.waitForCallback();
+ configCallback.reset();
+ List<CarAudioZoneConfigInfo> zoneConfigInfos =
+ service.getAudioZoneConfigInfos(PRIMARY_AUDIO_ZONE);
+ CarAudioZoneConfigInfo zoneConfigSwitchTo = zoneConfigInfos.stream()
+ .filter(c -> c.getName().equals(PRIMARY_CONFIG_NAME_DYNAMIC_DEVICES))
+ .findFirst().orElseThrow();
+ service.switchZoneToConfig(zoneConfigSwitchTo, callback);
+ configCallback.waitForCallback();
+ int volumeBefore = service.getGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0);
+ KeyEventListener listener = getAudioKeyEventListener();
+ when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
+ .thenReturn(PRIMARY_OCCUPANT_ZONE);
+ when(mMockOccupantZoneService.getAudioZoneIdForOccupant(PRIMARY_OCCUPANT_ZONE))
+ .thenReturn(PRIMARY_AUDIO_ZONE);
+ KeyEvent actionDownKeyEvent = new KeyEvent(ACTION_DOWN, KEYCODE_VOLUME_UP);
+ KeyEvent actionUpKeyEvent = new KeyEvent(ACTION_UP, KEYCODE_VOLUME_UP);
+ listener.onKeyEvent(actionDownKeyEvent, TEST_DISPLAY_TYPE, TEST_SEAT);
+
+ listener.onKeyEvent(actionUpKeyEvent, TEST_DISPLAY_TYPE, TEST_SEAT);
+
+ expectWithMessage("Volume after volume up in primary zone in primary group "
+ + "for volume group with dynamic devices while dynamic device key events enabled")
+ .that(service.getGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0))
+ .isEqualTo(volumeBefore + 1);
+ }
+
+ @Test
+ public void
+ onKeyEvent_forDynDevKeyEventDisabledForDynamicDeviceConfigForZoneWithDynamicDevices()
+ throws Exception {
+ enableVolumeKeyEventsToDynamicDevices(/* enableVolumeKeyEvents= */ false);
+ CarAudioService service = setUpAudioServiceWithDynamicDevices();
+ service.init();
+ assignOccupantToAudioZones();
+ SwitchAudioZoneConfigCallbackImpl callback = new SwitchAudioZoneConfigCallbackImpl();
+ TestAudioZoneConfigurationsChangeCallback configCallback =
+ getRegisteredZoneConfigCallback(service);
+ AudioDeviceCallback deviceCallback = captureAudioDeviceCallback();
+ deviceCallback.onAudioDevicesAdded(new AudioDeviceInfo[]{mBTAudioDeviceInfo});
+ configCallback.waitForCallback();
+ configCallback.reset();
+ List<CarAudioZoneConfigInfo> zoneConfigInfos =
+ service.getAudioZoneConfigInfos(PRIMARY_AUDIO_ZONE);
+ CarAudioZoneConfigInfo zoneConfigSwitchTo = zoneConfigInfos.stream()
+ .filter(c -> c.getName().equals(PRIMARY_CONFIG_NAME_DYNAMIC_DEVICES))
+ .findFirst().orElseThrow();
+ service.switchZoneToConfig(zoneConfigSwitchTo, callback);
+ configCallback.waitForCallback();
+ int volumeBefore = service.getGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0);
+ KeyEventListener listener = getAudioKeyEventListener();
+ when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
+ .thenReturn(PRIMARY_OCCUPANT_ZONE);
+ when(mMockOccupantZoneService.getAudioZoneIdForOccupant(PRIMARY_OCCUPANT_ZONE))
+ .thenReturn(PRIMARY_AUDIO_ZONE);
+ KeyEvent actionDownKeyEvent = new KeyEvent(ACTION_DOWN, KEYCODE_VOLUME_UP);
+ KeyEvent actionUpKeyEvent = new KeyEvent(ACTION_UP, KEYCODE_VOLUME_UP);
+ listener.onKeyEvent(actionDownKeyEvent, TEST_DISPLAY_TYPE, TEST_SEAT);
+
+ listener.onKeyEvent(actionUpKeyEvent, TEST_DISPLAY_TYPE, TEST_SEAT);
+
+ expectWithMessage("Volume after volume up in primary zone in primary group "
+ + "for volume group with dynamic devices while dynamic device key events disabled")
+ .that(service.getGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0))
+ .isEqualTo(volumeBefore);
+ }
+
+ @Test
public void onKeyEvent_forActionDownFollowedByActionUp() throws Exception {
CarAudioService service = setUpAudioService();
int volumeBefore = service.getGroupVolume(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0);
- CarInputService.KeyEventListener listener = getAudioKeyEventListener();
+ KeyEventListener listener = getAudioKeyEventListener();
when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
.thenReturn(PRIMARY_OCCUPANT_ZONE);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(PRIMARY_OCCUPANT_ZONE))
@@ -3167,7 +3677,7 @@
CarAudioService service = setUpAudioService();
int volumeBefore = service.getGroupVolume(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0);
- CarInputService.KeyEventListener listener = getAudioKeyEventListener();
+ KeyEventListener listener = getAudioKeyEventListener();
when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
.thenReturn(PRIMARY_OCCUPANT_ZONE);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(PRIMARY_OCCUPANT_ZONE))
@@ -3186,7 +3696,7 @@
CarAudioService service = setUpAudioService();
int volumeBefore = service.getGroupVolume(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0);
- CarInputService.KeyEventListener listener = getAudioKeyEventListener();
+ KeyEventListener listener = getAudioKeyEventListener();
when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
.thenReturn(PRIMARY_OCCUPANT_ZONE);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(PRIMARY_OCCUPANT_ZONE))
@@ -3211,7 +3721,7 @@
.setDeviceAddress(VOICE_TEST_DEVICE)
.build())
);
- CarInputService.KeyEventListener listener = getAudioKeyEventListener();
+ KeyEventListener listener = getAudioKeyEventListener();
when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
.thenReturn(PRIMARY_OCCUPANT_ZONE);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(PRIMARY_OCCUPANT_ZONE))
@@ -3243,7 +3753,7 @@
.setDeviceAddress(MEDIA_TEST_DEVICE)
.build())
);
- CarInputService.KeyEventListener listener = getAudioKeyEventListener();
+ KeyEventListener listener = getAudioKeyEventListener();
when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
.thenReturn(PRIMARY_OCCUPANT_ZONE);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(PRIMARY_OCCUPANT_ZONE))
@@ -3279,7 +3789,7 @@
.setDeviceAddress(MEDIA_TEST_DEVICE)
.build())
);
- CarInputService.KeyEventListener listener = getAudioKeyEventListener();
+ KeyEventListener listener = getAudioKeyEventListener();
when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
.thenReturn(PRIMARY_OCCUPANT_ZONE);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(PRIMARY_OCCUPANT_ZONE))
@@ -3301,7 +3811,7 @@
CarAudioService service = setUpAudioService();
boolean muteBefore = service.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0);
- CarInputService.KeyEventListener listener = getAudioKeyEventListener();
+ KeyEventListener listener = getAudioKeyEventListener();
when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
.thenReturn(PRIMARY_OCCUPANT_ZONE);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(PRIMARY_OCCUPANT_ZONE))
@@ -3320,7 +3830,7 @@
CarAudioService service = setUpAudioService();
int volumeBefore = service.getGroupVolume(TEST_REAR_LEFT_ZONE_ID,
SECONDARY_ZONE_VOLUME_GROUP_ID);
- CarInputService.KeyEventListener listener = getAudioKeyEventListener();
+ KeyEventListener listener = getAudioKeyEventListener();
when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
.thenReturn(TEST_DRIVER_OCCUPANT_ZONE_ID);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(TEST_DRIVER_OCCUPANT_ZONE_ID))
@@ -3340,7 +3850,7 @@
CarAudioService service = setUpAudioService();
int volumeBefore = service.getGroupVolume(TEST_REAR_LEFT_ZONE_ID,
SECONDARY_ZONE_VOLUME_GROUP_ID);
- CarInputService.KeyEventListener listener = getAudioKeyEventListener();
+ KeyEventListener listener = getAudioKeyEventListener();
when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
.thenReturn(TEST_DRIVER_OCCUPANT_ZONE_ID);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(TEST_DRIVER_OCCUPANT_ZONE_ID))
@@ -3360,7 +3870,7 @@
CarAudioService service = setUpAudioService();
boolean muteBefore = service.isVolumeGroupMuted(TEST_REAR_LEFT_ZONE_ID,
SECONDARY_ZONE_VOLUME_GROUP_ID);
- CarInputService.KeyEventListener listener = getAudioKeyEventListener();
+ KeyEventListener listener = getAudioKeyEventListener();
when(mMockOccupantZoneService.getOccupantZoneIdForSeat(TEST_SEAT))
.thenReturn(TEST_DRIVER_OCCUPANT_ZONE_ID);
when(mMockOccupantZoneService.getAudioZoneIdForOccupant(TEST_DRIVER_OCCUPANT_ZONE_ID))
@@ -3460,6 +3970,9 @@
public void onAudioPortsChanged_forMediaBus_changesVolumeRanges() throws Exception {
CarAudioService service = setUpAudioService();
HalAudioModuleChangeCallback callback = getHalModuleChangeCallback();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
+ service.registerCarVolumeEventCallback(volumeEventCallback);
HalAudioDeviceInfo mediaBusDeviceInfo = createHalAudioDeviceInfo(
TEST_MEDIA_PORT_ID, TEST_MEDIA_PORT_NAME, TEST_GAIN_MIN_VALUE, TEST_GAIN_MAX_VALUE,
TEST_GAIN_DEFAULT_VALUE, TEST_GAIN_STEP_VALUE, OUT_DEVICE, MEDIA_TEST_DEVICE);
@@ -3468,9 +3981,17 @@
callback.onAudioPortsChanged(List.of(mediaBusDeviceInfo));
+ CarVolumeGroupInfo volumeGroupInfoAfter = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_0);
expectWithMessage("update audio port for media device")
- .that(service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
- TEST_PRIMARY_ZONE_GROUP_0)).isNotEqualTo(volumeGroupInfoBefore);
+ .that(volumeGroupInfoAfter).isNotEqualTo(volumeGroupInfoBefore);
+ volumeEventCallback.waitForCallback();
+ expectWithMessage("Volume events count after switching zone configuration")
+ .that(volumeEventCallback.getVolumeGroupEvents()).hasSize(1);
+ CarVolumeGroupEvent groupEvent = volumeEventCallback.getVolumeGroupEvents().get(0);
+ expectWithMessage("Volume group infos after switching zone configuration")
+ .that(groupEvent.getCarVolumeGroupInfos())
+ .containsExactly(volumeGroupInfoAfter);
}
@Test
@@ -3516,6 +4037,20 @@
}
@Test
+ public void onAudioPortsChanged_withEmptyDeviceInfoList() throws Exception {
+ CarAudioService service = setUpAudioService();
+ HalAudioModuleChangeCallback callback = getHalModuleChangeCallback();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
+ service.registerCarVolumeEventCallback(volumeEventCallback);
+
+ callback.onAudioPortsChanged(Collections.EMPTY_LIST);
+
+ expectWithMessage("No volume event callback invocation with empty device info list")
+ .that(volumeEventCallback.waitForCallback()).isFalse();
+ }
+
+ @Test
public void getActiveAudioAttributesForZone() throws Exception {
CarAudioService service = setUpAudioService();
@@ -3561,9 +4096,10 @@
@Test
public void getCallStateForZone_forPrimaryZone() throws Exception {
- when(mMockTelephonyManager.getCallState()).thenReturn(TelephonyManager.CALL_STATE_OFFHOOK);
- when(mMockOccupantZoneService.getDriverUserId()).thenReturn(TEST_DRIVER_USER_ID);
+ when(mMockTelephonyManagerWithoutSubscriptionId.getCallState())
+ .thenReturn(TelephonyManager.CALL_STATE_OFFHOOK);
CarAudioService service = setUpAudioService();
+ when(mMockOccupantZoneService.getDriverUserId()).thenReturn(TEST_DRIVER_USER_ID);
when(mMockOccupantZoneService.getUserForOccupant(anyInt()))
.thenReturn(TEST_DRIVER_USER_ID, TEST_REAR_RIGHT_USER_ID);
assignOccupantToAudioZones();
@@ -3575,9 +4111,10 @@
@Test
public void getCallStateForZone_forNonPrimaryZone() throws Exception {
- when(mMockTelephonyManager.getCallState()).thenReturn(TelephonyManager.CALL_STATE_OFFHOOK);
- when(mMockOccupantZoneService.getDriverUserId()).thenReturn(TEST_DRIVER_USER_ID);
CarAudioService service = setUpAudioService();
+ when(mMockTelephonyManagerWithoutSubscriptionId.getCallState())
+ .thenReturn(TelephonyManager.CALL_STATE_OFFHOOK);
+ when(mMockOccupantZoneService.getDriverUserId()).thenReturn(TEST_DRIVER_USER_ID);
when(mMockOccupantZoneService.getUserForOccupant(anyInt()))
.thenReturn(TEST_REAR_LEFT_USER_ID, TEST_REAR_RIGHT_USER_ID);
assignOccupantToAudioZones();
@@ -4112,7 +4649,8 @@
throws Exception {
CarAudioService service = setUpAudioService();
SwitchAudioZoneConfigCallbackImpl callback = new SwitchAudioZoneConfigCallbackImpl();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
assignOccupantToAudioZones();
CarAudioZoneConfigInfo zoneConfigSwitchTo = getZoneConfigToSwitch(service,
TEST_REAR_LEFT_ZONE_ID);
@@ -4345,6 +4883,28 @@
}
@Test
+ public void onAudioDevicesAdded_forDynamicDevicesEnabled_withAudioServerDown()
+ throws Exception {
+ CarAudioService audioServiceWithDynamicDevices = setUpAudioServiceWithDynamicDevices();
+ audioServiceWithDynamicDevices.init();
+ TestAudioZoneConfigurationsChangeCallback
+ configCallback = getRegisteredZoneConfigCallback(audioServiceWithDynamicDevices);
+ AudioDeviceCallback deviceCallback = captureAudioDeviceCallback();
+ audioServiceWithDynamicDevices.releaseAudioCallbacks(/* isAudioServerDown= */ true);
+
+ deviceCallback.onAudioDevicesAdded(new AudioDeviceInfo[]{mBTAudioDeviceInfo});
+
+ configCallback.waitForCallback();
+ List<CarAudioZoneConfigInfo> zoneConfigInfos =
+ audioServiceWithDynamicDevices.getAudioZoneConfigInfos(PRIMARY_AUDIO_ZONE);
+ CarAudioZoneConfigInfo btConfig = zoneConfigInfos.stream()
+ .filter(config -> config.getName().equals(PRIMARY_CONFIG_NAME_DYNAMIC_DEVICES))
+ .findFirst().orElseThrow();
+ expectWithMessage("Disabled bluetooth configuration with audio server down")
+ .that(btConfig.isActive()).isFalse();
+ }
+
+ @Test
public void onAudioDevicesRemoved_forDynamicDevicesEnabled_triggersCallback()
throws Exception {
CarAudioService serviceWithDynamicDevices = setUpAudioServiceWithDynamicDevices();
@@ -4425,6 +4985,32 @@
}
@Test
+ public void onAudioDevicesRemoved_forDynamicDevicesEnabled_afterAddedWithAudioServerDown()
+ throws Exception {
+ CarAudioService audioServiceWithDynamicDevices = setUpAudioServiceWithDynamicDevices();
+ audioServiceWithDynamicDevices.init();
+ TestAudioZoneConfigurationsChangeCallback
+ configCallback = getRegisteredZoneConfigCallback(audioServiceWithDynamicDevices);
+ AudioDeviceCallback deviceCallback = captureAudioDeviceCallback();
+ deviceCallback.onAudioDevicesAdded(new AudioDeviceInfo[]{mBTAudioDeviceInfo});
+ configCallback.waitForCallback();
+ configCallback.reset();
+ audioServiceWithDynamicDevices.releaseAudioCallbacks(/* isAudioServerDown= */ true);
+
+ deviceCallback.onAudioDevicesRemoved(new AudioDeviceInfo[]{mBTAudioDeviceInfo});
+
+ configCallback.waitForCallback();
+ List<CarAudioZoneConfigInfo> zoneConfigInfos =
+ audioServiceWithDynamicDevices.getAudioZoneConfigInfos(PRIMARY_AUDIO_ZONE);
+ CarAudioZoneConfigInfo btConfig = zoneConfigInfos.stream()
+ .filter(config -> config.getName().equals(PRIMARY_CONFIG_NAME_DYNAMIC_DEVICES))
+ .findFirst().orElseThrow();
+ expectWithMessage(
+ "Enabled bluetooth configuration after removed device with audio server down")
+ .that(btConfig.isActive()).isTrue();
+ }
+
+ @Test
public void unregisterAudioZoneConfigsChangeCallback() throws Exception {
IAudioZoneConfigurationsChangeCallback callback =
new TestAudioZoneConfigurationsChangeCallback();
@@ -4837,9 +5423,35 @@
}
@Test
+ public void onAudioVolumeGroupChanged_withInvalidVolumeGroupName() throws Exception {
+ CarAudioService useCoreAudioCarAudioService =
+ setUpCarAudioServiceUsingCoreAudioRoutingAndVolume();
+
+ useCoreAudioCarAudioService.onAudioVolumeGroupChanged(PRIMARY_AUDIO_ZONE,
+ CoreAudioRoutingUtils.INVALID_GROUP_NAME, /* flags= */ 0);
+
+ verify(mCarVolumeCallbackHandler, never()).onVolumeGroupChange(eq(PRIMARY_AUDIO_ZONE),
+ anyInt(), anyInt());
+ }
+
+ @Test
+ public void callbackVolumeGroupEvent_withEmptyEventList() throws Exception {
+ CarAudioService service = setUpAudioService();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
+ service.registerCarVolumeEventCallback(volumeEventCallback);
+
+ service.callbackVolumeGroupEvent(Collections.EMPTY_LIST);
+
+ expectWithMessage("Volume group event callback reception status for empty event list")
+ .that(volumeEventCallback.waitForCallback()).isFalse();
+ }
+
+ @Test
public void onVolumeGroupEvent_withVolumeEvent_triggersCallback() throws Exception {
CarAudioService service = setUpAudioService();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
service.onVolumeGroupEvent(List.of(mTestCarVolumeGroupEvent));
@@ -4864,7 +5476,8 @@
@Test
public void onVolumeGroupEvent_withMuteEvent_triggersCallback() throws Exception {
CarAudioService service = setUpAudioService();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
service.onVolumeGroupEvent(List.of(mTestCarMuteGroupEvent));
@@ -4891,7 +5504,8 @@
public void onVolumeGroupEvent_withoutMuteOrVolumeEvent_triggersCallback()
throws Exception {
CarAudioService service = setUpAudioService();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
service.onVolumeGroupEvent(List.of(mTestCarZoneReconfigurationEvent));
@@ -4915,7 +5529,8 @@
@Test
public void setMuted_whenUnmuted_onActivation_triggersCallback() throws Exception {
CarAudioService service = setUpAudioService();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
service.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
@@ -4939,7 +5554,8 @@
@Test
public void setMuted_whenUnmuted_onDeactivation_doesNotTriggerCallback() throws Exception {
CarAudioService service = setUpAudioService();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
service.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
@@ -4953,13 +5569,12 @@
@Test
public void setMuted_whenMuted_onDeactivation_triggersCallback() throws Exception {
CarAudioService service = setUpAudioService();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
service.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
/* mute= */ true, TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ resetVolumeCallbacks(volumeEventCallback);
service.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
/* mute= */ false, TEST_FLAGS);
@@ -4982,16 +5597,15 @@
@Test
public void setUnmuted_whenMutedBySystem_triggersCallback() throws Exception {
CarAudioService service = setUpAudioService();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
CarAudioGainConfigInfo primaryAudioZoneCarGain = createCarAudioGainConfigInfo(
PRIMARY_AUDIO_ZONE, MEDIA_TEST_DEVICE, TEST_GAIN_INDEX);
HalAudioGainCallback halAudioGainCallback = getHalAudioGainCallback();
halAudioGainCallback.onAudioDeviceGainsChanged(List.of(Reasons.TCU_MUTE),
List.of(primaryAudioZoneCarGain));
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ resetVolumeCallbacks(volumeEventCallback);
service.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
/* mute= */ false, TEST_FLAGS);
@@ -5011,21 +5625,18 @@
@Test
public void setMuted_whenMutedByApiAndSystem_doesNotTriggerCallback() throws Exception {
CarAudioService service = setUpAudioService();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
service.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0, /* mute= */ true,
TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ resetVolumeCallbacks(volumeEventCallback);
CarAudioGainConfigInfo primaryAudioZoneCarGain = createCarAudioGainConfigInfo(
PRIMARY_AUDIO_ZONE, MEDIA_TEST_DEVICE, TEST_GAIN_INDEX);
HalAudioGainCallback halAudioGainCallback = getHalAudioGainCallback();
halAudioGainCallback.onAudioDeviceGainsChanged(List.of(Reasons.TCU_MUTE),
List.of(primaryAudioZoneCarGain));
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ resetVolumeCallbacks(volumeEventCallback);
service.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0, /* mute= */ true,
TEST_FLAGS);
@@ -5038,13 +5649,12 @@
@Test
public void setMuted_whenMuted_onActivation_doesNotTriggerCallback() throws Exception {
CarAudioService service = setUpAudioService();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
service.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
/* mute= */ true, TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ resetVolumeCallbacks(volumeEventCallback);
service.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
/* mute= */ true, TEST_FLAGS);
@@ -5059,25 +5669,26 @@
throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
+ int currentConfigId = service.getCurrentAudioZoneConfigInfo(PRIMARY_AUDIO_ZONE)
+ .getConfigId();
int mediaMaxActivationGainIndex = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0).getMaxActivationVolumeGainIndex();
- service.setGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
- mediaMaxActivationGainIndex + 1, TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ setVolumeForGroup(service, volumeEventCallback, PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_0, mediaMaxActivationGainIndex + 1);
int navMinActivationGainIndex = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
- TEST_PRIMARY_ZONE_GROUP_0).getMinActivationVolumeGainIndex();
- service.setGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_1,
- navMinActivationGainIndex - 1, TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ TEST_PRIMARY_ZONE_GROUP_1).getMinActivationVolumeGainIndex();
+ setVolumeForGroup(service, volumeEventCallback, PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_1, navMinActivationGainIndex - 1);
- service.handleActivationVolumeWithAudioAttributes(List.of(
- ATTRIBUTES_ASSISTANCE_NAVIGATION_GUIDANCE, ATTRIBUTES_MEDIA), PRIMARY_AUDIO_ZONE);
+ service.handleActivationVolumeWithActivationInfos(List.of(
+ new CarAudioPlaybackMonitor.ActivationInfo(TEST_PRIMARY_ZONE_GROUP_0,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT),
+ new CarAudioPlaybackMonitor.ActivationInfo(TEST_PRIMARY_ZONE_GROUP_1,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT)),
+ PRIMARY_AUDIO_ZONE, currentConfigId);
expectWithMessage("Media volume for above-activation gain index")
.that(service.getGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0))
@@ -5104,22 +5715,47 @@
}
@Test
+ public void handleActivationVolumeWithAudioAttributes_withNonCurrentZoneConfig()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
+ CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
+ service.registerCarVolumeEventCallback(volumeEventCallback);
+ int nonCurrentConfigId = getZoneConfigToSwitch(service, TEST_REAR_LEFT_ZONE_ID)
+ .getConfigId();
+ int mediaGainIndexAboveMaxActivation = service.getVolumeGroupInfo(TEST_REAR_LEFT_ZONE_ID,
+ SECONDARY_ZONE_VOLUME_GROUP_ID).getMaxActivationVolumeGainIndex() + 1;
+ setVolumeForGroup(service, volumeEventCallback, TEST_REAR_LEFT_ZONE_ID,
+ SECONDARY_ZONE_VOLUME_GROUP_ID, mediaGainIndexAboveMaxActivation);
+
+ service.handleActivationVolumeWithActivationInfos(List.of(
+ new CarAudioPlaybackMonitor.ActivationInfo(TEST_REAR_LEFT_ZONE_ID,
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT)),
+ TEST_REAR_LEFT_ZONE_ID, nonCurrentConfigId);
+
+ verify(mCarVolumeCallbackHandler, never()).onVolumeGroupChange(eq(TEST_REAR_LEFT_ZONE_ID),
+ eq(SECONDARY_ZONE_VOLUME_GROUP_ID), anyInt());
+ expectWithMessage("Volume event callback for non-current zone config activation volume")
+ .that(volumeEventCallback.waitForCallback()).isFalse();
+ }
+
+ @Test
public void onPlaybackConfigChanged_withActivationVolumeFlagDisabled() throws Exception {
mSetFlagsRule.disableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
AudioPlaybackCallback callback = getCarAudioPlaybackCallback();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
int gainIndex = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0).getMaxActivationVolumeGainIndex() + 1;
- service.setGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0, gainIndex,
- TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ setVolumeForGroup(service, volumeEventCallback, PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_0, gainIndex);
callback.onPlaybackConfigChanged(List.of(new AudioPlaybackConfigurationBuilder()
- .setUsage(USAGE_MEDIA).setDeviceAddress(MEDIA_TEST_DEVICE).build()));
+ .setUsage(USAGE_MEDIA).setDeviceAddress(MEDIA_TEST_DEVICE)
+ .setClientUid(TEST_PLAYBACK_UID).build()));
expectWithMessage("Playback group volume with activation volume flag disabled")
.that(service.getGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0))
@@ -5135,18 +5771,17 @@
mSetFlagsRule.disableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ false);
AudioPlaybackCallback callback = getCarAudioPlaybackCallback();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
int gainIndex = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0).getMaxActivationVolumeGainIndex() + 1;
- service.setGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0, gainIndex,
- TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ setVolumeForGroup(service, volumeEventCallback, PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_0, gainIndex);
callback.onPlaybackConfigChanged(List.of(new AudioPlaybackConfigurationBuilder()
- .setUsage(USAGE_MEDIA).setDeviceAddress(MEDIA_TEST_DEVICE).build()));
+ .setUsage(USAGE_MEDIA).setDeviceAddress(MEDIA_TEST_DEVICE)
+ .setClientUid(TEST_PLAYBACK_UID).build()));
expectWithMessage("Playback group volume with activation volume feature disabled")
.that(service.getGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0))
@@ -5162,18 +5797,17 @@
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
AudioPlaybackCallback callback = getCarAudioPlaybackCallback();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
int maxActivationVolume = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0).getMaxActivationVolumeGainIndex();
- service.setGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
- maxActivationVolume + 1, TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ setVolumeForGroup(service, volumeEventCallback, PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_0, maxActivationVolume + 1);
callback.onPlaybackConfigChanged(List.of(new AudioPlaybackConfigurationBuilder()
- .setUsage(USAGE_MEDIA).setDeviceAddress(MEDIA_TEST_DEVICE).build()));
+ .setUsage(USAGE_MEDIA).setDeviceAddress(MEDIA_TEST_DEVICE)
+ .setClientUid(TEST_PLAYBACK_UID).build()));
expectWithMessage("Playback group volume for above-activation gain index")
.that(service.getGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0))
@@ -5198,19 +5832,18 @@
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
AudioPlaybackCallback callback = getCarAudioPlaybackCallback();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
int minActivationVolume = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_1).getMinActivationVolumeGainIndex();
- service.setGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_1,
- minActivationVolume - 1, TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ setVolumeForGroup(service, volumeEventCallback, PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_1, minActivationVolume - 1);
callback.onPlaybackConfigChanged(List.of(new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
- .setDeviceAddress(NAVIGATION_TEST_DEVICE).build()));
+ .setDeviceAddress(NAVIGATION_TEST_DEVICE).setClientUid(TEST_PLAYBACK_UID)
+ .build()));
expectWithMessage("Playback group volume for below-activation gain index")
.that(service.getGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_1))
@@ -5235,18 +5868,17 @@
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
AudioPlaybackCallback callback = getCarAudioPlaybackCallback();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
int gainIndexInActivationVolumeRange = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0).getMaxActivationVolumeGainIndex() - 1;
- service.setGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
- gainIndexInActivationVolumeRange, TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ setVolumeForGroup(service, volumeEventCallback, PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_0, gainIndexInActivationVolumeRange);
callback.onPlaybackConfigChanged(List.of(new AudioPlaybackConfigurationBuilder()
- .setUsage(USAGE_MEDIA).setDeviceAddress(MEDIA_TEST_DEVICE).build()));
+ .setUsage(USAGE_MEDIA).setDeviceAddress(MEDIA_TEST_DEVICE)
+ .setClientUid(TEST_PLAYBACK_UID).build()));
expectWithMessage("Playback group volume in activation volume range")
.that(service.getGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0))
@@ -5262,22 +5894,20 @@
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
AudioPlaybackCallback callback = getCarAudioPlaybackCallback();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
int gainIndexAboveActivationVolume = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0).getMaxActivationVolumeGainIndex() + 1;
- service.setGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
- gainIndexAboveActivationVolume, TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
+ setVolumeForGroup(service, volumeEventCallback, PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_0, gainIndexAboveActivationVolume);
service.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
/* mute= */ true, TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ resetVolumeCallbacks(volumeEventCallback);
callback.onPlaybackConfigChanged(List.of(new AudioPlaybackConfigurationBuilder()
- .setUsage(USAGE_MEDIA).setDeviceAddress(MEDIA_TEST_DEVICE).build()));
+ .setUsage(USAGE_MEDIA).setDeviceAddress(MEDIA_TEST_DEVICE)
+ .setClientUid(TEST_PLAYBACK_UID).build()));
expectWithMessage("Mute state with playback volume higher than max activation volume")
.that(service.isVolumeGroupMuted(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0))
@@ -5291,27 +5921,123 @@
}
@Test
+ public void onPlaybackConfigChanged_afterZoneConfigSwitched() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
+ CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
+ SwitchAudioZoneConfigCallbackImpl zoneConfigSwitchCallback =
+ new SwitchAudioZoneConfigCallbackImpl();
+ assignOccupantToAudioZones();
+ AudioPlaybackCallback callback = getCarAudioPlaybackCallback();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
+ service.registerCarVolumeEventCallback(volumeEventCallback);
+ int maxActivationVolume = service.getVolumeGroupInfo(TEST_REAR_LEFT_ZONE_ID,
+ TEST_SECONDARY_ZONE_GROUP_0).getMaxActivationVolumeGainIndex();
+ setVolumeForGroup(service, volumeEventCallback, TEST_REAR_LEFT_ZONE_ID,
+ TEST_SECONDARY_ZONE_GROUP_0, maxActivationVolume + 1);
+ createActivePlayback(callback, volumeEventCallback, USAGE_MEDIA,
+ SECONDARY_TEST_DEVICE_CONFIG_0, TEST_PLAYBACK_UID);
+ CarAudioZoneConfigInfo zoneConfigSwitchTo = getZoneConfigToSwitch(service,
+ TEST_REAR_LEFT_ZONE_ID);
+ service.switchZoneToConfig(zoneConfigSwitchTo, zoneConfigSwitchCallback);
+ zoneConfigSwitchCallback.waitForCallback();
+ resetVolumeCallbacks(volumeEventCallback);
+ maxActivationVolume = service.getVolumeGroupInfo(TEST_REAR_LEFT_ZONE_ID,
+ TEST_SECONDARY_ZONE_GROUP_0).getMaxActivationVolumeGainIndex();
+ setVolumeForGroup(service, volumeEventCallback, TEST_REAR_LEFT_ZONE_ID,
+ TEST_SECONDARY_ZONE_GROUP_0, maxActivationVolume + 1);
+
+ callback.onPlaybackConfigChanged(List.of(new AudioPlaybackConfigurationBuilder()
+ .setUsage(USAGE_MEDIA).setDeviceAddress(SECONDARY_TEST_DEVICE_CONFIG_1_0)
+ .setClientUid(TEST_PLAYBACK_UID).build()));
+
+ expectWithMessage("Playback group volume after zone config switch")
+ .that(service.getGroupVolume(TEST_REAR_LEFT_ZONE_ID, TEST_SECONDARY_ZONE_GROUP_0))
+ .isEqualTo(maxActivationVolume);
+ verify(mCarVolumeCallbackHandler).onVolumeGroupChange(eq(TEST_REAR_LEFT_ZONE_ID),
+ eq(TEST_SECONDARY_ZONE_GROUP_0), anyInt());
+ expectWithMessage("Volume event callback after zone config switch")
+ .that(volumeEventCallback.waitForCallback()).isTrue();
+ expectWithMessage("Volume events count after zone config switch")
+ .that(volumeEventCallback.getVolumeGroupEvents()).hasSize(1);
+ CarVolumeGroupEvent groupEvent = volumeEventCallback.getVolumeGroupEvents().get(0);
+ expectWithMessage("Volume event type after zone config switch")
+ .that(groupEvent.getEventTypes())
+ .isEqualTo(CarVolumeGroupEvent.EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED);
+ expectWithMessage("Volume group info after zone config switch")
+ .that(groupEvent.getCarVolumeGroupInfos()).containsExactly(
+ service.getVolumeGroupInfo(TEST_REAR_LEFT_ZONE_ID,
+ TEST_SECONDARY_ZONE_GROUP_0));
+ }
+
+ @Test
+ public void onPlaybackConfigChanged_afterOccupantZoneConfigChanged() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
+ CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
+ AudioPlaybackCallback callback = getCarAudioPlaybackCallback();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
+ service.registerCarVolumeEventCallback(volumeEventCallback);
+ int maxActivationVolume = service.getVolumeGroupInfo(TEST_REAR_LEFT_ZONE_ID,
+ TEST_SECONDARY_ZONE_GROUP_0).getMaxActivationVolumeGainIndex();
+ setVolumeForGroup(service, volumeEventCallback, TEST_REAR_LEFT_ZONE_ID,
+ TEST_SECONDARY_ZONE_GROUP_0, maxActivationVolume + 1);
+ createActivePlayback(callback, volumeEventCallback, USAGE_MEDIA,
+ SECONDARY_TEST_DEVICE_CONFIG_0, TEST_PLAYBACK_UID);
+ callback.onPlaybackConfigChanged(List.of(new AudioPlaybackConfigurationBuilder()
+ .setUsage(USAGE_MEDIA).setDeviceAddress(SECONDARY_TEST_DEVICE_CONFIG_0)
+ .setClientUid(TEST_PLAYBACK_UID).setInactive().build()));
+ when(mMockOccupantZoneService.getDriverUserId()).thenReturn(TEST_DRIVER_USER_ID);
+ when(mMockOccupantZoneService.getUserForOccupant(anyInt()))
+ .thenReturn(TEST_REAR_LEFT_USER_ID, TEST_REAR_RIGHT_USER_ID);
+ ICarOccupantZoneCallback occupantZoneCallback = getOccupantZoneCallback();
+ occupantZoneCallback.onOccupantZoneConfigChanged(
+ CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
+ setVolumeForGroup(service, volumeEventCallback, TEST_REAR_LEFT_ZONE_ID,
+ TEST_SECONDARY_ZONE_GROUP_0, maxActivationVolume + 1);
+
+ callback.onPlaybackConfigChanged(List.of(new AudioPlaybackConfigurationBuilder()
+ .setUsage(USAGE_MEDIA).setDeviceAddress(SECONDARY_TEST_DEVICE_CONFIG_0)
+ .setClientUid(TEST_PLAYBACK_UID).build()));
+
+ expectWithMessage("Playback group volume after zone user switch")
+ .that(service.getGroupVolume(TEST_REAR_LEFT_ZONE_ID, TEST_SECONDARY_ZONE_GROUP_0))
+ .isEqualTo(maxActivationVolume);
+ verify(mCarVolumeCallbackHandler).onVolumeGroupChange(eq(TEST_REAR_LEFT_ZONE_ID),
+ eq(TEST_SECONDARY_ZONE_GROUP_0), anyInt());
+ expectWithMessage("Volume event callback after zone user switch")
+ .that(volumeEventCallback.waitForCallback()).isTrue();
+ expectWithMessage("Volume events count after zone user switch")
+ .that(volumeEventCallback.getVolumeGroupEvents()).hasSize(1);
+ CarVolumeGroupEvent groupEvent = volumeEventCallback.getVolumeGroupEvents().get(0);
+ expectWithMessage("Volume event type after zone user switch")
+ .that(groupEvent.getEventTypes())
+ .isEqualTo(CarVolumeGroupEvent.EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED);
+ expectWithMessage("Volume group info after zone user switch")
+ .that(groupEvent.getCarVolumeGroupInfos()).containsExactly(
+ service.getVolumeGroupInfo(TEST_REAR_LEFT_ZONE_ID,
+ TEST_SECONDARY_ZONE_GROUP_0));
+ }
+
+ @Test
public void setVolumeGroupMute_withUnMuteAfterPlaybackConfigChangedWhenMute() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
AudioPlaybackCallback callback = getCarAudioPlaybackCallback();
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
int maxActivationVolume = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_0).getMaxActivationVolumeGainIndex();
int gainIndexAboveActivationVolume = maxActivationVolume + 1;
service.setGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
gainIndexAboveActivationVolume, TEST_FLAGS);
+ resetVolumeCallbacks(volumeEventCallback);
service.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
/* mute= */ true, TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
- callback.onPlaybackConfigChanged(List.of(new AudioPlaybackConfigurationBuilder()
- .setUsage(USAGE_MEDIA).setDeviceAddress(MEDIA_TEST_DEVICE).build()));
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ resetVolumeCallbacks(volumeEventCallback);
+ createActivePlayback(callback, volumeEventCallback, USAGE_MEDIA, MEDIA_TEST_DEVICE,
+ TEST_PLAYBACK_UID);
service.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
/* mute= */ false, TEST_FLAGS);
@@ -5326,6 +6052,10 @@
.isEqualTo(maxActivationVolume);
verify(mCarVolumeCallbackHandler, never()).onVolumeGroupChange(eq(PRIMARY_AUDIO_ZONE),
eq(TEST_PRIMARY_ZONE_GROUP_0), anyInt());
+ expectWithMessage("Volume event callback for activation volume adjustment and unmute")
+ .that(volumeEventCallback.waitForCallback()).isTrue();
+ expectWithMessage("Volume events count for activation volume adjustment and unmute")
+ .that(volumeEventCallback.getVolumeGroupEvents()).hasSize(1);
CarVolumeGroupEvent groupEvent = volumeEventCallback.getVolumeGroupEvents().get(0);
expectWithMessage("Volume event type after activation volume adjustment and unmute")
.that(groupEvent.getEventTypes())
@@ -5342,15 +6072,13 @@
when(mAudioManager.requestAudioFocus(any())).thenReturn(
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
int maxActivationVolume = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_1).getMaxActivationVolumeGainIndex();
- service.setGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_1,
- maxActivationVolume + 1, TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ setVolumeForGroup(service, volumeEventCallback, PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_1, maxActivationVolume + 1);
requestHalAudioFocus(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE);
@@ -5380,15 +6108,13 @@
when(mAudioManager.requestAudioFocus(any())).thenReturn(
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
- CarVolumeEventCallbackImpl volumeEventCallback = new CarVolumeEventCallbackImpl();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
service.registerCarVolumeEventCallback(volumeEventCallback);
int gainIndexInActivationVolumeRange = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
TEST_PRIMARY_ZONE_GROUP_1).getMaxActivationVolumeGainIndex() - 1;
- service.setGroupVolume(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_1,
- gainIndexInActivationVolumeRange, TEST_FLAGS);
- volumeEventCallback.waitForCallback();
- volumeEventCallback.reset();
- reset(mCarVolumeCallbackHandler);
+ setVolumeForGroup(service, volumeEventCallback, PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_1, gainIndexInActivationVolumeRange);
requestHalAudioFocus(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE);
@@ -5401,12 +6127,131 @@
+ " index range").that(volumeEventCallback.waitForCallback()).isFalse();
}
+ @Test
+ public void onCallStateChanged_withOffHookStateAndVolumeBelowMinActivationVolume()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
+ CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
+ TelephonyCallback.CallStateListener callStateListener = getCallStateListener();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
+ service.registerCarVolumeEventCallback(volumeEventCallback);
+ int voiceGroupId = service.getVolumeGroupIdForUsage(PRIMARY_AUDIO_ZONE,
+ USAGE_VOICE_COMMUNICATION);
+ int minActivationVolume = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_1).getMinActivationVolumeGainIndex();
+ setVolumeForGroup(service, volumeEventCallback, PRIMARY_AUDIO_ZONE, voiceGroupId,
+ minActivationVolume - 1);
+
+ callStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+
+ expectWithMessage("Playback group volume for off-hook and below-activation gain index")
+ .that(service.getGroupVolume(PRIMARY_AUDIO_ZONE, voiceGroupId))
+ .isEqualTo(minActivationVolume);
+ verify(mCarVolumeCallbackHandler).onVolumeGroupChange(eq(PRIMARY_AUDIO_ZONE),
+ eq(voiceGroupId), anyInt());
+ expectWithMessage("Volume event callback for off-hook and below-activation gain index")
+ .that(volumeEventCallback.waitForCallback()).isTrue();
+ expectWithMessage("Volume events count for off-hook after below-activation gain index "
+ + "adjustment").that(volumeEventCallback.getVolumeGroupEvents()).hasSize(1);
+ CarVolumeGroupEvent groupEvent = volumeEventCallback.getVolumeGroupEvents().get(0);
+ expectWithMessage("Volume event type for off-hook after below-activation gain index "
+ + "adjustment").that(groupEvent.getEventTypes())
+ .isEqualTo(CarVolumeGroupEvent.EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED);
+ expectWithMessage("Volume group info for off-hook after below-activation gain index "
+ + "adjustment").that(groupEvent.getCarVolumeGroupInfos()).containsExactly(
+ service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE, voiceGroupId));
+ }
+
+ @Test
+ public void onCallStateChanged_withRingingStateAndVolumeBelowMinActivationVolume()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
+ CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
+ TelephonyCallback.CallStateListener callStateListener = getCallStateListener();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
+ service.registerCarVolumeEventCallback(volumeEventCallback);
+ int ringGroupId = service.getVolumeGroupIdForUsage(PRIMARY_AUDIO_ZONE,
+ USAGE_NOTIFICATION_RINGTONE);
+ int minActivationVolume = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_1).getMinActivationVolumeGainIndex();
+ setVolumeForGroup(service, volumeEventCallback, PRIMARY_AUDIO_ZONE, ringGroupId,
+ minActivationVolume - 1);
+
+ callStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING);
+
+ expectWithMessage("Playback group volume for ringing and below-activation gain index")
+ .that(service.getGroupVolume(PRIMARY_AUDIO_ZONE, ringGroupId))
+ .isEqualTo(minActivationVolume);
+ verify(mCarVolumeCallbackHandler).onVolumeGroupChange(eq(PRIMARY_AUDIO_ZONE),
+ eq(ringGroupId), anyInt());
+ expectWithMessage("Volume event callback for ringing and below-activation gain index")
+ .that(volumeEventCallback.waitForCallback()).isTrue();
+ expectWithMessage("Volume events count for ringing after below-activation gain index "
+ + "adjustment").that(volumeEventCallback.getVolumeGroupEvents()).hasSize(1);
+ CarVolumeGroupEvent groupEvent = volumeEventCallback.getVolumeGroupEvents().get(0);
+ expectWithMessage("Volume event type for ringing after below-activation gain index "
+ + "adjustment").that(groupEvent.getEventTypes())
+ .isEqualTo(CarVolumeGroupEvent.EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED);
+ expectWithMessage("Volume group info for ringing after below-activation gain index "
+ + "adjustment").that(groupEvent.getCarVolumeGroupInfos()).containsExactly(
+ service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE, ringGroupId));
+ }
+
+ @Test
+ public void onCallStateChanged_withRingingStateAndWithinActivationVolumeRange()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
+ CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
+ TelephonyCallback.CallStateListener callStateListener = getCallStateListener();
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
+ service.registerCarVolumeEventCallback(volumeEventCallback);
+ int ringGroupId = service.getVolumeGroupIdForUsage(PRIMARY_AUDIO_ZONE,
+ USAGE_NOTIFICATION_RINGTONE);
+ int gainIndexInActivationVolumeRange = service.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE,
+ TEST_PRIMARY_ZONE_GROUP_0).getMinActivationVolumeGainIndex() + 1;
+ setVolumeForGroup(service, volumeEventCallback, PRIMARY_AUDIO_ZONE, ringGroupId,
+ gainIndexInActivationVolumeRange);
+
+ callStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING);
+
+ expectWithMessage("Playback group volume for ring state in activation volume index range")
+ .that(service.getGroupVolume(PRIMARY_AUDIO_ZONE, ringGroupId))
+ .isEqualTo(gainIndexInActivationVolumeRange);
+ verify(mCarVolumeCallbackHandler, never()).onVolumeGroupChange(eq(PRIMARY_AUDIO_ZONE),
+ eq(ringGroupId), anyInt());
+ expectWithMessage("No volume event callback for ring state in activation volume"
+ + " index range").that(volumeEventCallback.waitForCallback()).isFalse();
+ }
+
+ @Test
+ public void unregisterCarVolumeEventCallback_forCarVolumeEventHandler() throws Exception {
+ CarAudioService service = setUpAudioServiceWithMinMaxActivationVolume(/* enabled= */ true);
+ TestCarVolumeEventCallback volumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_CALLBACK_TIMEOUT_MS);
+ service.registerCarVolumeEventCallback(volumeEventCallback);
+
+ service.unregisterCarVolumeEventCallback(volumeEventCallback);
+
+ service.setVolumeGroupMute(PRIMARY_AUDIO_ZONE, TEST_PRIMARY_ZONE_GROUP_0,
+ /* mute= */ true, TEST_FLAGS);
+ expectWithMessage("Volume event callback reception status with callback unregistered")
+ .that(volumeEventCallback.waitForCallback()).isFalse();
+ }
+ private void waitForInternalCallback() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ mHandler.post(latch::countDown);
+ latch.await(TEST_ZONE_CONFIG_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
private CarAudioService setUpCarAudioServiceWithoutZoneMapping() throws Exception {
setUpTempFileForAudioConfiguration(R.raw.car_audio_configuration_without_zone_mapping);
setUpTempFileForAudioFadeConfiguration(R.raw.car_audio_fade_configuration);
when(mMockAudioService.setUidDeviceAffinity(any(), anyInt(), any(), any()))
.thenReturn(SUCCESS);
- CarAudioService noZoneMappingAudioService = new CarAudioService(mMockContext,
+ CarAudioService noZoneMappingAudioService = new CarAudioService(mMockContext, mAudioManager,
mTempCarAudioConfigFile.getFile().getAbsolutePath(), mCarVolumeCallbackHandler,
/* audioFadeConfigurationPath= */ null);
noZoneMappingAudioService.init();
@@ -5416,7 +6261,7 @@
private CarAudioService setUpAudioService() throws Exception {
setUpTempFileForAudioConfiguration(R.raw.car_audio_configuration);
setUpTempFileForAudioFadeConfiguration(R.raw.car_audio_fade_configuration);
- CarAudioService service = new CarAudioService(mMockContext,
+ CarAudioService service = new CarAudioService(mMockContext, mAudioManager,
mTempCarAudioConfigFile.getFile().getAbsolutePath(), mCarVolumeCallbackHandler,
mTempCarAudioFadeConfigFile.getFile().getAbsolutePath());
service.init();
@@ -5426,7 +6271,7 @@
private CarAudioService setUpAudioServiceWithoutInit() throws Exception {
setUpTempFileForAudioConfiguration(R.raw.car_audio_configuration);
setUpTempFileForAudioFadeConfiguration(R.raw.car_audio_fade_configuration);
- CarAudioService service = new CarAudioService(mMockContext,
+ CarAudioService service = new CarAudioService(mMockContext, mAudioManager,
mTempCarAudioConfigFile.getFile().getAbsolutePath(), mCarVolumeCallbackHandler,
mTempCarAudioFadeConfigFile.getFile().getAbsolutePath());
return service;
@@ -5436,7 +6281,7 @@
setUpTempFileForAudioConfiguration(R.raw.car_audio_configuration);
setUpTempFileForAudioFadeConfiguration(R.raw.car_audio_fade_configuration);
when(mMockResources.getBoolean(audioUseDynamicRouting)).thenReturn(false);
- CarAudioService nonDynamicAudioService = new CarAudioService(mMockContext,
+ CarAudioService nonDynamicAudioService = new CarAudioService(mMockContext, mAudioManager,
mTempCarAudioConfigFile.getFile().getAbsolutePath(), mCarVolumeCallbackHandler,
/* audioFadeConfigurationPath= */ null);
nonDynamicAudioService.init();
@@ -5447,7 +6292,7 @@
setUpTempFileForAudioConfiguration(R.raw.car_audio_configuration);
setUpTempFileForAudioFadeConfiguration(R.raw.car_audio_fade_configuration);
when(mMockResources.getBoolean(resource)).thenReturn(false);
- CarAudioService nonDynamicAudioService = new CarAudioService(mMockContext,
+ CarAudioService nonDynamicAudioService = new CarAudioService(mMockContext, mAudioManager,
mTempCarAudioConfigFile.getFile().getAbsolutePath(), mCarVolumeCallbackHandler,
mTempCarAudioFadeConfigFile.getFile().getAbsolutePath());
nonDynamicAudioService.init();
@@ -5482,7 +6327,7 @@
when(mMockResources.getBoolean(audioUseCoreVolume)).thenReturn(true);
when(mMockResources.getBoolean(audioUseCoreRouting)).thenReturn(false);
CarAudioService audioServiceWithDynamicDevices = new CarAudioService(mMockContext,
- fileAudio.getFile().getAbsolutePath(), mCarVolumeCallbackHandler,
+ mAudioManager, fileAudio.getFile().getAbsolutePath(), mCarVolumeCallbackHandler,
fileFade.getFile().getAbsolutePath());
return audioServiceWithDynamicDevices;
}
@@ -5492,7 +6337,7 @@
setUpTempFileForAudioConfiguration(R.raw.car_audio_configuration_using_activation_volumes);
setUpTempFileForAudioFadeConfiguration(R.raw.car_audio_fade_configuration);
when(mMockResources.getBoolean(audioUseMinMaxActivationVolume)).thenReturn(enabled);
- CarAudioService service = new CarAudioService(mMockContext,
+ CarAudioService service = new CarAudioService(mMockContext, mAudioManager,
mTempCarAudioConfigFile.getFile().getAbsolutePath(), mCarVolumeCallbackHandler,
mTempCarAudioFadeConfigFile.getFile().getAbsolutePath());
service.init();
@@ -5626,13 +6471,19 @@
return captor.getValue();
}
- private CarInputService.KeyEventListener getAudioKeyEventListener() {
- ArgumentCaptor<CarInputService.KeyEventListener> captor =
- ArgumentCaptor.forClass(CarInputService.KeyEventListener.class);
+ private KeyEventListener getAudioKeyEventListener() {
+ ArgumentCaptor<KeyEventListener> captor = ArgumentCaptor.forClass(KeyEventListener.class);
verify(mMockCarInputService).registerKeyEventListener(captor.capture(), any());
return captor.getValue();
}
+ private TelephonyCallback.CallStateListener getCallStateListener() {
+ ArgumentCaptor<TelephonyCallback> captor =
+ ArgumentCaptor.forClass(TelephonyCallback.class);
+ verify(mMockTelephonyManager).registerTelephonyCallback(any(), captor.capture());
+ return (TelephonyCallback.CallStateListener) captor.getValue();
+ }
+
private void requestHalAudioFocus(int usage) {
ArgumentCaptor<HalFocusListener> captor =
ArgumentCaptor.forClass(HalFocusListener.class);
@@ -5663,8 +6514,8 @@
setUpTempFileForAudioFadeConfiguration(R.raw.car_audio_fade_configuration);
AudioDeviceInfo[] outputDevices = generateOutputDeviceInfos();
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).thenReturn(outputDevices);
- CarAudioService service = new CarAudioService(mMockContext, mTempCarAudioConfigFile
- .getFile().getAbsolutePath(), mCarVolumeCallbackHandler,
+ CarAudioService service = new CarAudioService(mMockContext, mAudioManager,
+ mTempCarAudioConfigFile.getFile().getAbsolutePath(), mCarVolumeCallbackHandler,
mTempCarAudioFadeConfigFile.getFile().getAbsolutePath());
service.init();
return service;
@@ -5675,7 +6526,7 @@
setUpTempFileForAudioFadeConfiguration(R.raw.car_audio_fade_configuration);
when(mMockResources.getInteger(audioVolumeAdjustmentContextsVersion))
.thenReturn(AUDIO_CONTEXT_PRIORITY_LIST_VERSION_TWO);
- CarAudioService service = new CarAudioService(mMockContext,
+ CarAudioService service = new CarAudioService(mMockContext, mAudioManager,
mTempCarAudioConfigFile.getFile().getAbsolutePath(), mCarVolumeCallbackHandler,
mTempCarAudioFadeConfigFile.getFile().getAbsolutePath());
service.init();
@@ -5696,6 +6547,13 @@
}
}
+ private CarAudioService setUpCarAudioServiceWithFadeManagerEnabled() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_FADE_MANAGER_CONFIGURATION);
+ when(mMockResources.getBoolean(audioUseFadeManagerConfiguration)).thenReturn(true);
+ return setUpAudioService();
+ }
+
private CarAudioService setUpCarAudioServiceUsingCoreAudioRoutingAndVolume() throws Exception {
when(mMockResources.getBoolean(audioUseCoreVolume)).thenReturn(true);
when(mMockResources.getBoolean(audioUseCoreRouting)).thenReturn(true);
@@ -5704,6 +6562,7 @@
setUpTempFileForAudioFadeConfiguration(R.raw.car_audio_fade_configuration);
CarAudioService useCoreAudioCarAudioService = new CarAudioService(mMockContext,
+ mAudioManager,
mTempCarAudioConfigFile.getFile().getAbsolutePath(), mCarVolumeCallbackHandler,
/* audioFadeConfigurationPath= */ null);
useCoreAudioCarAudioService.init();
@@ -5828,12 +6687,10 @@
private void mockCoreAudioRoutingAndVolume() {
doReturn(CoreAudioRoutingUtils.getProductStrategies())
- .when(() -> AudioManager.getAudioProductStrategies());
+ .when(AudioManagerWrapper::getAudioProductStrategies);
doReturn(CoreAudioRoutingUtils.getVolumeGroups())
- .when(() -> AudioManager.getAudioVolumeGroups());
+ .when(AudioManagerWrapper::getAudioVolumeGroups);
- when(mAudioManager.getVolumeGroupIdForAttributes(CoreAudioRoutingUtils.MUSIC_ATTRIBUTES))
- .thenReturn(CoreAudioRoutingUtils.MUSIC_GROUP_ID);
when(mAudioManager.getMinVolumeIndexForAttributes(
eq(CoreAudioRoutingUtils.MUSIC_ATTRIBUTES)))
.thenReturn(CoreAudioRoutingUtils.MUSIC_MIN_INDEX);
@@ -5847,8 +6704,6 @@
when(mAudioManager.isVolumeGroupMuted(CoreAudioRoutingUtils.MUSIC_GROUP_ID))
.thenReturn(false);
- when(mAudioManager.getVolumeGroupIdForAttributes(CoreAudioRoutingUtils.NAV_ATTRIBUTES))
- .thenReturn(CoreAudioRoutingUtils.NAV_GROUP_ID);
when(mAudioManager.getMinVolumeIndexForAttributes(eq(CoreAudioRoutingUtils.NAV_ATTRIBUTES)))
.thenReturn(CoreAudioRoutingUtils.NAV_MIN_INDEX);
when(mAudioManager.getMaxVolumeIndexForAttributes(eq(CoreAudioRoutingUtils.NAV_ATTRIBUTES)))
@@ -5856,8 +6711,6 @@
when(mAudioManager.isVolumeGroupMuted(CoreAudioRoutingUtils.NAV_GROUP_ID))
.thenReturn(false);
- when(mAudioManager.getVolumeGroupIdForAttributes(CoreAudioRoutingUtils.OEM_ATTRIBUTES))
- .thenReturn(CoreAudioRoutingUtils.OEM_GROUP_ID);
when(mAudioManager.getMinVolumeIndexForAttributes(eq(CoreAudioRoutingUtils.OEM_ATTRIBUTES)))
.thenReturn(CoreAudioRoutingUtils.OEM_MIN_INDEX);
when(mAudioManager.getMaxVolumeIndexForAttributes(eq(CoreAudioRoutingUtils.OEM_ATTRIBUTES)))
@@ -5933,6 +6786,30 @@
return null;
}
+ private void setVolumeForGroup(CarAudioService service,
+ TestCarVolumeEventCallback volumeEventCallback,
+ int zoneId, int groupId, int volumeIndex) throws Exception {
+ service.setGroupVolume(zoneId, groupId, volumeIndex, TEST_FLAGS);
+ resetVolumeCallbacks(volumeEventCallback);
+ }
+
+ private void createActivePlayback(AudioPlaybackCallback callback,
+ TestCarVolumeEventCallback volumeEventCallback,
+ int playbackUsage, String deviceAddress, int playbackUid)
+ throws Exception {
+ callback.onPlaybackConfigChanged(List.of(new AudioPlaybackConfigurationBuilder()
+ .setUsage(playbackUsage).setDeviceAddress(deviceAddress)
+ .setClientUid(playbackUid).build()));
+ resetVolumeCallbacks(volumeEventCallback);
+ }
+
+ private void resetVolumeCallbacks(TestCarVolumeEventCallback volumeEventCallback)
+ throws Exception {
+ volumeEventCallback.waitForCallback();
+ volumeEventCallback.reset();
+ reset(mCarVolumeCallbackHandler);
+ }
+
private static final class TestAudioZoneConfigurationsChangeCallback
extends IAudioZoneConfigurationsChangeCallback.Stub {
@@ -6104,32 +6981,4 @@
mStatusLatch = new CountDownLatch(1);
}
}
-
- private static final class CarVolumeEventCallbackImpl extends ICarVolumeEventCallback.Stub {
- private CountDownLatch mStatusLatch = new CountDownLatch(1);
- private List<CarVolumeGroupEvent> mVolumeGroupEvents;
-
- @Override
- public void onVolumeGroupEvent(List<CarVolumeGroupEvent> volumeGroupEvents) {
- mVolumeGroupEvents = volumeGroupEvents;
- mStatusLatch.countDown();
- }
-
- @Override
- public void onMasterMuteChanged(int zoneId, int flags) {
- mStatusLatch.countDown();
- }
-
- private boolean waitForCallback() throws Exception {
- return mStatusLatch.await(TEST_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- }
-
- List<CarVolumeGroupEvent> getVolumeGroupEvents() {
- return mVolumeGroupEvents;
- }
-
- public void reset() {
- mStatusLatch = new CountDownLatch(1);
- }
- }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioSettingsUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioSettingsUnitTest.java
index d59faf6..e7783fc 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioSettingsUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioSettingsUnitTest.java
@@ -44,12 +44,10 @@
private static final int TEST_CONFIG_ID = 1;
private static final int TEST_GROUP_ID = 0;
private static final int TEST_GAIN_INDEX = 10;
- private static final String TEST_GAIN_INDEX_KEY = new StringBuilder()
- .append("android.car.VOLUME_GROUP/").append(TEST_ZONE_ID).append("/")
- .append(TEST_CONFIG_ID).append("/").append(TEST_GROUP_ID).toString();
- private static final String TEST_MUTE_KEY = new StringBuilder()
- .append("android.car.VOLUME_GROUP_MUTE/").append(TEST_ZONE_ID).append("/")
- .append(TEST_CONFIG_ID).append("/").append(TEST_GROUP_ID).toString();
+ private static final String TEST_GAIN_INDEX_KEY = "android.car.VOLUME_GROUP/" + TEST_ZONE_ID
+ + "/" + TEST_CONFIG_ID + "/" + TEST_GROUP_ID;
+ private static final String TEST_MUTE_KEY = "android.car.VOLUME_GROUP_MUTE/" + TEST_ZONE_ID
+ + "/" + TEST_CONFIG_ID + "/" + TEST_GROUP_ID;
@Mock
private Context mMockContext;
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioTestUtils.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioTestUtils.java
index 0ef25a0..a9f7116 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioTestUtils.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioTestUtils.java
@@ -15,12 +15,17 @@
*/
package com.android.car.audio;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.media.AudioManager;
import android.os.Build;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public final class CarAudioTestUtils {
private static final String PACKAGE_NAME = "com.android.car.audio";
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioUtilsTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioUtilsTest.java
index e2a0fca..77d5327 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioUtilsTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioUtilsTest.java
@@ -36,6 +36,7 @@
import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET;
import static com.android.car.audio.CarAudioUtils.excludesDynamicDevices;
+import static com.android.car.audio.CarAudioUtils.getAudioAttributesForDynamicDevices;
import static com.android.car.audio.CarAudioUtils.getDynamicDevicesInConfig;
import static com.android.car.audio.CarAudioUtils.hasExpired;
import static com.android.car.audio.CarAudioUtils.isMicrophoneInputDevice;
@@ -53,6 +54,7 @@
import android.media.AudioManager;
import android.platform.test.flag.junit.SetFlagsRule;
+import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.car.internal.util.DebugUtils;
@@ -171,7 +173,7 @@
public void getAudioDeviceInfo() {
AudioDeviceInfo info1 = getTestAudioDeviceInfo(TEST_ADDRESS_1);
AudioDeviceInfo info2 = getTestAudioDeviceInfo(TEST_ADDRESS_2);
- AudioManager audioManager = Mockito.mock(AudioManager.class);
+ AudioManagerWrapper audioManager = Mockito.mock(AudioManagerWrapper.class);
when(audioManager.getDevices(anyInt())).thenReturn(new AudioDeviceInfo[]{info2, info1});
AudioDeviceAttributes attributes =
new AudioDeviceAttributes(TYPE_BLUETOOTH_A2DP, TEST_ADDRESS_1);
@@ -185,7 +187,7 @@
public void getAudioDeviceInfo_withDeviceNotAvailable() {
AudioDeviceInfo info1 = getTestAudioDeviceInfo(TEST_ADDRESS_1);
AudioDeviceInfo info2 = getTestAudioDeviceInfo(TEST_ADDRESS_2);
- AudioManager audioManager = Mockito.mock(AudioManager.class);
+ AudioManagerWrapper audioManager = Mockito.mock(AudioManagerWrapper.class);
when(audioManager.getDevices(anyInt())).thenReturn(new AudioDeviceInfo[]{info2, info1});
AudioDeviceAttributes attributes =
new AudioDeviceAttributes(TYPE_BLUETOOTH_A2DP, TEST_NOT_AVAILABLE_ADDRESS);
@@ -223,11 +225,7 @@
@Test
public void excludesDynamicDevices_withOutDynamicDevices() {
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
- CarAudioZoneConfigInfo testNoDynamicDevicesConfig =
- new CarAudioZoneConfigInfo("Non-dynamic-devices-config",
- List.of(TEST_MEDIA_VOLUME_INFO, TEST_NAV_VOLUME_INFO),
- TEST_ZONE_ID, TEST_CONFIG_ID, TEST_ACTIVE_STATUS, TEST_SELECTED_STATUS,
- TEST_DEFAULT_STATUS);
+ CarAudioZoneConfigInfo testNoDynamicDevicesConfig = getCarAudioZoneConfigInfo();
expectWithMessage("Info without dynamic devices")
.that(excludesDynamicDevices(testNoDynamicDevicesConfig)).isTrue();
@@ -236,11 +234,7 @@
@Test
public void excludesDynamicDevices_withDynamicDevices() {
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
- CarAudioZoneConfigInfo testDynamicDevicesConfig =
- new CarAudioZoneConfigInfo("dynamic-devices-config",
- List.of(TEST_MEDIA_VOLUME_INFO, TEST_NAV_VOLUME_INFO, TEST_BT_VOLUME_INFO),
- TEST_ZONE_ID, TEST_CONFIG_ID, TEST_ACTIVE_STATUS, TEST_SELECTED_STATUS,
- TEST_DEFAULT_STATUS);
+ CarAudioZoneConfigInfo testDynamicDevicesConfig = getTestDynamicDevicesConfig();
expectWithMessage("Info with dynamic devices")
.that(excludesDynamicDevices(testDynamicDevicesConfig)).isFalse();
@@ -249,11 +243,7 @@
@Test
public void excludesDynamicDevices_withOutDynamicDevices_withDynamicFlagsDisabled() {
mSetFlagsRule.disableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
- CarAudioZoneConfigInfo testNoDynamicDevicesConfig =
- new CarAudioZoneConfigInfo("Non-dynamic-devices-config",
- List.of(TEST_MEDIA_VOLUME_INFO, TEST_NAV_VOLUME_INFO),
- TEST_ZONE_ID, TEST_CONFIG_ID, TEST_ACTIVE_STATUS, TEST_SELECTED_STATUS,
- TEST_DEFAULT_STATUS);
+ CarAudioZoneConfigInfo testNoDynamicDevicesConfig = getCarAudioZoneConfigInfo();
expectWithMessage("Info without dynamic devices with dynamic flags disable")
.that(excludesDynamicDevices(testNoDynamicDevicesConfig)).isTrue();
@@ -262,11 +252,7 @@
@Test
public void excludesDynamicDevices_withDynamicDevices_withDynamicFlagsDisabled() {
mSetFlagsRule.disableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
- CarAudioZoneConfigInfo testDynamicDevicesConfig =
- new CarAudioZoneConfigInfo("dynamic-devices-config",
- List.of(TEST_MEDIA_VOLUME_INFO, TEST_NAV_VOLUME_INFO, TEST_BT_VOLUME_INFO),
- TEST_ZONE_ID, TEST_CONFIG_ID, TEST_ACTIVE_STATUS, TEST_SELECTED_STATUS,
- TEST_DEFAULT_STATUS);
+ CarAudioZoneConfigInfo testDynamicDevicesConfig = getTestDynamicDevicesConfig();
expectWithMessage("Info with dynamic devices with dynamic flags disable")
.that(excludesDynamicDevices(testDynamicDevicesConfig)).isTrue();
@@ -275,12 +261,8 @@
@Test
public void getDynamicDevicesInConfig_withDynamicDevices() {
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
- AudioManager manager = setUpMockAudioManager();
- CarAudioZoneConfigInfo testDynamicDevicesConfig =
- new CarAudioZoneConfigInfo("dynamic-devices-config",
- List.of(TEST_MEDIA_VOLUME_INFO, TEST_NAV_VOLUME_INFO, TEST_BT_VOLUME_INFO),
- TEST_ZONE_ID, TEST_CONFIG_ID, TEST_ACTIVE_STATUS, TEST_SELECTED_STATUS,
- TEST_DEFAULT_STATUS);
+ AudioManagerWrapper manager = setUpMockAudioManager();
+ CarAudioZoneConfigInfo testDynamicDevicesConfig = getTestDynamicDevicesConfig();
expectWithMessage("Non-dynamic devices")
.that(getDynamicDevicesInConfig(testDynamicDevicesConfig, manager))
@@ -290,12 +272,8 @@
@Test
public void getDynamicDevicesInConfig_withoutDynamicDevices() {
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
- AudioManager manager = setUpMockAudioManager();
- CarAudioZoneConfigInfo testDynamicDevicesConfig =
- new CarAudioZoneConfigInfo("dynamic-devices-config",
- List.of(TEST_MEDIA_VOLUME_INFO, TEST_NAV_VOLUME_INFO),
- TEST_ZONE_ID, TEST_CONFIG_ID, TEST_ACTIVE_STATUS, TEST_SELECTED_STATUS,
- TEST_DEFAULT_STATUS);
+ AudioManagerWrapper manager = setUpMockAudioManager();
+ CarAudioZoneConfigInfo testDynamicDevicesConfig = getCarAudioZoneConfigInfo();
expectWithMessage("Dynamic devices")
.that(getDynamicDevicesInConfig(testDynamicDevicesConfig, manager)).isEmpty();
@@ -304,12 +282,8 @@
@Test
public void getDynamicDevicesInConfig_withDynamicDevices_andFlagDisabled() {
mSetFlagsRule.disableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
- AudioManager manager = setUpMockAudioManager();
- CarAudioZoneConfigInfo testDynamicDevicesConfig =
- new CarAudioZoneConfigInfo("dynamic-devices-config",
- List.of(TEST_MEDIA_VOLUME_INFO, TEST_NAV_VOLUME_INFO, TEST_BT_VOLUME_INFO),
- TEST_ZONE_ID, TEST_CONFIG_ID, TEST_ACTIVE_STATUS, TEST_SELECTED_STATUS,
- TEST_DEFAULT_STATUS);
+ AudioManagerWrapper manager = setUpMockAudioManager();
+ CarAudioZoneConfigInfo testDynamicDevicesConfig = getTestDynamicDevicesConfig();
expectWithMessage("Dynamic devices with flags disabled")
.that(getDynamicDevicesInConfig(testDynamicDevicesConfig, manager)).isEmpty();
@@ -318,19 +292,67 @@
@Test
public void getDynamicDevicesInConfig_withoutDynamicDevices_andFlagDisabled() {
mSetFlagsRule.disableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
- AudioManager manager = setUpMockAudioManager();
- CarAudioZoneConfigInfo testDynamicDevicesConfig =
- new CarAudioZoneConfigInfo("dynamic-devices-config",
- List.of(TEST_MEDIA_VOLUME_INFO, TEST_NAV_VOLUME_INFO),
- TEST_ZONE_ID, TEST_CONFIG_ID, TEST_ACTIVE_STATUS, TEST_SELECTED_STATUS,
- TEST_DEFAULT_STATUS);
+ AudioManagerWrapper manager = setUpMockAudioManager();
+ CarAudioZoneConfigInfo testNonDynamicDevicesConfig = getCarAudioZoneConfigInfo();
expectWithMessage("Non-dynamic devices with flags disabled")
- .that(getDynamicDevicesInConfig(testDynamicDevicesConfig, manager)).isEmpty();
+ .that(getDynamicDevicesInConfig(testNonDynamicDevicesConfig, manager)).isEmpty();
}
- private AudioManager setUpMockAudioManager() {
- AudioManager manager = Mockito.mock(AudioManager.class);
+ @Test
+ public void getAudioAttributesForDynamicDevices_withoutDynamicDevicesAndFlagDisabled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
+ CarAudioZoneConfigInfo testNonDynamicDevicesConfig = getCarAudioZoneConfigInfo();
+
+ expectWithMessage("Audio attributes with flags disabled without dynamic device")
+ .that(getAudioAttributesForDynamicDevices(testNonDynamicDevicesConfig)).isEmpty();
+ }
+
+ @Test
+ public void getAudioAttributesForDynamicDevices_withDynamicDevicesAndFlagDisabled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
+ CarAudioZoneConfigInfo testDynamicDevicesConfig = getTestDynamicDevicesConfig();
+
+ expectWithMessage("Audio attributes with flags disabled with dynamic devices")
+ .that(getAudioAttributesForDynamicDevices(testDynamicDevicesConfig)).isEmpty();
+ }
+
+ @Test
+ public void getAudioAttributesForDynamicDevices_withDynamicDevicesAndFlagEnabled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
+ CarAudioZoneConfigInfo testDynamicDevicesConfig = getTestDynamicDevicesConfig();
+
+ expectWithMessage("Audio attributes for volume group with dynamic devices and "
+ + "flags enabled")
+ .that(getAudioAttributesForDynamicDevices(testDynamicDevicesConfig))
+ .containsExactlyElementsIn(TEST_BT_VOLUME_INFO.getAudioAttributes());
+ }
+
+ @Test
+ public void getAudioAttributesForDynamicDevices_withoutDynamicDevicesAndFlagEnabled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
+ CarAudioZoneConfigInfo testDynamicDevicesConfig = getCarAudioZoneConfigInfo();
+
+ expectWithMessage("Audio attributes for volume group with no dynamic devices and "
+ + "flags enabled")
+ .that(getAudioAttributesForDynamicDevices(testDynamicDevicesConfig)).isEmpty();
+ }
+
+ private static CarAudioZoneConfigInfo getTestDynamicDevicesConfig() {
+ return new CarAudioZoneConfigInfo("dynamic-devices-config",
+ List.of(TEST_MEDIA_VOLUME_INFO, TEST_NAV_VOLUME_INFO, TEST_BT_VOLUME_INFO),
+ TEST_ZONE_ID, TEST_CONFIG_ID, TEST_ACTIVE_STATUS, TEST_SELECTED_STATUS,
+ TEST_DEFAULT_STATUS);
+ }
+
+ private static @NonNull CarAudioZoneConfigInfo getCarAudioZoneConfigInfo() {
+ return new CarAudioZoneConfigInfo("Non-dynamic-devices-config",
+ List.of(TEST_MEDIA_VOLUME_INFO, TEST_NAV_VOLUME_INFO), TEST_ZONE_ID, TEST_CONFIG_ID,
+ TEST_ACTIVE_STATUS, TEST_SELECTED_STATUS, TEST_DEFAULT_STATUS);
+ }
+
+ private AudioManagerWrapper setUpMockAudioManager() {
+ AudioManagerWrapper manager = Mockito.mock(AudioManagerWrapper.class);
when(manager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
.thenReturn(getMockOutputDevices());
return manager;
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioVolumeGroupTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioVolumeGroupTest.java
index 41ef841..2f52891 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioVolumeGroupTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioVolumeGroupTest.java
@@ -49,7 +49,6 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioGain;
-import android.media.AudioManager;
import android.media.audio.common.AudioDevice;
import android.media.audio.common.AudioDeviceAddress;
import android.media.audio.common.AudioDeviceDescription;
@@ -85,8 +84,6 @@
DEFAULT_GAIN);
private static final int MIN_GAIN_INDEX = 0;
private static final int MAX_GAIN_INDEX = getIndexForGain(MIN_GAIN, STEP_SIZE, MAX_GAIN);
- private static final int MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE = 10;
- private static final int MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE = 90;
private static final int TEST_GAIN_INDEX = 35;
private static final int TEST_USER_11 = 11;
private static final String GROUP_NAME = "group_0";
@@ -108,6 +105,11 @@
private static final int EVENT_TYPE_VOLUME_INDEX_MIN_MAX = 0x7;
private static final int EVENT_TYPE_NONE = 0;
+ private static final CarActivationVolumeConfig CAR_ACTIVATION_VOLUME_CONFIG =
+ new CarActivationVolumeConfig(CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT,
+ /* minActivationVolumePercentage= */ 10,
+ /* maxActivationVolumePercentage= */ 90);
+
private static final CarAudioContext TEST_CAR_AUDIO_CONTEXT =
new CarAudioContext(CarAudioContext.getAllContextsInfo(),
/* useCoreAudioRouting= */ false);
@@ -135,7 +137,7 @@
private CarAudioDeviceInfo mNavigationDeviceInfo;
@Mock
- private AudioManager mAudioManager;
+ private AudioManagerWrapper mAudioManagerWrapper;
@Mock
CarAudioSettings mSettingsMock;
@@ -145,16 +147,15 @@
@Override
protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
- session.spyStatic(AudioManager.class)
- .spyStatic(AudioManagerHelper.class);
+ session.spyStatic(AudioManagerHelper.class);
}
@Before
public void setUp() {
- mMediaDeviceInfo = new CarAudioDeviceInfo(mAudioManager,
+ mMediaDeviceInfo = new CarAudioDeviceInfo(mAudioManagerWrapper,
getMockAudioDevice(MEDIA_DEVICE_ADDRESS));
mMediaDeviceInfo.setAudioDeviceInfo(getMockAudioDeviceInfo(MEDIA_DEVICE_ADDRESS));
- mNavigationDeviceInfo = new CarAudioDeviceInfo(mAudioManager,
+ mNavigationDeviceInfo = new CarAudioDeviceInfo(mAudioManagerWrapper,
getMockAudioDevice(NAVIGATION_DEVICE_ADDRESS));
mNavigationDeviceInfo.setAudioDeviceInfo(getMockAudioDeviceInfo(NAVIGATION_DEVICE_ADDRESS));
}
@@ -341,7 +342,7 @@
int eventType = carVolumeGroup.calculateNewGainStageFromDeviceInfos();
- expect.withMessage("Calculated event type for new gain stage\"")
+ expect.withMessage("Calculated event type for new gain stage")
.that(eventType).isEqualTo(EVENT_TYPE_NONE);
expect.withMessage("Calculated min gain index for new gain stage")
.that(carVolumeGroup.getMinGainIndex()).isEqualTo(MIN_GAIN_INDEX);
@@ -677,7 +678,7 @@
CarAudioVolumeGroup carVolumeGroup = new CarAudioVolumeGroup(TEST_CAR_AUDIO_CONTEXT,
mSettingsMock, contextToDeviceInfo, ZONE_ID, ZONE_CONFIG_ID, GROUP_ID, GROUP_NAME,
STEP_SIZE, DEFAULT_GAIN, MIN_GAIN, MAX_GAIN, /* useCarVolumeGroupMute= */ false,
- MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE, MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE);
+ CAR_ACTIVATION_VOLUME_CONFIG);
return carVolumeGroup;
}
@@ -713,8 +714,7 @@
return new CarAudioVolumeGroup(TEST_CAR_AUDIO_CONTEXT, settings, contextToDeviceInfo,
ZONE_ID, ZONE_CONFIG_ID, GROUP_ID, GROUP_NAME, STEP_SIZE, DEFAULT_GAIN, MIN_GAIN,
- MAX_GAIN, /* useCarVolumeGroupMute= */ false, MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE,
- MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE);
+ MAX_GAIN, /* useCarVolumeGroupMute= */ false, CAR_ACTIVATION_VOLUME_CONFIG);
}
private static final class SettingsBuilder {
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneConfigUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneConfigUnitTest.java
index 5554212..acf3d41 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneConfigUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneConfigUnitTest.java
@@ -16,6 +16,7 @@
package com.android.car.audio;
+import static android.car.feature.Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES;
import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_ATTENUATION_CHANGED;
import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_MUTE_CHANGED;
@@ -26,6 +27,8 @@
import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import static com.android.car.audio.CarAudioContext.AudioContext;
+import static com.android.car.audio.CarAudioContext.MUSIC;
+import static com.android.car.audio.CarAudioContext.getAudioAttributeFromUsage;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.any;
@@ -79,11 +82,11 @@
private static final int TEST_VOICE_GROUP_ID = 2;
private static final AudioAttributes TEST_MEDIA_ATTRIBUTE =
- CarAudioContext.getAudioAttributeFromUsage(USAGE_MEDIA);
+ getAudioAttributeFromUsage(USAGE_MEDIA);
private static final AudioAttributes TEST_ASSISTANT_ATTRIBUTE =
- CarAudioContext.getAudioAttributeFromUsage(USAGE_ASSISTANT);
+ getAudioAttributeFromUsage(USAGE_ASSISTANT);
private static final AudioAttributes TEST_NAVIGATION_ATTRIBUTE =
- CarAudioContext.getAudioAttributeFromUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE);
+ getAudioAttributeFromUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE);
private static final CarAudioContext TEST_CAR_AUDIO_CONTEXT = new CarAudioContext(
CarAudioContext.getAllContextsInfo(), /* useCoreAudioRouting= */ false);
@@ -468,6 +471,22 @@
}
@Test
+ public void validateVolumeGroups_withContextSharedAmongGroups() {
+ CarVolumeGroup mockMusicGroup = new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(TEST_MEDIA_CONTEXT, MUSIC_ADDRESS).build();
+ CarVolumeGroup mockNavGroupWithMusicContext = new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(TEST_MEDIA_CONTEXT, NAV_ADDRESS).build();
+ CarVolumeGroup mockAllOtherContextsGroup = mockContextsExceptMediaAndNavigation();
+
+ CarAudioZoneConfig zoneConfig = buildZoneConfig(
+ List.of(mockMusicGroup, mockNavGroupWithMusicContext, mockAllOtherContextsGroup));
+
+ expectWithMessage("Valid status for config with context shared among volume groups")
+ .that(zoneConfig.validateVolumeGroups(TEST_CAR_AUDIO_CONTEXT,
+ /* useCoreAudioRouting= */ false)).isFalse();
+ }
+
+ @Test
public void validateVolumeGroups_withInvalidDeviceTypesInGroup() {
CarVolumeGroup mockMusicGroup = new VolumeGroupBuilder()
.addDeviceAddressAndContexts(TEST_MEDIA_CONTEXT, MUSIC_ADDRESS).build();
@@ -500,7 +519,7 @@
}
@Test
- public void validateZoneConfigs_withAddressSharedAmongGroupNotUsingCoreAudioRouting_fails() {
+ public void validateVolumeGroups_withAddressSharedAmongGroupNotUsingCoreAudioRouting_fails() {
CarVolumeGroup mockMusicGroup = new VolumeGroupBuilder()
.addDeviceAddressAndContexts(TEST_MEDIA_CONTEXT, MUSIC_ADDRESS).build();
CarVolumeGroup mockNavGroupRoutingOnMusic = new VolumeGroupBuilder()
@@ -515,7 +534,7 @@
}
@Test
- public void validateZoneConfigs_withAddressSharedAmongGroupUsingCoreAudioRouting_succeeds() {
+ public void validateVolumeGroups_withAddressSharedAmongGroupUsingCoreAudioRouting_succeeds() {
CarVolumeGroup mockMusicGroup = new VolumeGroupBuilder()
.addDeviceAddressAndContexts(TEST_MEDIA_CONTEXT, MUSIC_ADDRESS).build();
CarVolumeGroup mockNavGroupRoutingOnMusic = new VolumeGroupBuilder()
@@ -530,6 +549,50 @@
}
@Test
+ public void validateVolumeGroups_withContextSharedAmongGroup_fails() {
+ CarVolumeGroup mockMusicGroup1 = new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(TEST_MEDIA_CONTEXT, MUSIC_ADDRESS).build();
+ CarVolumeGroup mockMusicGroup2 = new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(TEST_MEDIA_CONTEXT, NAV_ADDRESS).build();
+ CarVolumeGroup mockAllOtherContextsGroup = mockContextsExceptMediaAndNavigation();
+ CarAudioZoneConfig zoneConfig = buildZoneConfig(
+ List.of(mockMusicGroup1, mockMusicGroup2, mockAllOtherContextsGroup));
+
+ expectWithMessage("Volume group validates when sharing the same context")
+ .that(zoneConfig.validateVolumeGroups(TEST_CAR_AUDIO_CONTEXT,
+ /* useCoreAudioRouting= */ true)).isFalse();
+ }
+
+ @Test
+ public void validateVolumeGroups_withUnassignedAudioContext_fails() {
+ CarVolumeGroup mockMusicGroup = new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(TEST_MEDIA_CONTEXT, MUSIC_ADDRESS).build();
+ CarVolumeGroup mockAllOtherContextsGroup = mockContextsExceptMediaAndNavigation();
+ CarAudioZoneConfig zoneConfig = buildZoneConfig(
+ List.of(mockMusicGroup, mockAllOtherContextsGroup));
+
+ expectWithMessage("Volume group with unassigned audio context")
+ .that(zoneConfig.validateVolumeGroups(TEST_CAR_AUDIO_CONTEXT,
+ /* useCoreAudioRouting= */ true)).isFalse();
+ }
+
+ @Test
+ public void validateVolumeGroups_withUnsupportedAudioContext_fails() {
+ CarAudioContext carAudioContext = new CarAudioContext(List.of(new CarAudioContextInfo(
+ new AudioAttributes[] {getAudioAttributeFromUsage(AudioAttributes.USAGE_UNKNOWN),
+ getAudioAttributeFromUsage(AudioAttributes.USAGE_GAME),
+ getAudioAttributeFromUsage(AudioAttributes.USAGE_MEDIA)},
+ /* name= */ "MUSIC", MUSIC)), /* useCoreAudioRouting= */ false);
+ CarVolumeGroup mockMusicGroup = new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(TEST_MEDIA_CONTEXT, MUSIC_ADDRESS).build();
+ CarAudioZoneConfig zoneConfig = buildZoneConfig(List.of(mockMusicGroup));
+
+ expectWithMessage("Volume group with unsupported audio context")
+ .that(zoneConfig.validateVolumeGroups(carAudioContext,
+ /* useCoreAudioRouting= */ false)).isFalse();
+ }
+
+ @Test
public void getAudioDeviceInfos() {
AudioDeviceInfo musicAudioDeviceInfo = Mockito.mock(AudioDeviceInfo.class);
AudioDeviceAttributes musicDeviceAttributes =
@@ -684,6 +747,88 @@
}
@Test
+ public void onAudioGainChanged_withMultipleGainInfosForOneVolumeGroup() {
+ CarVolumeGroup mockNavAndVoiceGroup = new VolumeGroupBuilder().setName(TEST_NAV_GROUP_NAME)
+ .addDeviceAddressAndContexts(TEST_NAVIGATION_CONTEXT, NAV_ADDRESS)
+ .addDeviceAddressAndContexts(TEST_ASSISTANT_CONTEXT, VOICE_ADDRESS)
+ .setZoneId(TEST_ZONE_ID).setGroupId(TEST_NAV_GROUP_ID)
+ .addDeviceAddressAndUsages(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, NAV_ADDRESS)
+ .addDeviceAddressAndUsages(USAGE_ASSISTANT, VOICE_ADDRESS)
+ .build();
+ CarAudioZoneConfig zoneConfig = mTestAudioZoneConfigBuilder.addVolumeGroup(mMockMusicGroup)
+ .addVolumeGroup(mockNavAndVoiceGroup).build();
+ List<Integer> reasons = List.of(Reasons.REMOTE_MUTE, Reasons.NAV_DUCKING);
+ AudioGainConfigInfo navGainInfo = new AudioGainConfigInfo();
+ navGainInfo.zoneId = PRIMARY_AUDIO_ZONE;
+ navGainInfo.devicePortAddress = NAV_ADDRESS;
+ navGainInfo.volumeIndex = 666;
+ AudioGainConfigInfo voiceGainInfo = new AudioGainConfigInfo();
+ voiceGainInfo.zoneId = PRIMARY_AUDIO_ZONE;
+ voiceGainInfo.devicePortAddress = VOICE_ADDRESS;
+ voiceGainInfo.volumeIndex = 666;
+ CarAudioGainConfigInfo carNavGainInfo = new CarAudioGainConfigInfo(navGainInfo);
+ CarAudioGainConfigInfo carVoiceGainInfo = new CarAudioGainConfigInfo(voiceGainInfo);
+ when(mockNavAndVoiceGroup.onAudioGainChanged(any(), any()))
+ .thenReturn(EVENT_TYPE_MUTE_CHANGED);
+ when(mMockMusicGroup.onAudioGainChanged(any(), any()))
+ .thenReturn(EVENT_TYPE_ATTENUATION_CHANGED, EVENT_TYPE_MUTE_CHANGED);
+
+ List<CarVolumeGroupEvent> events = zoneConfig.onAudioGainChanged(reasons,
+ List.of(carNavGainInfo, carVoiceGainInfo));
+
+ expectWithMessage("Changed audio gain in navigation and voice group")
+ .that(events.isEmpty()).isFalse();
+ verify(mMockMusicGroup, never()).onAudioGainChanged(any(), any());
+ verify(mockNavAndVoiceGroup).onAudioGainChanged(reasons, carNavGainInfo);
+ verify(mockNavAndVoiceGroup).onAudioGainChanged(reasons, carVoiceGainInfo);
+ }
+
+ @Test
+ public void onAudioGainChanged_withInvalidEventType() {
+ CarAudioZoneConfig zoneConfig = mTestAudioZoneConfigBuilder.addVolumeGroup(mMockMusicGroup)
+ .addVolumeGroup(mMockNavGroup).build();
+ List<Integer> reasons = List.of(Reasons.REMOTE_MUTE, Reasons.NAV_DUCKING);
+ AudioGainConfigInfo musicGainInfo = new AudioGainConfigInfo();
+ musicGainInfo.zoneId = PRIMARY_AUDIO_ZONE;
+ musicGainInfo.devicePortAddress = MUSIC_ADDRESS;
+ musicGainInfo.volumeIndex = 666;
+ CarAudioGainConfigInfo carMusicGainInfo = new CarAudioGainConfigInfo(musicGainInfo);
+ when(mMockNavGroup.onAudioGainChanged(any(), any())).thenReturn(EVENT_TYPE_MUTE_CHANGED);
+ when(mMockMusicGroup.onAudioGainChanged(any(), any())).thenReturn(0);
+ when(mMockVoiceGroup.onAudioGainChanged(any(), any()))
+ .thenReturn(EVENT_TYPE_ATTENUATION_CHANGED);
+
+ List<CarVolumeGroupEvent> events = zoneConfig.onAudioGainChanged(reasons,
+ List.of(carMusicGainInfo));
+
+ expectWithMessage("Car volume group events with invalid event type")
+ .that(events.isEmpty()).isTrue();
+ }
+
+ @Test
+ public void onAudioGainChanged_withInvalidAddress() {
+ CarAudioZoneConfig zoneConfig = mTestAudioZoneConfigBuilder.addVolumeGroup(mMockMusicGroup)
+ .addVolumeGroup(mMockNavGroup).build();
+ List<Integer> reasons = List.of(Reasons.REMOTE_MUTE, Reasons.NAV_DUCKING);
+ AudioGainConfigInfo musicGainInfo = new AudioGainConfigInfo();
+ musicGainInfo.zoneId = PRIMARY_AUDIO_ZONE;
+ musicGainInfo.devicePortAddress = "invalid_address";
+ musicGainInfo.volumeIndex = 666;
+ CarAudioGainConfigInfo carMusicGainInfo = new CarAudioGainConfigInfo(musicGainInfo);
+ when(mMockNavGroup.onAudioGainChanged(any(), any())).thenReturn(EVENT_TYPE_MUTE_CHANGED);
+ when(mMockMusicGroup.onAudioGainChanged(any(), any()))
+ .thenReturn(EVENT_TYPE_ATTENUATION_CHANGED);
+ when(mMockVoiceGroup.onAudioGainChanged(any(), any()))
+ .thenReturn(EVENT_TYPE_ATTENUATION_CHANGED);
+
+ List<CarVolumeGroupEvent> events = zoneConfig.onAudioGainChanged(reasons,
+ List.of(carMusicGainInfo));
+
+ expectWithMessage("Car volume group events with invalid device address")
+ .that(events.isEmpty()).isTrue();
+ }
+
+ @Test
public void onAudioGainChanged_withoutAnyDeviceAddressInZone() {
CarAudioZoneConfig zoneConfig = mTestAudioZoneConfigBuilder.addVolumeGroup(mMockVoiceGroup)
.build();
@@ -720,7 +865,21 @@
}
@Test
- public void getCarAudioZoneConfigInfo() {
+ public void getCarAudioZoneConfigInfo_withDynamicDevicesDisabled() {
+ mSetFlagsRule.disableFlags(FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
+ CarAudioZoneConfig zoneConfig = mTestAudioZoneConfigBuilder.build();
+ CarAudioZoneConfigInfo zoneConfigInfoExpected = new CarAudioZoneConfigInfo(
+ TEST_ZONE_CONFIG_NAME, PRIMARY_AUDIO_ZONE, TEST_ZONE_CONFIG_ID);
+
+ CarAudioZoneConfigInfo zoneConfigInfo = zoneConfig.getCarAudioZoneConfigInfo();
+
+ expectWithMessage("Zone configuration info with dynamic devices disabled")
+ .that(zoneConfigInfo).isEqualTo(zoneConfigInfoExpected);
+ }
+
+ @Test
+ public void getCarAudioZoneConfigInfo_withDynamicDevicesEnabled() {
+ mSetFlagsRule.enableFlags(FLAG_CAR_AUDIO_DYNAMIC_DEVICES);
CarAudioZoneConfig zoneConfig = mTestAudioZoneConfigBuilder.build();
CarAudioZoneConfigInfo zoneConfigInfoExpected = new CarAudioZoneConfigInfo(
TEST_ZONE_CONFIG_NAME, EMPTY_LIST, PRIMARY_AUDIO_ZONE, TEST_ZONE_CONFIG_ID,
@@ -728,7 +887,7 @@
CarAudioZoneConfigInfo zoneConfigInfo = zoneConfig.getCarAudioZoneConfigInfo();
- expectWithMessage("Zone configuration info")
+ expectWithMessage("Zone configuration info with dynamic devices enabled")
.that(zoneConfigInfo).isEqualTo(zoneConfigInfoExpected);
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneUnitTest.java
index 332024f..89a876f 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneUnitTest.java
@@ -23,8 +23,10 @@
import static android.media.AudioAttributes.USAGE_MEDIA;
import static com.android.car.audio.CarAudioContext.AudioContext;
+import static com.android.car.audio.CarAudioContext.SAFETY;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
@@ -93,6 +95,8 @@
private static final int TEST_GAIN_DEFAULT_VALUE = -2000;
private static final int TEST_GAIN_STEP_VALUE = 2;
+ private static final int TEST_USER_ID = 13;
+
private static final AudioAttributes TEST_MEDIA_ATTRIBUTE =
CarAudioContext.getAudioAttributeFromUsage(USAGE_MEDIA);
@@ -284,6 +288,20 @@
}
@Test
+ public void setCurrentCarZoneConfig_withCurrentZoneConfig() {
+ mTestAudioZone.addZoneConfig(mMockZoneConfig0);
+ mTestAudioZone.addZoneConfig(mMockZoneConfig1);
+ CarAudioZoneConfigInfo currentZoneConfigInfo = mTestAudioZone
+ .getCurrentCarAudioZoneConfig().getCarAudioZoneConfigInfo();
+
+ mTestAudioZone.setCurrentCarZoneConfig(currentZoneConfigInfo);
+
+ expectWithMessage("Current zone config info after setting to current zone config info")
+ .that(mTestAudioZone.getCurrentCarAudioZoneConfig().getCarAudioZoneConfigInfo())
+ .isEqualTo(currentZoneConfigInfo);
+ }
+
+ @Test
public void setCurrentCarZoneConfig_withCoreAudioRoutingEnabled() {
CarAudioContext contextWithCoreAudioRouting =
new CarAudioContext(CarAudioContext.getAllContextsInfo(),
@@ -365,18 +383,10 @@
}
@Test
- public void validateZoneConfigs_withoutInvalidDefaultZoneConfigId_returnsFalse() {
- mTestAudioZone.addZoneConfig(mMockZoneConfig1);
-
- expectWithMessage("Invalid zone with invalid default zone configuration id")
- .that(mTestAudioZone.validateZoneConfigs(/* useCoreAudioRouting= */ false))
- .isFalse();
- }
-
- @Test
public void validateZoneConfigs_withWrongZoneIdInZoneConfigs_returnsFalse() {
CarAudioZoneConfig zoneConfig2 = new TestCarAudioZoneConfigBuilder(TEST_ZONE_ID + 1,
/* configId= */ 2, TEST_ZONE_CONFIG_NAME_1).build();
+ mTestAudioZone.addZoneConfig(mMockZoneConfig0);
mTestAudioZone.addZoneConfig(zoneConfig2);
expectWithMessage("Invalid zone with wrong zone id in zone configurations")
@@ -870,6 +880,34 @@
.hasMessageThat().contains("Audio devices");
}
+ @Test
+ public void updateVolumeGroupsSettingsForUser() {
+ when(mMockZoneConfig1.isSelected()).thenReturn(true);
+ mTestAudioZone.addZoneConfig(mMockZoneConfig0);
+ mTestAudioZone.addZoneConfig(mMockZoneConfig1);
+
+ mTestAudioZone.updateVolumeGroupsSettingsForUser(TEST_USER_ID);
+
+ verify(mMockZoneConfig0, never()).updateVolumeGroupsSettingsForUser(TEST_USER_ID);
+ verify(mMockZoneConfig1).updateVolumeGroupsSettingsForUser(TEST_USER_ID);
+ }
+
+ @Test
+ public void getAudioDeviceForContext_withoutOutputDeviceFound() {
+ when(mMockMusicGroup0.getAudioDeviceForContext(anyInt())).thenReturn(null);
+ when(mMockNavGroup0.getAudioDeviceForContext(anyInt())).thenReturn(null);
+ CarAudioZoneConfig zoneConfig = new TestCarAudioZoneConfigBuilder(TEST_ZONE_ID,
+ TEST_ZONE_CONFIG_ID_0, TEST_ZONE_CONFIG_NAME_0).setIsDefault(true)
+ .addVolumeGroup(mMockMusicGroup0).addVolumeGroup(mMockNavGroup0).build();
+ mTestAudioZone.addZoneConfig(zoneConfig);
+
+ IllegalStateException exception = assertThrows(IllegalStateException.class,
+ () -> mTestAudioZone.getAudioDeviceForContext(SAFETY));
+
+ expectWithMessage("Null audio device exception").that(exception).hasMessageThat()
+ .contains("Could not find output device");
+ }
+
private CarAudioZoneConfigInfo getFirstNonCurrentZoneConfigInfo(CarAudioZone audioZone) {
CarAudioZoneConfigInfo currentZoneConfigInfo = audioZone.getCurrentCarAudioZoneConfig()
.getCarAudioZoneConfigInfo();
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarHalAudioUtilsTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarHalAudioUtilsTest.java
index df2f52a..d28a6f4 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarHalAudioUtilsTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarHalAudioUtilsTest.java
@@ -22,6 +22,7 @@
import static android.media.AudioAttributes.USAGE_MEDIA;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
+import static android.media.AudioAttributes.USAGE_SAFETY;
import static com.android.car.audio.CoreAudioRoutingUtils.MUSIC_ATTRIBUTES;
import static com.android.car.audio.CoreAudioRoutingUtils.OEM_ATTRIBUTES;
@@ -67,6 +68,9 @@
private static final AudioAttributes MEDIA_AUDIO_ATTRIBUTE = MUSIC_ATTRIBUTES;
private static final AudioAttributes NOTIFICATION_AUDIO_ATTRIBUTE =
CarAudioContext.getAudioAttributeFromUsage(USAGE_NOTIFICATION);
+ private static final AudioAttributes SAFETY_ATTRIBUTES = new AudioAttributes.Builder()
+ .setSystemUsage(USAGE_SAFETY).setContentType(CONTENT_TYPE_UNKNOWN)
+ .build();
private static final List<AudioAttributes> AUDIO_ATTRIBUTES_HOLDING_FOCUS = List.of(
MEDIA_AUDIO_ATTRIBUTE, NOTIFICATION_AUDIO_ATTRIBUTE);
private static final String[] USAGES_LITERAL_HOLDING_FOCUS = {
@@ -238,6 +242,26 @@
}
@Test
+ public void metadataToAudioAttribute_withSystemUsage_succeeds() {
+ PlaybackTrackMetadata metadata = new PlaybackTrackMetadata();
+ metadata.usage = USAGE_SAFETY;
+ metadata.contentType = CONTENT_TYPE_UNKNOWN;
+ metadata.tags = new String[0];
+ metadata.channelMask = AudioChannelLayout.none(0);
+ AudioDeviceDescription audioDeviceDescription = new AudioDeviceDescription();
+ audioDeviceDescription.connection = new String();
+ AudioDevice audioDevice = new AudioDevice();
+ audioDevice.type = audioDeviceDescription;
+ audioDevice.address = AudioDeviceAddress.id("");
+ metadata.sourceDevice = audioDevice;
+
+ AudioAttributes audioAttributes = CarHalAudioUtils.metadataToAudioAttribute(metadata);
+
+ assertWithMessage("Safety audioAttributes converted from meta data")
+ .that(audioAttributes).isEqualTo(SAFETY_ATTRIBUTES);
+ }
+
+ @Test
public void metadataToAudioAttributes_withOemTags_succeeds() {
PlaybackTrackMetadata metadata = new PlaybackTrackMetadata();
metadata.usage = USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeEventHandlerTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeEventHandlerTest.java
new file mode 100644
index 0000000..58ea2ce
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeEventHandlerTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.audio;
+
+import static org.mockito.Mockito.mock;
+
+import android.car.media.CarVolumeGroupEvent;
+import android.car.test.AbstractExpectableTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CarVolumeEventHandlerTest extends AbstractExpectableTestCase {
+ private static final long TEST_TIMEOUT_MS = 100;
+ private static final int TEST_ZONE_ID = 1;
+ private static final int TEST_FLAG = 0;
+ private static final int TEST_UID = 10103;
+ private static final CarVolumeEventHandler EVENT_HANDLER = new CarVolumeEventHandler();
+
+ private final TestCarVolumeEventCallback mCarVolumeEventCallback =
+ new TestCarVolumeEventCallback(TEST_TIMEOUT_MS);
+
+ @Test
+ public void registerCarVolumeEventCallback() {
+ EVENT_HANDLER.registerCarVolumeEventCallback(mCarVolumeEventCallback, TEST_UID);
+
+ expectWithMessage("registered UID")
+ .that(EVENT_HANDLER.checkIfUidIsRegistered(TEST_UID)).isTrue();
+ }
+
+ @Test
+ public void unregisterCarVolumeEventCallback() {
+ EVENT_HANDLER.registerCarVolumeEventCallback(mCarVolumeEventCallback, TEST_UID);
+
+ EVENT_HANDLER.unregisterCarVolumeEventCallback(mCarVolumeEventCallback, TEST_UID);
+
+ expectWithMessage("unregistered UID")
+ .that(EVENT_HANDLER.checkIfUidIsRegistered(TEST_UID)).isFalse();
+ }
+
+ @Test
+ public void onMasterMuteChanged() throws Exception {
+ EVENT_HANDLER.registerCarVolumeEventCallback(mCarVolumeEventCallback, TEST_UID);
+
+ EVENT_HANDLER.onMasterMuteChanged(TEST_ZONE_ID, TEST_FLAG);
+
+ expectWithMessage("Invocation of callback for master mute change").that(
+ mCarVolumeEventCallback.waitForCallback()).isTrue();
+ }
+
+ @Test
+ public void onVolumeGroupEvent() throws Exception {
+ CarVolumeGroupEvent eventMock = mock(CarVolumeGroupEvent.class);
+ EVENT_HANDLER.registerCarVolumeEventCallback(mCarVolumeEventCallback, TEST_UID);
+
+ EVENT_HANDLER.onVolumeGroupEvent(List.of(eventMock));
+
+ expectWithMessage("Invocation of callback for volume group event change").that(
+ mCarVolumeEventCallback.waitForCallback()).isTrue();
+ }
+
+ @Test
+ public void release() {
+ EVENT_HANDLER.registerCarVolumeEventCallback(mCarVolumeEventCallback, TEST_UID);
+
+ EVENT_HANDLER.release();
+
+ expectWithMessage("Released UID")
+ .that(EVENT_HANDLER.checkIfUidIsRegistered(TEST_UID)).isFalse();
+ }
+
+ @Test
+ public void onCallbackDied() {
+ EVENT_HANDLER.registerCarVolumeEventCallback(mCarVolumeEventCallback, TEST_UID);
+
+ EVENT_HANDLER.onCallbackDied(mCarVolumeEventCallback, TEST_UID);
+
+ expectWithMessage("UID with dead callback")
+ .that(EVENT_HANDLER.checkIfUidIsRegistered(TEST_UID)).isFalse();
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupFactoryTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupFactoryTest.java
index ed0fd9c..06526ef 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupFactoryTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupFactoryTest.java
@@ -22,7 +22,6 @@
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.when;
-import android.media.AudioManager;
import android.os.UserHandle;
import com.google.common.truth.Expect;
@@ -44,8 +43,10 @@
private static final int MIN_GAIN_INDEX = 0;
private static final int MAX_GAIN_INDEX = (TestCarAudioDeviceInfoBuilder.MAX_GAIN
- TestCarAudioDeviceInfoBuilder.MIN_GAIN) / TestCarAudioDeviceInfoBuilder.STEP_VALUE;
- private static final int MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE = 10;
- private static final int MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE = 90;
+ private static final CarActivationVolumeConfig CAR_ACTIVATION_VOLUME_CONFIG =
+ new CarActivationVolumeConfig(CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT,
+ /* minActivationVolumePercentage= */ 10,
+ /* maxActivationVolumePercentage= */ 90);
private static final String GROUP_NAME = "group_0";
private static final String MEDIA_DEVICE_ADDRESS = "music";
private static final String NAVIGATION_DEVICE_ADDRESS = "navigation";
@@ -67,7 +68,7 @@
@Mock
CarAudioSettings mSettingsMock;
@Mock
- AudioManager mAudioManagerMock;
+ AudioManagerWrapper mAudioManagerMock;
CarVolumeGroupFactory mFactory;
@@ -296,8 +297,7 @@
() -> new CarVolumeGroupFactory(/* audioManager= */ null,
/* carAudioSettings= */ null, TEST_CAR_AUDIO_CONTEXT, ZONE_ID,
CONFIG_ID, GROUP_ID, GROUP_NAME, /* useCarVolumeGroupMute= */ true,
- MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE,
- MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE));
+ CAR_ACTIVATION_VOLUME_CONFIG));
expect.withMessage("Constructor null car audio settings exception")
.that(thrown).hasMessageThat()
@@ -310,17 +310,27 @@
() -> new CarVolumeGroupFactory(/* audioManager= */ null, mSettingsMock,
/* carAudioContext= */ null, ZONE_ID, CONFIG_ID, GROUP_ID,
GROUP_NAME, /* useCarVolumeGroupMute= */ true,
- MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE,
- MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE));
+ CAR_ACTIVATION_VOLUME_CONFIG));
expect.withMessage("Constructor null car audio context exception")
.that(thrown).hasMessageThat()
.contains("Car audio context");
}
+ @Test
+ public void factoryConstructor_withNullCarActivationVolumeConfig_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class,
+ () -> new CarVolumeGroupFactory(mAudioManagerMock, mSettingsMock,
+ TEST_CAR_AUDIO_CONTEXT, ZONE_ID, CONFIG_ID, GROUP_ID, GROUP_NAME,
+ /* useCarVolumeGroupMute= */ true, /* carActivationVolumeConfig= */ null));
+
+ expect.withMessage("Constructor null car ativation volume config")
+ .that(thrown).hasMessageThat().contains("Car activation volume config");
+ }
+
CarVolumeGroupFactory getFactory() {
return new CarVolumeGroupFactory(mAudioManagerMock, mSettingsMock, TEST_CAR_AUDIO_CONTEXT,
ZONE_ID, CONFIG_ID, GROUP_ID, GROUP_NAME, /* useCarVolumeGroupMute= */ true,
- MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE, MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE);
+ CAR_ACTIVATION_VOLUME_CONFIG);
}
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
index 5c5083f..d844e7c 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
@@ -29,6 +29,7 @@
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_EVENT;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+import static android.media.AudioAttributes.USAGE_SAFETY;
import static android.media.AudioAttributes.USAGE_UNKNOWN;
import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING;
@@ -51,7 +52,6 @@
import android.hardware.automotive.audiocontrol.Reasons;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
-import android.media.AudioManager;
import android.os.UserHandle;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
@@ -81,6 +81,13 @@
- TestCarAudioDeviceInfoBuilder.MIN_GAIN) / TestCarAudioDeviceInfoBuilder.STEP_VALUE;
private static final int MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE = 20;
private static final int MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE = 80;
+ private static final int SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE =
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_SOURCE_CHANGED;
+ private static final int UNSUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE =
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED;
+ private static final int ACTIVATION_VOLUME_INVOCATION_TYPE =
+ CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT
+ | CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_SOURCE_CHANGED;
private static final int MIN_ACTIVATION_GAIN_INDEX = MIN_GAIN_INDEX + (int) Math.round(
MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE / 100.0 * (MAX_GAIN_INDEX - MIN_GAIN_INDEX));
private static final int MAX_ACTIVATION_GAIN_INDEX = MIN_GAIN_INDEX + (int) Math.round(
@@ -94,6 +101,10 @@
private static final String OTHER_ADDRESS = "other_address";
private static final int EVENT_TYPE_NONE = 0;
+ private static final CarActivationVolumeConfig CAR_ACTIVATION_VOLUME_CONFIG =
+ new CarActivationVolumeConfig(ACTIVATION_VOLUME_INVOCATION_TYPE,
+ MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE, MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE);
+
private static final CarAudioContext TEST_CAR_AUDIO_CONTEXT =
new CarAudioContext(CarAudioContext.getAllContextsInfo(),
/* useCoreAudioRouting= */ false);
@@ -127,7 +138,7 @@
@Mock
CarAudioSettings mSettingsMock;
@Mock
- AudioManager mAudioManagerMock;
+ AudioManagerWrapper mAudioManagerMock;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -386,7 +397,7 @@
}
@Test
- public void getMaxActivationGainIndex_returnsExpectedDevice() {
+ public void getMaxActivationGainIndex() {
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
@@ -397,7 +408,7 @@
}
@Test
- public void getMinActivationGainIndex_returnsExpectedDevice() {
+ public void getMinActivationGainIndex() {
mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
@@ -408,6 +419,16 @@
}
@Test
+ public void getActivationVolumeInvocationType() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
+ CarVolumeGroup carVolumeGroup = testInactiveVolumeGroupSetup();
+
+ expectWithMessage("Activation volume invocation types")
+ .that(carVolumeGroup.getActivationVolumeInvocationType())
+ .isEqualTo(ACTIVATION_VOLUME_INVOCATION_TYPE);
+ }
+
+ @Test
public void setCurrentGainIndex_setsGainOnAllBoundDevices() {
CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
@@ -482,9 +503,23 @@
CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
carVolumeGroup.setCurrentGainIndex(MAX_GAIN_INDEX);
- expectWithMessage("No adjustment to activation volume with activation volume disabled")
- .that(carVolumeGroup.handleActivationVolume()).isFalse();
- expectWithMessage("Unchanged gain index with activation volume disabled")
+ expectWithMessage("Adjustment to activation volume with activation volume disabled")
+ .that(carVolumeGroup.handleActivationVolume(
+ SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE)).isFalse();
+ expectWithMessage("Gain index with activation volume disabled")
+ .that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MAX_GAIN_INDEX);
+ }
+
+ @Test
+ public void handleActivationVolume_withUnsupportedActivationInvocationType() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME);
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+ carVolumeGroup.setCurrentGainIndex(MAX_GAIN_INDEX);
+
+ expectWithMessage("Adjustment to activation volume with unsupported activation "
+ + "invocation type").that(carVolumeGroup.handleActivationVolume(
+ UNSUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE)).isFalse();
+ expectWithMessage("Gain index with unsupported activation invocation type")
.that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MAX_GAIN_INDEX);
}
@@ -495,8 +530,9 @@
CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
carVolumeGroup.setCurrentGainIndex(currentGainIndex);
- expectWithMessage("No adjustment for activation volume")
- .that(carVolumeGroup.handleActivationVolume()).isFalse();
+ expectWithMessage("Adjustment for activation volume")
+ .that(carVolumeGroup.handleActivationVolume(
+ SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE)).isFalse();
expectWithMessage("Gain index without activation volume adjustment")
.that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(currentGainIndex);
}
@@ -508,7 +544,8 @@
carVolumeGroup.setCurrentGainIndex(MIN_ACTIVATION_GAIN_INDEX - 1);
expectWithMessage("Success for adjusting to min activation volume")
- .that(carVolumeGroup.handleActivationVolume()).isTrue();
+ .that(carVolumeGroup.handleActivationVolume(
+ SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE)).isTrue();
expectWithMessage("Gain index for adjusting to min activation volume")
.that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MIN_ACTIVATION_GAIN_INDEX);
}
@@ -520,7 +557,8 @@
carVolumeGroup.setCurrentGainIndex(MAX_ACTIVATION_GAIN_INDEX + 1);
expectWithMessage("Success for adjusting to max activation volume")
- .that(carVolumeGroup.handleActivationVolume()).isTrue();
+ .that(carVolumeGroup.handleActivationVolume(
+ SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE)).isTrue();
expectWithMessage("Gain index for adjusting to max activation volume")
.that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MAX_ACTIVATION_GAIN_INDEX);
}
@@ -533,7 +571,8 @@
carVolumeGroup.setMute(true);
expectWithMessage("Failure for changing to activation volume with mute")
- .that(carVolumeGroup.handleActivationVolume()).isFalse();
+ .that(carVolumeGroup.handleActivationVolume(
+ SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE)).isFalse();
expectWithMessage("Gain index with activation volume and mute")
.that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MIN_GAIN_INDEX);
}
@@ -544,7 +583,7 @@
CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
carVolumeGroup.setCurrentGainIndex(MAX_ACTIVATION_GAIN_INDEX + 1);
carVolumeGroup.setMute(true);
- carVolumeGroup.handleActivationVolume();
+ carVolumeGroup.handleActivationVolume(SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE);
carVolumeGroup.setMute(false);
@@ -561,7 +600,8 @@
carVolumeGroup.setBlocked(blockedIndex);
expectWithMessage("Failure for changing to activation volume with block")
- .that(carVolumeGroup.handleActivationVolume()).isFalse();
+ .that(carVolumeGroup.handleActivationVolume(
+ SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE)).isFalse();
expectWithMessage("Gain index with activation volume and block")
.that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(blockedIndex);
}
@@ -573,7 +613,7 @@
carVolumeGroup.setCurrentGainIndex(MAX_GAIN_INDEX);
int blockedIndex = MAX_ACTIVATION_GAIN_INDEX + 1;
carVolumeGroup.setBlocked(blockedIndex);
- carVolumeGroup.handleActivationVolume();
+ carVolumeGroup.handleActivationVolume(SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE);
carVolumeGroup.resetBlocked();
@@ -590,7 +630,8 @@
carVolumeGroup.setAttenuatedGain(attenuatedIndex);
expectWithMessage("Changing to activation volume with attenuated gain")
- .that(carVolumeGroup.handleActivationVolume()).isFalse();
+ .that(carVolumeGroup.handleActivationVolume(
+ SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE)).isFalse();
expectWithMessage("Gain index with activation volume and attenuated gain applied")
.that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(attenuatedIndex);
}
@@ -602,7 +643,7 @@
carVolumeGroup.setCurrentGainIndex(MAX_GAIN_INDEX);
int attenuatedIndex = MAX_ACTIVATION_GAIN_INDEX + 1;
carVolumeGroup.setAttenuatedGain(attenuatedIndex);
- carVolumeGroup.handleActivationVolume();
+ carVolumeGroup.handleActivationVolume(SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE);
carVolumeGroup.resetAttenuation();
@@ -619,7 +660,8 @@
carVolumeGroup.setLimit(limitedGainIndex);
expectWithMessage("Volume changed due to activation volume over limit and limit "
- + "over current gain").that(carVolumeGroup.handleActivationVolume()).isTrue();
+ + "over current gain").that(carVolumeGroup.handleActivationVolume(
+ SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE)).isTrue();
expectWithMessage("Gain index with activation volume over limit and limit "
+ "over current gain").that(carVolumeGroup.getCurrentGainIndex())
.isEqualTo(limitedGainIndex);
@@ -632,7 +674,7 @@
carVolumeGroup.setCurrentGainIndex(MIN_ACTIVATION_GAIN_INDEX - 2);
int limitedGainIndex = MIN_ACTIVATION_GAIN_INDEX - 1;
carVolumeGroup.setLimit(limitedGainIndex);
- carVolumeGroup.handleActivationVolume();
+ carVolumeGroup.handleActivationVolume(SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE);
carVolumeGroup.resetLimit();
@@ -650,8 +692,8 @@
carVolumeGroup.setLimit(limitedGainIndex);
expectWithMessage("Volume changed due to activation volume over current gain and "
- + "current gain over limit").that(carVolumeGroup.handleActivationVolume())
- .isFalse();
+ + "current gain over limit").that(carVolumeGroup.handleActivationVolume(
+ SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE)).isFalse();
expectWithMessage("Gain index with activation volume over current gain and "
+ "current gain over limit").that(carVolumeGroup.getCurrentGainIndex())
.isEqualTo(limitedGainIndex);
@@ -664,7 +706,7 @@
carVolumeGroup.setCurrentGainIndex(MIN_ACTIVATION_GAIN_INDEX - 1);
int limitedGainIndex = MIN_ACTIVATION_GAIN_INDEX - 2;
carVolumeGroup.setLimit(limitedGainIndex);
- carVolumeGroup.handleActivationVolume();
+ carVolumeGroup.handleActivationVolume(SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE);
carVolumeGroup.resetLimit();
expectWithMessage("Gain index with activation volume over current gain, current gain "
@@ -681,7 +723,8 @@
carVolumeGroup.setLimit(limitedGainIndex);
expectWithMessage("Changing to activation volume below limit")
- .that(carVolumeGroup.handleActivationVolume()).isTrue();
+ .that(carVolumeGroup.handleActivationVolume(
+ SUPPORTED_ACTIVATION_VOLUME_INVOCATION_TYPE)).isTrue();
expectWithMessage("Gain index with activation volume below limit")
.that(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MIN_ACTIVATION_GAIN_INDEX);
}
@@ -2001,6 +2044,15 @@
}
@Test
+ public void hasAudioAttributes_withAttributeUsageInVolumeGroup() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+ expectWithMessage("Audio attributes with game usage in volume group")
+ .that(carVolumeGroup.hasAudioAttributes(new AudioAttributes.Builder()
+ .setUsage(USAGE_GAME).build())).isTrue();
+ }
+
+ @Test
public void hasAudioAttributes_withAttributesNotInVolumeGroup() {
CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
@@ -2106,6 +2158,24 @@
.that(existingDevices).containsExactly(TYPE_BLUETOOTH_A2DP);
}
+ @Test
+ public void onAudioVolumeGroupChanged() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup(/* useCarVolumeGroupMute= */ false);
+
+ expectWithMessage("Flags for audio volume group change")
+ .that(carVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0)).isEqualTo(0);
+ }
+
+ @Test
+ public void getAddressForContext_withoutContextFound() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+ int safetyContextId = TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(
+ CarAudioContext.getAudioAttributeFromUsage(USAGE_SAFETY));
+
+ expectWithMessage("Device address for context not found in volume group")
+ .that(carVolumeGroup.getAddressForContext(safetyContextId)).isNull();
+ }
+
private CarVolumeGroup getCarVolumeGroupWithMusicBound() {
CarVolumeGroupFactory factory = getFactory(/* useCarVolumeGroupMute= */ true);
factory.setDeviceInfoForContext(TEST_MEDIA_CONTEXT_ID, mMediaDeviceInfo);
@@ -2116,8 +2186,7 @@
boolean useCarVolumeGroupMute) {
CarVolumeGroupFactory factory = new CarVolumeGroupFactory(mAudioManagerMock, settings,
TEST_CAR_AUDIO_CONTEXT, ZONE_ID, CONFIG_ID, GROUP_ID, /* name= */ "0",
- useCarVolumeGroupMute, MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE,
- MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE);
+ useCarVolumeGroupMute, CAR_ACTIVATION_VOLUME_CONFIG);
factory.setDeviceInfoForContext(TEST_NAVIGATION_CONTEXT_ID, mNavigationDeviceInfo);
return factory.getCarVolumeGroup(/* useCoreAudioVolume= */ false);
}
@@ -2176,7 +2245,7 @@
CarVolumeGroupFactory getFactory(boolean useCarVolumeGroupMute) {
return new CarVolumeGroupFactory(mAudioManagerMock, mSettingsMock, TEST_CAR_AUDIO_CONTEXT,
ZONE_ID, CONFIG_ID, GROUP_ID, GROUP_NAME, useCarVolumeGroupMute,
- MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE, MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE);
+ CAR_ACTIVATION_VOLUME_CONFIG);
}
private static final class SettingsBuilder {
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeTest.java
index ee448bf..54794a5 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeTest.java
@@ -40,6 +40,8 @@
import android.media.AudioAttributes;
+import androidx.annotation.NonNull;
+
import com.android.car.audio.CarAudioContext.AudioContext;
import com.google.common.collect.ImmutableList;
@@ -88,6 +90,8 @@
public static final AudioAttributes TEST_ANNOUNCEMENT_ATTRIBUTE =
CarAudioContext.getAudioAttributeFromUsage(USAGE_ANNOUNCEMENT);
+ public static final List<AudioAttributes> TEST_INACTIVE_ATTRIBUTES = Collections.emptyList();
+
@Mock
private SystemClockWrapper mMockClock;
@@ -145,21 +149,22 @@
public void getSuggestedAudioContext_withNullActivePlayback_fails() {
assertThrows(NullPointerException.class,
() -> mCarVolume.getSuggestedAudioContextAndSaveIfFound(
- null, CALL_STATE_IDLE, new ArrayList<>()));
+ null, CALL_STATE_IDLE, new ArrayList<>(), TEST_INACTIVE_ATTRIBUTES));
}
@Test
public void getSuggestedAudioContext_withNullHallAttributes_fails() {
assertThrows(NullPointerException.class,
() -> mCarVolume.getSuggestedAudioContextAndSaveIfFound(
- new ArrayList<>(), CALL_STATE_IDLE, null));
+ new ArrayList<>(), CALL_STATE_IDLE, /* activeHalAttributes= */ null,
+ TEST_INACTIVE_ATTRIBUTES));
}
@Test
public void getSuggestedAudioContext_withNoActivePlaybackAndIdleTelephony_returnsDefault() {
@AudioContext int suggestedContext =
mCarVolume.getSuggestedAudioContextAndSaveIfFound(new ArrayList<>(),
- CALL_STATE_IDLE, new ArrayList<>());
+ CALL_STATE_IDLE, new ArrayList<>(), TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(CAR_DEFAULT_AUDIO_ATTRIBUTE));
@@ -172,7 +177,7 @@
@AudioContext int suggestedContext = mCarVolume
.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes, CALL_STATE_IDLE,
- new ArrayList<>());
+ new ArrayList<>(), TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAttributes(TEST_ASSISTANT_ATTRIBUTE));
@@ -182,7 +187,7 @@
public void getSuggestedAudioContext_withCallStateOffHook_returnsCallContext() {
@AudioContext int suggestedContext =
mCarVolume.getSuggestedAudioContextAndSaveIfFound(new ArrayList<>(),
- CALL_STATE_OFFHOOK, new ArrayList<>());
+ CALL_STATE_OFFHOOK, new ArrayList<>(), TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(TEST_CALL_ATTRIBUTE));
@@ -191,12 +196,11 @@
@Test
public void getSuggestedAudioContext_withV1AndCallStateRinging_returnsCallRingContext() {
- CarVolume carVolume = new CarVolume(TEST_CAR_AUDIO_CONTEXT, mMockClock,
- VERSION_ONE, KEY_EVENT_TIMEOUT_MS);
+ CarVolume carVolume = getCarVolumeWithVersionOnePriorities();
@AudioContext int suggestedContext =
carVolume.getSuggestedAudioContextAndSaveIfFound(new ArrayList<>(),
- CALL_STATE_RINGING, new ArrayList<>());
+ CALL_STATE_RINGING, new ArrayList<>(), TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(TEST_CALL_RING_ATTRIBUTE));
@@ -210,7 +214,7 @@
@AudioContext int suggestedContext = mCarVolume
.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes, CALL_STATE_IDLE,
- new ArrayList<>());
+ new ArrayList<>(), TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(TEST_CALL_ATTRIBUTE));
@@ -224,7 +228,7 @@
@AudioContext int suggestedContext = mCarVolume
.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
- CALL_STATE_OFFHOOK, new ArrayList<>());
+ CALL_STATE_OFFHOOK, new ArrayList<>(), TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(TEST_CALL_ATTRIBUTE));
@@ -232,15 +236,13 @@
@Test
public void getSuggestedAudioContext_withV1AndNavigationConfigurationAndCall_returnsNav() {
- CarVolume carVolume = new CarVolume(TEST_CAR_AUDIO_CONTEXT, mMockClock,
- VERSION_ONE, KEY_EVENT_TIMEOUT_MS);
+ CarVolume carVolume = getCarVolumeWithVersionOnePriorities();
List<AudioAttributes> activePlaybackAttributes = ImmutableList.of(CarAudioContext
.getAudioAttributeFromUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE));
@AudioContext int suggestedContext = carVolume
.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
- CALL_STATE_OFFHOOK, new ArrayList<>());
-
+ CALL_STATE_OFFHOOK, new ArrayList<>(), TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(TEST_NAVIGATION_ATTRIBUTE));
}
@@ -252,7 +254,7 @@
@AudioContext int suggestedContext = mCarVolume
.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
- CALL_STATE_OFFHOOK, new ArrayList<>());
+ CALL_STATE_OFFHOOK, new ArrayList<>(), TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(TEST_CALL_ATTRIBUTE));
@@ -265,7 +267,7 @@
@AudioContext int suggestedContext = mCarVolume
.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
- CALL_STATE_IDLE, new ArrayList<>());
+ CALL_STATE_IDLE, new ArrayList<>(), TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(CAR_DEFAULT_AUDIO_ATTRIBUTE));
@@ -278,7 +280,7 @@
@AudioContext int suggestedContext =
mCarVolume.getSuggestedAudioContextAndSaveIfFound(new ArrayList<>(),
- CALL_STATE_IDLE, activeHalAudioAttributes);
+ CALL_STATE_IDLE, activeHalAudioAttributes, TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(
TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(TEST_ASSISTANT_ATTRIBUTE));
@@ -292,7 +294,7 @@
@AudioContext int suggestedContext =
mCarVolume.getSuggestedAudioContextAndSaveIfFound(new ArrayList<>(),
- CALL_STATE_IDLE, activeHalAudioAttributes);
+ CALL_STATE_IDLE, activeHalAudioAttributes, TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(CAR_DEFAULT_AUDIO_ATTRIBUTE));
@@ -306,8 +308,7 @@
@AudioContext int suggestedContext = mCarVolume
.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
- CALL_STATE_IDLE, activeHalAudioAttributes);
-
+ CALL_STATE_IDLE, activeHalAudioAttributes, TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(
TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(TEST_MEDIA_ATTRIBUTE));
}
@@ -320,7 +321,7 @@
@AudioContext int suggestedContext = mCarVolume
.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
- CALL_STATE_IDLE, activeHalAudioAttributes);
+ CALL_STATE_IDLE, activeHalAudioAttributes, TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(
TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(TEST_MEDIA_ATTRIBUTE));
@@ -332,7 +333,8 @@
List<AudioAttributes> activePlaybackAttributes = ImmutableList.of();
@AudioContext int suggestedContext = mCarVolume.getSuggestedAudioContextAndSaveIfFound(
- activePlaybackAttributes, CALL_STATE_OFFHOOK, activeHalAudioAttributes);
+ activePlaybackAttributes, CALL_STATE_OFFHOOK, activeHalAudioAttributes,
+ TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(TEST_CALL_ATTRIBUTE));
@@ -346,7 +348,7 @@
@AudioContext int suggestedContext = mCarVolume
.getSuggestedAudioContextAndSaveIfFound(Collections.EMPTY_LIST,
- CALL_STATE_IDLE, activeHalAudioAttributes);
+ CALL_STATE_IDLE, activeHalAudioAttributes, TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(
TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(TEST_MEDIA_ATTRIBUTE));
@@ -358,13 +360,15 @@
ImmutableList.of(TEST_ASSISTANT_ATTRIBUTE);
mCarVolume.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
- CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0));
+ CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0),
+ TEST_INACTIVE_ATTRIBUTES);
when(mMockClock.uptimeMillis()).thenReturn(START_TIME_ONE_SECOND);
@AudioContext int suggestedContext =
mCarVolume.getSuggestedAudioContextAndSaveIfFound(new ArrayList<>(),
- CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0));
+ CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0),
+ TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(
TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(TEST_ASSISTANT_ATTRIBUTE));
@@ -377,7 +381,8 @@
ImmutableList.of(TEST_ASSISTANT_ATTRIBUTE);
mCarVolume.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
- CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0));
+ CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0),
+ TEST_INACTIVE_ATTRIBUTES);
long deltaTime = KEY_EVENT_TIMEOUT_MS - 1;
for (int volumeCounter = 1; volumeCounter < TRIAL_COUNTS; volumeCounter++) {
@@ -386,7 +391,8 @@
@AudioContext int suggestedContext =
mCarVolume.getSuggestedAudioContextAndSaveIfFound(new ArrayList<>(),
- CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0));
+ CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0),
+ TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(
TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(TEST_ASSISTANT_ATTRIBUTE));
}
@@ -399,13 +405,15 @@
ImmutableList.of(TEST_ASSISTANT_ATTRIBUTE);
mCarVolume.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
- CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0));
+ CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0),
+ TEST_INACTIVE_ATTRIBUTES);
when(mMockClock.uptimeMillis()).thenReturn(START_TIME_ONE_SECOND);
@AudioContext int suggestedContext =
mCarVolume.getSuggestedAudioContextAndSaveIfFound(new ArrayList<>(),
- CALL_STATE_OFFHOOK, new ArrayList<>(/* initialCapacity= */ 0));
+ CALL_STATE_OFFHOOK, new ArrayList<>(/* initialCapacity= */ 0),
+ TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(
TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(TEST_ASSISTANT_ATTRIBUTE));
@@ -415,15 +423,15 @@
public void getSuggestedAudioContext_afterActiveContextTimeout_returnsDefaultContext() {
List<AudioAttributes> activePlaybackAttributes =
ImmutableList.of(TEST_ASSISTANT_ATTRIBUTE);
-
mCarVolume.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
- CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0));
-
+ CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0),
+ TEST_INACTIVE_ATTRIBUTES);
when(mMockClock.uptimeMillis()).thenReturn(START_TIME_FOUR_SECOND);
@AudioContext int suggestedContext =
mCarVolume.getSuggestedAudioContextAndSaveIfFound(new ArrayList<>(),
- CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0));
+ CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0),
+ TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(CAR_DEFAULT_AUDIO_ATTRIBUTE));
@@ -436,13 +444,15 @@
ImmutableList.of(TEST_ASSISTANT_ATTRIBUTE);
mCarVolume.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
- CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0));
+ CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0),
+ TEST_INACTIVE_ATTRIBUTES);
when(mMockClock.uptimeMillis()).thenReturn(START_TIME_FOUR_SECOND);
@AudioContext int suggestedContext =
mCarVolume.getSuggestedAudioContextAndSaveIfFound(new ArrayList<>(),
- CALL_STATE_OFFHOOK, new ArrayList<>(/* initialCapacity= */ 0));
+ CALL_STATE_OFFHOOK, new ArrayList<>(/* initialCapacity= */ 0),
+ TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(TEST_CALL_ATTRIBUTE));
@@ -453,25 +463,22 @@
getSuggestedAudioContext_afterMultipleQueriesAndNewContextCall_returnsNewContext() {
List<AudioAttributes> activePlaybackAttributes =
ImmutableList.of(TEST_ASSISTANT_ATTRIBUTE);
-
mCarVolume.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
- CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0));
-
+ CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0),
+ TEST_INACTIVE_ATTRIBUTES);
long deltaTime = KEY_EVENT_TIMEOUT_MS - 1;
-
for (int volumeCounter = 1; volumeCounter < TRIAL_COUNTS; volumeCounter++) {
when(mMockClock.uptimeMillis()).thenReturn(START_TIME + volumeCounter * deltaTime);
-
mCarVolume.getSuggestedAudioContextAndSaveIfFound(new ArrayList<>(), CALL_STATE_IDLE,
- new ArrayList<>(/* initialCapacity= */ 0));
+ new ArrayList<>(/* initialCapacity= */ 0), TEST_INACTIVE_ATTRIBUTES);
}
-
when(mMockClock.uptimeMillis())
.thenReturn(START_TIME + (TRIAL_COUNTS * deltaTime) + KEY_EVENT_TIMEOUT_MS);
@AudioContext int newContext =
mCarVolume.getSuggestedAudioContextAndSaveIfFound(new ArrayList<>(),
- CALL_STATE_OFFHOOK, new ArrayList<>(/* initialCapacity= */ 0));
+ CALL_STATE_OFFHOOK, new ArrayList<>(/* initialCapacity= */ 0),
+ TEST_INACTIVE_ATTRIBUTES);
assertThat(newContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(TEST_CALL_ATTRIBUTE));
@@ -483,7 +490,8 @@
ImmutableList.of(TEST_ASSISTANT_ATTRIBUTE);
mCarVolume.getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
- CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0));
+ CALL_STATE_IDLE, new ArrayList<>(/* initialCapacity= */ 0),
+ TEST_INACTIVE_ATTRIBUTES);
when(mMockClock.uptimeMillis()).thenReturn(START_TIME_ONE_SECOND);
@@ -491,12 +499,83 @@
@AudioContext int suggestedContext =
mCarVolume.getSuggestedAudioContextAndSaveIfFound(Collections.EMPTY_LIST,
- CALL_STATE_IDLE, Collections.EMPTY_LIST);
+ CALL_STATE_IDLE, Collections.EMPTY_LIST,
+ TEST_INACTIVE_ATTRIBUTES);
assertThat(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
.getContextForAudioAttribute(CAR_DEFAULT_AUDIO_ATTRIBUTE));
}
+ @Test
+ public void getSuggestedAudioContext_withV1AndMediaInactive_returnsNextDefaultContext() {
+ int nextDefault = mCarVolume.getSuggestedAudioContextAndSaveIfFound(Collections.emptyList(),
+ CALL_STATE_IDLE, Collections.emptyList(), List.of(TEST_MEDIA_ATTRIBUTE));
+
+ assertWithMessage("Next default audio context").that(nextDefault)
+ .isEqualTo(TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(
+ TEST_CALL_ATTRIBUTE));
+ }
+
+ @Test
+ public void getSuggestedAudioContext_withV1AndMediaContextsInactive_returnsNextValidContext() {
+ CarVolume carVolume = getCarVolumeWithVersionOnePriorities();
+
+ int nextDefault = carVolume.getSuggestedAudioContextAndSaveIfFound(Collections.emptyList(),
+ CALL_STATE_IDLE, Collections.emptyList(), List.of(TEST_MEDIA_ATTRIBUTE));
+
+ assertWithMessage("Next default audio context for version 1 priority list")
+ .that(nextDefault).isEqualTo(TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(
+ TEST_NAVIGATION_ATTRIBUTE));
+ }
+
+ @Test
+ public void getSuggestedAudioContext_withV2AndAllContextsInactive_returnsInvalidContext() {
+ int nextDefault = mCarVolume.getSuggestedAudioContextAndSaveIfFound(Collections.emptyList(),
+ CALL_STATE_IDLE, Collections.emptyList(), getAllAudioAttributes());
+
+ assertWithMessage("Invalid audio context for version 2 priority list")
+ .that(nextDefault).isEqualTo(CarAudioContext.getInvalidContext());
+ }
+
+ @Test
+ public void getSuggestedAudioContext_withV1AndAllContextsInactive_returnsInvalidContext() {
+ CarVolume carVolume = getCarVolumeWithVersionOnePriorities();
+ int nextDefault = carVolume.getSuggestedAudioContextAndSaveIfFound(Collections.emptyList(),
+ CALL_STATE_IDLE, Collections.emptyList(), getAllAudioAttributes());
+
+ assertWithMessage("Invalid audio context for version 1 priority list")
+ .that(nextDefault).isEqualTo(CarAudioContext.getInvalidContext());
+ }
+
+ @Test
+ public void getSuggestedAudioContext_withV1AndNavConfigAndMediaInactive_returnsNav() {
+ CarVolume carVolume = getCarVolumeWithVersionOnePriorities();
+ List<AudioAttributes> activePlaybackAttributes = ImmutableList.of(CarAudioContext
+ .getAudioAttributeFromUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE));
+
+ @AudioContext int suggestedContext = carVolume
+ .getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
+ CALL_STATE_IDLE, new ArrayList<>(), List.of(TEST_MEDIA_ATTRIBUTE));
+
+ assertWithMessage("Active navigation context while media inactive")
+ .that(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
+ .getContextForAudioAttribute(TEST_NAVIGATION_ATTRIBUTE));
+ }
+
+ @Test
+ public void getSuggestedAudioContext_withV1AndNavConfigAndNavInactive_returnsDefault() {
+ CarVolume carVolume = getCarVolumeWithVersionOnePriorities();
+ List<AudioAttributes> activePlaybackAttributes = ImmutableList.of(CarAudioContext
+ .getAudioAttributeFromUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE));
+
+ @AudioContext int suggestedContext = carVolume
+ .getSuggestedAudioContextAndSaveIfFound(activePlaybackAttributes,
+ CALL_STATE_IDLE, new ArrayList<>(), List.of(TEST_NAVIGATION_ATTRIBUTE));
+
+ assertWithMessage("Media context while navigation inactive and navigation configuration")
+ .that(suggestedContext).isEqualTo(TEST_CAR_AUDIO_CONTEXT
+ .getContextForAudioAttribute(TEST_MEDIA_ATTRIBUTE));
+ }
@Test
public void isAnyContextActive_withOneConfigurationAndMatchedContext_returnsTrue() {
@@ -630,4 +709,17 @@
() -> mCarVolume.isAnyContextActive(activeContexts,
Collections.EMPTY_LIST, CALL_STATE_OFFHOOK, null));
}
+
+ private @NonNull CarVolume getCarVolumeWithVersionOnePriorities() {
+ return new CarVolume(TEST_CAR_AUDIO_CONTEXT, mMockClock,
+ VERSION_ONE, KEY_EVENT_TIMEOUT_MS);
+ }
+
+ private static List<AudioAttributes> getAllAudioAttributes() {
+ List<AudioAttributes> audioAttributes = new ArrayList<>();
+ for (CarAudioContextInfo info : CarAudioContext.getAllContextsInfo()) {
+ Collections.addAll(audioAttributes, info.getAudioAttributes());
+ }
+ return audioAttributes;
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarZonesAudioFocusUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarZonesAudioFocusUnitTest.java
index 6acdcc7..7d225d1 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarZonesAudioFocusUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarZonesAudioFocusUnitTest.java
@@ -21,6 +21,7 @@
import static android.media.AudioAttributes.USAGE_MEDIA;
import static android.media.AudioManager.AUDIOFOCUS_GAIN;
import static android.media.AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
+import static android.media.AudioManager.AUDIOFOCUS_REQUEST_FAILED;
import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -30,6 +31,9 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.description;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -70,7 +74,7 @@
private final SparseArray<CarAudioZone> mMockZones = generateAudioZones();
@Mock
- private AudioManager mMockAudioManager;
+ private AudioManagerWrapper mMockAudioManager;
@Mock
private AudioPolicy mAudioPolicy;
@Mock
@@ -246,6 +250,49 @@
}
@Test
+ public void onAudioFocusRequest_withNullAudioService() {
+ AudioFocusInfo audioFocusInfo = generateAudioFocusRequest();
+ mCarZonesAudioFocus.setOwningPolicy(/* carAudioService= */ null, /* parentPolicy= */null);
+
+ mCarZonesAudioFocus.onAudioFocusRequest(audioFocusInfo, AUDIOFOCUS_REQUEST_GRANTED);
+
+ verify(mFocusMocks.get(PRIMARY_ZONE_ID), never())
+ .onAudioFocusRequest(audioFocusInfo, AUDIOFOCUS_REQUEST_GRANTED);
+ verify(mMockCarFocusCallback, never()).onFocusChange(any(), any());
+ }
+
+ @Test
+ public void onAudioFocusAbandon_withNullAudioService() {
+ AudioFocusInfo audioFocusInfo = generateAudioFocusRequest();
+ mCarZonesAudioFocus.onAudioFocusRequest(audioFocusInfo, AUDIOFOCUS_REQUEST_GRANTED);
+ mCarZonesAudioFocus.setOwningPolicy(/* carAudioService= */ null, /* parentPolicy= */null);
+ reset(mMockCarFocusCallback);
+
+ mCarZonesAudioFocus.onAudioFocusAbandon(audioFocusInfo);
+
+ verify(mMockCarFocusCallback, never()).onFocusChange(any(), any());
+ }
+
+ @Test
+ public void onAudioFocusAbandon() {
+ List<AudioFocusInfo> focusHolders = List.of(generateAudioFocusRequest());
+ when(mFocusMocks.get(PRIMARY_ZONE_ID).getAudioFocusHolders()).thenReturn(focusHolders);
+ AudioFocusInfo audioFocusInfo = generateAudioFocusRequest();
+ mCarZonesAudioFocus.onAudioFocusRequest(audioFocusInfo, AUDIOFOCUS_REQUEST_GRANTED);
+
+ mCarZonesAudioFocus.onAudioFocusAbandon(audioFocusInfo);
+
+ verify(mFocusMocks.get(PRIMARY_ZONE_ID)).onAudioFocusAbandon(audioFocusInfo);
+ ArgumentCaptor<SparseArray<List<AudioFocusInfo>>> captor =
+ ArgumentCaptor.forClass(SparseArray.class);
+ verify(mMockCarFocusCallback, times(2))
+ .onFocusChange(eq(new int[]{PRIMARY_ZONE_ID}), captor.capture());
+ SparseArray<List<AudioFocusInfo>> results = captor.getValue();
+ assertWithMessage("Focus holder after abandoned focus called")
+ .that(results.get(PRIMARY_ZONE_ID)).isEqualTo(focusHolders);
+ }
+
+ @Test
public void setRestrictFocus_withTrue_restrictsFocusForAllZones() {
mCarZonesAudioFocus.setRestrictFocus(true);
@@ -358,6 +405,39 @@
}
@Test
+ public void reevaluateAndRegainAudioFocusList_withCarAudioServiceUnavailable() {
+ AudioFocusInfo mediaFocusInfo = generateAudioFocusRequest();
+ int mediaFocusRequestResult = AUDIOFOCUS_REQUEST_GRANTED;
+ when(mCarAudioService.getZoneIdForAudioFocusInfo(mediaFocusInfo))
+ .thenReturn(PRIMARY_ZONE_ID);
+ when(mFocusMocks.get(PRIMARY_ZONE_ID).reevaluateAndRegainAudioFocus(any()))
+ .thenReturn(mediaFocusRequestResult);
+ mCarZonesAudioFocus.setOwningPolicy(/* carAudioService= */ null, mAudioPolicy);
+
+ List<Integer> resList = mCarZonesAudioFocus.reevaluateAndRegainAudioFocusList(
+ List.of(mediaFocusInfo));
+
+ assertWithMessage("Result list with car audio service unavailable")
+ .that(resList).isEmpty();
+ }
+
+ @Test
+ public void reevaluateAndRegainAudioFocus_withCarAudioServiceUnavailable() {
+ AudioFocusInfo mediaFocusInfo = generateAudioFocusRequest();
+ int mediaFocusRequestResult = AUDIOFOCUS_REQUEST_GRANTED;
+ when(mCarAudioService.getZoneIdForAudioFocusInfo(mediaFocusInfo))
+ .thenReturn(PRIMARY_ZONE_ID);
+ when(mFocusMocks.get(PRIMARY_ZONE_ID).reevaluateAndRegainAudioFocus(any()))
+ .thenReturn(mediaFocusRequestResult);
+ mCarZonesAudioFocus.setOwningPolicy(/* carAudioService= */ null, mAudioPolicy);
+
+ int result = mCarZonesAudioFocus.reevaluateAndRegainAudioFocus(mediaFocusInfo);
+
+ assertWithMessage("Result list with car audio service unavailable")
+ .that(result).isEqualTo(AUDIOFOCUS_REQUEST_FAILED);
+ }
+
+ @Test
public void transientlyLoseAudioFocusForZone_forActiveFocusHolders() {
AudioFocusInfo mediaFocusInfo = generateAudioFocusRequest();
AudioFocusInfo navigationFocusInfo =
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioHelperTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioHelperTest.java
index 6b329b7..941eb0c 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioHelperTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioHelperTest.java
@@ -48,7 +48,6 @@
import static org.junit.Assert.assertThrows;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
-import android.media.AudioManager;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
@@ -70,15 +69,15 @@
@Override
protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
- session.spyStatic(AudioManager.class);
+ session.spyStatic(AudioManagerWrapper.class);
}
@Before
public void setUp() throws Exception {
List<AudioVolumeGroup> groups = CoreAudioRoutingUtils.getVolumeGroups();
List<AudioProductStrategy> strategies = CoreAudioRoutingUtils.getProductStrategies();
- doReturn(strategies).when(AudioManager::getAudioProductStrategies);
- doReturn(groups).when(AudioManager::getAudioVolumeGroups);
+ doReturn(strategies).when(AudioManagerWrapper::getAudioProductStrategies);
+ doReturn(groups).when(AudioManagerWrapper::getAudioVolumeGroups);
}
@Test
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioRoutingUtils.java b/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioRoutingUtils.java
index 2e7c233..789ca04 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioRoutingUtils.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioRoutingUtils.java
@@ -16,6 +16,8 @@
package com.android.car.audio;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
import static com.google.common.collect.Sets.newHashSet;
import android.media.AudioAttributes;
@@ -24,9 +26,12 @@
import android.media.audiopolicy.AudioVolumeGroup;
import android.os.Parcel;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
import java.util.ArrayList;
import java.util.List;
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public final class CoreAudioRoutingUtils {
public static final int MUSIC_MIN_INDEX = 0;
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioVolumeGroupCallbackTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioVolumeGroupCallbackTest.java
index c22730c..286ea91 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioVolumeGroupCallbackTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioVolumeGroupCallbackTest.java
@@ -19,6 +19,7 @@
import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
@@ -28,27 +29,29 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.AbstractExpectableTestCase;
import android.content.Context;
import android.media.AudioManager;
import android.media.AudioManager.VolumeGroupCallback;
import androidx.test.core.app.ApplicationProvider;
-import com.google.common.truth.Expect;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
import java.util.concurrent.Executor;
@RunWith(MockitoJUnitRunner.class)
-public final class CoreAudioVolumeGroupCallbackTest extends AbstractExtendedMockitoTestCase {
+public final class CoreAudioVolumeGroupCallbackTest extends AbstractExpectableTestCase {
private static final String TAG = CoreAudioVolumeGroupCallbackTest.class.getSimpleName();
private static final int VALID_VOLUME_GROUP_ID = 77;
@@ -61,7 +64,7 @@
private Context mContext;
@Mock
- AudioManager mMockAudioManager;
+ AudioManagerWrapper mMockAudioManager;
@Mock
CarVolumeInfoWrapper mMockVolumeInfoWrapper;
@@ -69,37 +72,37 @@
ArgumentCaptor.forClass(VolumeGroupCallback.class);
private CoreAudioVolumeGroupCallback mCoreAudioVolumeGroupCallback;
-
- public CoreAudioVolumeGroupCallbackTest() {
- super(CoreAudioVolumeGroupCallback.TAG);
- }
-
- @Override
- protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
- session.spyStatic(CoreAudioHelper.class);
- }
-
- @Rule
- public final Expect expect = Expect.create();
+ private StaticMockitoSession mSession;
@Before
public void setUp() {
+ StaticMockitoSessionBuilder builder = mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(CoreAudioHelper.class);
+
+ mSession = builder.initMocks(this).startMocking();
+
mContext = ApplicationProvider.getApplicationContext();
mCoreAudioVolumeGroupCallback =
new CoreAudioVolumeGroupCallback(mMockVolumeInfoWrapper, mMockAudioManager);
}
+ @After
+ public void tearDown() {
+ mSession.finishMocking();
+ }
+
@Test
public void registerVolumeGroupCallbackToAudioManager_withNullExecutor_fails() {
String npeMessage = "executor must not be null";
- doThrow(new NullPointerException(npeMessage)).when(mMockAudioManager)
- .registerVolumeGroupCallback(eq(null), any());
+ doThrow(new NullPointerException(npeMessage))
+ .when(mMockAudioManager).registerVolumeGroupCallback(eq(null), any());
NullPointerException thrown = assertThrows(NullPointerException.class, () ->
mCoreAudioVolumeGroupCallback.init(/* executor= */ null));
- expect.withMessage("register VolumeGroupCallback with null executor")
+ expectWithMessage("register VolumeGroupCallback with null executor")
.that(thrown).hasMessageThat().contains(npeMessage);
}
@@ -126,7 +129,7 @@
NullPointerException thrown = assertThrows(NullPointerException.class, () ->
new CoreAudioVolumeGroupCallback(mMockVolumeInfoWrapper,
/* audioManager= */ null));
- expect.withMessage("Car AudioVolumeGroup Callback Construction")
+ expectWithMessage("Car AudioVolumeGroup Callback Construction")
.that(thrown).hasMessageThat().contains("AudioManager cannot be null");
}
@@ -135,7 +138,7 @@
NullPointerException thrown = assertThrows(NullPointerException.class, () ->
new CoreAudioVolumeGroupCallback(/* carVolumeInfoWrapper= */ null,
mMockAudioManager));
- expect.withMessage("Car AudioVolumeGroup Callback Construction")
+ expectWithMessage("Car AudioVolumeGroup Callback Construction")
.that(thrown).hasMessageThat().contains("CarVolumeInfoWrapper cannot be null");
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioVolumeGroupTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioVolumeGroupTest.java
index b83c29f..83ff897 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioVolumeGroupTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioVolumeGroupTest.java
@@ -55,7 +55,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -63,7 +62,6 @@
import static org.mockito.Mockito.when;
import android.car.Car;
-import android.car.builtin.media.AudioManagerHelper;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
@@ -82,9 +80,13 @@
public final class CoreAudioVolumeGroupTest extends AbstractExtendedMockitoTestCase {
private static final String TAG = CoreAudioVolumeGroupTest.class.getSimpleName();
+ private static final int TEST_EMPTY_FLAGS = 0;
private static final int ZONE_CONFIG_ID = 0;
- private static final int MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE = 10;
- private static final int MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE = 90;
+ private static final CarActivationVolumeConfig CAR_ACTIVATION_VOLUME_CONFIG =
+ new CarActivationVolumeConfig(CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT,
+ /* minActivationVolumePercentage= */ 10,
+ /* maxActivationVolumePercentage= */ 90);
+
private CarAudioContext mMusicContext;
private CarAudioContext mNavContext;
private CarAudioContext mOemContext;
@@ -93,7 +95,7 @@
private CarAudioDeviceInfo mOemInfoMock = mock(CarAudioDeviceInfo.class);
@Mock
- AudioManager mMockAudioManager;
+ AudioManagerWrapper mMockAudioManager;
@Mock
CarAudioSettings mSettingsMock;
@@ -109,7 +111,6 @@
@Override
protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
session.spyStatic(CoreAudioHelper.class)
- .spyStatic(AudioManagerHelper.class)
.spyStatic(Car.class);
}
void setupMock() {
@@ -176,7 +177,7 @@
mMusicCoreAudioVolumeGroup = new CoreAudioVolumeGroup(mMockAudioManager, mMusicContext,
mSettingsMock, musicContextToDeviceInfo, PRIMARY_AUDIO_ZONE, ZONE_CONFIG_ID,
MUSIC_CAR_GROUP_ID, MUSIC_GROUP_NAME, /* useCarVolumeGroupMute= */ false,
- MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE, MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE);
+ CAR_ACTIVATION_VOLUME_CONFIG);
SparseArray<CarAudioDeviceInfo> navContextToDeviceInfo = new SparseArray<>();
navContextToDeviceInfo.put(NAV_STRATEGY_ID, mNavInfoMock);
@@ -184,7 +185,7 @@
mNavCoreAudioVolumeGroup = new CoreAudioVolumeGroup(mMockAudioManager, mNavContext,
mSettingsMock, navContextToDeviceInfo, PRIMARY_AUDIO_ZONE, ZONE_CONFIG_ID,
NAV_CAR_GROUP_ID, NAV_GROUP_NAME, /* useCarVolumeGroupMute= */ false,
- MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE, MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE);
+ CAR_ACTIVATION_VOLUME_CONFIG);
SparseArray<CarAudioDeviceInfo> oemContextToDeviceInfo = new SparseArray<>();
oemContextToDeviceInfo.put(OEM_STRATEGY_ID, mOemInfoMock);
@@ -192,8 +193,7 @@
mOemCoreAudioVolumeGroup = new CoreAudioVolumeGroup(mMockAudioManager, mOemContext,
mSettingsMock, oemContextToDeviceInfo,
PRIMARY_AUDIO_ZONE, ZONE_CONFIG_ID, OEM_CAR_GROUP_ID, OEM_GROUP_NAME,
- /* useCarVolumeGroupMute= */ false, MAX_ACTIVATION_GAIN_INDEX_PERCENTAGE,
- MIN_ACTIVATION_GAIN_INDEX_PERCENTAGE);
+ /* useCarVolumeGroupMute= */ false, CAR_ACTIVATION_VOLUME_CONFIG);
}
@Test
@@ -260,8 +260,8 @@
mMusicCoreAudioVolumeGroup.setCurrentGainIndex(MUSIC_AM_INIT_INDEX);
mMusicCoreAudioVolumeGroup.setMute(true);
- verify(() -> AudioManagerHelper.adjustVolumeGroupVolume(any(),
- eq(MUSIC_GROUP_ID), eq(AudioManager.ADJUST_MUTE), anyInt()));
+ verify(mMockAudioManager).adjustVolumeGroupVolume(
+ eq(MUSIC_GROUP_ID), eq(AudioManager.ADJUST_MUTE), anyInt());
expectWithMessage("Car volume group mute state after group muted")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isTrue();
expectWithMessage("Index after group muted")
@@ -271,8 +271,8 @@
mMusicCoreAudioVolumeGroup.setMute(false);
- verify(() -> AudioManagerHelper.adjustVolumeGroupVolume(any(),
- eq(MUSIC_GROUP_ID), eq(AudioManager.ADJUST_UNMUTE), anyInt()));
+ verify(mMockAudioManager).adjustVolumeGroupVolume(
+ eq(MUSIC_GROUP_ID), eq(AudioManager.ADJUST_UNMUTE), anyInt());
expectWithMessage("Car volume group mute state after group unmuted")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isFalse();
expectWithMessage("Index after group unmuted")
@@ -291,8 +291,8 @@
mMusicCoreAudioVolumeGroup.setMute(true);
- verify(() -> AudioManagerHelper.adjustVolumeGroupVolume(any(),
- eq(MUSIC_GROUP_ID), eq(AudioManager.ADJUST_MUTE), anyInt()));
+ verify(mMockAudioManager).adjustVolumeGroupVolume(
+ eq(MUSIC_GROUP_ID), eq(AudioManager.ADJUST_MUTE), anyInt());
expectWithMessage("Car volume group mute state after group muted")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isTrue();
expectWithMessage("Index after group muted")
@@ -302,8 +302,8 @@
mMusicCoreAudioVolumeGroup.setMute(false);
- verify(() -> AudioManagerHelper.adjustVolumeGroupVolume(any(),
- eq(MUSIC_GROUP_ID), eq(AudioManager.ADJUST_UNMUTE), anyInt()));
+ verify(mMockAudioManager).adjustVolumeGroupVolume(
+ eq(MUSIC_GROUP_ID), eq(AudioManager.ADJUST_UNMUTE), anyInt());
expectWithMessage("Car volume group mute state after group unmuted")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isFalse();
expectWithMessage("Index after group unmuted")
@@ -311,6 +311,27 @@
}
@Test
+ public void setMute_whenNotMutable() {
+ when(mMockAudioManager.getMinVolumeIndexForAttributes(MUSIC_ATTRIBUTES))
+ .thenReturn(MUSIC_MIN_INDEX + 1);
+ SparseArray<CarAudioDeviceInfo> musicContextToDeviceInfo = new SparseArray<>();
+ musicContextToDeviceInfo.put(MUSIC_STRATEGY_ID, mOemInfoMock);
+ CoreAudioVolumeGroup unmutableMusicCoreAudioVolumeGroup = new CoreAudioVolumeGroup(
+ mMockAudioManager, mMusicContext, mSettingsMock, musicContextToDeviceInfo,
+ PRIMARY_AUDIO_ZONE, ZONE_CONFIG_ID, MUSIC_CAR_GROUP_ID, MUSIC_GROUP_NAME,
+ /* useCarVolumeGroupMute= */ false, CAR_ACTIVATION_VOLUME_CONFIG);
+
+ unmutableMusicCoreAudioVolumeGroup.setCurrentGainIndex(MUSIC_MAX_INDEX);
+
+ expectWithMessage("Mute status changed").that(unmutableMusicCoreAudioVolumeGroup
+ .setMute(true)).isTrue();
+ verify(mMockAudioManager, never()).setVolumeGroupVolumeIndex(
+ eq(MUSIC_GROUP_ID), eq(MUSIC_MIN_INDEX), anyInt());
+ expectWithMessage("Car volume group mute state when not mutable")
+ .that(unmutableMusicCoreAudioVolumeGroup.isMuted()).isTrue();
+ }
+
+ @Test
public void audioManagerIndexSynchronization() {
int index = (MUSIC_MAX_INDEX + MUSIC_MIN_INDEX) / 2;
mMusicCoreAudioVolumeGroup.setCurrentGainIndex(index);
@@ -320,7 +341,7 @@
int amIndex = MUSIC_AM_INIT_INDEX + 2;
when(mMockAudioManager.getVolumeIndexForAttributes(MUSIC_ATTRIBUTES)).thenReturn(amIndex);
- int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
expectWithMessage("Reported event flags after am callback")
.that(flags).isEqualTo(EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED);
@@ -329,7 +350,7 @@
.isEqualTo(amIndex);
// Double sync is a no-op
- flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
expectWithMessage("Reported event flags after double am callback").that(flags).isEqualTo(0);
expectWithMessage("Index after double am callback")
@@ -345,7 +366,7 @@
.thenReturn(MUSIC_AM_INIT_INDEX);
when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(true);
// Mute event reported by AudioManager
- int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
expectWithMessage("Car volume group mute state after am group muted")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isTrue();
@@ -356,7 +377,7 @@
.thenReturn(MUSIC_AM_INIT_INDEX);
when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(false);
// Unmute event from AM reported
- flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
expectWithMessage("Car volume group mute state after am group unmuted")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isFalse();
@@ -374,7 +395,7 @@
.thenReturn(MUSIC_AM_INIT_INDEX);
when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(true);
// Mute event reported by AudioManager
- int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
expectWithMessage("Car volume group mute state after am group muted")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isTrue();
@@ -388,7 +409,7 @@
.thenReturn(amIndex);
when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(false);
- flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
expectWithMessage("Car volume group mute state after am group unmuted")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isFalse();
@@ -408,7 +429,7 @@
.thenReturn(MUSIC_MIN_INDEX);
when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(true);
// Mute event at volume zero reported by AudioManager
- int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
expectWithMessage("Car volume group mute state after am group muted by volume zero")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isFalse();
@@ -419,7 +440,7 @@
.thenReturn(MUSIC_MIN_INDEX);
when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(false);
// Unmute event reported by AudioManager
- flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
expectWithMessage("Car volume group mute state after am group unmuted by volume zero")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isFalse();
@@ -438,7 +459,7 @@
.thenReturn(MUSIC_AM_INIT_INDEX);
when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(true);
// Mute event reported by AudioManager
- mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
when(mMockAudioManager.getVolumeIndexForAttributes(MUSIC_ATTRIBUTES))
.thenReturn(MUSIC_MIN_INDEX);
when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(false);
@@ -446,7 +467,7 @@
.thenReturn(MUSIC_MIN_INDEX);
// Unmute (at volume zero) event reported by AudioManager
- int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
expectWithMessage("Car volume group muted state after am unmuted")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isFalse();
@@ -467,7 +488,7 @@
.thenReturn(MUSIC_MIN_INDEX);
when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(true);
// Mute event at volume zero reported by AudioManager
- int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
// Mute by volume 0 is not seen as muted from CarAudioManager api
expectWithMessage("Car volume group muted state after am muted by zero")
@@ -481,7 +502,7 @@
when(mMockAudioManager.getLastAudibleVolumeForVolumeGroup(MUSIC_GROUP_ID))
.thenReturn(MUSIC_AM_INIT_INDEX + 1);
// Unmute event (with non zero index) reported by AudioManager
- flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
expectWithMessage("Car volume group muted state after am unmuted")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isFalse();
@@ -501,12 +522,12 @@
.thenReturn(MUSIC_MIN_INDEX + 1);
when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(true);
// Mute is reported by AudioManager
- mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
when(mMockAudioManager.getLastAudibleVolumeForVolumeGroup(MUSIC_GROUP_ID))
.thenReturn(MUSIC_MIN_INDEX);
// Muted by volume 0 is now reported by AudioManager
- int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
expectWithMessage("Car volume group mute state after am muted by 0 while already muted")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isTrue();
@@ -519,7 +540,7 @@
when(mMockAudioManager.getLastAudibleVolumeForVolumeGroup(MUSIC_GROUP_ID))
.thenReturn(MUSIC_AM_INIT_INDEX + 1);
// Unmute event (with non zero index) reported by AudioManager
- flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(/* flags= */ 0);
+ flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
expectWithMessage("Car volume group mute state after am unmuted ")
.that(mMusicCoreAudioVolumeGroup.isMuted()).isFalse();
@@ -532,6 +553,76 @@
}
@Test
+ public void onAudioVolumeGroupChanged_withUnmuteWhenBlocked() {
+ mMusicCoreAudioVolumeGroup.setCurrentGainIndex(MUSIC_AM_INIT_INDEX);
+ when(mMockAudioManager.getVolumeIndexForAttributes(MUSIC_ATTRIBUTES))
+ .thenReturn(MUSIC_MIN_INDEX);
+ when(mMockAudioManager.getLastAudibleVolumeForVolumeGroup(MUSIC_GROUP_ID))
+ .thenReturn(MUSIC_AM_INIT_INDEX);
+ when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(true);
+ mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
+ mMusicCoreAudioVolumeGroup.setBlocked(MUSIC_MIN_INDEX + 1);
+ when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(false);
+
+ int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
+
+ expectWithMessage("Car volume group muted state after am unmuted when blocked")
+ .that(mMusicCoreAudioVolumeGroup.isMuted()).isTrue();
+ expectWithMessage("Event flags reported after am muted when blocked")
+ .that(flags).isEqualTo(0);
+ expectWithMessage("Index after am muted when blocked")
+ .that(mMusicCoreAudioVolumeGroup.getCurrentGainIndex())
+ .isEqualTo(MUSIC_MIN_INDEX);
+ }
+
+ @Test
+ public void onAudioVolumeGroupChanged_withIndexChangeWhenBlocked() {
+ int amIndex = MUSIC_AM_INIT_INDEX;
+ int blockIndex = MUSIC_MIN_INDEX + 1;
+ mMusicCoreAudioVolumeGroup.setCurrentGainIndex(amIndex);
+ when(mMockAudioManager.getVolumeIndexForAttributes(MUSIC_ATTRIBUTES))
+ .thenReturn(MUSIC_MIN_INDEX);
+ when(mMockAudioManager.getLastAudibleVolumeForVolumeGroup(MUSIC_GROUP_ID))
+ .thenReturn(MUSIC_AM_INIT_INDEX);
+ when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(true);
+ mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
+ mMusicCoreAudioVolumeGroup.setBlocked(blockIndex);
+ amIndex += 1;
+ when(mMockAudioManager.getVolumeIndexForAttributes(MUSIC_ATTRIBUTES)).thenReturn(amIndex);
+ when(mMockAudioManager.getLastAudibleVolumeForVolumeGroup(MUSIC_GROUP_ID))
+ .thenReturn(amIndex);
+
+ int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
+
+ expectWithMessage("Car volume group muted state after am index change when blocked")
+ .that(mMusicCoreAudioVolumeGroup.isMuted()).isTrue();
+ expectWithMessage("Event flags reported after am index change when blocked")
+ .that(flags).isEqualTo(0);
+ expectWithMessage("Index after am index change when blocked")
+ .that(mMusicCoreAudioVolumeGroup.getCurrentGainIndex()).isEqualTo(MUSIC_MIN_INDEX);
+ }
+
+ @Test
+ public void onAudioVolumeGroupChanged_withoutChange() {
+ mMusicCoreAudioVolumeGroup.setCurrentGainIndex(MUSIC_AM_INIT_INDEX);
+ when(mMockAudioManager.getVolumeIndexForAttributes(MUSIC_ATTRIBUTES))
+ .thenReturn(MUSIC_MIN_INDEX);
+ when(mMockAudioManager.getLastAudibleVolumeForVolumeGroup(MUSIC_GROUP_ID))
+ .thenReturn(MUSIC_AM_INIT_INDEX);
+ when(mMockAudioManager.isVolumeGroupMuted(MUSIC_GROUP_ID)).thenReturn(true);
+ mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
+
+ int flags = mMusicCoreAudioVolumeGroup.onAudioVolumeGroupChanged(TEST_EMPTY_FLAGS);
+
+ expectWithMessage("Car volume group muted state without am change")
+ .that(mMusicCoreAudioVolumeGroup.isMuted()).isTrue();
+ expectWithMessage("Event flags reported without am change")
+ .that(flags).isEqualTo(0);
+ expectWithMessage("Index without am change")
+ .that(mMusicCoreAudioVolumeGroup.getCurrentGainIndex()).isEqualTo(MUSIC_MIN_INDEX);
+ }
+
+ @Test
public void updateDevices_withCoreAudioRoutingDisabled() {
boolean useCoreAudioRouting = false;
@@ -550,4 +641,11 @@
verify(mMockAudioManager, never()).setPreferredDeviceForStrategy(MUSIC_STRATEGY,
mMusicDeviceAttributes);
}
+
+ @Test
+ public void calculateNewGainStageFromDeviceInfos() {
+ expectWithMessage("Gain stage from device infos")
+ .that(mMusicCoreAudioVolumeGroup.calculateNewGainStageFromDeviceInfos())
+ .isEqualTo(0);
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/HalAudioDeviceInfoBuilder.java b/tests/carservice_unit_test/src/com/android/car/audio/HalAudioDeviceInfoBuilder.java
index 430310a..8862da7 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/HalAudioDeviceInfoBuilder.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/HalAudioDeviceInfoBuilder.java
@@ -24,6 +24,7 @@
import static com.android.car.audio.GainBuilder.MAX_GAIN;
import static com.android.car.audio.GainBuilder.MIN_GAIN;
import static com.android.car.audio.GainBuilder.STEP_SIZE;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
import android.media.audio.common.AudioDevice;
import android.media.audio.common.AudioDeviceAddress;
@@ -33,7 +34,9 @@
import android.media.audio.common.AudioPortExt;
import com.android.car.audio.hal.HalAudioDeviceInfo;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public final class HalAudioDeviceInfoBuilder {
private String mAddress;
private String mName = " ";
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/TestCarAudioDeviceInfoBuilder.java b/tests/carservice_unit_test/src/com/android/car/audio/TestCarAudioDeviceInfoBuilder.java
index b16e38c..0a997a6 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/TestCarAudioDeviceInfoBuilder.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/TestCarAudioDeviceInfoBuilder.java
@@ -18,12 +18,17 @@
import static android.media.AudioDeviceInfo.TYPE_BUS;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
import static org.mockito.Mockito.when;
import android.media.AudioDeviceAttributes;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
import org.mockito.Mockito;
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public final class TestCarAudioDeviceInfoBuilder {
public static final int STEP_VALUE = 2;
public static final int MIN_GAIN = 3;
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/TestCarAudioZoneBuilder.java b/tests/carservice_unit_test/src/com/android/car/audio/TestCarAudioZoneBuilder.java
index 297ca9e..6fdfecf 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/TestCarAudioZoneBuilder.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/TestCarAudioZoneBuilder.java
@@ -16,6 +16,7 @@
package com.android.car.audio;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEBUGGING_CODE;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
@@ -23,6 +24,7 @@
import java.util.ArrayList;
import java.util.List;
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public final class TestCarAudioZoneBuilder {
private final int mAudioZoneId;
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/TestCarVolumeEventCallback.java b/tests/carservice_unit_test/src/com/android/car/audio/TestCarVolumeEventCallback.java
new file mode 100644
index 0000000..91eed23
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/TestCarVolumeEventCallback.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.audio;
+
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+
+import android.car.media.CarVolumeGroupEvent;
+import android.car.media.ICarVolumeEventCallback;
+
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
+final class TestCarVolumeEventCallback extends ICarVolumeEventCallback.Stub {
+ private final long mTimeOutMs;
+ private CountDownLatch mStatusLatch = new CountDownLatch(1);
+ private List<CarVolumeGroupEvent> mVolumeGroupEvents;
+
+ TestCarVolumeEventCallback(long timeOutMs) {
+ mTimeOutMs = timeOutMs;
+ }
+
+ @Override
+ public void onVolumeGroupEvent(List<CarVolumeGroupEvent> volumeGroupEvents) {
+ mVolumeGroupEvents = volumeGroupEvents;
+ mStatusLatch.countDown();
+ }
+
+ @Override
+ public void onMasterMuteChanged(int zoneId, int flags) {
+ mStatusLatch.countDown();
+ }
+
+ boolean waitForCallback() throws Exception {
+ return mStatusLatch.await(mTimeOutMs, TimeUnit.MILLISECONDS);
+ }
+
+ List<CarVolumeGroupEvent> getVolumeGroupEvents() {
+ return mVolumeGroupEvents;
+ }
+
+ public void reset() {
+ mVolumeGroupEvents = null;
+ mStatusLatch = new CountDownLatch(1);
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/VolumeGroupBuilder.java b/tests/carservice_unit_test/src/com/android/car/audio/VolumeGroupBuilder.java
index 08ef34c..3f5550b 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/VolumeGroupBuilder.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/VolumeGroupBuilder.java
@@ -21,6 +21,7 @@
import static com.android.car.audio.GainBuilder.DEFAULT_GAIN;
import static com.android.car.audio.GainBuilder.MAX_GAIN;
import static com.android.car.audio.GainBuilder.STEP_SIZE;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
@@ -33,6 +34,8 @@
import android.util.ArrayMap;
import android.util.SparseArray;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
@@ -42,6 +45,7 @@
/**
* Class to build mock volume group
*/
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
public final class VolumeGroupBuilder {
private static final int TEST_MIN_VOLUME = 0;
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/ZoneAudioPlaybackCallbackTest.java b/tests/carservice_unit_test/src/com/android/car/audio/ZoneAudioPlaybackCallbackTest.java
index 9eb1d2c..e7e23e3 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/ZoneAudioPlaybackCallbackTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/ZoneAudioPlaybackCallbackTest.java
@@ -35,6 +35,7 @@
import android.media.AudioAttributes;
import android.media.AudioPlaybackConfiguration;
+import android.util.Pair;
import com.google.common.collect.ImmutableList;
@@ -66,6 +67,9 @@
private static final long TIMER_AFTER_TIMEOUT_MS =
TIMER_START_TIME_MS + KEY_EVENT_TIMEOUT_MS + 1;
+ private static final int PLAYBACK_UID_1 = 10101;
+ private static final int PLAYBACK_UID_2 = 10102;
+
private static final AudioAttributes TEST_MEDIA_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build();
private static final AudioAttributes TEST_NAVIGATION_AUDIO_ATTRIBUTE =
@@ -93,7 +97,7 @@
@Mock
private SystemClockWrapper mClock;
@Captor
- private ArgumentCaptor<List<AudioAttributes>> mAudioAttributesCaptor;
+ private ArgumentCaptor<List<Pair<AudioAttributes, Integer>>> mAudioAttributesCaptor;
private CarAudioZone mPrimaryZone;
@@ -519,10 +523,12 @@
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_MEDIA)
.setDeviceAddress(PRIMARY_MEDIA_ADDRESS)
+ .setClientUid(PLAYBACK_UID_1)
.build(),
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.setDeviceAddress(PRIMARY_NAVIGATION_ADDRESS)
+ .setClientUid(PLAYBACK_UID_2)
.build()
);
ZoneAudioPlaybackCallback callback = new ZoneAudioPlaybackCallback(mPrimaryZone,
@@ -530,8 +536,8 @@
callback.onPlaybackConfigChanged(activeConfigurations);
- verifyActivationPlaybacks(List.of(TEST_MEDIA_AUDIO_ATTRIBUTE,
- TEST_NAVIGATION_AUDIO_ATTRIBUTE));
+ verifyActivationPlaybacks(List.of(new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_1),
+ new Pair<>(TEST_NAVIGATION_AUDIO_ATTRIBUTE, PLAYBACK_UID_2)));
}
@Test
@@ -541,11 +547,13 @@
.setUsage(USAGE_MEDIA)
.setDeviceAddress(PRIMARY_MEDIA_ADDRESS)
.setInactive()
+ .setClientUid(PLAYBACK_UID_1)
.build(),
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.setDeviceAddress(PRIMARY_NAVIGATION_ADDRESS)
.setInactive()
+ .setClientUid(PLAYBACK_UID_2)
.build()
);
@@ -559,15 +567,35 @@
}
@Test
+ public void onPlaybackConfigChanged_withDeviceAddressNotFound() {
+ List<AudioPlaybackConfiguration> activeConfigurations = ImmutableList.of(
+ new AudioPlaybackConfigurationBuilder()
+ .setUsage(USAGE_MEDIA)
+ .setDeviceAddress("music_bus101")
+ .setClientUid(PLAYBACK_UID_1)
+ .build()
+ );
+ ZoneAudioPlaybackCallback callback = new ZoneAudioPlaybackCallback(mPrimaryZone,
+ mCarAudioPlaybackMonitor, mClock, KEY_EVENT_TIMEOUT_MS);
+
+ callback.onPlaybackConfigChanged(activeConfigurations);
+
+ verify(mCarAudioPlaybackMonitor, never()).onActiveAudioPlaybackAttributesAdded(any(),
+ anyInt());
+ }
+
+ @Test
public void onPlaybackConfigChanged_withUpdatedPlaybacks() {
List<AudioPlaybackConfiguration> activeConfigurations = ImmutableList.of(
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_MEDIA)
.setDeviceAddress(PRIMARY_MEDIA_ADDRESS)
+ .setClientUid(PLAYBACK_UID_1)
.build(),
new AudioPlaybackConfigurationBuilder()
.setUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.setDeviceAddress(PRIMARY_NAVIGATION_ADDRESS)
+ .setClientUid(PLAYBACK_UID_2)
.build()
);
List<AudioPlaybackConfiguration> configurationsChanged = ImmutableList.of(
@@ -587,8 +615,8 @@
callback.onPlaybackConfigChanged(activeConfigurations);
callback.onPlaybackConfigChanged(configurationsChanged);
- verifyActivationPlaybacks(List.of(TEST_MEDIA_AUDIO_ATTRIBUTE,
- TEST_NAVIGATION_AUDIO_ATTRIBUTE));
+ verifyActivationPlaybacks(List.of(new Pair<>(TEST_MEDIA_AUDIO_ATTRIBUTE, PLAYBACK_UID_1),
+ new Pair<>(TEST_NAVIGATION_AUDIO_ATTRIBUTE, PLAYBACK_UID_2)));
}
private CarAudioZone generatePrimaryZone() {
@@ -614,10 +642,11 @@
}
- private void verifyActivationPlaybacks(List<AudioAttributes> newlyActiveAudioAttributes) {
+ private void verifyActivationPlaybacks(List<Pair<AudioAttributes, Integer>>
+ newlyActiveAudioAttributes) {
verify(mCarAudioPlaybackMonitor).onActiveAudioPlaybackAttributesAdded(
mAudioAttributesCaptor.capture(), eq(PRIMARY_ZONE_ID));
- assertWithMessage("Audio attributes for newly active playbacks")
+ assertWithMessage("Audio attributes with uid for newly active playbacks")
.that(mAudioAttributesCaptor.getValue())
.containsExactlyElementsIn(newlyActiveAudioAttributes);
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperAidlTest.java b/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperAidlTest.java
index 35d7bc4..66e6639 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperAidlTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperAidlTest.java
@@ -18,6 +18,9 @@
import static android.media.AudioAttributes.USAGE_MEDIA;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
+import static android.media.audio.common.AudioDeviceDescription.CONNECTION_BUS;
+import static android.media.audio.common.AudioDeviceType.OUT_DEVICE;
+import static android.media.audio.common.AudioGainMode.JOINT;
import static android.os.IBinder.DeathRecipient;
import static com.android.car.audio.CarHalAudioUtils.usageToMetadata;
@@ -26,6 +29,7 @@
import static com.android.car.audio.hal.AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_FOCUS_WITH_METADATA;
import static com.android.car.audio.hal.AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_GAIN_CALLBACK;
import static com.android.car.audio.hal.AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_GROUP_MUTING;
+import static com.android.car.audio.hal.AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_MODULE_CALLBACK;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.google.common.truth.Truth.assertThat;
@@ -36,8 +40,11 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.after;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -50,10 +57,14 @@
import android.hardware.automotive.audiocontrol.IAudioControl;
import android.hardware.automotive.audiocontrol.IAudioGainCallback;
import android.hardware.automotive.audiocontrol.IFocusListener;
+import android.hardware.automotive.audiocontrol.IModuleChangeCallback;
import android.hardware.automotive.audiocontrol.MutingInfo;
import android.hardware.automotive.audiocontrol.Reasons;
import android.media.AudioAttributes;
import android.media.AudioManager;
+import android.media.audio.common.AudioGain;
+import android.media.audio.common.AudioPort;
+import android.media.audio.common.AudioPortDeviceExt;
import android.os.IBinder;
import android.os.RemoteException;
@@ -82,6 +93,7 @@
@RunWith(AndroidJUnit4.class)
public final class AudioControlWrapperAidlTest extends AbstractExtendedMockitoTestCase {
+ private static final long TEST_CALLBACK_TIMEOUT_MS = 100;
private static final float FADE_VALUE = 5;
private static final float BALANCE_VALUE = 6;
private static final int USAGE = USAGE_MEDIA;
@@ -100,7 +112,25 @@
private static final String SECONDARY_NOTIFICATION_ADDRESS = "secondary notification";
private static final PlaybackTrackMetadata METADATA = usageToMetadata(USAGE);
+ private static final int PORT_ID_MEDIA = 0;
+ private static final String PORT_NAME_MEDIA = "Media Port";
+ private static final String ADDRESS_BUS_MEDIA = "BUS100_MEDIA";
+ private static final int PORT_ID_NAV = 1;
+ private static final String PORT_NAME_NAV = "Nav Port";
+ private static final String ADDRESS_BUS_NAV = "BUS101_NAV";
+ private static final AudioGain[] GAINS = new AudioGain[] {
+ new AudioGain() {{
+ mode = JOINT;
+ minValue = 0;
+ maxValue = 100;
+ defaultValue = 50;
+ stepValue = 2;
+ }}
+ };
+
private static final int AIDL_AUDIO_CONTROL_VERSION_1 = 1;
+ private static final int AIDL_AUDIO_CONTROL_VERSION_2 = 2;
+ private static final int AIDL_AUDIO_CONTROL_VERSION_3 = 3;
private static final CarAudioContext TEST_CAR_AUDIO_CONTEXT =
new CarAudioContext(CarAudioContext.getAllContextsInfo(),
/* useCoreAudioRouting= */ false);
@@ -126,6 +156,8 @@
@Mock
private AudioControlDeathRecipient mDeathRecipient;
+ @Mock
+ HalAudioModuleChangeCallback mHalAudioModuleChangeCallback;
private AudioControlWrapperAidl mAudioControlWrapperAidl;
private MutingInfo mPrimaryZoneMutingInfo;
@@ -141,7 +173,8 @@
}
@Before
- public void setUp() {
+ public void setUp() throws RemoteException {
+ when(mAudioControl.getInterfaceVersion()).thenReturn(AIDL_AUDIO_CONTROL_VERSION_3);
when(mBinder.queryLocalInterface(anyString())).thenReturn(mAudioControl);
doReturn(mBinder).when(AudioControlWrapperAidl::getService);
mAudioControlWrapperAidl = new AudioControlWrapperAidl(mBinder);
@@ -194,6 +227,25 @@
}
@Test
+ public void supportsFeature_forGainCallbackEithRemoteException_returnsTrue() throws Exception {
+ doThrow(new RemoteException()).when(mAudioControl).getInterfaceVersion();
+
+ assertWithMessage("Gain callback support with failure for getting version")
+ .that(mAudioControlWrapperAidl.supportsFeature(
+ AUDIOCONTROL_FEATURE_AUDIO_GAIN_CALLBACK)).isFalse();
+ }
+
+ @Test
+ public void supportsFeature_forModuleCallbackEithRemoteException_returnsTrue()
+ throws Exception {
+ doThrow(new RemoteException()).when(mAudioControl).getInterfaceVersion();
+
+ assertWithMessage("Module callback support with failure for getting version")
+ .that(mAudioControlWrapperAidl.supportsFeature(
+ AUDIOCONTROL_FEATURE_AUDIO_MODULE_CALLBACK)).isFalse();
+ }
+
+ @Test
public void registerFocusListener_succeeds() throws Exception {
HalFocusListener mockListener = mock(HalFocusListener.class);
mAudioControlWrapperAidl.registerFocusListener(mockListener);
@@ -693,6 +745,15 @@
}
@Test
+ public void registerAudioGainCallback_withLowerVersion() throws Exception {
+ when(mAudioControl.getInterfaceVersion()).thenReturn(AIDL_AUDIO_CONTROL_VERSION_1);
+
+ mAudioControlWrapperAidl.registerAudioGainCallback(mHalAudioGainCallback);
+
+ verify(mAudioControl, never()).registerGainCallback(any());
+ }
+
+ @Test
public void onAudioDeviceGainsChanged_succeeds() throws Exception {
ArgumentCaptor<IAudioGainCallback.Stub> captor =
ArgumentCaptor.forClass(IAudioGainCallback.Stub.class);
@@ -799,6 +860,77 @@
assertThat(captorGains.getValue()).containsExactlyElementsIn(carGains);
}
+ @Test
+ public void setModuleChangeCallback() throws Exception {
+ mAudioControlWrapperAidl.setModuleChangeCallback(mHalAudioModuleChangeCallback);
+
+ verify(mAudioControl, timeout(TEST_CALLBACK_TIMEOUT_MS)).setModuleChangeCallback(any());
+ }
+
+ @Test
+ public void setModuleChangeCallback_withLowerVersion() throws Exception {
+ when(mAudioControl.getInterfaceVersion()).thenReturn(AIDL_AUDIO_CONTROL_VERSION_2);
+
+ mAudioControlWrapperAidl.setModuleChangeCallback(mHalAudioModuleChangeCallback);
+
+ verify(mAudioControl, after(TEST_CALLBACK_TIMEOUT_MS).never())
+ .setModuleChangeCallback(any());
+ }
+
+ @Test
+ public void setModuleChangeCallback_withIllegalStateException_retries() throws Exception {
+ doThrow(new IllegalStateException()).when(mAudioControl).setModuleChangeCallback(any());
+
+ mAudioControlWrapperAidl.setModuleChangeCallback(mHalAudioModuleChangeCallback);
+
+ verify(mAudioControl, timeout(TEST_CALLBACK_TIMEOUT_MS)).clearModuleChangeCallback();
+ }
+
+ @Test
+ public void onAudioPortsChanged() throws Exception {
+ AudioPortDeviceExt mediaDeviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(
+ OUT_DEVICE, CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort mediaAudioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID_MEDIA,
+ PORT_NAME_MEDIA, GAINS, mediaDeviceExt);
+ AudioPortDeviceExt navDeviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(
+ OUT_DEVICE, CONNECTION_BUS, ADDRESS_BUS_NAV);
+ AudioPort navAudioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID_NAV,
+ PORT_NAME_NAV, GAINS, navDeviceExt);
+ HalAudioDeviceInfo mediaDeviceInfo = new HalAudioDeviceInfo(mediaAudioPort);
+ HalAudioDeviceInfo navDeviceInfo = new HalAudioDeviceInfo(navAudioPort);
+ mAudioControlWrapperAidl.setModuleChangeCallback(mHalAudioModuleChangeCallback);
+ ArgumentCaptor<IModuleChangeCallback> callbackCaptor =
+ ArgumentCaptor.forClass(IModuleChangeCallback.class);
+ verify(mAudioControl, timeout(TEST_CALLBACK_TIMEOUT_MS)).setModuleChangeCallback(
+ callbackCaptor.capture());
+ IModuleChangeCallback moduleChangeCallback = callbackCaptor.getValue();
+
+ moduleChangeCallback.onAudioPortsChanged(new AudioPort[]{mediaAudioPort, navAudioPort});
+
+ ArgumentCaptor<List<HalAudioDeviceInfo>> audioDeviceInfoCaptor = ArgumentCaptor.forClass(
+ List.class);
+ verify(mHalAudioModuleChangeCallback).onAudioPortsChanged(audioDeviceInfoCaptor.capture());
+ assertWithMessage("Hal audio device info changed").that(audioDeviceInfoCaptor.getValue())
+ .containsExactly(mediaDeviceInfo, navDeviceInfo);
+ }
+
+ @Test
+ public void clearModuleChangeCallback() throws Exception {
+ mAudioControlWrapperAidl.clearModuleChangeCallback();
+
+ verify(mAudioControl, timeout(TEST_CALLBACK_TIMEOUT_MS)).clearModuleChangeCallback();
+ }
+
+ @Test
+ public void clearModuleChangeCallback_withLowerVersion() throws Exception {
+ when(mAudioControl.getInterfaceVersion()).thenReturn(AIDL_AUDIO_CONTROL_VERSION_2);
+
+ mAudioControlWrapperAidl.clearModuleChangeCallback();
+
+ verify(mAudioControl, after(TEST_CALLBACK_TIMEOUT_MS).never())
+ .clearModuleChangeCallback();
+ }
+
private static CarAudioZone generateAudioZoneMock() {
CarAudioZone mockZone = mock(CarAudioZone.class);
when(mockZone.getAddressForContext(TEST_MEDIA_CONTEXT_ID))
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperV1Test.java b/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperV1Test.java
index 0a5c74c..c73f9a9 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperV1Test.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperV1Test.java
@@ -190,6 +190,25 @@
}
@Test
+ public void setModuleChangeCallback_throws() {
+ UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+ () -> mAudioControlWrapperV1.setModuleChangeCallback(mock(
+ HalAudioModuleChangeCallback.class)));
+
+ assertWithMessage("UnsupportedOperationException thrown by setModuleChangeCallback")
+ .that(thrown).hasMessageThat().contains("unsupported for IAudioControl@1.0");
+ }
+
+ @Test
+ public void clearModuleChangeCallback_throws() {
+ UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+ () -> mAudioControlWrapperV1.clearModuleChangeCallback());
+
+ assertWithMessage("UnsupportedOperationException thrown by clearModuleChangeCallback")
+ .that(thrown).hasMessageThat().contains("unsupported for IAudioControl@1.0");
+ }
+
+ @Test
public void linkToDeath_succeeds() throws Exception {
AudioControlWrapper.AudioControlDeathRecipient deathRecipient =
mock(AudioControlWrapper.AudioControlDeathRecipient.class);
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperV2Test.java b/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperV2Test.java
index 9c6ec1f..271056b 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperV2Test.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperV2Test.java
@@ -234,6 +234,27 @@
}
@Test
+ public void setModuleChangeCallback_throws() {
+ UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+ () -> mAudioControlWrapperV2.setModuleChangeCallback(mock(
+ HalAudioModuleChangeCallback.class)));
+
+ assertWithMessage("UnsupportedOperationException thrown by setModuleChangeCallback")
+ .that(thrown).hasMessageThat()
+ .contains("Module change callback is unsupported for IAudioControl@2.0");
+ }
+
+ @Test
+ public void clearModuleChangeCallback_throws() {
+ UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+ () -> mAudioControlWrapperV2.clearModuleChangeCallback());
+
+ assertWithMessage("UnsupportedOperationException thrown by clearModuleChangeCallback")
+ .that(thrown).hasMessageThat()
+ .contains("Module change callback is unsupported for IAudioControl@2.0");
+ }
+
+ @Test
public void linkToDeath_succeeds() throws Exception {
AudioControlWrapper.AudioControlDeathRecipient deathRecipient =
mock(AudioControlWrapper.AudioControlDeathRecipient.class);
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/hal/CarAudioHalTestUtils.java b/tests/carservice_unit_test/src/com/android/car/audio/hal/CarAudioHalTestUtils.java
new file mode 100644
index 0000000..bfbfe5f
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/hal/CarAudioHalTestUtils.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.audio.hal;
+
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR;
+
+import android.media.audio.common.AudioDevice;
+import android.media.audio.common.AudioDeviceAddress;
+import android.media.audio.common.AudioDeviceDescription;
+import android.media.audio.common.AudioGain;
+import android.media.audio.common.AudioPort;
+import android.media.audio.common.AudioPortDeviceExt;
+import android.media.audio.common.AudioPortExt;
+
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
+@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
+public final class CarAudioHalTestUtils {
+
+ @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR)
+ private CarAudioHalTestUtils() {
+ throw new UnsupportedOperationException();
+ }
+
+ static AudioPortDeviceExt createAudioPortDeviceExt(int type, String connection,
+ String address) {
+ AudioPortDeviceExt deviceExt = new AudioPortDeviceExt();
+ deviceExt.device = new AudioDevice();
+ deviceExt.device.type = new AudioDeviceDescription();
+ deviceExt.device.type.type = type;
+ deviceExt.device.type.connection = connection;
+ deviceExt.device.address = AudioDeviceAddress.id(address);
+ return deviceExt;
+ }
+
+ static AudioPort createAudioPort(int id, String name, AudioGain[] gains,
+ AudioPortDeviceExt deviceExt) {
+ AudioPort audioPort = new AudioPort();
+ audioPort.id = id;
+ audioPort.name = name;
+ audioPort.gains = gains;
+ audioPort.ext = AudioPortExt.device(deviceExt);
+ return audioPort;
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/hal/HalAudioDeviceInfoTest.java b/tests/carservice_unit_test/src/com/android/car/audio/hal/HalAudioDeviceInfoTest.java
index 48a1bb9..d845c1a 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/hal/HalAudioDeviceInfoTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/hal/HalAudioDeviceInfoTest.java
@@ -28,9 +28,6 @@
import static org.junit.Assert.assertThrows;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
-import android.media.audio.common.AudioDevice;
-import android.media.audio.common.AudioDeviceAddress;
-import android.media.audio.common.AudioDeviceDescription;
import android.media.audio.common.AudioGain;
import android.media.audio.common.AudioPort;
import android.media.audio.common.AudioPortDeviceExt;
@@ -69,9 +66,10 @@
@Test
public void constructor_succeeds() {
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExt);
HalAudioDeviceInfo deviceInfo = new HalAudioDeviceInfo(audioPort);
@@ -121,9 +119,10 @@
@Test
public void constructor_requiresNonNullAudioGains() {
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, null, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, null,
+ deviceExt);
Throwable thrown = assertThrows(NullPointerException.class,
() -> new HalAudioDeviceInfo(audioPort));
@@ -143,9 +142,10 @@
stepValue = TEST_GAIN_STEP_VALUE;
}}
};
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, gains, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, gains,
+ deviceExt);
Throwable thrown = assertThrows(IllegalStateException.class,
() -> new HalAudioDeviceInfo(audioPort));
@@ -165,9 +165,10 @@
stepValue = TEST_GAIN_STEP_VALUE;
}}
};
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, gains, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, gains,
+ deviceExt);
Throwable thrown = assertThrows(IllegalArgumentException.class,
() -> new HalAudioDeviceInfo(audioPort));
@@ -187,9 +188,10 @@
stepValue = TEST_GAIN_STEP_VALUE;
}}
};
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, gains, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, gains,
+ deviceExt);
Throwable thrown = assertThrows(IllegalArgumentException.class,
() -> new HalAudioDeviceInfo(audioPort));
@@ -209,9 +211,10 @@
stepValue = TEST_GAIN_STEP_VALUE;
}}
};
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, gains, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, gains,
+ deviceExt);
Throwable thrown = assertThrows(IllegalArgumentException.class,
() -> new HalAudioDeviceInfo(audioPort));
@@ -231,9 +234,10 @@
stepValue = 7;
}}
};
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, gains, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, gains,
+ deviceExt);
Throwable thrown = assertThrows(IllegalArgumentException.class,
() -> new HalAudioDeviceInfo(audioPort));
@@ -253,9 +257,10 @@
stepValue = 7;
}}
};
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, gains, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, gains,
+ deviceExt);
Throwable thrown = assertThrows(IllegalArgumentException.class,
() -> new HalAudioDeviceInfo(audioPort));
@@ -282,9 +287,10 @@
@Test
public void constructor_requiresConnectionBus() {
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_USB,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_USB, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExt);
Throwable thrown = assertThrows(IllegalArgumentException.class,
() -> new HalAudioDeviceInfo(audioPort));
@@ -295,9 +301,10 @@
@Test
public void constructor_requiresDeviceTypeInOrOut() {
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_SPEAKER, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_SPEAKER,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExt);
Throwable thrown = assertThrows(IllegalArgumentException.class,
() -> new HalAudioDeviceInfo(audioPort));
@@ -308,9 +315,10 @@
@Test
public void constructor_requiresNotEmptyAddress() {
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- /* address= */ "");
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, /* address= */ "");
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExt);
Throwable thrown = assertThrows(IllegalArgumentException.class,
() -> new HalAudioDeviceInfo(audioPort));
@@ -328,9 +336,10 @@
defaultValue = TEST_GAIN_DEFAULT_VALUE;
stepValue = TEST_GAIN_STEP_VALUE;
}};
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExt);
HalAudioDeviceInfo deviceInfo = new HalAudioDeviceInfo(audioPort);
expectWithMessage("Hal Audio Device Infos hash")
@@ -348,9 +357,10 @@
defaultValue = TEST_GAIN_DEFAULT_VALUE;
stepValue = TEST_GAIN_STEP_VALUE;
}};
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExt);
HalAudioDeviceInfo deviceInfo = new HalAudioDeviceInfo(audioPort);
expectWithMessage("Hal Audio Device Infos hash")
@@ -360,9 +370,10 @@
@Test
public void hash_forTheSameObject_Equals() {
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExt);
HalAudioDeviceInfo deviceInfo1 = new HalAudioDeviceInfo(audioPort);
HalAudioDeviceInfo deviceInfo2 = new HalAudioDeviceInfo(audioPort);
@@ -372,9 +383,10 @@
@Test
public void equals_forTheSameObject_succeeds() {
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExt);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExt);
HalAudioDeviceInfo deviceInfo1 = new HalAudioDeviceInfo(audioPort);
HalAudioDeviceInfo deviceInfo2 = new HalAudioDeviceInfo(audioPort);
@@ -395,10 +407,12 @@
stepValue = TEST_GAIN_STEP_VALUE;
}}
};
- AudioPortDeviceExt deviceExtRef = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPortRef = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExtRef);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, gains, deviceExtRef);
+ AudioPortDeviceExt deviceExtRef = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPortRef = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExtRef);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, gains,
+ deviceExtRef);
HalAudioDeviceInfo deviceInfoRef = new HalAudioDeviceInfo(audioPortRef);
HalAudioDeviceInfo deviceInfo = new HalAudioDeviceInfo(audioPort);
@@ -411,10 +425,12 @@
@Test
public void equals_withDifferentNames_fails() {
- AudioPortDeviceExt deviceExtRef = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPortRef = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExtRef);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME_ALT, GAINS, deviceExtRef);
+ AudioPortDeviceExt deviceExtRef = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPortRef = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExtRef);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME_ALT, GAINS,
+ deviceExtRef);
HalAudioDeviceInfo deviceInfoRef = new HalAudioDeviceInfo(audioPortRef);
HalAudioDeviceInfo deviceInfo = new HalAudioDeviceInfo(audioPort);
@@ -426,12 +442,14 @@
@Test
public void equals_withDifferentTypes_fails() {
- AudioPortDeviceExt deviceExtRef = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(IN_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPort audioPortRef = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExtRef);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExt);
+ AudioPortDeviceExt deviceExtRef = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(IN_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPort audioPortRef = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExtRef);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExt);
HalAudioDeviceInfo deviceInfoRef = new HalAudioDeviceInfo(audioPortRef);
HalAudioDeviceInfo deviceInfo = new HalAudioDeviceInfo(audioPort);
@@ -444,12 +462,14 @@
@Test
public void equals_withDifferentAddress_fails() {
- AudioPortDeviceExt deviceExtRef = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_MEDIA);
- AudioPortDeviceExt deviceExt = createAudioPortDeviceExt(OUT_DEVICE, CONNECTION_BUS,
- ADDRESS_BUS_NAV);
- AudioPort audioPortRef = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExtRef);
- AudioPort audioPort = createAudioPort(PORT_ID, PORT_NAME, GAINS, deviceExt);
+ AudioPortDeviceExt deviceExtRef = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_MEDIA);
+ AudioPortDeviceExt deviceExt = CarAudioHalTestUtils.createAudioPortDeviceExt(OUT_DEVICE,
+ CONNECTION_BUS, ADDRESS_BUS_NAV);
+ AudioPort audioPortRef = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExtRef);
+ AudioPort audioPort = CarAudioHalTestUtils.createAudioPort(PORT_ID, PORT_NAME, GAINS,
+ deviceExt);
HalAudioDeviceInfo deviceInfoRef = new HalAudioDeviceInfo(audioPortRef);
HalAudioDeviceInfo deviceInfo = new HalAudioDeviceInfo(audioPort);
@@ -458,25 +478,4 @@
expectWithMessage("Hal Audio Device Infos equals").that(deviceInfoRef)
.isNotEqualTo(deviceInfo);
}
-
- private static AudioPort createAudioPort(int id, String name, AudioGain[] gains,
- AudioPortDeviceExt deviceExt) {
- AudioPort audioPort = new AudioPort();
- audioPort.id = id;
- audioPort.name = name;
- audioPort.gains = gains;
- audioPort.ext = AudioPortExt.device(deviceExt);
- return audioPort;
- }
-
- private static AudioPortDeviceExt createAudioPortDeviceExt(int type, String connection,
- String address) {
- AudioPortDeviceExt deviceExt = new AudioPortDeviceExt();
- deviceExt.device = new AudioDevice();
- deviceExt.device.type = new AudioDeviceDescription();
- deviceExt.device.type.type = type;
- deviceExt.device.type.connection = connection;
- deviceExt.device.address = AudioDeviceAddress.id(address);
- return deviceExt;
- }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/hal/HalAudioFocusTest.java b/tests/carservice_unit_test/src/com/android/car/audio/hal/HalAudioFocusTest.java
index f525d73..cb106c7 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/hal/HalAudioFocusTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/hal/HalAudioFocusTest.java
@@ -23,6 +23,7 @@
import static android.media.AudioManager.AUDIOFOCUS_GAIN;
import static android.media.AudioManager.AUDIOFOCUS_LOSS;
import static android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
+import static android.media.AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
import static android.media.AudioManager.AUDIOFOCUS_REQUEST_FAILED;
import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
@@ -49,12 +50,14 @@
import android.hardware.audio.common.PlaybackTrackMetadata;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
-import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.os.Binder;
import android.os.Bundle;
+import android.util.Pair;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.car.audio.AudioManagerWrapper;
import com.android.car.audio.CarAudioContext;
import com.android.car.audio.CarAudioPlaybackMonitor;
import com.android.car.audio.CoreAudioRoutingUtils;
@@ -95,13 +98,13 @@
public MockitoRule rule = MockitoJUnit.rule();
@Mock
- private AudioManager mMockAudioManager;
+ private AudioManagerWrapper mAudioManagerWrapper;
@Mock
private AudioControlWrapper mAudioControlWrapper;
@Mock
private CarAudioPlaybackMonitor mCarAudioPlaybackMonitor;
@Captor
- private ArgumentCaptor<List<AudioAttributes>> mAudioAttributesCaptor;
+ private ArgumentCaptor<List<Pair<AudioAttributes, Integer>>> mAudioAttributesCaptor;
private static final CarAudioContext TEST_CAR_AUDIO_CONTEXT =
new CarAudioContext(CarAudioContext.getAllContextsInfo(),
/* useCoreAudioRouting= */ false);
@@ -110,7 +113,7 @@
@Before
public void setUp() {
- mHalAudioFocus = new HalAudioFocus(mMockAudioManager, mAudioControlWrapper,
+ mHalAudioFocus = new HalAudioFocus(mAudioManagerWrapper, mAudioControlWrapper,
mCarAudioPlaybackMonitor, TEST_CAR_AUDIO_CONTEXT, AUDIO_ZONE_IDS);
}
@@ -133,13 +136,15 @@
@Test
public void requestAudioFocus_notifiesHalOfFocusChange() {
whenAnyFocusRequestGranted();
+ int uid = Binder.getCallingUid();
mHalAudioFocus.requestAudioFocus(METADATA_MEDIA, ZONE_ID, AUDIOFOCUS_GAIN);
verify(mAudioControlWrapper).onAudioFocusChange(METADATA_MEDIA, ZONE_ID,
AUDIOFOCUS_REQUEST_GRANTED);
- expectWithMessage("Playback audio attributes with audio focus requested")
- .that(getCarAudioPlaybackMonitorAttributes(ZONE_ID)).containsExactly(ATTR_MEDIA);
+ expectWithMessage("Playback audio attributes and uid pairs with audio focus requested")
+ .that(getCarAudioPlaybackMonitorAttributes(ZONE_ID))
+ .containsExactly(new Pair<>(ATTR_MEDIA, uid));
}
@Test
@@ -185,8 +190,19 @@
}
@Test
+ public void requestAudioFocus_withFocusRequestDelayed() {
+ whenAnyFocusRequestGranted();
+ mHalAudioFocus.requestAudioFocus(METADATA_MEDIA, ZONE_ID, AUDIOFOCUS_GAIN);
+ when(mAudioManagerWrapper.requestAudioFocus(any())).thenReturn(AUDIOFOCUS_REQUEST_DELAYED);
+
+ mHalAudioFocus.requestAudioFocus(METADATA_ALARM, ZONE_ID, AUDIOFOCUS_GAIN);
+
+ verify(mAudioControlWrapper).onAudioFocusChange(METADATA_ALARM, ZONE_ID, AUDIOFOCUS_LOSS);
+ }
+
+ @Test
public void requestAudioFocus_withNullPlaybackMonitor() {
- HalAudioFocus halAudioFocus = new HalAudioFocus(mMockAudioManager, mAudioControlWrapper,
+ HalAudioFocus halAudioFocus = new HalAudioFocus(mAudioManagerWrapper, mAudioControlWrapper,
/* carAudioPlaybackMonitor= */ null, TEST_CAR_AUDIO_CONTEXT, AUDIO_ZONE_IDS);
whenAnyFocusRequestGranted();
@@ -217,14 +233,16 @@
@Test
public void requestAudioFocus_withSameZoneAndUsage_keepsExistingRequest() {
whenAnyFocusRequestGranted();
+ int uid = Binder.getCallingUid();
mHalAudioFocus.requestAudioFocus(METADATA_MEDIA, ZONE_ID, AUDIOFOCUS_GAIN);
AudioFocusRequest firstRequest = getLastRequest();
mHalAudioFocus.requestAudioFocus(METADATA_MEDIA, ZONE_ID, AUDIOFOCUS_GAIN);
- verify(mMockAudioManager, never()).abandonAudioFocusRequest(firstRequest);
- expectWithMessage("Playback audio attributes with the same zone and usage focuses")
- .that(getCarAudioPlaybackMonitorAttributes(ZONE_ID)).containsExactly(ATTR_MEDIA);
+ verify(mAudioManagerWrapper, never()).abandonAudioFocusRequest(firstRequest);
+ expectWithMessage("Playback audio attributes and uid pairs with the same zone and "
+ + "usage focuses").that(getCarAudioPlaybackMonitorAttributes(ZONE_ID))
+ .containsExactly(new Pair<>(ATTR_MEDIA, uid));
}
@Test
@@ -253,7 +271,7 @@
.getContextForAudioAttribute(ATTR_MEDIA);
doReturn(CoreAudioRoutingUtils.MUSIC_STRATEGY_ID).when(carAudioContextUsingCoreRouting)
.getContextForAudioAttribute(CoreAudioRoutingUtils.MOVIE_ATTRIBUTES);
- HalAudioFocus halAudioFocus = new HalAudioFocus(mMockAudioManager, mAudioControlWrapper,
+ HalAudioFocus halAudioFocus = new HalAudioFocus(mAudioManagerWrapper, mAudioControlWrapper,
mCarAudioPlaybackMonitor, carAudioContextUsingCoreRouting, AUDIO_ZONE_IDS);
whenAnyFocusRequestGranted();
halAudioFocus.requestAudioFocus(METADATA_MEDIA, ZONE_ID, AUDIOFOCUS_GAIN);
@@ -279,7 +297,7 @@
AudioFocusRequest firstRequest = getLastRequest();
mHalAudioFocus.requestAudioFocus(METADATA_MEDIA, SECOND_ZONE_ID, AUDIOFOCUS_GAIN);
- verify(mMockAudioManager, never()).abandonAudioFocusRequest(firstRequest);
+ verify(mAudioManagerWrapper, never()).abandonAudioFocusRequest(firstRequest);
}
@Test
@@ -290,19 +308,19 @@
AudioFocusRequest firstRequest = getLastRequest();
mHalAudioFocus.requestAudioFocus(METADATA_MEDIA, ZONE_ID, AUDIOFOCUS_GAIN);
- verify(mMockAudioManager, never()).abandonAudioFocusRequest(firstRequest);
+ verify(mAudioManagerWrapper, never()).abandonAudioFocusRequest(firstRequest);
}
@Test
public void requestAudioFocus_withPreviouslyFailedRequest_doesNothingForOldRequest() {
- when(mMockAudioManager.requestAudioFocus(any())).thenReturn(AUDIOFOCUS_REQUEST_FAILED,
+ when(mAudioManagerWrapper.requestAudioFocus(any())).thenReturn(AUDIOFOCUS_REQUEST_FAILED,
AUDIOFOCUS_REQUEST_GRANTED);
mHalAudioFocus.requestAudioFocus(METADATA_MEDIA, ZONE_ID, AUDIOFOCUS_GAIN);
AudioFocusRequest firstRequest = getLastRequest();
mHalAudioFocus.requestAudioFocus(METADATA_MEDIA, ZONE_ID, AUDIOFOCUS_GAIN);
- verify(mMockAudioManager, never()).abandonAudioFocusRequest(firstRequest);
+ verify(mAudioManagerWrapper, never()).abandonAudioFocusRequest(firstRequest);
}
@Test
@@ -327,7 +345,7 @@
mHalAudioFocus.abandonAudioFocus(METADATA_MEDIA, ZONE_ID);
- verify(mMockAudioManager, never()).abandonAudioFocusRequest(any());
+ verify(mAudioManagerWrapper, never()).abandonAudioFocusRequest(any());
}
@Test
@@ -346,7 +364,7 @@
mHalAudioFocus.abandonAudioFocus(METADATA_MEDIA, ZONE_ID);
- verify(mMockAudioManager).abandonAudioFocusRequest(actualRequest);
+ verify(mAudioManagerWrapper).abandonAudioFocusRequest(actualRequest);
}
@Test
@@ -354,7 +372,7 @@
whenAnyFocusRequestGranted();
mHalAudioFocus.requestAudioFocus(METADATA_MEDIA, ZONE_ID, AUDIOFOCUS_GAIN);
AudioFocusRequest actualRequest = getLastRequest();
- when(mMockAudioManager.abandonAudioFocusRequest(actualRequest)).thenReturn(
+ when(mAudioManagerWrapper.abandonAudioFocusRequest(actualRequest)).thenReturn(
AUDIOFOCUS_REQUEST_GRANTED);
mHalAudioFocus.abandonAudioFocus(METADATA_MEDIA, ZONE_ID);
@@ -372,7 +390,7 @@
mHalAudioFocus.abandonAudioFocus(METADATA_MEDIA, ZONE_ID);
- verify(mMockAudioManager, never()).abandonAudioFocusRequest(actualRequest);
+ verify(mAudioManagerWrapper, never()).abandonAudioFocusRequest(actualRequest);
}
@Test
@@ -386,7 +404,7 @@
mHalAudioFocus.abandonAudioFocus(METADATA_MEDIA, ZONE_ID);
- verify(mMockAudioManager).abandonAudioFocusRequest(actualRequest);
+ verify(mAudioManagerWrapper).abandonAudioFocusRequest(actualRequest);
}
@Test
@@ -396,7 +414,7 @@
mHalAudioFocus.abandonAudioFocus(METADATA_ALARM, ZONE_ID);
- verify(mMockAudioManager, never()).abandonAudioFocusRequest(any());
+ verify(mAudioManagerWrapper, never()).abandonAudioFocusRequest(any());
}
@Test
@@ -406,7 +424,7 @@
mHalAudioFocus.abandonAudioFocus(METADATA_MEDIA, SECOND_ZONE_ID);
- verify(mMockAudioManager, never()).abandonAudioFocusRequest(any());
+ verify(mAudioManagerWrapper, never()).abandonAudioFocusRequest(any());
}
@Test
@@ -415,7 +433,7 @@
mHalAudioFocus.requestAudioFocus(METADATA_MEDIA, ZONE_ID, AUDIOFOCUS_GAIN);
AudioFocusRequest request = getLastRequest();
- when(mMockAudioManager.abandonAudioFocusRequest(request))
+ when(mAudioManagerWrapper.abandonAudioFocusRequest(request))
.thenReturn(AUDIOFOCUS_REQUEST_FAILED);
mHalAudioFocus.abandonAudioFocus(METADATA_MEDIA, ZONE_ID);
@@ -432,13 +450,13 @@
mHalAudioFocus.requestAudioFocus(METADATA_MEDIA, ZONE_ID, AUDIOFOCUS_GAIN);
AudioFocusRequest alarmRequest = getLastRequest();
- verify(mMockAudioManager, never()).abandonAudioFocusRequest(any());
+ verify(mAudioManagerWrapper, never()).abandonAudioFocusRequest(any());
mHalAudioFocus.reset();
- verify(mMockAudioManager).abandonAudioFocusRequest(mediaRequest);
- verify(mMockAudioManager).abandonAudioFocusRequest(alarmRequest);
- verifyNoMoreInteractions(mMockAudioManager);
+ verify(mAudioManagerWrapper).abandonAudioFocusRequest(mediaRequest);
+ verify(mAudioManagerWrapper).abandonAudioFocusRequest(alarmRequest);
+ verifyNoMoreInteractions(mAudioManagerWrapper);
}
@Test
@@ -449,7 +467,7 @@
verify(mAudioControlWrapper, never()).onAudioFocusChange(any(), eq(ZONE_ID),
eq(AUDIOFOCUS_LOSS));
- when(mMockAudioManager.abandonAudioFocusRequest(any())).thenReturn(
+ when(mAudioManagerWrapper.abandonAudioFocusRequest(any())).thenReturn(
AUDIOFOCUS_REQUEST_GRANTED);
mHalAudioFocus.reset();
@@ -498,16 +516,16 @@
}
private void whenAnyFocusRequestGranted() {
- when(mMockAudioManager.requestAudioFocus(any())).thenReturn(AUDIOFOCUS_REQUEST_GRANTED);
+ when(mAudioManagerWrapper.requestAudioFocus(any())).thenReturn(AUDIOFOCUS_REQUEST_GRANTED);
}
private AudioFocusRequest getLastRequest() {
ArgumentCaptor<AudioFocusRequest> captor = ArgumentCaptor.forClass(AudioFocusRequest.class);
- verify(mMockAudioManager, atLeastOnce()).requestAudioFocus(captor.capture());
+ verify(mAudioManagerWrapper, atLeastOnce()).requestAudioFocus(captor.capture());
return captor.getValue();
}
- private List<AudioAttributes> getCarAudioPlaybackMonitorAttributes(int zoneId) {
+ private List<Pair<AudioAttributes, Integer>> getCarAudioPlaybackMonitorAttributes(int zoneId) {
verify(mCarAudioPlaybackMonitor).onActiveAudioPlaybackAttributesAdded(
mAudioAttributesCaptor.capture(), eq(zoneId));
return mAudioAttributesCaptor.getValue();
diff --git a/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothConnectionRetryManagerTest.java b/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothConnectionRetryManagerTest.java
index a6a3a50..dcb4679 100644
--- a/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothConnectionRetryManagerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothConnectionRetryManagerTest.java
@@ -56,9 +56,6 @@
@RequiresDevice
public class BluetoothConnectionRetryManagerTest
extends AbstractExtendedMockitoBluetoothTestCase {
- private static final String TAG = BluetoothConnectionRetryManagerTest.class.getSimpleName();
- private static final boolean VERBOSE = false;
-
private static final List<String> DEVICE_LIST = Arrays.asList(
"DE:AD:BE:EF:00:00",
"DE:AD:BE:EF:00:01",
diff --git a/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothDeviceConnectionPolicyTest.java b/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothDeviceConnectionPolicyTest.java
index e9548ef..dd4b176 100644
--- a/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothDeviceConnectionPolicyTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothDeviceConnectionPolicyTest.java
@@ -67,7 +67,6 @@
*/
public class BluetoothDeviceConnectionPolicyTest extends AbstractExtendedMockitoBluetoothTestCase {
private static final String TAG = BluetoothDeviceConnectionPolicyTest.class.getSimpleName();
- private static final boolean VERBOSE = false;
private static final long WAIT_TIMEOUT_MS = 5000;
@@ -109,9 +108,7 @@
mMockContext.addMockedSystemService(BluetoothManager.class, mMockBluetoothManager);
when(mMockBluetoothManager.getAdapter()).thenReturn(mMockBluetoothAdapter);
- /**
- * Mocks {@code mBluetoothAdapter.enable()}
- */
+ // Mocks mBluetoothAdapter.enable()
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
@@ -119,9 +116,7 @@
return null;
}
}).when(mMockBluetoothAdapter).enable();
- /**
- * Mocks {@code mBluetoothAdapter.disable(boolean)}
- */
+ // Mocks mBluetoothAdapter.disable(boolean)
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
@@ -135,10 +130,8 @@
return null;
}
}).when(mMockBluetoothAdapter).disable(anyBoolean());
- /**
- * Adapter needs to be in *some* state at the beginning of each test. Default ON.
- * This will also set Bluetooth persisted state in Settings to ON.
- */
+ // Adapter needs to be in *some* state at the beginning of each test. Default ON.
+ // This will also set Bluetooth persisted state in Settings to ON.
turnAdapterOn();
mockGetCarLocalService(CarPropertyService.class, mMockCarPropertyService);
@@ -148,7 +141,7 @@
when(mMockCarPropertyService
.getPropertySafe(eq(VehiclePropertyIds.INFO_DRIVER_SEAT), anyInt()))
.thenReturn(new CarPropertyValue<Integer>(VehiclePropertyIds.INFO_DRIVER_SEAT,
- 0 /*areaId*/, new Integer(DRIVER_SEAT)));
+ 0 /*areaId*/, DRIVER_SEAT));
mPolicy = BluetoothDeviceConnectionPolicy.create(mMockContext, USER_ID,
mMockBluetoothService);
@@ -233,8 +226,7 @@
private void sendSeatOnOccupied(int seat) {
CarPropertyValue<Integer> value = new CarPropertyValue<Integer>(
- VehiclePropertyIds.SEAT_OCCUPANCY, seat,
- new Integer(VehicleSeatOccupancyState.OCCUPIED));
+ VehiclePropertyIds.SEAT_OCCUPANCY, seat, VehicleSeatOccupancyState.OCCUPIED);
CarPropertyEvent event = new CarPropertyEvent(
CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value);
try {
diff --git a/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothDeviceManagerTest.java b/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothDeviceManagerTest.java
index 5568024..57e6f0f 100644
--- a/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothDeviceManagerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothDeviceManagerTest.java
@@ -135,9 +135,7 @@
when(mMockBluetoothManager.getAdapter()).thenReturn(mMockBluetoothAdapter);
when(mMockBluetoothAdapter.getUuidsList()).thenReturn(Arrays.asList(mLocalUuids));
- /**
- * Mocks {@link BluetoothAdapter#getRemoteDevice(boolean)}
- */
+ // Mocks BluetoothAdapter#getRemoteDevice(boolean)
doAnswer(new Answer<BluetoothDevice>() {
@Override
public BluetoothDevice answer(InvocationOnMock invocation) throws Throwable {
diff --git a/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothPowerPolicyTest.java b/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothPowerPolicyTest.java
index 67e00f0..c36e14d 100644
--- a/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothPowerPolicyTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/bluetooth/BluetoothPowerPolicyTest.java
@@ -81,9 +81,7 @@
mMockContext.addMockedSystemService(BluetoothManager.class, mMockBluetoothManager);
when(mMockBluetoothManager.getAdapter()).thenReturn(mMockBluetoothAdapter);
- /**
- * Mocks {@code mBluetoothAdapter.enable()}
- */
+ // Mocks mBluetoothAdapter.enable()
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
@@ -91,9 +89,7 @@
return null;
}
}).when(mMockBluetoothAdapter).enable();
- /**
- * Mocks {@code mBluetoothAdapter.disable(boolean)}
- */
+ // Mocks mBluetoothAdapter.disable(boolean)
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
@@ -107,10 +103,8 @@
return null;
}
}).when(mMockBluetoothAdapter).disable(anyBoolean());
- /**
- * Adapter needs to be in *some* state at the beginning of each test. Default ON.
- * This will also set Bluetooth persisted state in Settings to ON.
- */
+ // Adapter needs to be in *some* state at the beginning of each test. Default ON.
+ // This will also set Bluetooth persisted state in Settings to ON.
turnAdapterOn();
mPolicy = BluetoothPowerPolicy.create(mMockContext, USER_ID);
diff --git a/tests/carservice_unit_test/src/com/android/car/bluetooth/CarBluetoothUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/bluetooth/CarBluetoothUserServiceTest.java
index 995d545b..d1c7f92 100644
--- a/tests/carservice_unit_test/src/com/android/car/bluetooth/CarBluetoothUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/bluetooth/CarBluetoothUserServiceTest.java
@@ -102,7 +102,6 @@
@Mock private PhoneAccountHandle mMockPhoneAccountHandle;
@Captor private ArgumentCaptor<BluetoothDevice> mBvraDeviceCaptor;
@Mock private Resources mMockResources;
- @Mock private UserManager mMockUserManager;
@Override
protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
@@ -219,7 +218,7 @@
public void testBvra_defaultDeviceSupports_bvraOnDefaultDevice() {
setBluetoothProfileProxy(BluetoothProfile.HEADSET_CLIENT, mMockBluetoothHeadsetClient);
- assertThat(DEFAULT_DEVICE).isNotEqualTo(DEVICE_LIST_WITH_DEFAULT.get(0));
+ assertThat(DEVICE_LIST_WITH_DEFAULT.get(0)).isNotEqualTo(DEFAULT_DEVICE);
List<BluetoothDevice> devicesToReturn = DEVICE_LIST_WITH_DEFAULT.stream()
.map(CarBluetoothUserServiceTest::createMockDevice).collect(Collectors.toList());
mockHeadsetClientGetConnectedBvraDevices(devicesToReturn);
diff --git a/tests/carservice_unit_test/src/com/android/car/bluetooth/FastPairGattServerTest.java b/tests/carservice_unit_test/src/com/android/car/bluetooth/FastPairGattServerTest.java
index 8ea389a..6f8e591 100644
--- a/tests/carservice_unit_test/src/com/android/car/bluetooth/FastPairGattServerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/bluetooth/FastPairGattServerTest.java
@@ -516,7 +516,7 @@
setCurrentRpa(TEST_RPA_STRING);
connectDevice(mMockBluetoothDevice);
byte[] request = buildKeyBasedPairingRequest(KEY_BASED_PAIRING_REQUEST_MSG_TYPE,
- /* flags= */ (byte) 0x00, TEST_RPA_BYTES, /* seeker_address= */ null, TEST_SALT_8,
+ /* flags= */ (byte) 0x00, TEST_RPA_BYTES, /* seeker= */ null, TEST_SALT_8,
TEST_PUBLIC_KEY_A, TEST_GENERATED_KEY);
byte[] encryptedResponse = sendKeyBasedPairingRequest(mMockBluetoothDevice, request);
assertThat(encryptedResponse).isNotNull();
@@ -546,7 +546,7 @@
setAvailableAccountKeys(List.of(new AccountKey(TEST_VALID_ACCOUNT_KEY)));
connectDevice(mMockBluetoothDevice);
byte[] request = buildKeyBasedPairingRequest(KEY_BASED_PAIRING_REQUEST_MSG_TYPE,
- /* flags= */ (byte) 0x00, TEST_RPA_BYTES, /* seeker_address= */ null, TEST_SALT_8,
+ /* flags= */ (byte) 0x00, TEST_RPA_BYTES, /* seeker= */ null, TEST_SALT_8,
TEST_PUBLIC_KEY_A, TEST_VALID_ACCOUNT_KEY);
request = Arrays.copyOfRange(request, 0, 16);
byte[] encryptedResponse = sendKeyBasedPairingRequest(mMockBluetoothDevice, request);
@@ -576,7 +576,7 @@
setCurrentRpa(TEST_RPA_STRING);
connectDevice(mMockBluetoothDevice);
byte[] request = buildKeyBasedPairingRequest(KEY_BASED_PAIRING_REQUEST_MSG_TYPE,
- /* flags= */ (byte) 0x00, TEST_RPA_BYTES, /* seeker_address= */ null, TEST_SALT_8,
+ /* flags= */ (byte) 0x00, TEST_RPA_BYTES, /* seeker= */ null, TEST_SALT_8,
TEST_PUBLIC_KEY_A, TEST_WRONG_GENERATED_KEY);
byte[] encryptedResponse = sendKeyBasedPairingRequest(mMockBluetoothDevice, request);
assertThat(encryptedResponse).isNull();
@@ -593,7 +593,7 @@
for (int i = 0; i < 10; i++) {
connectDevice(mMockBluetoothDevice);
byte[] request = buildKeyBasedPairingRequest(KEY_BASED_PAIRING_REQUEST_MSG_TYPE,
- /* flags= */ (byte) 0x00, TEST_RPA_BYTES, /* seeker_address= */ null,
+ /* flags= */ (byte) 0x00, TEST_RPA_BYTES, /* seeker= */ null,
TEST_SALT_8, TEST_PUBLIC_KEY_A, TEST_WRONG_GENERATED_KEY);
byte[] encryptedResponse = sendKeyBasedPairingRequest(mMockBluetoothDevice, request);
assertThat(encryptedResponse).isNull();
@@ -603,7 +603,7 @@
connectDevice(mMockBluetoothDevice);
byte[] request = buildKeyBasedPairingRequest(KEY_BASED_PAIRING_REQUEST_MSG_TYPE,
- /* flags= */ (byte) 0x00, TEST_RPA_BYTES, /* seeker_address= */ null, TEST_SALT_8,
+ /* flags= */ (byte) 0x00, TEST_RPA_BYTES, /* seeker= */ null, TEST_SALT_8,
TEST_PUBLIC_KEY_A, TEST_GENERATED_KEY);
byte[] encryptedResponse = sendKeyBasedPairingRequest(mMockBluetoothDevice, request);
assertThat(encryptedResponse).isNull();
@@ -648,7 +648,7 @@
sendPairingRequestBroadcast(mMockBluetoothDevice, TEST_PAIRING_KEY);
byte[] request = buildPasskeyRequest(PASSKEY_REQUEST_SEEKER, TEST_WRONG_PAIRING_KEY_BYTES,
TEST_SALT_12, TEST_GENERATED_KEY);
- byte[] encryptedResponse = sendPasskeyRequest(mMockBluetoothDevice, request);
+ sendPasskeyRequest(mMockBluetoothDevice, request);
verify(mMockBluetoothDevice).setPairingConfirmation(eq(false));
assertThat(mTestGattServer.isFastPairSessionActive()).isFalse();
}
@@ -660,7 +660,7 @@
sendBondStateChangeBroadcast(mMockBluetoothDevice,
BluetoothDevice.BOND_BONDING, BluetoothDevice.BOND_NONE);
sendPairingRequestBroadcast(mMockBluetoothDevice, TEST_PAIRING_KEY);
- byte[] encryptedResponse = sendPasskeyRequest(mMockBluetoothDevice, TEST_REQUEST_SHORT);
+ sendPasskeyRequest(mMockBluetoothDevice, TEST_REQUEST_SHORT);
assertThat(mTestGattServer.isFastPairSessionActive()).isFalse();
}
@@ -671,7 +671,7 @@
sendBondStateChangeBroadcast(mMockBluetoothDevice,
BluetoothDevice.BOND_BONDING, BluetoothDevice.BOND_NONE);
sendPairingRequestBroadcast(mMockBluetoothDevice, TEST_PAIRING_KEY);
- byte[] encryptedResponse = sendPasskeyRequest(mMockBluetoothDevice, null);
+ sendPasskeyRequest(mMockBluetoothDevice, null);
assertThat(mTestGattServer.isFastPairSessionActive()).isFalse();
}
@@ -709,7 +709,7 @@
BluetoothDevice.BOND_BONDING, BluetoothDevice.BOND_NONE);
byte[] request = buildPasskeyRequest(PASSKEY_REQUEST_SEEKER, TEST_PAIRING_KEY_BYTES,
TEST_SALT_12, TEST_GENERATED_KEY);
- byte[] unused = sendPasskeyRequest(mMockBluetoothDevice, request);
+ sendPasskeyRequest(mMockBluetoothDevice, request);
sendPairingRequestBroadcast(mMockBluetoothDevice, TEST_PAIRING_KEY);
byte[] encryptedResponse = mPasskeyCharacteristic.getValue();
byte[] passkeyResponse = decrypt(encryptedResponse, TEST_GENERATED_KEY);
@@ -753,7 +753,7 @@
BluetoothDevice.BOND_BONDED, BluetoothDevice.BOND_BONDING);
assertThat(mTestGattServer.isFastPairSessionActive()).isTrue();
byte[] request = buildAccountKeyRequest(TEST_VALID_ACCOUNT_KEY, TEST_GENERATED_KEY);
- byte[] encryptedResponse = sendAccountKeyRequest(mMockBluetoothDevice, request);
+ sendAccountKeyRequest(mMockBluetoothDevice, request);
verify(mMockFastPairAccountKeyStorage).add(eq(new AccountKey(TEST_VALID_ACCOUNT_KEY)));
assertThat(mTestGattServer.isFastPairSessionActive()).isFalse();
}
@@ -766,7 +766,7 @@
BluetoothDevice.BOND_BONDED, BluetoothDevice.BOND_BONDING);
assertThat(mTestGattServer.isFastPairSessionActive()).isTrue();
byte[] request = buildAccountKeyRequest(TEST_INVALID_ACCOUNT_KEY, TEST_GENERATED_KEY);
- byte[] encryptedResponse = sendAccountKeyRequest(mMockBluetoothDevice, request);
+ sendAccountKeyRequest(mMockBluetoothDevice, request);
verify(mMockFastPairAccountKeyStorage, never()).add(any());
assertThat(mTestGattServer.isFastPairSessionActive()).isFalse();
}
@@ -778,7 +778,7 @@
sendBondStateChangeBroadcast(mMockBluetoothDevice,
BluetoothDevice.BOND_BONDED, BluetoothDevice.BOND_BONDING);
assertThat(mTestGattServer.isFastPairSessionActive()).isTrue();
- byte[] encryptedResponse = sendAccountKeyRequest(mMockBluetoothDevice, new byte[]{});
+ sendAccountKeyRequest(mMockBluetoothDevice, new byte[]{});
verify(mMockFastPairAccountKeyStorage, never()).add(any());
assertThat(mTestGattServer.isFastPairSessionActive()).isFalse();
}
@@ -790,7 +790,7 @@
sendBondStateChangeBroadcast(mMockBluetoothDevice,
BluetoothDevice.BOND_BONDED, BluetoothDevice.BOND_BONDING);
assertThat(mTestGattServer.isFastPairSessionActive()).isTrue();
- byte[] encryptedResponse = sendAccountKeyRequest(mMockBluetoothDevice, TEST_REQUEST_SHORT);
+ sendAccountKeyRequest(mMockBluetoothDevice, TEST_REQUEST_SHORT);
verify(mMockFastPairAccountKeyStorage, never()).add(any());
assertThat(mTestGattServer.isFastPairSessionActive()).isFalse();
}
@@ -802,7 +802,7 @@
sendBondStateChangeBroadcast(mMockBluetoothDevice,
BluetoothDevice.BOND_BONDED, BluetoothDevice.BOND_BONDING);
assertThat(mTestGattServer.isFastPairSessionActive()).isTrue();
- byte[] encryptedResponse = sendAccountKeyRequest(mMockBluetoothDevice, null);
+ sendAccountKeyRequest(mMockBluetoothDevice, null);
verify(mMockFastPairAccountKeyStorage, never()).add(any());
assertThat(mTestGattServer.isFastPairSessionActive()).isFalse();
}
diff --git a/tests/carservice_unit_test/src/com/android/car/bluetooth/FastPairProviderTest.java b/tests/carservice_unit_test/src/com/android/car/bluetooth/FastPairProviderTest.java
index 29430b4..58fcb24 100644
--- a/tests/carservice_unit_test/src/com/android/car/bluetooth/FastPairProviderTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/bluetooth/FastPairProviderTest.java
@@ -84,7 +84,6 @@
@RunWith(MockitoJUnitRunner.class)
public class FastPairProviderTest {
private static final String KEY_NUM_ACCOUNT_KEYS = "AccountKeysCount";
- private static final String FAST_PAIR_PREFERENCES = "com.candroid.car.bluetooth";
public static final ParcelUuid SERVICE_UUID = ParcelUuid
.fromString("0000FE2C-0000-1000-8000-00805f9b34fb");
@@ -289,10 +288,6 @@
when(mMockResources.getString(anyInt())).thenReturn(keyBase64);
}
- private void setAutomaticAcceptance(boolean shouldAcceptAutomatically) {
- when(mMockResources.getBoolean(anyInt())).thenReturn(shouldAcceptAutomatically);
- }
-
private void createProviderUnderTest() {
mFastPairProvider = new FastPairProvider(mMockContext);
mAdvertiserCallbacks = mFastPairProvider.mAdvertiserCallbacks;
diff --git a/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java
index 4c8759e..ad3fefd 100644
--- a/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java
@@ -362,7 +362,7 @@
public void stopFixedActivityModeInvokesFixedActivityService() {
mClusterHomeService.stopFixedActivityMode();
- verify(mFixedActivityService).stopFixedActivityMode(eq(CLUSTER_DISPLAY_ID));
+ verify(mFixedActivityService).stopFixedActivityMode(CLUSTER_DISPLAY_ID);
}
@Test
@@ -372,7 +372,7 @@
mClusterHomeService.sendHeartbeat(epochTimeNs, appMetadata);
- verify(mClusterHalService).sendHeartbeat(
- eq(epochTimeNs), eq(/* visibility= */ 0L), eq(EMPTY_BYTE_ARRAY));
+ verify(mClusterHalService).sendHeartbeat(epochTimeNs, /* visibility= */ 0L,
+ EMPTY_BYTE_ARRAY);
}
}
diff --git a/tests/carservice_unit_test/src/com/android/car/cluster/InstrumentClusterServiceTest.java b/tests/carservice_unit_test/src/com/android/car/cluster/InstrumentClusterServiceTest.java
index f37ff2c..9c40227 100644
--- a/tests/carservice_unit_test/src/com/android/car/cluster/InstrumentClusterServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/cluster/InstrumentClusterServiceTest.java
@@ -331,8 +331,8 @@
extends IInstrumentClusterNavigation.Stub {
private final CarNavigationInstrumentCluster mClusterInfo =
- CarNavigationInstrumentCluster.createCustomImageCluster(/*minIntervalMs= */ 100,
- /* imageWidth= */ 800, /* imageHeight= */ 480,
+ CarNavigationInstrumentCluster.createCustomImageCluster(
+ /* minIntervalMillis= */ 100, /* imageWidth= */ 800, /* imageHeight= */ 480,
/* imageColorDepthBits= */ 32);
private Bundle mLastBundle;
diff --git a/tests/carservice_unit_test/src/com/android/car/evs/CarEvsServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/evs/CarEvsServiceUnitTest.java
index 09623e5..eedc7f9 100644
--- a/tests/carservice_unit_test/src/com/android/car/evs/CarEvsServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/evs/CarEvsServiceUnitTest.java
@@ -22,21 +22,15 @@
import static android.car.evs.CarEvsManager.SERVICE_STATE_INACTIVE;
import static android.car.evs.CarEvsManager.SERVICE_STATE_REQUESTED;
import static android.car.evs.CarEvsManager.SERVICE_STATE_UNAVAILABLE;
-import static android.car.evs.CarEvsManager.SERVICE_TYPE_DRIVERVIEW;
import static android.car.evs.CarEvsManager.SERVICE_TYPE_FRONTVIEW;
-import static android.car.evs.CarEvsManager.SERVICE_TYPE_FRONT_PASSENGERSVIEW;
import static android.car.evs.CarEvsManager.SERVICE_TYPE_LEFTVIEW;
import static android.car.evs.CarEvsManager.SERVICE_TYPE_REARVIEW;
-import static android.car.evs.CarEvsManager.SERVICE_TYPE_REAR_PASSENGERSVIEW;
import static android.car.evs.CarEvsManager.SERVICE_TYPE_RIGHTVIEW;
import static android.car.evs.CarEvsManager.SERVICE_TYPE_SURROUNDVIEW;
-import static android.car.evs.CarEvsManager.SERVICE_TYPE_USER_DEFINED;
-import static android.car.evs.CarEvsManager.STREAM_EVENT_STREAM_STOPPED;
import static android.car.hardware.property.CarPropertyEvent.PROPERTY_EVENT_ERROR;
import static android.car.hardware.property.CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE;
import static com.android.car.CarLog.TAG_EVS;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -44,8 +38,9 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static junit.framework.Assert.assertEquals;
+
import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyFloat;
import static org.mockito.Mockito.anyInt;
@@ -53,7 +48,6 @@
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
@@ -86,8 +80,10 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.view.Display;
import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.Display;
import com.android.car.BuiltinPackageDependency;
import com.android.car.CarPropertyService;
@@ -105,6 +101,7 @@
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
@@ -154,9 +151,6 @@
"serviceType=RIGHTVIEW,cameraId=" + DEFAULT_RIGHTVIEW_CAMERA_ID,
};
- private static final int SERVICE_TYPE_SHIFT = 24;
- private static final int SERVICE_TYPE_MASK = (0xFF << SERVICE_TYPE_SHIFT);
- private static final int DATA_MASK = ~SERVICE_TYPE_MASK;
private static final int MINIMUM_NUMBER_OF_FRAMES_TO_VERIFY = 10;
private static final int MAXIMUM_FRAME_INTERVAL_IN_MS = 67; // 15 frames per seconds.
// Test-only service type to skip a service-type check in test result verifications.
@@ -252,7 +246,8 @@
mGearSelectionListenerCaptor.capture())).thenReturn(true);
mCarEvsService = new CarEvsService(mMockContext, mMockBuiltinPackageContext,
- mMockEvsHalService, mMockCarPropertyService);
+ mMockEvsHalService, mMockCarPropertyService,
+ /* checkDependencies= */ false);
mCarEvsService.init();
}
@@ -334,7 +329,7 @@
/* format= */ HardwareBuffer.RGBA_8888,
/* layers= */ 1,
/* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN);
- int bufferId = mRandom.nextInt() & DATA_MASK;
+ int bufferId = mRandom.nextInt();
EvsStreamCallbackImpl spiedCallback = spy(new EvsStreamCallbackImpl());
EvsStatusListenerImpl spiedStatusListener = spy(new EvsStatusListenerImpl());
mCarEvsService.registerStatusListener(spiedStatusListener);
@@ -357,13 +352,15 @@
.isTrue();
mHalCallbackCaptor.getValue().onFrameEvent(bufferId, buffer);
- assertThat(spiedCallback.waitForFrames(/* expected= */ 1)).isTrue();
+ assertThat(spiedCallback.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
+ /* expected= */ 1)).isTrue();
// Request stopping a current activity. CarEvsService should give us a callback with
// STREAM_STOPPED event and ehter INACTIVE state.
mCarEvsService.mEvsTriggerListener.onEvent(SERVICE_TYPE_REARVIEW, /* on= */ false);
assertThat(spiedStatusListener.waitFor(SERVICE_STATE_INACTIVE)).isTrue();
- assertThat(spiedCallback.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
+ assertThat(spiedCallback.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
assertThat(mCarEvsService.getCurrentStatus(SERVICE_TYPE_REARVIEW).getState())
.isEqualTo(SERVICE_STATE_INACTIVE);
@@ -382,7 +379,7 @@
/* format= */ HardwareBuffer.RGBA_8888,
/* layers= */ 1,
/* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN);
- int bufferId = mRandom.nextInt() & DATA_MASK;
+ int bufferId = mRandom.nextInt();
mCarEvsService.addStreamCallback(SERVICE_TYPE_REARVIEW, null);
mCarEvsService.setServiceState(SERVICE_TYPE_REARVIEW, SERVICE_STATE_INACTIVE);
mHalCallbackCaptor.getValue().onFrameEvent(bufferId, buffer);
@@ -391,7 +388,7 @@
verify(mMockEvsHalWrapper).doneWithFrame(anyInt());
// Nothing to verify from below line but added to increase the code coverage.
- mHalCallbackCaptor.getValue().onHalEvent(/* eventType= */ 0);
+ mHalCallbackCaptor.getValue().onHalEvent(/* event= */ 0);
}
@Test
@@ -781,7 +778,7 @@
public void testStartActivity() {
EvsStreamCallbackImpl spiedCallback = spy(new EvsStreamCallbackImpl());
EvsStatusListenerImpl spiedStatusListener = spy(new EvsStatusListenerImpl());
- int bufferId = mRandom.nextInt() & DATA_MASK;
+ int bufferId = mRandom.nextInt();
HardwareBuffer buffer =
HardwareBuffer.create(/* width= */ 64, /* height= */ 32,
/* format= */ HardwareBuffer.RGBA_8888,
@@ -798,7 +795,8 @@
assertThat(spiedStatusListener.waitFor(SERVICE_STATE_ACTIVE)).isTrue();
mHalCallbackCaptor.getValue().onFrameEvent(bufferId, buffer);
- assertThat(spiedCallback.waitForFrames(/* expected= */ 1)).isTrue();
+ assertThat(spiedCallback.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
+ /* expected= */ 1)).isTrue();
verify(spiedCallback)
.onNewFrame(argThat(received -> received.getId() == bufferId));
}
@@ -877,7 +875,7 @@
/* format= */ HardwareBuffer.RGBA_8888,
/* layers= */ 1,
/* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN);
- int bufferId = mRandom.nextInt() & DATA_MASK;
+ int bufferId = mRandom.nextInt();
EvsStreamCallbackImpl spiedStreamCallback = spy(new EvsStreamCallbackImpl());
EvsStatusListenerImpl spiedStatusListener = spy(new EvsStatusListenerImpl());
@@ -903,14 +901,15 @@
assertThat(spiedStatusListener.waitFor(SERVICE_STATE_ACTIVE)).isTrue();
mHalCallbackCaptor.getValue().onFrameEvent(bufferId, buffer);
- assertThat(spiedStreamCallback.waitForFrames(/* expected= */ 1)).isTrue();
+ assertThat(spiedStreamCallback.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
+ /* expected= */ 1)).isTrue();
// Request stopping a current activity. CarEvsService should give us a callback with
// STREAM_STOPPED event and ehter INACTIVE state.
mCarEvsService.stopVideoStream(spiedStreamCallback);
assertThat(spiedStatusListener.waitFor(SERVICE_STATE_INACTIVE)).isTrue();
- assertThat(spiedStreamCallback.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED))
- .isTrue();
+ assertThat(spiedStreamCallback.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
assertThat(mCarEvsService.getCurrentStatus(0).getState())
.isEqualTo(SERVICE_STATE_INACTIVE);
}
@@ -943,20 +942,22 @@
/* format= */ HardwareBuffer.RGBA_8888,
/* layers= */ 1,
/* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN);
- int bufferId = mRandom.nextInt() & DATA_MASK;
+ int bufferId = mRandom.nextInt();
assertThat(mCarEvsService
.startVideoStream(SERVICE_TYPE_REARVIEW, null, spiedStreamCallback0))
.isEqualTo(ERROR_NONE);
mHalCallbackCaptor.getValue().onFrameEvent(bufferId, buffer);
- int anotherBufferId = mRandom.nextInt() & DATA_MASK;
+ int anotherBufferId = mRandom.nextInt();
assertThat(mCarEvsService
.startVideoStream(SERVICE_TYPE_REARVIEW, null, spiedStreamCallback1))
.isEqualTo(ERROR_NONE);
mHalCallbackCaptor.getValue().onFrameEvent(anotherBufferId, buffer);
- assertThat(spiedStreamCallback0.waitForFrames(/* expected= */ 1)).isTrue();
- assertThat(spiedStreamCallback1.waitForFrames(/* expected= */ 1)).isTrue();
+ assertThat(spiedStreamCallback0.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
+ /* expected= */ 1)).isTrue();
+ assertThat(spiedStreamCallback1.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
+ /* expected= */ 1)).isTrue();
verify(spiedStreamCallback0)
.onNewFrame(argThat(received -> received.getId() == bufferId));
verify(spiedStreamCallback1)
@@ -971,7 +972,7 @@
/* format= */ HardwareBuffer.RGBA_8888,
/* layers= */ 1,
/* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN);
- int bufferId = mRandom.nextInt() & DATA_MASK;
+ int bufferId = mRandom.nextInt();
EvsStreamCallbackImpl spiedCallback = spy(new EvsStreamCallbackImpl());
assertThat(mCarEvsService
.startVideoStream(SERVICE_TYPE_REARVIEW,
@@ -979,7 +980,8 @@
.isEqualTo(ERROR_NONE);
mHalCallbackCaptor.getValue().onFrameEvent(bufferId, buffer);
- assertThat(spiedCallback.waitForFrames(/* expected= */ 1)).isTrue();
+ assertThat(spiedCallback.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
+ /* expected= */ 1)).isTrue();
verify(spiedCallback)
.onNewFrame(argThat(received -> received.getId() == bufferId));
mCarEvsService.stopVideoStream(spiedCallback);
@@ -1001,14 +1003,15 @@
/* format= */ HardwareBuffer.RGBA_8888,
/* layers= */ 1,
/* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN);
- int bufferId = mRandom.nextInt() & DATA_MASK;
+ int bufferId = mRandom.nextInt();
EvsStreamCallbackImpl spiedCallback = spy(new EvsStreamCallbackImpl());
assertThat(mCarEvsService
.startVideoStream(SERVICE_TYPE_REARVIEW, token, spiedCallback))
.isEqualTo(ERROR_NONE);
mHalCallbackCaptor.getValue().onFrameEvent(bufferId, buffer);
- assertThat(spiedCallback.waitForFrames(/* expected= */ 1)).isTrue();
+ assertThat(spiedCallback.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
+ /* expected= */ 1)).isTrue();
mCarEvsService.stopVideoStream(spiedCallback);
verify(spiedCallback)
.onNewFrame(argThat(received -> received.getId() == bufferId));
@@ -1054,38 +1057,52 @@
mCarEvsService.addStreamCallback(SERVICE_TYPE_REARVIEW, spiedCallback);
mHalCallbackCaptor.getValue().onHalEvent(CarEvsManager.STREAM_EVENT_NONE);
- assertThat(spiedCallback.waitForEvent(CarEvsManager.STREAM_EVENT_NONE)).isTrue();
- verify(spiedCallback).onStreamEvent(CarEvsManager.STREAM_EVENT_NONE);
+ assertThat(spiedCallback.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_NONE)).isTrue();
+ verify(spiedCallback).onStreamEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_NONE);
mHalCallbackCaptor.getValue().onHalEvent(CarEvsManager.STREAM_EVENT_STREAM_STARTED);
- assertThat(spiedCallback.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STARTED)).isTrue();
- verify(spiedCallback).onStreamEvent(CarEvsManager.STREAM_EVENT_STREAM_STARTED);
+ assertThat(spiedCallback.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STARTED)).isTrue();
+ verify(spiedCallback).onStreamEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STARTED);
mHalCallbackCaptor.getValue().onHalEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED);
- assertThat(spiedCallback.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
- verify(spiedCallback).onStreamEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED);
+ assertThat(spiedCallback.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
+ verify(spiedCallback).onStreamEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED);
mHalCallbackCaptor.getValue().onHalEvent(CarEvsManager.STREAM_EVENT_FRAME_DROPPED);
- assertThat(spiedCallback.waitForEvent(CarEvsManager.STREAM_EVENT_FRAME_DROPPED)).isTrue();
- verify(spiedCallback).onStreamEvent(CarEvsManager.STREAM_EVENT_FRAME_DROPPED);
+ assertThat(spiedCallback.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_FRAME_DROPPED)).isTrue();
+ verify(spiedCallback).onStreamEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_FRAME_DROPPED);
mHalCallbackCaptor.getValue().onHalEvent(CarEvsManager.STREAM_EVENT_TIMEOUT);
- assertThat(spiedCallback.waitForEvent(CarEvsManager.STREAM_EVENT_TIMEOUT)).isTrue();
- verify(spiedCallback).onStreamEvent(CarEvsManager.STREAM_EVENT_TIMEOUT);
+ assertThat(spiedCallback.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_TIMEOUT)).isTrue();
+ verify(spiedCallback).onStreamEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_TIMEOUT);
mHalCallbackCaptor.getValue().onHalEvent(CarEvsManager.STREAM_EVENT_PARAMETER_CHANGED);
- assertThat(spiedCallback.waitForEvent(CarEvsManager.STREAM_EVENT_PARAMETER_CHANGED))
- .isTrue();
- verify(spiedCallback).onStreamEvent(CarEvsManager.STREAM_EVENT_PARAMETER_CHANGED);
+ assertThat(spiedCallback.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_PARAMETER_CHANGED)).isTrue();
+ verify(spiedCallback).onStreamEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_PARAMETER_CHANGED);
mHalCallbackCaptor.getValue().onHalEvent(CarEvsManager.STREAM_EVENT_PRIMARY_OWNER_CHANGED);
- assertThat(spiedCallback.waitForEvent(CarEvsManager.STREAM_EVENT_PRIMARY_OWNER_CHANGED))
- .isTrue();
- verify(spiedCallback).onStreamEvent(CarEvsManager.STREAM_EVENT_PRIMARY_OWNER_CHANGED);
+ assertThat(spiedCallback.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_PRIMARY_OWNER_CHANGED)).isTrue();
+ verify(spiedCallback).onStreamEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_PRIMARY_OWNER_CHANGED);
mHalCallbackCaptor.getValue().onHalEvent(CarEvsManager.STREAM_EVENT_OTHER_ERRORS);
- assertThat(spiedCallback.waitForEvent(CarEvsManager.STREAM_EVENT_OTHER_ERRORS)).isTrue();
- verify(spiedCallback).onStreamEvent(CarEvsManager.STREAM_EVENT_OTHER_ERRORS);
+ assertThat(spiedCallback.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_OTHER_ERRORS)).isTrue();
+ verify(spiedCallback).onStreamEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_OTHER_ERRORS);
}
@Test
@@ -1159,7 +1176,7 @@
/* format= */ HardwareBuffer.RGBA_8888,
/* layers= */ 1,
/* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN);
- int bufferId = mRandom.nextInt() & DATA_MASK;
+ int bufferId = mRandom.nextInt();
EvsStreamCallbackImpl spiedStreamCallback0 = spy(new EvsStreamCallbackImpl());
EvsStreamCallbackImpl spiedStreamCallback1 = spy(new EvsStreamCallbackImpl());
EvsStatusListenerImpl spiedStatusListener = spy(new EvsStatusListenerImpl());
@@ -1174,21 +1191,23 @@
.startVideoStream(SERVICE_TYPE_REARVIEW, token, spiedStreamCallback1))
.isEqualTo(ERROR_NONE);
mHalCallbackCaptor.getValue().onFrameEvent(bufferId, buffer);
- assertThat(spiedStreamCallback0.waitForFrames(/* expected= */ 1)).isTrue();
- assertThat(spiedStreamCallback1.waitForFrames(/* expected= */ 1)).isTrue();
+ assertThat(spiedStreamCallback0.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
+ /* expected= */ 1)).isTrue();
+ assertThat(spiedStreamCallback1.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
+ /* expected= */ 1)).isTrue();
verify(spiedStreamCallback0)
.onNewFrame(argThat(received -> received.getId() == bufferId));
verify(spiedStreamCallback1)
.onNewFrame(argThat(received -> received.getId() == bufferId));
mCarEvsService.stopVideoStream(spiedStreamCallback1);
- assertThat(spiedStreamCallback1.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED))
- .isTrue();
+ assertThat(spiedStreamCallback1.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
assertThat(spiedStatusListener.waitFor(SERVICE_STATE_INACTIVE)).isFalse();
mCarEvsService.stopVideoStream(spiedStreamCallback0);
- assertThat(spiedStreamCallback0.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED))
- .isTrue();
+ assertThat(spiedStreamCallback0.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
assertThat(spiedStatusListener.waitFor(SERVICE_STATE_INACTIVE)).isTrue();
}
@@ -1201,7 +1220,7 @@
/* format= */ HardwareBuffer.RGBA_8888,
/* layers= */ 1,
/* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN);
- int bufferId = mRandom.nextInt() & DATA_MASK;
+ int bufferId = mRandom.nextInt();
EvsStreamCallbackImpl spiedCallback0 = spy(new EvsStreamCallbackImpl());
EvsStreamCallbackImpl spiedCallback1 = spy(new EvsStreamCallbackImpl());
EvsStatusListenerImpl spiedStatusListener = spy(new EvsStatusListenerImpl());
@@ -1224,20 +1243,24 @@
mHalCallbackCaptor.getValue().onFrameEvent(bufferId, buffer);
// Confirm that a buffer is forwarded to both clients.
- assertThat(spiedCallback0.waitForFrames(/* expected= */ 1)).isTrue();
- assertThat(spiedCallback1.waitForFrames(/* expected= */ 1)).isTrue();
+ assertThat(spiedCallback0.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
+ /* expected= */ 1)).isTrue();
+ assertThat(spiedCallback1.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
+ /* expected= */ 1)).isTrue();
// Stop a video stream for the first client and verify that the service still in the active
// state.
mCarEvsService.stopVideoStream(spiedCallback0);
- assertThat(spiedCallback0.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
+ assertThat(spiedCallback0.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
assertThat(spiedStatusListener.waitFor(SERVICE_STATE_INACTIVE)).isFalse();
assertThat(mCarEvsService.getCurrentStatus(0).getState()).isEqualTo(SERVICE_STATE_ACTIVE);
// Stop a video stream for the second client and verify that the service entered the
// inactive state.
mCarEvsService.stopVideoStream(spiedCallback1);
- assertThat(spiedCallback1.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
+ assertThat(spiedCallback1.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
assertThat(spiedStatusListener.waitFor(SERVICE_STATE_INACTIVE)).isTrue();
assertThat(mCarEvsService.getCurrentStatus(0).getState()).isEqualTo(SERVICE_STATE_INACTIVE);
verify(spiedStatusListener, times(2)).onStatusChanged(argThat(
@@ -1303,24 +1326,24 @@
// Confirm that a buffer is forwarded to both clients.
int timeoutInMs = MINIMUM_NUMBER_OF_FRAMES_TO_VERIFY * MAXIMUM_FRAME_INTERVAL_IN_MS;
- assertThat(spiedRearviewCallback.waitForFrames(
+ assertThat(spiedRearviewCallback.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
/* expected= */ MINIMUM_NUMBER_OF_FRAMES_TO_VERIFY, timeoutInMs)).isTrue();
- assertThat(spiedFrontviewCallback.waitForFrames(
+ assertThat(spiedFrontviewCallback.waitForFrames(/* from= */ SERVICE_TYPE_FRONTVIEW,
/* expected= */ MINIMUM_NUMBER_OF_FRAMES_TO_VERIFY, timeoutInMs)).isTrue();
// Stop a video stream for the rearview client and verify that the service is stopped
// properly.
mCarEvsService.stopVideoStream(spiedRearviewCallback);
- assertThat(spiedRearviewCallback.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED))
- .isTrue();
+ assertThat(spiedRearviewCallback.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
assertThat(spiedStatusListener.waitFor(SERVICE_STATE_INACTIVE)).isTrue();
// Stop a video stream for the frontview client and verify that the service is stopped
// properly.
mCarEvsService.stopVideoStream(spiedFrontviewCallback);
assertThat(spiedStatusListener.waitFor(SERVICE_STATE_INACTIVE)).isTrue();
- assertThat(spiedFrontviewCallback.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED))
- .isTrue();
+ assertThat(spiedFrontviewCallback.waitForEvent(SERVICE_TYPE_FRONTVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
verify(spiedStatusListener, times(2)).onStatusChanged(argThat(
received -> received.getState() == CarEvsManager.SERVICE_STATE_INACTIVE &&
(received.getServiceType() == SERVICE_TYPE_REARVIEW ||
@@ -1377,13 +1400,13 @@
// Confirm that a buffer is forwarded to all four clients.
int timeoutInMs = MINIMUM_NUMBER_OF_FRAMES_TO_VERIFY * MAXIMUM_FRAME_INTERVAL_IN_MS;
- assertThat(spiedRearviewCallback0.waitForFrames(
+ assertThat(spiedRearviewCallback0.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
/* expected= */ MINIMUM_NUMBER_OF_FRAMES_TO_VERIFY, timeoutInMs)).isTrue();
- assertThat(spiedLeftviewCallback0.waitForFrames(
+ assertThat(spiedLeftviewCallback0.waitForFrames(/* from= */ SERVICE_TYPE_LEFTVIEW,
/* expected= */ MINIMUM_NUMBER_OF_FRAMES_TO_VERIFY, timeoutInMs)).isTrue();
- assertThat(spiedRearviewCallback1.waitForFrames(
+ assertThat(spiedRearviewCallback1.waitForFrames(/* from= */ SERVICE_TYPE_REARVIEW,
/* expected= */ MINIMUM_NUMBER_OF_FRAMES_TO_VERIFY, timeoutInMs)).isTrue();
- assertThat(spiedLeftviewCallback1.waitForFrames(
+ assertThat(spiedLeftviewCallback1.waitForFrames(/* from= */ SERVICE_TYPE_LEFTVIEW,
/* expected= */ MINIMUM_NUMBER_OF_FRAMES_TO_VERIFY, timeoutInMs)).isTrue();
verify(spiedStatusListener, times(2)).onStatusChanged(argThat(
received -> received.getState() == CarEvsManager.SERVICE_STATE_ACTIVE &&
@@ -1393,21 +1416,21 @@
// Stop a video stream for the first clients and verify that the services still in the
// active state.
mCarEvsService.stopVideoStream(spiedRearviewCallback0);
- assertThat(spiedRearviewCallback0.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED))
- .isTrue();
+ assertThat(spiedRearviewCallback0.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
mCarEvsService.stopVideoStream(spiedLeftviewCallback0);
- assertThat(spiedLeftviewCallback0.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED))
- .isTrue();
+ assertThat(spiedLeftviewCallback0.waitForEvent(SERVICE_TYPE_LEFTVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
assertThat(spiedStatusListener.waitFor(SERVICE_STATE_INACTIVE)).isFalse();
// Stop a video stream for the second clients and verify that the services entered the
// inactive state.
mCarEvsService.stopVideoStream(spiedRearviewCallback1);
- assertThat(spiedRearviewCallback1.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED))
- .isTrue();
+ assertThat(spiedRearviewCallback1.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
mCarEvsService.stopVideoStream(spiedLeftviewCallback1);
- assertThat(spiedLeftviewCallback1.waitForEvent(CarEvsManager.STREAM_EVENT_STREAM_STOPPED))
- .isTrue();
+ assertThat(spiedLeftviewCallback1.waitForEvent(SERVICE_TYPE_LEFTVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
assertThat(spiedStatusListener.waitFor(SERVICE_STATE_INACTIVE)).isTrue();
verify(spiedStatusListener, times(2)).onStatusChanged(argThat(
received -> received.getState() == CarEvsManager.SERVICE_STATE_INACTIVE &&
@@ -1424,28 +1447,100 @@
/* format= */ HardwareBuffer.RGBA_8888,
/* layers= */ 1,
/* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN);
- int bufferId = mRandom.nextInt() & DATA_MASK;
EvsStreamCallbackImpl spiedCallback = spy(new EvsStreamCallbackImpl());
- int[] types = {SERVICE_TYPE_REARVIEW, SERVICE_TYPE_FRONTVIEW, SERVICE_TYPE_LEFTVIEW,
- SERVICE_TYPE_RIGHTVIEW};
- String[] typeStrings = {"REARVIEW", "FRONTVIEW", "LEFTVIEW", "RIGHTVIEW"};
- String[] cameraIds = {DEFAULT_REARVIEW_CAMERA_ID, DEFAULT_FRONTVIEW_CAMERA_ID,
- DEFAULT_LEFTVIEW_CAMERA_ID, DEFAULT_RIGHTVIEW_CAMERA_ID};
+ int[] types = {SERVICE_TYPE_FRONTVIEW, SERVICE_TYPE_LEFTVIEW, SERVICE_TYPE_RIGHTVIEW};
+ String[] typeStrings = {"FRONTVIEW", "LEFTVIEW", "RIGHTVIEW"};
+ String[] cameraIds = {DEFAULT_FRONTVIEW_CAMERA_ID, DEFAULT_LEFTVIEW_CAMERA_ID,
+ DEFAULT_RIGHTVIEW_CAMERA_ID};
for (int i = 0; i < types.length; i++) {
- mCarEvsService.enableServiceTypeFromCommand(typeStrings[i], cameraIds[i]);
+ int bufferId = mRandom.nextInt();
+ assertThat(mCarEvsService.enableServiceTypeFromCommand(typeStrings[i], cameraIds[i]))
+ .isTrue();
+ assertThat(mCarEvsService.isServiceTypeEnabledFromCommand(typeStrings[i])).isTrue();
assertThat(mCarEvsService.startVideoStream(types[i], /* token= */ null, spiedCallback))
.isEqualTo(ERROR_NONE);
mHalCallbackCaptor.getValue().onFrameEvent(bufferId, buffer);
- assertThat(spiedCallback.waitForFrames(/* expected= */ 1)).isTrue();
+ assertThat(spiedCallback.waitForFrames(/* from= */ types[i], /* expected= */ 1))
+ .isTrue();
verify(spiedCallback)
.onNewFrame(argThat(received -> received.getId() == bufferId));
mCarEvsService.stopVideoStream(spiedCallback);
}
}
+ @Test
+ public void testTwoConcurrentStreamsOnSingleCallbackObject() throws Exception {
+ HardwareBuffer buffer =
+ HardwareBuffer.create(
+ /* width= */ 64,
+ /* height= */ 32,
+ /* format= */ HardwareBuffer.RGBA_8888,
+ /* layers= */ 1,
+ /* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN);
+ EvsStreamCallbackImpl spiedCallback = spy(new EvsStreamCallbackImpl(mCarEvsService));
+ EvsStatusListenerImpl spiedStatusListener = spy(new EvsStatusListenerImpl());
+
+ // Enable the frontview service.
+ mCarEvsService.enableServiceTypeFromCommand("FRONTVIEW", DEFAULT_FRONTVIEW_CAMERA_ID);
+
+ // Configure the services to be in the inactive state.
+ mCarEvsService.setServiceState(SERVICE_TYPE_REARVIEW, SERVICE_STATE_INACTIVE);
+ mCarEvsService.setServiceState(SERVICE_TYPE_FRONTVIEW, SERVICE_STATE_INACTIVE);
+
+ // Register a status listener and request starting video streams.
+ mCarEvsService.registerStatusListener(spiedStatusListener);
+ assertThat(mCarEvsService.startVideoStream(SERVICE_TYPE_REARVIEW,
+ /* token= */ null, spiedCallback)).isEqualTo(ERROR_NONE);
+ assertThat(mCarEvsService.startVideoStream(SERVICE_TYPE_FRONTVIEW,
+ /* token= */ null, spiedCallback)).isEqualTo(ERROR_NONE);
+
+ // Verify that the service entered the active state.
+ verify(spiedStatusListener, times(2)).onStatusChanged(argThat(
+ received -> received.getState() == CarEvsManager.SERVICE_STATE_ACTIVE &&
+ (received.getServiceType() == SERVICE_TYPE_REARVIEW ||
+ received.getServiceType() ==
+ SERVICE_TYPE_FRONTVIEW)));
+
+ // Send buffers in separate thread.
+ mHandler.post(() -> {
+ List<StateMachine.HalCallback> callbacks = mHalCallbackCaptor.getAllValues();
+ for (int i = 0; i < MINIMUM_NUMBER_OF_FRAMES_TO_VERIFY; i++) {
+ for (var cb : callbacks) {
+ cb.onFrameEvent(i, buffer);
+ }
+ }
+ });
+
+ // Confirm that a buffer is forwarded to both clients.
+ int timeoutInMs = MINIMUM_NUMBER_OF_FRAMES_TO_VERIFY * MAXIMUM_FRAME_INTERVAL_IN_MS;
+ assertThat(spiedCallback.waitForFrames(
+ /* from= */ new int[] { SERVICE_TYPE_REARVIEW, SERVICE_TYPE_FRONTVIEW },
+ /* expected= */ new int[] { MINIMUM_NUMBER_OF_FRAMES_TO_VERIFY,
+ MINIMUM_NUMBER_OF_FRAMES_TO_VERIFY }, timeoutInMs)).isTrue();
+
+ // Stop a video stream for the rearview client and verify that the service is stopped
+ // properly.
+ mCarEvsService.stopVideoStreamFrom(SERVICE_TYPE_REARVIEW, spiedCallback);
+ assertThat(spiedCallback.waitForEvent(SERVICE_TYPE_REARVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
+ assertThat(spiedStatusListener.waitFor(SERVICE_STATE_INACTIVE)).isTrue();
+
+ // Stop a video stream for the frontview client and verify that the service is stopped
+ // properly.
+ mCarEvsService.stopVideoStreamFrom(SERVICE_TYPE_FRONTVIEW, spiedCallback);
+ assertThat(spiedStatusListener.waitFor(SERVICE_STATE_INACTIVE)).isTrue();
+ assertThat(spiedCallback.waitForEvent(SERVICE_TYPE_FRONTVIEW,
+ CarEvsManager.STREAM_EVENT_STREAM_STOPPED)).isTrue();
+ verify(spiedStatusListener, times(2)).onStatusChanged(argThat(
+ received -> received.getState() == CarEvsManager.SERVICE_STATE_INACTIVE &&
+ (received.getServiceType() == SERVICE_TYPE_REARVIEW ||
+ received.getServiceType() ==
+ SERVICE_TYPE_FRONTVIEW)));
+ }
+
private void mockEvsHalService() throws Exception {
when(mMockEvsHalService.isEvsServiceRequestSupported())
.thenReturn(true);
@@ -1508,15 +1603,31 @@
* {@link android.hardware.automotive.evs.IEvsCameraStream}.
*/
private final static class EvsStreamCallbackImpl extends ICarEvsStreamCallback.Stub {
+ private static final int MAX_WAIT_FOR_EVENTS_AGAIN = 5;
+ private static final int KEY_NOT_EXIST = Integer.MIN_VALUE;
private final Semaphore mFrameSemaphore = new Semaphore(0);
private final Semaphore mEventSemaphore = new Semaphore(0);
+ private final Object mLock = new Object();
- private int mLastEvent = CarEvsManager.STREAM_EVENT_NONE;
+ private final CarEvsService mCarEvsService;
+ private SparseIntArray mLastEvents = new SparseIntArray();
+ private SparseArray mLastFrames = new SparseArray<ArrayList<CarEvsBufferDescriptor>>();
+
+ public EvsStreamCallbackImpl() {
+ this(null);
+ }
+
+ public EvsStreamCallbackImpl(CarEvsService svc) {
+ mCarEvsService = svc;
+ }
@Override
- public void onStreamEvent(@CarEvsStreamEvent int event) {
- Log.i(TAG, "Received stream event 0x" + Integer.toHexString(event));
- mLastEvent = event & DATA_MASK;
+ public void onStreamEvent(@CarEvsServiceType int origin, @CarEvsStreamEvent int event) {
+ Log.i(TAG, "Received stream event 0x" + Integer.toHexString(event) +
+ " from " + origin);
+ synchronized (mLock) {
+ mLastEvents.append(origin, event);
+ }
mEventSemaphore.release();
}
@@ -1524,18 +1635,39 @@
public void onNewFrame(CarEvsBufferDescriptor buffer) {
// Return a buffer immediately
Log.i(TAG, "Received buffer 0x" + Integer.toHexString(buffer.getId()));
+ synchronized (mLock) {
+ ArrayList<CarEvsBufferDescriptor> queue =
+ (ArrayList<CarEvsBufferDescriptor>) mLastFrames.get(buffer.getType());
+ if (queue != null) {
+ queue.add(buffer);
+ } else {
+ mLastFrames.put(buffer.getType(), new ArrayList<>(Arrays.asList(buffer)));
+ }
+ }
mFrameSemaphore.release();
}
- public boolean waitForEvent(int expected) {
- return waitForEvent(expected, DEFAULT_TIMEOUT_IN_MS);
+ public boolean waitForEvent(int from, int expected) {
+ return waitForEvent(from, expected, DEFAULT_TIMEOUT_IN_MS);
}
- public boolean waitForEvent(int expected, int timeout) {
+ public boolean waitForEvent(int from, int expected, int timeout) {
try {
- while (true) {
+ int retry = 0;
+ while (retry++ < MAX_WAIT_FOR_EVENTS_AGAIN) {
JavaMockitoHelper.await(mEventSemaphore, timeout);
- if (expected == mLastEvent) {
+
+ int event;
+ synchronized (mLock) {
+ event = mLastEvents.get(from, KEY_NOT_EXIST);
+ if (event == KEY_NOT_EXIST) {
+ // No event has arrived from a target origin.
+ continue;
+ }
+ mLastEvents.delete(from);
+ }
+
+ if (expected == event) {
return true;
}
}
@@ -1543,20 +1675,49 @@
Log.d(TAG, "Failure to wait for an event " + expected);
return false;
}
+
+ return false;
}
- public boolean waitForFrames(int expected) {
- return waitForFrames(expected, DEFAULT_TIMEOUT_IN_MS);
+ public boolean waitForFrames(int from, int expected) {
+ return waitForFrames(new int[] { from }, new int[] { expected },
+ DEFAULT_TIMEOUT_IN_MS);
}
- public boolean waitForFrames(int expected, int timeout) {
+ public boolean waitForFrames(int from, int expected, int timeout) {
+ return waitForFrames(new int[] { from }, new int[] { expected }, timeout);
+ }
+
+ public boolean waitForFrames(int[] from, int[] expected, int timeout) {
+ assertEquals(from.length, expected.length);
try {
- while (expected > 0) {
+ boolean done = false;
+ do {
JavaMockitoHelper.await(mFrameSemaphore, timeout);
- expected -= 1;
- }
+
+ done = true;
+ synchronized (mLock) {
+ for (int i = 0; i < from.length; i++) {
+ ArrayList<CarEvsBufferDescriptor> queue =
+ (ArrayList<CarEvsBufferDescriptor>) mLastFrames.get(from[i]);
+ if (queue == null) {
+ continue;
+ }
+
+ expected[i] -= queue.size();
+ done = done && (expected[i] < 1);
+ if (mCarEvsService == null) {
+ continue;
+ }
+
+ while (!queue.isEmpty()) {
+ mCarEvsService.returnFrameBuffer(queue.removeFirst());
+ }
+ }
+ }
+ } while (!done);
} catch (IllegalStateException | InterruptedException e) {
- Log.d(TAG, "Failure to wait for " + expected + " frames.");
+ Log.d(TAG, "Failure to wait for " + Arrays.toString(expected) + " frames.");
return false;
}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/DiagnosticHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/DiagnosticHalServiceTest.java
index 1449160..2a12313 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/DiagnosticHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/DiagnosticHalServiceTest.java
@@ -63,7 +63,8 @@
private DiagnosticHalService.DiagnosticListener mListener;
private DiagnosticHalService mService;
- private final HalPropValueBuilder mPropValueBuilder = new HalPropValueBuilder(/* aidl= */ true);
+ private final HalPropValueBuilder mPropValueBuilder = new HalPropValueBuilder(
+ /* isAidl= */ true);
private VehiclePropConfig mFreezeFrameConfig;
private VehiclePropConfig mLiveFrameConfig;
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/EvsHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/EvsHalServiceTest.java
index 5dae269..48452f3 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/EvsHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/EvsHalServiceTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.car.evs.CarEvsManager;
import android.hardware.automotive.vehicle.EvsServiceState;
@@ -39,6 +40,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@@ -52,6 +55,7 @@
public class EvsHalServiceTest {
@Mock VehicleHal mVehicleHal;
@Mock EvsHalService.EvsHalEventListener mListener;
+ @Captor ArgumentCaptor<HalPropValue> mPropCaptor;
private static final HalPropConfig EVS_SERVICE_REQUEST =
new AidlHalPropConfig(
@@ -62,11 +66,20 @@
private static final int TRUE = 1;
private static final int FALSE = 0;
+ private static final HalPropValueBuilder PROP_VALUE_BUILDER = new HalPropValueBuilder(
+ /*isAidl=*/true);
- private final HalPropValueBuilder mPropValueBuilder = new HalPropValueBuilder(/*isAidl=*/true);
+ private List<Integer> getIntValues(HalPropValue value) {
+ List<Integer> intValues = new ArrayList<Integer>();
+ for (int i = 0; i < value.getInt32ValuesSize(); i++) {
+ intValues.add(value.getInt32Value(i));
+ }
+ return intValues;
+ }
@Before
public void setUp() {
+ when(mVehicleHal.getHalPropValueBuilder()).thenReturn(PROP_VALUE_BUILDER);
mEvsHalService = new EvsHalService(mVehicleHal);
mEvsHalService.init();
}
@@ -124,14 +137,15 @@
@Test
public void handleInvalidHalEvents() {
subscribeListener(ImmutableSet.of(EVS_SERVICE_REQUEST));
- HalPropValue v = mPropValueBuilder.build(VehicleProperty.EVS_SERVICE_REQUEST, /*areaId=*/0);
+ HalPropValue v = PROP_VALUE_BUILDER.build(VehicleProperty.EVS_SERVICE_REQUEST,
+ /*areaId=*/0);
// Not type, no state.
mEvsHalService.onHalEvents(ImmutableList.of(v));
verify(mListener, never()).onEvent(anyInt(), anyBoolean());
// Not state.
- v = mPropValueBuilder.build(VehicleProperty.EVS_SERVICE_REQUEST, /*areaId=*/0,
+ v = PROP_VALUE_BUILDER.build(VehicleProperty.EVS_SERVICE_REQUEST, /*areaId=*/0,
EvsServiceType.REARVIEW);
mEvsHalService.onHalEvents(ImmutableList.of(v));
verify(mListener, never()).onEvent(anyInt(), anyBoolean());
@@ -147,14 +161,31 @@
.isTrue();
}
- // TODO(b/179029031): Adds more tests to verify the surround view service integration.
+ @Test
+ public void testUpdateCameraServiceCurrentState() {
+ int[] states = {CarEvsManager.SERVICE_STATE_ACTIVE, CarEvsManager.SERVICE_STATE_INACTIVE,
+ CarEvsManager.SERVICE_STATE_INACTIVE, CarEvsManager.SERVICE_STATE_INACTIVE,
+ CarEvsManager.SERVICE_STATE_INACTIVE, CarEvsManager.SERVICE_STATE_INACTIVE,
+ CarEvsManager.SERVICE_STATE_INACTIVE, CarEvsManager.SERVICE_STATE_INACTIVE};
+
+ mEvsHalService.reportCurrentState(states);
+
+ verify(mVehicleHal).set(mPropCaptor.capture());
+ HalPropValue prop = mPropCaptor.getValue();
+ assertThat(prop.getPropId()).isEqualTo(VehicleProperty.CAMERA_SERVICE_CURRENT_STATE);
+ assertThat(getIntValues(prop)).containsExactly(
+ CarEvsManager.SERVICE_STATE_ACTIVE, CarEvsManager.SERVICE_STATE_INACTIVE,
+ CarEvsManager.SERVICE_STATE_INACTIVE, CarEvsManager.SERVICE_STATE_INACTIVE,
+ CarEvsManager.SERVICE_STATE_INACTIVE, CarEvsManager.SERVICE_STATE_INACTIVE,
+ CarEvsManager.SERVICE_STATE_INACTIVE, CarEvsManager.SERVICE_STATE_INACTIVE);
+ }
private void dispatchEvsServiceRequest(int type, int state) {
mEvsHalService.onHalEvents(ImmutableList.of(buildEvsServiceRequestProp(type, state)));
}
private HalPropValue buildEvsServiceRequestProp(int type, int state) {
- return mPropValueBuilder.build(VehicleProperty.EVS_SERVICE_REQUEST, /*areaId=*/0,
+ return PROP_VALUE_BUILDER.build(VehicleProperty.EVS_SERVICE_REQUEST, /*areaId=*/0,
new int[]{type, state});
}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/HalPropConfigTest.java b/tests/carservice_unit_test/src/com/android/car/hal/HalPropConfigTest.java
index 2f2e060..cd44b98 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/HalPropConfigTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/HalPropConfigTest.java
@@ -39,6 +39,8 @@
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import com.android.car.hal.property.PropertyHalServiceConfigs;
+
import org.junit.Rule;
import org.junit.Test;
@@ -62,7 +64,6 @@
private static final int TEST_AREA_ID = 2;
private static final int TEST_ALTERNATE_AREA_ID = 3;
private static final int TEST_ACCESS = VehiclePropertyAccess.READ_WRITE;
- private static final int TEST_ALTERNATE_ACCESS = VehiclePropertyAccess.READ;
private static final int TEST_CHANGE_MODE = VehiclePropertyChangeMode.ON_CHANGE;
private static final int[] TEST_CONFIG_ARRAY = new int[]{1, 2, 3};
private static final ArrayList<Integer> TEST_CONFIG_ARRAY_LIST = new ArrayList<Integer>(
@@ -88,6 +89,9 @@
VehicleProperty.HVAC_TEMPERATURE_DISPLAY_UNITS,
VehicleProperty.VEHICLE_SPEED_DISPLAY_UNITS);
+ private final PropertyHalServiceConfigs mPropertyHalServiceConfigs =
+ PropertyHalServiceConfigs.getInstance();
+
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -270,13 +274,13 @@
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
CarPropertyConfig<?> carPropertyConfig = halPropConfig.toCarPropertyConfig(
- GLOBAL_INTEGER_PROP_ID);
+ GLOBAL_INTEGER_PROP_ID, mPropertyHalServiceConfigs);
assertThat(carPropertyConfig.getPropertyId()).isEqualTo(GLOBAL_INTEGER_PROP_ID);
assertThat(carPropertyConfig.getAreaIdConfigs()).hasSize(1);
AreaIdConfig<?> areaIdConfig = carPropertyConfig.getAreaIdConfig(/*areaId=*/0);
assertThat(areaIdConfig).isNotNull();
- assertThat(areaIdConfig.getAreaId()).isEqualTo(/*areaId=*/0);
+ assertThat(areaIdConfig.getAreaId()).isEqualTo(0);
assertThat(areaIdConfig.getMinValue()).isNull();
assertThat(areaIdConfig.getMaxValue()).isNull();
assertThat(areaIdConfig.getSupportedEnumValues()).isEmpty();
@@ -293,7 +297,7 @@
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
AreaIdConfig<?> areaIdConfig = halPropConfig.toCarPropertyConfig(
- GLOBAL_INTEGER_PROP_ID).getAreaIdConfig(TEST_AREA_ID);
+ GLOBAL_INTEGER_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(TEST_AREA_ID);
assertThat(areaIdConfig).isNotNull();
assertThat(areaIdConfig.getAreaId()).isEqualTo(TEST_AREA_ID);
assertThat(areaIdConfig.getMinValue()).isEqualTo(MIN_INT32_VALUE);
@@ -313,7 +317,7 @@
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
AreaIdConfig<?> areaIdConfig = halPropConfig.toCarPropertyConfig(
- GLOBAL_INTEGER_PROP_ID).getAreaIdConfig(TEST_AREA_ID);
+ GLOBAL_INTEGER_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(TEST_AREA_ID);
assertThat(areaIdConfig).isNotNull();
assertThat(areaIdConfig.getAreaId()).isEqualTo(TEST_AREA_ID);
assertThat(areaIdConfig.getMinValue()).isNull();
@@ -332,7 +336,7 @@
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
AreaIdConfig<?> areaIdConfig = halPropConfig.toCarPropertyConfig(
- GLOBAL_LONG_PROP_ID).getAreaIdConfig(TEST_AREA_ID);
+ GLOBAL_LONG_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(TEST_AREA_ID);
assertThat(areaIdConfig).isNotNull();
assertThat(areaIdConfig.getAreaId()).isEqualTo(TEST_AREA_ID);
assertThat(areaIdConfig.getMinValue()).isEqualTo(MIN_INT64_VALUE);
@@ -353,7 +357,7 @@
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
AreaIdConfig<?> areaIdConfig = halPropConfig.toCarPropertyConfig(
- GLOBAL_LONG_PROP_ID).getAreaIdConfig(TEST_AREA_ID);
+ GLOBAL_LONG_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(TEST_AREA_ID);
assertThat(areaIdConfig).isNotNull();
assertThat(areaIdConfig.getAreaId()).isEqualTo(TEST_AREA_ID);
assertThat(areaIdConfig.getMinValue()).isNull();
@@ -372,7 +376,7 @@
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
AreaIdConfig<?> areaIdConfig = halPropConfig.toCarPropertyConfig(
- GLOBAL_FLOAT_PROP_ID).getAreaIdConfig(TEST_AREA_ID);
+ GLOBAL_FLOAT_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(TEST_AREA_ID);
assertThat(areaIdConfig).isNotNull();
assertThat(areaIdConfig.getAreaId()).isEqualTo(TEST_AREA_ID);
assertThat(areaIdConfig.getMinValue()).isEqualTo(MIN_FLOAT_VALUE);
@@ -393,7 +397,7 @@
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
AreaIdConfig<?> areaIdConfig = halPropConfig.toCarPropertyConfig(
- GLOBAL_FLOAT_PROP_ID).getAreaIdConfig(TEST_AREA_ID);
+ GLOBAL_FLOAT_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(TEST_AREA_ID);
assertThat(areaIdConfig).isNotNull();
assertThat(areaIdConfig.getAreaId()).isEqualTo(TEST_AREA_ID);
assertThat(areaIdConfig.getMinValue()).isNull();
@@ -412,7 +416,8 @@
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
AreaIdConfig<?> areaIdConfig = halPropConfig.toCarPropertyConfig(
- GLOBAL_INTEGER_VEC_PROP_ID).getAreaIdConfig(TEST_AREA_ID);
+ GLOBAL_INTEGER_VEC_PROP_ID, mPropertyHalServiceConfigs)
+ .getAreaIdConfig(TEST_AREA_ID);
assertThat(areaIdConfig).isNotNull();
assertThat(areaIdConfig.getAreaId()).isEqualTo(TEST_AREA_ID);
assertThat(areaIdConfig.getMinValue()).isNull();
@@ -430,7 +435,8 @@
aidlVehiclePropConfig.areaConfigs[0].supportedEnumValues = null;
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
- assertThat(halPropConfig.toCarPropertyConfig(GLOBAL_INTEGER_PROP_ID).getAreaIdConfig(
+ assertThat(halPropConfig.toCarPropertyConfig(
+ GLOBAL_INTEGER_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(
TEST_AREA_ID).getSupportedEnumValues()).isEmpty();
}
@@ -440,7 +446,8 @@
aidlVehiclePropConfig.areaConfigs = new VehicleAreaConfig[]{getTestAidlAreaConfig()};
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
- assertThat(halPropConfig.toCarPropertyConfig(GLOBAL_INTEGER_PROP_ID).getAreaIdConfig(
+ assertThat(halPropConfig.toCarPropertyConfig(
+ GLOBAL_INTEGER_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(
TEST_AREA_ID).getSupportedEnumValues()).containsExactly(99, 100);
}
@@ -451,7 +458,8 @@
aidlVehiclePropConfig.areaConfigs = new VehicleAreaConfig[]{getTestAidlAreaConfig()};
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
- assertThat(halPropConfig.toCarPropertyConfig(GLOBAL_INTEGER_PROP_ID).getAreaIdConfig(
+ assertThat(halPropConfig.toCarPropertyConfig(
+ GLOBAL_INTEGER_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(
TEST_AREA_ID).getSupportedEnumValues()).isEmpty();
}
@@ -463,7 +471,8 @@
aidlVehiclePropConfig.areaConfigs[0].supportedEnumValues = null;
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
- assertThat(halPropConfig.toCarPropertyConfig(GLOBAL_INTEGER_PROP_ID).getAreaIdConfig(
+ assertThat(halPropConfig.toCarPropertyConfig(
+ GLOBAL_INTEGER_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(
TEST_AREA_ID).getSupportedEnumValues()).containsExactlyElementsIn(
List.of(
VehicleOilLevel.LEVEL_CRITICALLY_LOW,
@@ -482,7 +491,8 @@
Arrays.asList(getTestHidlAreaConfig()));
HidlHalPropConfig halPropConfig = new HidlHalPropConfig(hidlVehiclePropConfig);
- assertThat(halPropConfig.toCarPropertyConfig(GLOBAL_INTEGER_PROP_ID).getAreaIdConfig(
+ assertThat(halPropConfig.toCarPropertyConfig(
+ GLOBAL_INTEGER_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(
TEST_AREA_ID).getSupportedEnumValues()).isEmpty();
}
@@ -496,7 +506,8 @@
Arrays.asList(getTestHidlAreaConfig()));
HidlHalPropConfig halPropConfig = new HidlHalPropConfig(hidlVehiclePropConfig);
- assertThat(halPropConfig.toCarPropertyConfig(GLOBAL_INTEGER_PROP_ID).getAreaIdConfig(
+ assertThat(halPropConfig.toCarPropertyConfig(
+ GLOBAL_INTEGER_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(
TEST_AREA_ID).getAccess()).isEqualTo(halPropConfig.getAccess());
}
@@ -511,7 +522,7 @@
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
CarPropertyConfig<?> carPropertyConfig = halPropConfig.toCarPropertyConfig(
- GLOBAL_INTEGER_PROP_ID);
+ GLOBAL_INTEGER_PROP_ID, mPropertyHalServiceConfigs);
assertThat(carPropertyConfig.getAccess()).isEqualTo(TEST_ACCESS);
AreaIdConfig<?> areaIdConfig1 = carPropertyConfig.getAreaIdConfig(TEST_AREA_ID);
@@ -532,7 +543,8 @@
for (Integer propId: CONFIG_ARRAY_DEFINES_SUPPORTED_ENUM_VALUES) {
aidlVehiclePropConfig.prop = propId;
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
- assertThat(halPropConfig.toCarPropertyConfig(GLOBAL_INTEGER_PROP_ID).getAreaIdConfig(
+ assertThat(halPropConfig.toCarPropertyConfig(
+ GLOBAL_INTEGER_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(
TEST_AREA_ID).getSupportedEnumValues())
.containsExactlyElementsIn(TEST_CONFIG_ARRAY_LIST);
}
@@ -544,7 +556,8 @@
aidlVehiclePropConfig.areaConfigs = new VehicleAreaConfig[]{getTestAidlAreaConfig()};
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
- assertThat(halPropConfig.toCarPropertyConfig(GLOBAL_INTEGER_PROP_ID).getAreaIdConfig(
+ assertThat(halPropConfig.toCarPropertyConfig(
+ GLOBAL_INTEGER_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(
TEST_AREA_ID).isVariableUpdateRateSupported()).isTrue();
}
@@ -557,7 +570,8 @@
Arrays.asList(getTestHidlAreaConfig()));
HidlHalPropConfig halPropConfig = new HidlHalPropConfig(hidlConfig);
- assertThat(halPropConfig.toCarPropertyConfig(GLOBAL_INTEGER_PROP_ID).getAreaIdConfig(
+ assertThat(halPropConfig.toCarPropertyConfig(
+ GLOBAL_INTEGER_PROP_ID, mPropertyHalServiceConfigs).getAreaIdConfig(
TEST_AREA_ID).isVariableUpdateRateSupported()).isFalse();
}
@@ -572,7 +586,7 @@
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
CarPropertyConfig<?> carPropertyConfig = halPropConfig.toCarPropertyConfig(
- VENDOR_INTEGER_VEC_PROP_ID);
+ VENDOR_INTEGER_VEC_PROP_ID, mPropertyHalServiceConfigs);
assertThat(carPropertyConfig.getPropertyId()).isEqualTo(VENDOR_INTEGER_VEC_PROP_ID);
assertThat(carPropertyConfig.getAreaType())
.isEqualTo(VehicleAreaType.VEHICLE_AREA_TYPE_VENDOR);
@@ -589,6 +603,6 @@
HalPropConfig halPropConfig = new AidlHalPropConfig(aidlVehiclePropConfig);
assertThrows(RuntimeException.class, () -> halPropConfig.toCarPropertyConfig(
- VENDOR_INTEGER_VEC_PROP_ID));
+ VENDOR_INTEGER_VEC_PROP_ID, mPropertyHalServiceConfigs));
}
}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/InputHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/InputHalServiceTest.java
index ee81547..8519a8a 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/InputHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/InputHalServiceTest.java
@@ -886,7 +886,7 @@
/* floatValues= */ new float[] {},
/* int64Values= */ new long[] {downTime},
/* stringValue= */ "",
- /* byteValue= */ new byte[] {}
+ /* byteValues= */ new byte[] {}
)));
verify(mInputListener).onKeyEvent(captor.capture(), eq(expectedDisplay),
eq(seat));
@@ -927,7 +927,7 @@
floatArray,
/* int64Values= */ new long[] {downTimeNanos},
/* stringValue= */ "",
- /* byteValue= */ new byte[] {}
+ /* byteValues= */ new byte[] {}
)));
verify(mInputListener).onMotionEvent(captor.capture(), eq(expectedDisplay), eq(seat));
reset(mInputListener);
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/MockedPowerHalService.java b/tests/carservice_unit_test/src/com/android/car/hal/MockedPowerHalService.java
index 53896df..4c8d13f 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/MockedPowerHalService.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/MockedPowerHalService.java
@@ -18,6 +18,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.car.feature.FeatureFlagsImpl;
import android.car.test.mocks.JavaMockitoHelper;
import android.content.Context;
import android.hardware.automotive.vehicle.VehicleApPowerStateReq;
@@ -88,7 +89,8 @@
public MockedPowerHalService(boolean isPowerStateSupported, boolean isDeepSleepAllowed,
boolean isHibernationAllowed, boolean isTimedWakeupAllowed) {
- super(mock(Context.class), createVehicleHalWithMockedServices());
+ super(mock(Context.class), new FeatureFlagsImpl(),
+ createVehicleHalWithMockedServices());
mIsPowerStateSupported = isPowerStateSupported;
mIsDeepSleepAllowed = isDeepSleepAllowed;
mIsHibernationAllowed = isHibernationAllowed;
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/PowerHalServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/hal/PowerHalServiceUnitTest.java
index 53c5f9f..cb0cb98 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/PowerHalServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/PowerHalServiceUnitTest.java
@@ -27,8 +27,8 @@
import static android.hardware.automotive.vehicle.VehicleApPowerStateShutdownParam.SHUTDOWN_IMMEDIATELY;
import static android.hardware.automotive.vehicle.VehicleApPowerStateShutdownParam.SHUTDOWN_ONLY;
import static android.hardware.automotive.vehicle.VehicleApPowerStateShutdownParam.SLEEP_IMMEDIATELY;
-import static android.hardware.automotive.vehicle.VehicleProperty.AP_POWER_STATE_REPORT;
import static android.hardware.automotive.vehicle.VehicleProperty.AP_POWER_BOOTUP_REASON;
+import static android.hardware.automotive.vehicle.VehicleProperty.AP_POWER_STATE_REPORT;
import static android.hardware.automotive.vehicle.VehicleProperty.AP_POWER_STATE_REQ;
import static android.hardware.automotive.vehicle.VehicleProperty.DISPLAY_BRIGHTNESS;
import static android.hardware.automotive.vehicle.VehicleProperty.PER_DISPLAY_BRIGHTNESS;
@@ -52,6 +52,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.car.feature.FakeFeatureFlagsImpl;
+import android.car.feature.Flags;
import android.car.test.util.FakeContext;
import android.hardware.automotive.vehicle.StatusCode;
import android.hardware.automotive.vehicle.VehicleApPowerBootupReason;
@@ -84,6 +86,10 @@
private static final String TAG = PowerHalServiceUnitTest.class.getSimpleName();
+ // TODO(b/337307388): replace this with VehicleProperty.PER_DISPLAY_MAX_BRIGHTNESS once we use
+ // property V4.
+ private static final int PER_DISPLAY_MAX_BRIGHTNESS = 0x11410F4E;
+
private static final SparseBooleanArray CAN_POSTPONE_SHUTDOWN = new SparseBooleanArray(6);
static {
CAN_POSTPONE_SHUTDOWN.put(CAN_HIBERNATE, true);
@@ -125,11 +131,14 @@
private DisplayManager mDisplayManager;
private PowerHalService mPowerHalService;
+ private FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl();
+
@Before
public void setUp() {
mFakeContext.setSystemService(DisplayManager.class, mDisplayManager);
+ mFakeFeatureFlags.setFlag(Flags.FLAG_PER_DISPLAY_MAX_BRIGHTNESS, false);
- mPowerHalService = new PowerHalService(mFakeContext, mHal);
+ mPowerHalService = new PowerHalService(mFakeContext, mFakeFeatureFlags, mHal);
mPowerHalService.setListener(mEventListener);
}
@@ -139,6 +148,7 @@
AP_POWER_STATE_REPORT,
DISPLAY_BRIGHTNESS,
PER_DISPLAY_BRIGHTNESS,
+ PER_DISPLAY_MAX_BRIGHTNESS,
VEHICLE_IN_USE,
};
List<HalPropConfig> configs = new ArrayList<>();
@@ -248,6 +258,11 @@
}
@Test
+ public void testHalEventListenerMaxBrightness() {
+ assertThat(PowerHalService.MAX_BRIGHTNESS).isEqualTo(100);
+ }
+
+ @Test
public void testHalEventListenerDisplayBrightnessChange() {
AidlHalPropConfig config = new AidlHalPropConfig(
AidlVehiclePropConfigBuilder.newBuilder(DISPLAY_BRIGHTNESS)
@@ -287,7 +302,7 @@
}
@Test
- public void testHalEventListenerPerDisplayBrightnessChange() {
+ public void testPerDisplayBrightnessChange_perDisplayMaxNotSupported() {
AidlHalPropConfig config = new AidlHalPropConfig(
AidlVehiclePropConfigBuilder.newBuilder(PER_DISPLAY_BRIGHTNESS)
.addAreaConfig(/* areaId= */ 0, /* minValue= */ 0, /* maxValue= */ 100).build());
@@ -312,7 +327,7 @@
}
@Test
- public void testHalEventListenerPerDisplayBrightnessChange_non100MaxBrightness() {
+ public void testPerDisplayBrightnessChange_non100MaxBrightness_perDisplayMaxNotSupported() {
AidlHalPropConfig config = new AidlHalPropConfig(
AidlVehiclePropConfigBuilder.newBuilder(PER_DISPLAY_BRIGHTNESS)
.addAreaConfig(/* areaId= */ 0, /* minValue= */ 0, /* maxValue= */ 50).build());
@@ -340,6 +355,83 @@
}
@Test
+ public void testPerDisplayBrightnessChange_perDisplayMaxSupported() {
+ mFakeFeatureFlags.setFlag(Flags.FLAG_PER_DISPLAY_MAX_BRIGHTNESS, true);
+
+ var config1 = new AidlHalPropConfig(
+ AidlVehiclePropConfigBuilder.newBuilder(PER_DISPLAY_BRIGHTNESS)
+ .build());
+ var config2 = new AidlHalPropConfig(
+ AidlVehiclePropConfigBuilder.newBuilder(PER_DISPLAY_MAX_BRIGHTNESS)
+ .build());
+ mPowerHalService.takeProperties(List.of(config1, config2));
+
+ int displayId = 11;
+ int displayPort = 11;
+ int displayPort2 = 12;
+ // Set the max display brightness for display port to be 100.
+ when(mHal.get(PER_DISPLAY_MAX_BRIGHTNESS)).thenReturn(mPropValueBuilder.build(
+ PER_DISPLAY_MAX_BRIGHTNESS, /* areaId= */ 0,
+ new int[] {displayPort, 100, displayPort2, 50}));
+
+ mPowerHalService.init();
+ mPowerHalService.setListener(mEventListener);
+
+ Display display = createMockDisplay(displayId, displayPort);
+ when(mDisplayManager.getDisplays()).thenReturn(new Display[]{display});
+ when(mDisplayManager.getDisplay(displayId)).thenReturn(display);
+
+ int expectedBrightness = 73;
+ HalPropValue value = mPropValueBuilder.build(PER_DISPLAY_BRIGHTNESS, /* areaId= */ 0,
+ new int[]{displayPort, expectedBrightness});
+ mPowerHalService.onHalEvents(List.of(value));
+
+ assertWithMessage("Display brightness")
+ .that(mEventListener.getDisplayBrightness(displayPort))
+ .isEqualTo(expectedBrightness);
+ }
+
+ @Test
+ public void testPerDisplayBrightnessChange_non100MaxBrightness_perDisplaySupported() {
+ mFakeFeatureFlags.setFlag(Flags.FLAG_PER_DISPLAY_MAX_BRIGHTNESS, true);
+
+ var config1 = new AidlHalPropConfig(
+ AidlVehiclePropConfigBuilder.newBuilder(PER_DISPLAY_BRIGHTNESS)
+ .build());
+ var config2 = new AidlHalPropConfig(
+ AidlVehiclePropConfigBuilder.newBuilder(PER_DISPLAY_MAX_BRIGHTNESS)
+ .build());
+ mPowerHalService.takeProperties(List.of(config1, config2));
+
+ int displayId = 11;
+ int displayPort = 11;
+ int displayPort2 = 12;
+ // Set the max display brightness for display port to be 50.
+ when(mHal.get(PER_DISPLAY_MAX_BRIGHTNESS)).thenReturn(mPropValueBuilder.build(
+ PER_DISPLAY_MAX_BRIGHTNESS, /* areaId= */ 0,
+ new int[] {displayPort2, 100, displayPort, 50}));
+
+ mPowerHalService.init();
+ mPowerHalService.setListener(mEventListener);
+
+ Display display = createMockDisplay(displayId, displayPort);
+ when(mDisplayManager.getDisplays()).thenReturn(new Display[]{display});
+ when(mDisplayManager.getDisplay(displayId)).thenReturn(display);
+
+ int brightness = 24;
+ // Max brightness is 50, so the expected brightness should be multiplied 2 times to fit in
+ // 100 scale.
+ int expectedBrightness = brightness * 2;
+ HalPropValue value = mPropValueBuilder.build(PER_DISPLAY_BRIGHTNESS, /* areaId= */ 0,
+ new int[]{displayPort, brightness});
+ mPowerHalService.onHalEvents(List.of(value));
+
+ assertWithMessage("Display brightness")
+ .that(mEventListener.getDisplayBrightness(displayPort))
+ .isEqualTo(expectedBrightness);
+ }
+
+ @Test
public void testSendDisplayBrightness() {
VehicleHal.HalPropValueSetter propValueSetter = mock(VehicleHal.HalPropValueSetter.class);
when(mHal.set(VehicleProperty.DISPLAY_BRIGHTNESS, /* areaId= */ 0))
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceTest.java
index 2bcabac..987da28 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceTest.java
@@ -151,8 +151,6 @@
new AsyncPropertyServiceRequest(REQUEST_ID_2, HVAC_TEMPERATURE_SET, /* areaId= */ 0);
private static final AsyncPropertyServiceRequest GET_PROPERTY_SERVICE_REQUEST_STATIC_1 =
new AsyncPropertyServiceRequest(REQUEST_ID_1, INT32_PROP, /* areaId= */ 0);
- private static final AsyncPropertyServiceRequest GET_PROPERTY_SERVICE_REQUEST_STATIC_2 =
- new AsyncPropertyServiceRequest(REQUEST_ID_2, INT32_PROP, /* areaId= */ 0);
private static final AsyncPropertyServiceRequest SET_HVAC_REQUEST_ID_1 =
new AsyncPropertyServiceRequest(REQUEST_ID_1, HVAC_TEMPERATURE_SET, /* areaId= */ 0,
new CarPropertyValue(HVAC_TEMPERATURE_SET, /* areaId= */ 0, SAMPLE_RATE_HZ));
@@ -200,7 +198,7 @@
HalPropConfig mockPropConfig1 = mock(HalPropConfig.class);
when(mockPropConfig1.getPropId()).thenReturn(VehicleProperty.HVAC_TEMPERATURE_SET);
when(mockPropConfig1.getChangeMode()).thenReturn(VehiclePropertyChangeMode.ON_CHANGE);
- when(mockPropConfig1.toCarPropertyConfig(VehicleProperty.HVAC_TEMPERATURE_SET))
+ when(mockPropConfig1.toCarPropertyConfig(eq(VehicleProperty.HVAC_TEMPERATURE_SET), any()))
.thenReturn(mMockCarPropertyConfig1);
when(mMockCarPropertyConfig1.getChangeMode())
.thenReturn(VehiclePropertyChangeMode.ON_CHANGE);
@@ -211,7 +209,7 @@
when(mockPropConfig2.getChangeMode()).thenReturn(VehiclePropertyChangeMode.CONTINUOUS);
when(mockPropConfig2.getMinSampleRate()).thenReturn(20.0f);
when(mockPropConfig2.getMaxSampleRate()).thenReturn(100.0f);
- when(mockPropConfig2.toCarPropertyConfig(VehicleProperty.PERF_VEHICLE_SPEED))
+ when(mockPropConfig2.toCarPropertyConfig(eq(VehicleProperty.PERF_VEHICLE_SPEED), any()))
.thenReturn(mMockCarPropertyConfig2);
when(mMockCarPropertyConfig2.getChangeMode())
.thenReturn(VehiclePropertyChangeMode.CONTINUOUS);
@@ -889,8 +887,8 @@
public void testSetCarPropertyValuesAsync_configNotFound() {
doReturn(mSetAsyncPropertyResultBinder).when(mSetAsyncPropertyResultCallback).asBinder();
AsyncPropertyServiceRequest request = new AsyncPropertyServiceRequest(
- REQUEST_ID_1, /* propId= */ 1, /* areaId= */ 0,
- new CarPropertyValue(/* propId= */ 1, /* areaId= */ 0, SAMPLE_RATE_HZ));
+ REQUEST_ID_1, /* propertyId= */ 1, /* areaId= */ 0,
+ new CarPropertyValue(/* propertyId= */ 1, /* areaId= */ 0, SAMPLE_RATE_HZ));
assertThrows(IllegalArgumentException.class, () -> {
mPropertyHalService.setCarPropertyValuesAsync(List.of(request),
@@ -2545,10 +2543,10 @@
@Test
public void testHalSubscribeOptions_equals() {
- HalSubscribeOptions options1 = new HalSubscribeOptions(/* propId= */ 5,
- /* areaId= */ new int[]{0, 3}, /* updateRateHz= */ 53f);
- HalSubscribeOptions options2 = new HalSubscribeOptions(/* propId= */ 5,
- /* areaId= */ new int[]{0, 3}, /* updateRateHz= */ 53f);
+ HalSubscribeOptions options1 = new HalSubscribeOptions(/* halPropId= */ 5,
+ /* areaIds= */ new int[]{0, 3}, /* updateRateHz= */ 53f);
+ HalSubscribeOptions options2 = new HalSubscribeOptions(/* halPropId= */ 5,
+ /* areaIds= */ new int[]{0, 3}, /* updateRateHz= */ 53f);
assertWithMessage("Equal hal subscribe options")
.that(options1.equals(options2)).isTrue();
@@ -2556,11 +2554,11 @@
@Test
public void testHalSubscribeOptions_notEquals() {
- HalSubscribeOptions options1 = new HalSubscribeOptions(/* propId= */ 5,
- /* areaId= */ new int[]{0, 3}, /* updateRateHz= */ 55f,
+ HalSubscribeOptions options1 = new HalSubscribeOptions(/* halPropId= */ 5,
+ /* areaIds= */ new int[]{0, 3}, /* updateRateHz= */ 55f,
/* enableVariableUpdateRate= */ true);
- HalSubscribeOptions options2 = new HalSubscribeOptions(/* propId= */ 5,
- /* areaId= */ new int[]{0, 3}, /* updateRateHz= */ 53f,
+ HalSubscribeOptions options2 = new HalSubscribeOptions(/* halPropId= */ 5,
+ /* areaIds= */ new int[]{0, 3}, /* updateRateHz= */ 53f,
/* enableVariableUpdateRate= */ true);
assertWithMessage("Non-equal hal subscribe options")
@@ -2569,11 +2567,11 @@
@Test
public void testHalSubscribeOptions_hashcode() {
- HalSubscribeOptions options1 = new HalSubscribeOptions(/* propId= */ 5,
- /* areaId= */ new int[]{0, 3}, /* updateRateHz= */ 53f,
+ HalSubscribeOptions options1 = new HalSubscribeOptions(/* halPropId= */ 5,
+ /* areaIds= */ new int[]{0, 3}, /* updateRateHz= */ 53f,
/* enableVariableUpdateRate= */ true);
- HalSubscribeOptions options2 = new HalSubscribeOptions(/* propId= */ 5,
- /* areaId= */ new int[]{0, 3}, /* updateRateHz= */ 53f,
+ HalSubscribeOptions options2 = new HalSubscribeOptions(/* halPropId= */ 5,
+ /* areaIds= */ new int[]{0, 3}, /* updateRateHz= */ 53f,
/* enableVariableUpdateRate= */ true);
assertWithMessage("Hashcode hal subscribe options")
@@ -2588,7 +2586,6 @@
public void testOnHalEvnts_unsubscribeAndUpdateSampleRates()
throws Exception {
List<InvocationOnMock> setInvocationWrap = new ArrayList<>();
- List<InvocationOnMock> getInvocationWrap = new ArrayList<>();
List<HalServiceBase> serviceWrap = new ArrayList<>();
doAnswer((invocation) -> {
@@ -2596,10 +2593,6 @@
return null;
}).when(mVehicleHal).setAsync(anyList(), any(VehicleStubCallbackInterface.class));
doAnswer((invocation) -> {
- getInvocationWrap.add(invocation);
- return null;
- }).when(mVehicleHal).getAsync(anyList(), any(VehicleStubCallbackInterface.class));
- doAnswer((invocation) -> {
serviceWrap.add(invocation.getArgument(0));
return null;
}).when(mVehicleHal).subscribeProperty(any(), anyList());
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/UserHalHelperTest.java b/tests/carservice_unit_test/src/com/android/car/hal/UserHalHelperTest.java
index 14164c9..7e85689 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/UserHalHelperTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/UserHalHelperTest.java
@@ -454,7 +454,7 @@
@Test
public void testToUserIdentificationResponse_invalidPropType() {
- HalPropValue prop = mPropValueBuilder.build(/* propId= */ 0, /* areaId= */ 0);
+ HalPropValue prop = mPropValueBuilder.build(/* prop= */ 0, /* areaId= */ 0);
assertThrows(IllegalArgumentException.class,
() -> UserHalHelper.toUserIdentificationResponse(prop));
@@ -552,7 +552,7 @@
@Test
public void testToInitialUserInfoResponse_invalidPropType() {
- HalPropValue prop = mPropValueBuilder.build(/* propId= */ 0, /* areaId= */ 0);
+ HalPropValue prop = mPropValueBuilder.build(/* prop= */ 0, /* areaId= */ 0);
assertThrows(IllegalArgumentException.class,
() -> UserHalHelper.toInitialUserInfoResponse(prop));
}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
index 4f5c687..5e8f926 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
@@ -1829,7 +1829,7 @@
* @param prop prop to be set
* @param response response to be set on event
* @param rightRequestId whether the response id should match the request
- * @param countDownLatch A {@link CountDownLatch} to control when to send the event.
+ * @param latch A {@link CountDownLatch} to control when to send the event.
* @return A copy of the update property value.
*
* @return reference to the value passed to {@code set()}.
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/VehicleHalTest.java b/tests/carservice_unit_test/src/com/android/car/hal/VehicleHalTest.java
index f69251f..d80dd67 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/VehicleHalTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/VehicleHalTest.java
@@ -41,7 +41,7 @@
import android.car.feature.FeatureFlags;
import android.car.hardware.property.CarPropertyManager;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.AbstractExpectableTestCase;
import android.content.Context;
import android.hardware.automotive.vehicle.StatusCode;
import android.hardware.automotive.vehicle.SubscribeOptions;
@@ -57,6 +57,7 @@
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
+import android.platform.test.ravenwood.RavenwoodRule;
import com.android.car.CarServiceUtils;
import com.android.car.VehicleStub;
@@ -65,6 +66,7 @@
import com.android.car.internal.util.ArrayUtils;
import com.android.car.internal.util.IndentingPrintWriter;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -83,7 +85,7 @@
import java.util.List;
@RunWith(MockitoJUnitRunner.class)
-public class VehicleHalTest extends AbstractExtendedMockitoTestCase {
+public class VehicleHalTest extends AbstractExpectableTestCase {
private static final int WAIT_TIMEOUT_MS = 1000;
@@ -122,22 +124,27 @@
@Mock private VehicleStub.SubscriptionClient mSubscriptionClient;
@Mock private FeatureFlags mFeatureFlags;
- private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
- VehicleHal.class.getSimpleName());
- private final Handler mHandler = new Handler(mHandlerThread.getLooper());
- private final HalPropValueBuilder mPropValueBuilder = new HalPropValueBuilder(/*isAidl=*/true);
+ private final HalPropValueBuilder mPropValueBuilder = new HalPropValueBuilder(
+ /* isAidl= */ true);
private static final int REQUEST_ID_1 = 1;
private static final int REQUEST_ID_2 = 1;
private final HalPropValue mHalPropValue = mPropValueBuilder.build(HVAC_TEMPERATURE_SET, 0);
private final AsyncGetSetRequest mGetVehicleRequest1 =
- new AsyncGetSetRequest(REQUEST_ID_1, mHalPropValue, /* timeoutInMs= */ 0);
+ new AsyncGetSetRequest(REQUEST_ID_1, mHalPropValue, /* timeoutUptimeMs= */ 0);
private final AsyncGetSetRequest mGetVehicleRequest2 =
- new AsyncGetSetRequest(REQUEST_ID_2, mHalPropValue, /* timeoutInMs= */ 0);
+ new AsyncGetSetRequest(REQUEST_ID_2, mHalPropValue, /* timeoutUptimeMs= */ 0);
private final AsyncGetSetRequest mSetVehicleRequest =
- new AsyncGetSetRequest(REQUEST_ID_1, mHalPropValue, /* timeoutInMs= */ 0);
+ new AsyncGetSetRequest(REQUEST_ID_1, mHalPropValue, /* timeoutUptimeMs= */ 0);
@Rule public final TestName mTestName = new TestName();
+ // Required for HandlerThread to work.
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
private VehicleHal mVehicleHal;
/** Hal services configurations */
@@ -250,6 +257,11 @@
@Before
public void setUp() throws Exception {
+ mHandlerThread = new HandlerThread("VehicleHalTest");
+ mHandlerThread.start();
+
+ mHandler = mHandlerThread.getThreadHandler();
+
when(mVehicle.getHalPropValueBuilder()).thenReturn(mPropValueBuilder);
when(mVehicle.newSubscriptionClient(any())).thenReturn(mSubscriptionClient);
@@ -273,6 +285,11 @@
}
}
+ @After
+ public void tearDown() {
+ mHandlerThread.quitSafely();
+ }
+
@Test
public void isPropertySubscribable_true() {
VehiclePropConfig continuousPropConfig = new VehiclePropConfig();
@@ -514,22 +531,6 @@
@Test
public void testInitGetAllProdConfigsException_skipSetupInit() throws Exception {
- // Initialize PowerHAL service with a READ_WRITE and ON_CHANGE property
- when(mPowerHalService.getAllSupportedProperties()).thenReturn(
- new int[]{SOME_READ_ON_CHANGE_PROPERTY});
-
- // Initialize PropertyHAL service with a READ_WRITE and STATIC property
- when(mPropertyHalService.getAllSupportedProperties()).thenReturn(
- new int[]{SOME_READ_WRITE_STATIC_PROPERTY});
-
- // Initialize the remaining services with empty properties
- when(mInputHalService.getAllSupportedProperties()).thenReturn(new int[0]);
- when(mVmsHalService.getAllSupportedProperties()).thenReturn(new int[0]);
- when(mUserHalService.getAllSupportedProperties()).thenReturn(new int[0]);
- when(mDiagnosticHalService.getAllSupportedProperties()).thenReturn(new int[0]);
- when(mTimeHalService.getAllSupportedProperties()).thenReturn(new int[0]);
- when(mClusterHalService.getAllSupportedProperties()).thenReturn(new int[0]);
-
// Throw exception.
when(mVehicle.getAllPropConfigs()).thenThrow(new RemoteException());
@@ -725,11 +726,11 @@
public void testSubscribeProperty_enableVur() throws Exception {
int[] areaIds = new int[] {AREA_ID_1};
HalSubscribeOptions option = new HalSubscribeOptions(CONTINUOUS_PROPERTY,
- areaIds, ANY_SAMPLING_RATE_1, /*enableVariableUpdateRate=*/ true);
+ areaIds, ANY_SAMPLING_RATE_1, /* enableVariableUpdateRate= */ true);
mVehicleHal.subscribeProperty(mPropertyHalService, List.of(option));
SubscribeOptions expectedOptions = createSubscribeOptions(CONTINUOUS_PROPERTY,
- ANY_SAMPLING_RATE_1, areaIds, /*enableVur=*/ true);
+ ANY_SAMPLING_RATE_1, areaIds, /* enableVur= */ true);
verify(mSubscriptionClient).subscribe(eq(new SubscribeOptions[]{expectedOptions}));
}
@@ -739,11 +740,11 @@
when(mFeatureFlags.variableUpdateRate()).thenReturn(false);
int[] areaIds = new int[] {AREA_ID_1};
HalSubscribeOptions option = new HalSubscribeOptions(CONTINUOUS_PROPERTY,
- areaIds, ANY_SAMPLING_RATE_1, /*enableVariableUpdateRate=*/ true);
+ areaIds, ANY_SAMPLING_RATE_1, /* enableVariableUpdateRate= */ true);
mVehicleHal.subscribeProperty(mPropertyHalService, List.of(option));
SubscribeOptions expectedOptions = createSubscribeOptions(CONTINUOUS_PROPERTY,
- ANY_SAMPLING_RATE_1, areaIds, /*enableVur=*/ false);
+ ANY_SAMPLING_RATE_1, areaIds, /* enableVur= */ false);
verify(mSubscriptionClient).subscribe(eq(new SubscribeOptions[]{expectedOptions}));
}
@@ -752,19 +753,19 @@
public void testSubscribeProperty_enableVur_vurChange() throws Exception {
int[] areaIds = new int[] {AREA_ID_1};
HalSubscribeOptions option = new HalSubscribeOptions(CONTINUOUS_PROPERTY,
- areaIds, ANY_SAMPLING_RATE_1, /*enableVariableUpdateRate=*/ true);
+ areaIds, ANY_SAMPLING_RATE_1, /* enableVariableUpdateRate= */ true);
mVehicleHal.subscribeProperty(mPropertyHalService, List.of(option));
verify(mSubscriptionClient).subscribe(eq(new SubscribeOptions[]{createSubscribeOptions(
- CONTINUOUS_PROPERTY, ANY_SAMPLING_RATE_1, areaIds, /*enableVur=*/ true)}));
+ CONTINUOUS_PROPERTY, ANY_SAMPLING_RATE_1, areaIds, /* enableVur= */ true)}));
HalSubscribeOptions option2 = new HalSubscribeOptions(CONTINUOUS_PROPERTY,
- areaIds, ANY_SAMPLING_RATE_1, /*enableVariableUpdateRate=*/ false);
+ areaIds, ANY_SAMPLING_RATE_1, /* enableVariableUpdateRate= */ false);
clearInvocations(mSubscriptionClient);
mVehicleHal.subscribeProperty(mPropertyHalService, List.of(option2));
verify(mSubscriptionClient).subscribe(eq(new SubscribeOptions[]{createSubscribeOptions(
- CONTINUOUS_PROPERTY, ANY_SAMPLING_RATE_1, areaIds, /*enableVur=*/ false)}));
+ CONTINUOUS_PROPERTY, ANY_SAMPLING_RATE_1, areaIds, /* enableVur= */ false)}));
clearInvocations(mSubscriptionClient);
// No option change.
@@ -777,11 +778,11 @@
public void testSubscribeProperty_enableVur_falseForOnChangeProperty() throws Exception {
int[] areaIds = new int[] {AREA_ID_1};
HalSubscribeOptions option = new HalSubscribeOptions(SOME_READ_ON_CHANGE_PROPERTY,
- areaIds, ANY_SAMPLING_RATE_1, /*enableVariableUpdateRate=*/ true);
+ areaIds, ANY_SAMPLING_RATE_1, /* enableVariableUpdateRate= */ true);
mVehicleHal.subscribeProperty(mPowerHalService, List.of(option));
SubscribeOptions expectedOptions = createSubscribeOptions(SOME_READ_ON_CHANGE_PROPERTY,
- ANY_SAMPLING_RATE_1, areaIds, /*enableVur=*/ false);
+ ANY_SAMPLING_RATE_1, areaIds, /* enableVur= */ false);
verify(mSubscriptionClient).subscribe(eq(new SubscribeOptions[]{expectedOptions}));
}
@@ -790,12 +791,12 @@
public void testSubscribeProperty_withResolution() throws Exception {
int[] areaIds = new int[] {AREA_ID_1};
HalSubscribeOptions option = new HalSubscribeOptions(CONTINUOUS_PROPERTY,
- areaIds, ANY_SAMPLING_RATE_1, /*enableVariableUpdateRate=*/ true,
- /*resolution*/ 1.0f);
+ areaIds, ANY_SAMPLING_RATE_1, /* enableVariableUpdateRate= */ true,
+ /* resolution= */ 1.0f);
mVehicleHal.subscribeProperty(mPropertyHalService, List.of(option));
SubscribeOptions expectedOptions = createSubscribeOptions(CONTINUOUS_PROPERTY,
- ANY_SAMPLING_RATE_1, areaIds, /*enableVur=*/ true, /*resolution*/ 1.0f);
+ ANY_SAMPLING_RATE_1, areaIds, /* enableVur= */ true, /* resolution= */ 1.0f);
verify(mSubscriptionClient).subscribe(eq(new SubscribeOptions[]{expectedOptions}));
}
@@ -805,12 +806,12 @@
when(mFeatureFlags.subscriptionWithResolution()).thenReturn(false);
int[] areaIds = new int[] {AREA_ID_1};
HalSubscribeOptions option = new HalSubscribeOptions(CONTINUOUS_PROPERTY,
- areaIds, ANY_SAMPLING_RATE_1, /*enableVariableUpdateRate=*/ true,
- /*resolution*/ 1.0f);
+ areaIds, ANY_SAMPLING_RATE_1, /* enableVariableUpdateRate= */ true,
+ /* resolution= */ 1.0f);
mVehicleHal.subscribeProperty(mPropertyHalService, List.of(option));
SubscribeOptions expectedOptions = createSubscribeOptions(CONTINUOUS_PROPERTY,
- ANY_SAMPLING_RATE_1, areaIds, /*enableVur=*/ true, /*resolution*/ 0.0f);
+ ANY_SAMPLING_RATE_1, areaIds, /* enableVur= */ true, /* resolution= */ 0.0f);
verify(mSubscriptionClient).subscribe(eq(new SubscribeOptions[]{expectedOptions}));
}
@@ -819,23 +820,23 @@
public void testSubscribeProperty_withResolution_resolutionChange() throws Exception {
int[] areaIds = new int[] {AREA_ID_1};
HalSubscribeOptions option = new HalSubscribeOptions(CONTINUOUS_PROPERTY,
- areaIds, ANY_SAMPLING_RATE_1, /*enableVariableUpdateRate=*/ true,
- /*resolution*/ 1.0f);
+ areaIds, ANY_SAMPLING_RATE_1, /* enableVariableUpdateRate= */ true,
+ /* resolution= */ 1.0f);
mVehicleHal.subscribeProperty(mPropertyHalService, List.of(option));
verify(mSubscriptionClient).subscribe(eq(new SubscribeOptions[]{createSubscribeOptions(
- CONTINUOUS_PROPERTY, ANY_SAMPLING_RATE_1, areaIds, /*enableVur=*/ true,
- /*resolution*/ 1.0f)}));
+ CONTINUOUS_PROPERTY, ANY_SAMPLING_RATE_1, areaIds, /* enableVur= */ true,
+ /* resolution*/ 1.0f)}));
HalSubscribeOptions option2 = new HalSubscribeOptions(CONTINUOUS_PROPERTY,
- areaIds, ANY_SAMPLING_RATE_1, /*enableVariableUpdateRate=*/ true,
- /*resolution*/ 0.1f);
+ areaIds, ANY_SAMPLING_RATE_1, /* enableVariableUpdateRate= */ true,
+ /* resolution= */ 0.1f);
clearInvocations(mSubscriptionClient);
mVehicleHal.subscribeProperty(mPropertyHalService, List.of(option2));
verify(mSubscriptionClient).subscribe(eq(new SubscribeOptions[]{createSubscribeOptions(
- CONTINUOUS_PROPERTY, ANY_SAMPLING_RATE_1, areaIds, /*enableVur=*/ true,
- /*resolution*/ 0.1f)}));
+ CONTINUOUS_PROPERTY, ANY_SAMPLING_RATE_1, areaIds, /* enableVur= */ true,
+ /* resolution= */ 0.1f)}));
clearInvocations(mSubscriptionClient);
// No option change.
@@ -848,12 +849,12 @@
public void testSubscribeProperty_withResolution_zeroForOnChangeProperty() throws Exception {
int[] areaIds = new int[] {AREA_ID_1};
HalSubscribeOptions option = new HalSubscribeOptions(SOME_READ_ON_CHANGE_PROPERTY,
- areaIds, ANY_SAMPLING_RATE_1, /*enableVariableUpdateRate=*/ true,
- /*resolution*/ 1.0f);
+ areaIds, ANY_SAMPLING_RATE_1, /* enableVariableUpdateRate= */ true,
+ /* resolution= */ 1.0f);
mVehicleHal.subscribeProperty(mPowerHalService, List.of(option));
SubscribeOptions expectedOptions = createSubscribeOptions(SOME_READ_ON_CHANGE_PROPERTY,
- ANY_SAMPLING_RATE_1, areaIds, /*enableVur=*/ false, /*resolution*/ 0.0f);
+ ANY_SAMPLING_RATE_1, areaIds, /* enableVur= */ false, /* resolution= */ 0.0f);
verify(mSubscriptionClient).subscribe(eq(new SubscribeOptions[]{expectedOptions}));
}
@@ -1329,7 +1330,7 @@
// Act
HalPropValue actual = mVehicleHal.getIfSupportedOrFail(SOME_READ_ON_CHANGE_PROPERTY,
- /* numberOfRetries= */ 1);
+ /* maxRetries= */ 1);
// Assert
assertThat(actual).isEqualTo(propValue);
@@ -1338,7 +1339,7 @@
@Test
public void testGetIfSupportedOrFail_unsupportedProperty() {
HalPropValue actual = mVehicleHal.getIfSupportedOrFail(UNSUPPORTED_PROPERTY,
- /* numberOfRetries= */ 1);
+ /* maxRetries= */ 1);
assertThat(actual).isNull();
}
@@ -1349,7 +1350,7 @@
new ServiceSpecificException(StatusCode.INTERNAL_ERROR));
assertThrows(IllegalStateException.class, () -> mVehicleHal.getIfSupportedOrFail(
- SOME_READ_ON_CHANGE_PROPERTY, /* numberOfRetries= */ 1));
+ SOME_READ_ON_CHANGE_PROPERTY, /* maxRetries= */ 1));
}
@Test
@@ -1361,7 +1362,7 @@
// Retry once.
HalPropValue actual = mVehicleHal.getIfSupportedOrFail(SOME_READ_ON_CHANGE_PROPERTY,
- /* numberOfRetries= */ 2);
+ /* maxRetries= */ 2);
assertThat(actual).isEqualTo(propValue);
verify(mVehicle, times(2)).get(propValue);
@@ -1374,15 +1375,13 @@
new ServiceSpecificException(StatusCode.TRY_AGAIN));
assertThrows(IllegalStateException.class, () -> mVehicleHal.getIfSupportedOrFail(
- SOME_READ_ON_CHANGE_PROPERTY, /* numberOfRetries= */ 2));
+ SOME_READ_ON_CHANGE_PROPERTY, /* maxRetries= */ 2));
}
@Test
public void testGetIfSupportedOrFailForEarlyStage_skipSetupInit() throws Exception {
// Skip setup init() because this function would be called before init() is called.
// Initialize PowerHAL service with a READ_WRITE and ON_CHANGE property
- when(mPowerHalService.getAllSupportedProperties()).thenReturn(
- new int[]{SOME_READ_ON_CHANGE_PROPERTY});
mConfigs.add(getPowerHalConfig());
var halPropConfigs = toHalPropConfigs(mConfigs);
when(mVehicle.getAllPropConfigs()).thenReturn(halPropConfigs);
@@ -1394,7 +1393,7 @@
// Act
HalPropValue actual = mVehicleHal.getIfSupportedOrFailForEarlyStage(
- SOME_READ_ON_CHANGE_PROPERTY, /* numberOfRetries= */ 1);
+ SOME_READ_ON_CHANGE_PROPERTY, /* maxRetries= */ 1);
// Assert
assertThat(actual).isEqualTo(propValue);
@@ -1543,7 +1542,7 @@
}
// A test class to class protected method of VehicleHal.
- private class VehicleHalTestClass extends VehicleHal {
+ private static final class VehicleHalTestClass extends VehicleHal {
VehicleHalTestClass(Context context,
PowerHalService powerHal,
PropertyHalService propertyHal,
@@ -1787,11 +1786,12 @@
StringWriter writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
- HalPropValue propValue = mPropValueBuilder.build(/*propId=*/0, /*areaId=*/0, "some_value");
+ HalPropValue propValue = mPropValueBuilder.build(/* prop= */0, /* areaId= */0,
+ "some_value");
when(mVehicle.get(any(HalPropValue.class))).thenReturn(propValue);
// Act
- mVehicleHal.dumpPropertyValueByCommand(printWriter, /* propId= */ -1, /* areaId= */-1);
+ mVehicleHal.dumpPropertyValueByCommand(printWriter, /* propertyId= */ -1, /* areaId= */ -1);
// Assert
assertThat(writer.toString()).contains("string: some_value");
@@ -1803,7 +1803,8 @@
StringWriter writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
- HalPropValue propValue = mPropValueBuilder.build(/*propId=*/0, /*areaId=*/0, "some_value");
+ HalPropValue propValue = mPropValueBuilder.build(/* prop= */0, /* areaId= */0,
+ "some_value");
when(mVehicle.get(any(HalPropValue.class))).thenReturn(propValue);
// Act
@@ -1835,12 +1836,13 @@
StringWriter writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
- HalPropValue propValue = mPropValueBuilder.build(/*propId=*/0, /*areaId=*/0, "some_value");
+ HalPropValue propValue = mPropValueBuilder.build(/* prop= */ 0, /* areaId= */ 0,
+ "some_value");
when(mVehicle.get(any(HalPropValue.class))).thenReturn(propValue);
// Act
mVehicleHal.dumpPropertyValueByCommand(printWriter, SOME_READ_ON_CHANGE_PROPERTY,
- /* areaId= */-1);
+ /* areaId= */ -1);
// Assert
assertThat(writer.toString()).contains("string: some_value");
@@ -1865,7 +1867,8 @@
StringWriter writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
- HalPropValue propValue = mPropValueBuilder.build(/*propId=*/0, /*areaId=*/0, "some_value");
+ HalPropValue propValue = mPropValueBuilder.build(/* prop= */ 0, /* areaId= */ 0,
+ "some_value");
when(mVehicle.get(any(HalPropValue.class))).thenReturn(propValue);
// Act
@@ -1890,7 +1893,7 @@
// Act
mVehicleHal.dumpPropertyValueByCommand(printWriter, SOME_READ_ON_CHANGE_PROPERTY,
- /* areaId= */-1);
+ /* areaId= */ -1);
// Assert
assertThat(writer.toString()).contains("Can not get property value");
@@ -1918,7 +1921,7 @@
// Act
mVehicleHal.dumpPropertyValueByCommand(printWriter, SOME_READ_ON_CHANGE_PROPERTY,
- /* areaId= */-1);
+ /* areaId= */ -1);
// Assert
assertThat(writer.toString()).contains("Can not get property value");
@@ -1949,13 +1952,13 @@
StringWriter writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
- HalPropValue propValue = mPropValueBuilder.build(/*propId=*/0, /*areaId=*/0, "some_value");
- when(mVehicle.get(any(HalPropValue.class))).thenReturn(propValue);
+ HalPropValue propValue = mPropValueBuilder.build(/* prop= */ 0, /* areaId= */ 0,
+ "some_value");
// Act
// Note here we cannot use UNSUPPORTED_PROPERTY because its value -1 has special meaning
// in this function call.
- mVehicleHal.dumpPropertyValueByCommand(printWriter, 0, /* areaId= */-1);
+ mVehicleHal.dumpPropertyValueByCommand(printWriter, 0, /* areaId= */ -1);
// Assert
assertThat(writer.toString()).contains("not supported by HAL");
@@ -2344,14 +2347,14 @@
}
private SubscribeOptions createSubscribeOptions(int propId, float sampleRateHz, int[] areaIds) {
- return createSubscribeOptions(propId, sampleRateHz, areaIds, /*enableVur=*/ false,
- /*resolution*/ 0.0f);
+ return createSubscribeOptions(propId, sampleRateHz, areaIds, /* enableVur= */ false,
+ /* resolution= */ 0.0f);
}
private SubscribeOptions createSubscribeOptions(int propId, float sampleRateHz, int[] areaIds,
boolean enableVur) {
return createSubscribeOptions(propId, sampleRateHz, areaIds, enableVur,
- /*resolution*/ 0.0f);
+ /* resolution= */ 0.0f);
}
private SubscribeOptions createSubscribeOptions(int propId, float sampleRateHz, int[] areaIds,
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
index 6480912..792f02d 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/VmsHalServiceTest.java
@@ -1063,7 +1063,7 @@
@Test
public void testDumpMetrics_NonVendorProperty() throws Exception {
- HalPropValue vehicleProp = mPropValueBuilder.build(/*propId=*/0, /*areaId=*/0,
+ HalPropValue vehicleProp = mPropValueBuilder.build(/* prop= */ 0, /* areaId= */ 0,
toByteArray(PAYLOAD_AS_LIST));
when(mVehicleHal.get(anyInt())).thenReturn(vehicleProp);
@@ -1084,7 +1084,7 @@
metricsPropertyId);
setUp();
- HalPropValue metricsProperty = mPropValueBuilder.build(/*propId=*/0, /*areaId=*/0,
+ HalPropValue metricsProperty = mPropValueBuilder.build(/* prop= */ 0, /* areaId= */ 0,
toByteArray(PAYLOAD_AS_LIST));
when(mVehicleHal.get(metricsPropertyId)).thenReturn(metricsProperty);
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/fakevhal/FakeVehicleStubUnitTest.java b/tests/carservice_unit_test/src/com/android/car/hal/fakevhal/FakeVehicleStubUnitTest.java
index 6efc881..0e41f95 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/fakevhal/FakeVehicleStubUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/fakevhal/FakeVehicleStubUnitTest.java
@@ -49,8 +49,11 @@
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.util.SparseArray;
+import androidx.test.filters.SmallTest;
+
import com.android.car.IVehicleDeathRecipient;
import com.android.car.VehicleStub;
import com.android.car.VehicleStub.AsyncGetSetRequest;
@@ -83,6 +86,7 @@
import java.util.ArrayList;
import java.util.List;
+@SmallTest
@RunWith(MockitoJUnitRunner.class)
public class FakeVehicleStubUnitTest {
@@ -151,6 +155,10 @@
@Rule
public final Expect expect = Expect.create();
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
@Before
public void setup() throws Exception {
@@ -293,7 +301,7 @@
customFileList.addAll(createFilenameList(invalidJsonString));
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.INFO_FUEL_TYPE, /* areaId= */ 0);
+ .build(/* prop= */ VehicleProperty.INFO_FUEL_TYPE, /* areaId= */ 0);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -308,10 +316,9 @@
public void testGetMethodPropIdNotSupported() throws Exception {
// Mock config files parsing results to be empty.
when(mParser.parseJsonConfig(any(InputStream.class))).thenReturn(new SparseArray<>());
- when(mParser.parseJsonConfig(any(File.class))).thenReturn(new SparseArray<>());
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.INFO_FUEL_TYPE, /* areaId= */ 0);
+ .build(/* prop= */ VehicleProperty.INFO_FUEL_TYPE, /* areaId= */ 0);
// Create a FakeVehicleStub instance.
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub, mParser,
new ArrayList<>());
@@ -334,7 +341,7 @@
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.ANDROID_EPOCH_TIME, /* areaId= */ 0);
+ .build(/* prop= */ VehicleProperty.ANDROID_EPOCH_TIME, /* areaId= */ 0);
// Create a FakeVehicleStub instance.
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -354,7 +361,7 @@
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.INFO_FUEL_CAPACITY, /* areaId= */ 123);
+ .build(/* prop= */ VehicleProperty.INFO_FUEL_CAPACITY, /* areaId= */ 123);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -378,7 +385,7 @@
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.DISPLAY_BRIGHTNESS, /* areaId= */ 1);
+ .build(/* prop= */ VehicleProperty.DISPLAY_BRIGHTNESS, /* areaId= */ 1);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -399,7 +406,7 @@
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.INFO_FUEL_TYPE, /* areaId= */ 123);
+ .build(/* prop= */ VehicleProperty.INFO_FUEL_TYPE, /* areaId= */ 123);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -420,7 +427,7 @@
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.WINDOW_POS, /* areaId= */ WINDOW_1_LEFT);
+ .build(/* prop= */ VehicleProperty.WINDOW_POS, /* areaId= */ WINDOW_1_LEFT);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -444,7 +451,7 @@
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.WINDOW_POS, /* areaId= */ WINDOW_1_LEFT);
+ .build(/* prop= */ VehicleProperty.WINDOW_POS, /* areaId= */ WINDOW_1_LEFT);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -487,7 +494,7 @@
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.SEAT_BELT_BUCKLED, /* areaId= */ 0);
+ .build(/* prop= */ VehicleProperty.SEAT_BELT_BUCKLED, /* areaId= */ 0);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -511,7 +518,7 @@
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ HVAC_ALL);
+ .build(/* prop= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ HVAC_ALL);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -528,7 +535,7 @@
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ SEAT_1_LEFT);
+ .build(/* prop= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ SEAT_1_LEFT);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -553,7 +560,7 @@
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ HVAC_LEFT);
+ .build(/* prop= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ HVAC_LEFT);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -581,7 +588,7 @@
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ HVAC_LEFT);
+ .build(/* prop= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ HVAC_LEFT);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -611,7 +618,7 @@
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ HVAC_ALL);
+ .build(/* prop= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ HVAC_ALL);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -626,7 +633,7 @@
+ "\"defaultValue\": {\"int32Values\": [\"FuelType::FUEL_TYPE_UNLEADED\"]}}]}";
List<File> customFileList = createFilenameList(jsonString);
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.INFO_FUEL_TYPE, /* areaId= */ 123);
+ .build(/* prop= */ VehicleProperty.INFO_FUEL_TYPE, /* areaId= */ 123);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
AsyncGetSetRequest getRequest = new AsyncGetSetRequest(/* serviceRequestId= */ 0,
@@ -651,7 +658,7 @@
+ "\"access\": \"VehiclePropertyAccess::WRITE\"}]}";
List<File> customFileList = createFilenameList(jsonString);
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.ANDROID_EPOCH_TIME, /* areaId= */ 0);
+ .build(/* prop= */ VehicleProperty.ANDROID_EPOCH_TIME, /* areaId= */ 0);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
AsyncGetSetRequest getRequest = new AsyncGetSetRequest(/* serviceRequestId= */ 0,
@@ -673,7 +680,7 @@
String jsonString = "{\"properties\": [" + PROPERTY_CONFIG_STRING_HVAC_POWER_OFF + "]}";
List<File> customFileList = createFilenameList(jsonString);
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ SEAT_1_LEFT);
+ .build(/* prop= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ SEAT_1_LEFT);
AsyncGetSetRequest getRequest = new AsyncGetSetRequest(/* serviceRequestId= */ 0,
requestPropValue, DEFAULT_TIMEOUT);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
@@ -718,9 +725,9 @@
+ "\"access\": \"VehiclePropertyAccess::WRITE\"}]}";
List<File> customFileList = createFilenameList(jsonString);
HalPropValue requestPropValue1 = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.ANDROID_EPOCH_TIME, /* areaId= */ 0);
+ .build(/* prop= */ VehicleProperty.ANDROID_EPOCH_TIME, /* areaId= */ 0);
HalPropValue requestPropValue2 = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.INFO_FUEL_TYPE, /* areaId= */ 123);
+ .build(/* prop= */ VehicleProperty.INFO_FUEL_TYPE, /* areaId= */ 123);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
AsyncGetSetRequest getRequest1 = new AsyncGetSetRequest(/* serviceRequestId= */ 0,
@@ -754,8 +761,8 @@
+ "\"areas\": [{\"areaId\": \"Constants::SEAT_1_LEFT\"}]}]}";
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
- HalPropValue requestPropValue = new HalPropValueBuilder(/* isAdil= */ true)
- .build(/* propId= */ 123456, /* areaId= */ 0);
+ HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
+ .build(/* prop= */ 123456, /* areaId= */ 0);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -968,7 +975,7 @@
List<File> customFileList = createFilenameList(jsonString);
// Create a request prop value.
HalPropValue requestPropValue = new HalPropValueBuilder(/* isAidl= */ true)
- .build(/* propId= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ SEAT_1_LEFT);
+ .build(/* prop= */ VehicleProperty.HVAC_FAN_SPEED, /* areaId= */ SEAT_1_LEFT);
FakeVehicleStub fakeVehicleStub = new FakeVehicleStub(mMockRealVehicleStub,
new FakeVhalConfigParser(), customFileList);
@@ -1583,12 +1590,14 @@
VehicleStub.SubscriptionClient client = fakeVehicleStub.newSubscriptionClient(callback);
client.subscribe(options);
- verify(callback, timeout(100).atLeast(10)).onPropertyEvent(any(ArrayList.class));
+ // 500ms should generate 2 * 20 = 40 events, check for 10 events to be safe.
+ verify(callback, timeout(200).atLeast(10)).onPropertyEvent(any(ArrayList.class));
client.unsubscribe(VehicleProperty.FUEL_LEVEL);
clearInvocations(callback);
- verify(callback, timeout(100).atLeast(5)).onPropertyEvent(any(ArrayList.class));
+ // 200ms should generate 20 events, check for 5 events to be safe.
+ verify(callback, timeout(200).atLeast(5)).onPropertyEvent(any(ArrayList.class));
}
@Test
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/fakevhal/FakeVhalConfigParserUnitTest.java b/tests/carservice_unit_test/src/com/android/car/hal/fakevhal/FakeVhalConfigParserUnitTest.java
index 4326ed1..236c161 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/fakevhal/FakeVhalConfigParserUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/fakevhal/FakeVhalConfigParserUnitTest.java
@@ -35,22 +35,20 @@
import android.hardware.automotive.vehicle.VehicleUnit;
import android.util.SparseArray;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
-import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
-import org.junit.runner.RunWith;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.InputStream;
-@RunWith(AndroidJUnit4.class)
+@SmallTest
public class FakeVhalConfigParserUnitTest {
private static final int DOOR_1_LEFT = VehicleAreaDoor.ROW_1_LEFT;
- private static final int SEAT_1_LEFT = VehicleAreaSeat.ROW_1_LEFT;
private static final int WHEEL_FRONT_LEFT = VehicleAreaWheel.LEFT_FRONT;
private FakeVhalConfigParser mFakeVhalConfigParser;
@@ -65,11 +63,8 @@
InputStream tempFileIS =
new FileInputStream(createTempFileWithContent(/* fileContent= */ ""));
- IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ assertThrows(IOException.class,
() -> mFakeVhalConfigParser.parseJsonConfig(tempFileIS));
-
- assertThat(thrown).hasMessageThat().contains("This file does not contain a valid "
- + "JSONObject.");
}
@Test
@@ -103,6 +98,17 @@
}
@Test
+ public void testConfigFileInvalidJsonKey() throws Exception {
+ String jsonString = "{[]: 123}";
+ File tempFile = createTempFileWithContent(jsonString);
+
+ var thrown = assertThrows(IllegalArgumentException.class, () ->
+ mFakeVhalConfigParser.parseJsonConfig(tempFile));
+
+ assertThat(thrown).hasMessageThat().contains("Invalid json syntax");
+ }
+
+ @Test
public void testConfigFileRootIsNotArray() throws Exception {
String jsonString = "{\"properties\": 123}";
File tempFile = createTempFileWithContent(jsonString);
@@ -128,15 +134,12 @@
@Test
public void testParseEachPropertyJsonObjectIsEmpty() throws Exception {
String jsonString = "{\"properties\": [{}]}";
- JSONObject jsonObject = new JSONObject(jsonString);
- Object propertyObject = jsonObject.optJSONArray("properties").optJSONObject(0);
File tempFile = createTempFileWithContent(jsonString);
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
mFakeVhalConfigParser.parseJsonConfig(tempFile));
- assertThat(thrown).hasMessageThat().contains("The JSONObject " + propertyObject
- + " is empty.");
+ assertThat(thrown).hasMessageThat().contains("is empty");
}
@Test
@@ -160,7 +163,7 @@
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
mFakeVhalConfigParser.parseJsonConfig(tempFile));
- assertThat(thrown).hasMessageThat().contains(" doesn't have propId. PropId is required.");
+ assertThat(thrown).hasMessageThat().contains("PropId is required");
}
@Test
@@ -171,18 +174,18 @@
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
mFakeVhalConfigParser.parseJsonConfig(tempFile));
- assertThat(thrown).hasMessageThat().contains("property doesn't have a mapped value.");
+ assertThat(thrown).hasMessageThat().contains("property doesn't have a valid int value.");
}
@Test
public void testParsePropertyIdWithWrongValueType() throws Exception {
- String jsonString = "{\"properties\": [{\"property\": 12.3f}]}";
+ String jsonString = "{\"properties\": [{\"property\": 12.3}]}";
File tempFile = createTempFileWithContent(jsonString);
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
mFakeVhalConfigParser.parseJsonConfig(tempFile));
- assertThat(thrown).hasMessageThat().contains("property doesn't have a mapped int value.");
+ assertThat(thrown).hasMessageThat().contains("property doesn't have a valid int value.");
}
@Test
@@ -304,7 +307,8 @@
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
mFakeVhalConfigParser.parseJsonConfig(tempFile));
- assertThat(thrown).hasMessageThat().contains("configString doesn't have a mapped value.");
+ assertThat(thrown).hasMessageThat().contains(
+ "configString doesn't have a valid string value.");
}
@Test
@@ -341,7 +345,7 @@
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
mFakeVhalConfigParser.parseJsonConfig(tempFile));
- assertThat(thrown).hasMessageThat().contains("minSampleRate doesn't have a mapped float "
+ assertThat(thrown).hasMessageThat().contains("minSampleRate doesn't have a valid float "
+ "value.");
}
@@ -446,7 +450,7 @@
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
mFakeVhalConfigParser.parseJsonConfig(tempFile));
- assertThat(thrown).hasMessageThat().contains("configArray doesn't have a mapped JSONArray "
+ assertThat(thrown).hasMessageThat().contains("configArray doesn't have a valid JSONArray "
+ "value.");
}
@@ -459,7 +463,7 @@
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
mFakeVhalConfigParser.parseJsonConfig(tempFile));
- assertThat(thrown).hasMessageThat().contains("[123,null] doesn't have a mapped int value "
+ assertThat(thrown).hasMessageThat().contains("configArray doesn't have a valid int value "
+ "at index 1");
}
@@ -468,10 +472,8 @@
String jsonString = "{\"properties\": [{\"property\": 286261504, \"defaultValue\": null}]}";
File tempFile = createTempFileWithContent(jsonString);
- ConfigDeclaration configDeclaration = mFakeVhalConfigParser.parseJsonConfig(tempFile)
- .get(286261504);
-
- assertThat(configDeclaration.getInitialValue()).isEqualTo(null);
+ assertThrows(IllegalArgumentException.class, () ->
+ mFakeVhalConfigParser.parseJsonConfig(tempFile));
}
@Test
@@ -494,8 +496,8 @@
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
mFakeVhalConfigParser.parseJsonConfig(tempFile));
- assertThat(thrown).hasMessageThat().contains("Failed to parse the field name: int32Values "
- + "for defaultValueObject: {\"int32Values\":null}");
+ assertThat(thrown).hasMessageThat().contains(
+ "int32Values doesn't have a valid JSONArray value");
}
@Test
@@ -546,7 +548,7 @@
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
mFakeVhalConfigParser.parseJsonConfig(tempFile));
- assertThat(thrown).hasMessageThat().contains("areas doesn't have a mapped array value.");
+ assertThat(thrown).hasMessageThat().contains("areas doesn't have a valid JSONArray value.");
}
@Test
@@ -569,7 +571,7 @@
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
mFakeVhalConfigParser.parseJsonConfig(tempFile));
- assertThat(thrown).hasMessageThat().contains("The JSONObject {} is empty.");
+ assertThat(thrown).hasMessageThat().contains("is empty");
}
@Test
@@ -581,8 +583,7 @@
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
mFakeVhalConfigParser.parseJsonConfig(tempFile));
- assertThat(thrown).hasMessageThat().contains("{\"minInt32Value\":0} doesn't have areaId. "
- + "AreaId is required.");
+ assertThat(thrown).hasMessageThat().contains("doesn't have areaId. AreaId is required.");
}
@Test
@@ -833,19 +834,20 @@
assertThat(thrown).hasMessageThat().contains("Access field is not set for this property:");
assertThat(thrown).hasMessageThat().contains("ChangeMode field is not set for this "
+ "property:");
- assertThat(thrown).hasMessageThat().contains("Unable to parse JSON object:");
- assertThat(thrown).hasMessageThat().contains("at index 0");
+ assertThat(thrown).hasMessageThat().contains("Unable to parse property config at index 0");
assertThat(thrown).hasMessageThat().contains("properties array has an invalid JSON element "
+ "at index 1");
- assertThat(thrown).hasMessageThat().contains("The JSONObject {} is empty.");
+ assertThat(thrown).hasMessageThat().contains("is empty");
assertThat(thrown).hasMessageThat().contains("at index 2");
- assertThat(thrown).hasMessageThat().contains("doesn't have propId. PropId is required.");
+ assertThat(thrown).hasMessageThat().contains("PropId is required");
assertThat(thrown).hasMessageThat().contains("at index 3");
assertThat(thrown).hasMessageThat().contains("is not a valid class name.");
assertThat(thrown).hasMessageThat().contains("at index 4");
- assertThat(thrown).hasMessageThat().contains("doesn't have a mapped JSONArray value.");
+ assertThat(thrown).hasMessageThat().contains(
+ "configArray doesn't have a valid JSONArray value");
assertThat(thrown).hasMessageThat().contains("at index 5");
- assertThat(thrown).hasMessageThat().contains("Failed to parse the field name:");
+ assertThat(thrown).hasMessageThat().contains(
+ "int32Values doesn't have a valid JSONArray value");
assertThat(thrown).hasMessageThat().contains("at index 6");
assertThat(thrown).hasMessageThat().contains("doesn't have areaId. AreaId is required.");
assertThat(thrown).hasMessageThat().contains("at index 7");
@@ -873,6 +875,73 @@
.isEqualTo(WHEEL_FRONT_LEFT);
}
+ @Test
+ public void testParseJsonConfig_areaAccessInheritFromGlobal() throws Exception {
+ // Create a JSON config object with all field values set.
+ String jsonString = "{\"properties\": [{"
+ + " \"property\": 12345,"
+ + " \"areas\": [{"
+ + " \"minInt32Value\": 0,"
+ + " \"maxInt32Value\": 10,"
+ + " \"areaId\": 54321"
+ + " }],"
+ + " \"access\": \"VehiclePropertyAccess::READ\","
+ + " \"changeMode\": \"VehiclePropertyChangeMode::STATIC\""
+ + " }]}";
+ File tempFile = createTempFileWithContent(jsonString);
+
+ ConfigDeclaration configDeclaration = mFakeVhalConfigParser.parseJsonConfig(tempFile)
+ .get(12345);
+
+ assertThat(configDeclaration.getConfig().areaConfigs[0].access).isEqualTo(
+ VehiclePropertyAccess.READ);
+ }
+
+ @Test
+ public void testParseJsonConfig_areaAccessOverwritesGlobal() throws Exception {
+ // Create a JSON config object with all field values set.
+ String jsonString = "{\"properties\": [{"
+ + " \"property\": 12345,"
+ + " \"areas\": [{"
+ + " \"minInt32Value\": 0,"
+ + " \"maxInt32Value\": 10,"
+ + " \"access\": \"VehiclePropertyAccess::READ_WRITE\","
+ + " \"areaId\": 54321"
+ + " }],"
+ + " \"access\": \"VehiclePropertyAccess::READ\","
+ + " \"changeMode\": \"VehiclePropertyChangeMode::STATIC\""
+ + " }]}";
+ File tempFile = createTempFileWithContent(jsonString);
+
+ ConfigDeclaration configDeclaration = mFakeVhalConfigParser.parseJsonConfig(tempFile)
+ .get(12345);
+
+ assertThat(configDeclaration.getConfig().areaConfigs[0].access).isEqualTo(
+ VehiclePropertyAccess.READ_WRITE);
+ }
+
+ @Test
+ public void testParseJsonConfig_areaAccessUseDefault() throws Exception {
+ // Create a JSON config object with all field values set.
+ String jsonString = "{\"properties\": [{"
+ + " \"property\": \"VehicleProperty::WHEEL_TICK\","
+ + " \"areas\": [{"
+ + " \"minInt32Value\": 0,"
+ + " \"maxInt32Value\": 10,"
+ + " \"areaId\": 54321"
+ + " }],"
+ + " \"changeMode\": \"VehiclePropertyChangeMode::STATIC\""
+ + " }]}";
+ File tempFile = createTempFileWithContent(jsonString);
+
+ ConfigDeclaration configDeclaration = mFakeVhalConfigParser.parseJsonConfig(tempFile)
+ .get(VehicleProperty.WHEEL_TICK);
+
+ // WHEEL_TICK default access is READ.
+ assertThat(configDeclaration.getConfig().areaConfigs[0].access).isEqualTo(
+ VehiclePropertyAccess.READ);
+ }
+
private File createTempFileWithContent(String fileContent) throws Exception {
File tempFile = File.createTempFile("test", ".json");
tempFile.deleteOnExit();
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/property/HalPropertyDebugUtilsUnitTest.java b/tests/carservice_unit_test/src/com/android/car/hal/property/HalPropertyDebugUtilsUnitTest.java
index 2a0b1b2..a0e1d1a 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/property/HalPropertyDebugUtilsUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/property/HalPropertyDebugUtilsUnitTest.java
@@ -126,7 +126,7 @@
@Test
public void testToAreaIdString_handlesUnknownAreaType() {
- assertThat(toAreaIdString(VehicleProperty.INVALID, /*areadId=*/
+ assertThat(toAreaIdString(VehicleProperty.INVALID, /*areaId=*/
0)).isEqualTo("UNKNOWN_AREA_ID(0x0)");
}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/property/PropertyHalServiceConfigsTest.java b/tests/carservice_unit_test/src/com/android/car/hal/property/PropertyHalServiceConfigsUnitTest.java
similarity index 96%
rename from tests/carservice_unit_test/src/com/android/car/hal/property/PropertyHalServiceConfigsTest.java
rename to tests/carservice_unit_test/src/com/android/car/hal/property/PropertyHalServiceConfigsUnitTest.java
index 6e1356a..841b030 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/property/PropertyHalServiceConfigsTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/property/PropertyHalServiceConfigsUnitTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,11 +38,10 @@
import android.hardware.automotive.vehicle.VehicleUnit;
import android.hardware.automotive.vehicle.VehicleVendorPermission;
import android.os.SystemClock;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.util.ArraySet;
import android.util.SparseArray;
-import androidx.test.runner.AndroidJUnit4;
-
import com.android.car.hal.HalPropValue;
import com.android.car.hal.HalPropValueBuilder;
import com.android.car.hal.property.PropertyHalServiceConfigs.CarSvcPropertyConfig;
@@ -55,8 +54,8 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -68,8 +67,7 @@
import java.util.List;
import java.util.Set;
-@RunWith(AndroidJUnit4.class)
-public class PropertyHalServiceConfigsTest extends AbstractExpectableTestCase {
+public final class PropertyHalServiceConfigsUnitTest extends AbstractExpectableTestCase {
private static final int VENDOR_PROPERTY_1 = 0x21e01111;
private static final int VENDOR_PROPERTY_2 = 0x21e01112;
private static final int VENDOR_PROPERTY_3 = 0x21e01113;
@@ -83,11 +81,14 @@
@Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().build();
+
@Mock
private Context mContext;
private PropertyHalServiceConfigs mPropertyHalServiceConfigs;
- private static final String TAG = PropertyHalServiceConfigsTest.class.getSimpleName();
private static final HalPropValueBuilder PROP_VALUE_BUILDER =
new HalPropValueBuilder(/*isAidl=*/true);
//payload test
@@ -134,6 +135,8 @@
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
mFakeFeatureFlags = new FakeFeatureFlagsImpl();
mFakeFeatureFlags.setFlag(Flags.FLAG_ANDROID_VIC_VEHICLE_PROPERTIES, true);
@@ -418,7 +421,7 @@
}
@Test
- public void testParseJsonConfig_validConfig() {
+ public void testParseJsonConfig_validConfig() throws Exception {
InputStream is = strToInputStream("""
{
'version': 1,
@@ -458,7 +461,7 @@
}
}
}
- """);
+ """);
CarSvcPropertyConfig expectedConfig = new CarSvcPropertyConfig();
expectedConfig.propertyId = 1234;
expectedConfig.halPropId = 2345;
@@ -466,8 +469,8 @@
expectedConfig.description = "DESCRIPTION";
expectedConfig.permissions = new PropertyPermissions.Builder()
.setReadPermission(new AnyOfPermissions(
- new SinglePermission("PERM1"),
- new SinglePermission("PERM2")
+ new SinglePermission("PERM1"),
+ new SinglePermission("PERM2")
))
.setWritePermission(new AllOfPermissions(
new SinglePermission("PERM1"),
@@ -486,7 +489,7 @@
}
@Test
- public void testParseJsonConfig_halPropIdDefaultEqualPropId() {
+ public void testParseJsonConfig_halPropIdDefaultEqualPropId() throws Exception {
InputStream is = strToInputStream("""
{
"version": 1,
@@ -502,7 +505,7 @@
}
}
}
- """);
+ """);
CarSvcPropertyConfig expectedConfig = new CarSvcPropertyConfig();
expectedConfig.propertyId = 1234;
expectedConfig.halPropId = 1234;
@@ -520,7 +523,7 @@
}
@Test
- public void testParseJsonConfig_dataFlags() {
+ public void testParseJsonConfig_dataFlags() throws Exception {
InputStream is = strToInputStream("""
{
"version": 1,
@@ -537,7 +540,7 @@
}
}
}
- """);
+ """);
CarSvcPropertyConfig expectedConfig = new CarSvcPropertyConfig();
expectedConfig.propertyId = 1234;
expectedConfig.halPropId = 1234;
@@ -557,7 +560,7 @@
}
@Test
- public void testParseJsonConfig_ignoreDeprecate() {
+ public void testParseJsonConfig_ignoreDeprecate() throws Exception {
InputStream is = strToInputStream("""
{
"version": 1,
@@ -579,7 +582,7 @@
}
}
}
- """);
+ """);
SparseArray<CarSvcPropertyConfig> configs = mPropertyHalServiceConfigs.parseJsonConfig(
is, /* path= */ "test");
@@ -599,7 +602,7 @@
}
@Test
- public void testParseJsonConfig_invalidJsonFormat() {
+ public void testParseJsonConfig_invalidJsonFormat() throws Exception {
InputStream is = strToInputStream("{");
assertThrows(IllegalArgumentException.class, () ->
@@ -607,7 +610,7 @@
}
@Test
- public void testParseJsonConfig_missingPropertyIdField() {
+ public void testParseJsonConfig_missingPropertyIdField() throws Exception {
InputStream is = strToInputStream("""
{
"version": 1,
@@ -623,14 +626,14 @@
}
}
}
- """);
+ """);
assertThrows(IllegalArgumentException.class, () ->
mPropertyHalServiceConfigs.parseJsonConfig(is, /* path= */ "test"));
}
@Test
- public void testParseJsonConfig_noReadOrWritePermission() {
+ public void testParseJsonConfig_noReadOrWritePermission() throws Exception {
InputStream is = strToInputStream("""
{
"version": 1,
@@ -643,7 +646,7 @@
}
}
}
- """);
+ """);
assertThrows(IllegalArgumentException.class, () ->
mPropertyHalServiceConfigs.parseJsonConfig(is, /* path= */ "test"));
diff --git a/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
index 52366de..56bda10 100644
--- a/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
@@ -52,7 +52,6 @@
import android.car.hardware.power.PowerComponent;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.car.test.mocks.JavaMockitoHelper;
-import android.car.test.util.TemporaryFile;
import android.car.testapi.FakeRefactoredCarPowerPolicyDaemon;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -84,10 +83,13 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.mockito.Mock;
import org.mockito.Spy;
+import java.io.File;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@@ -104,19 +106,22 @@
// A shorter value for use when the test is expected to time out
private static final long WAIT_WHEN_TIMEOUT_EXPECTED_MS = 100;
+ @Rule
+ public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
private final MockDisplayInterface mDisplayInterface = new MockDisplayInterface();
private final MockSystemStateInterface mSystemStateInterface = new MockSystemStateInterface();
private final ICarPowerPolicyDelegate mRefactoredPowerPolicyDaemon =
new FakeRefactoredCarPowerPolicyDaemon(
- /* fileKernelSilentMode= */ new TemporaryFile("KERNEL_SILENT_FILE"),
+ /* fileKernelSilentMode= */ new File("KERNEL_SILENT_FILE"),
/* customComponents= */ null);
@Spy
private final Context mContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
private final Executor mExecutor = mContext.getMainExecutor();
- private final TemporaryFile mComponentStateFile;
+ private File mComponentStateFile;
private MockedPowerHalService mPowerHal;
private SystemInterface mSystemInterface;
private CarPowerManagementService mService;
@@ -139,11 +144,11 @@
public CarPowerManagerUnitTest() throws Exception {
super(CarPowerManager.TAG);
- mComponentStateFile = new TemporaryFile("COMPONENT_STATE_FILE");
}
@Before
public void setUp() throws Exception {
+ mComponentStateFile = temporaryFolder.newFile("COMPONENT_STATE_FILE");
mPowerHal = new MockedPowerHalService(/*isPowerStateSupported=*/true,
/*isDeepSleepAllowed=*/true,
/*isHibernationAllowed=*/true,
@@ -456,18 +461,18 @@
doReturn(false).when(mResources).getBoolean(
R.bool.config_enablePassengerDisplayPowerSaving);
mPowerComponentHandler = new PowerComponentHandler(mContext, mSystemInterface,
- new AtomicFile(mComponentStateFile.getFile()));
+ new AtomicFile(mComponentStateFile));
IInterface powerPolicyDaemon;
if (Flags.carPowerPolicyRefactoring()) {
powerPolicyDaemon = mRefactoredPowerPolicyDaemon;
} else {
powerPolicyDaemon = mPowerPolicyDaemon;
}
- mService = new CarPowerManagementService(mContext, mResources, mPowerHal, mSystemInterface,
- mUserManager, mCarUserService, powerPolicyDaemon, mPowerComponentHandler,
- /* featureFlags= */ null, /* screenOffHandler= */ null,
- /* silentModeHwStatePath= */ null, /* silentModeKernelStatePath= */ null,
- /* bootReason= */ null);
+ mService = new CarPowerManagementService.Builder().setContext(mContext)
+ .setResources(mResources).setPowerHalService(mPowerHal)
+ .setSystemInterface(mSystemInterface).setUserManager(mUserManager)
+ .setCarUserService(mCarUserService).setPowerPolicyDaemon(powerPolicyDaemon)
+ .setPowerComponentHandler(mPowerComponentHandler).build();
mService.init();
if (Flags.carPowerPolicyRefactoring()) {
mService.initializePowerPolicy();
@@ -693,18 +698,11 @@
private final Semaphore mSleepWait = new Semaphore(0);
private final Semaphore mSleepExitWait = new Semaphore(0);
- @GuardedBy("sLock")
- private boolean mWakeupCausedByTimer = false;
-
@Override
public void shutdown() {
mShutdownWait.release();
}
- public void waitForShutdown(long timeoutMs) throws Exception {
- JavaMockitoHelper.await(mShutdownWait, timeoutMs);
- }
-
@Override
public boolean enterDeepSleep() {
return simulateSleep();
@@ -724,25 +722,14 @@
return true;
}
- public void waitForSleepEntryAndWakeup(long timeoutMs) throws Exception {
- JavaMockitoHelper.await(mSleepWait, timeoutMs);
- mSleepExitWait.release();
- }
-
@Override
public void scheduleActionForBootCompleted(Runnable action, Duration delay,
Duration delayRange) {}
@Override
public boolean isWakeupCausedByTimer() {
- Log.i(TAG, "isWakeupCausedByTimer:" + mWakeupCausedByTimer);
- return mWakeupCausedByTimer;
- }
-
- public void setWakeupCausedByTimer(boolean set) {
- synchronized (sLock) {
- mWakeupCausedByTimer = set;
- }
+ Log.i(TAG, "isWakeupCausedByTimer: false");
+ return false;
}
@Override
@@ -751,7 +738,7 @@
}
}
- private final class MockedPowerPolicyListener implements
+ private static final class MockedPowerPolicyListener implements
CarPowerManager.CarPowerPolicyListener {
private static final int MAX_LISTENER_WAIT_TIME_SEC = 1;
diff --git a/tests/carservice_unit_test/src/com/android/car/internal/property/InputSanitizationUtilsUnitTest.java b/tests/carservice_unit_test/src/com/android/car/internal/property/InputSanitizationUtilsUnitTest.java
index a7bcffa..7632966 100644
--- a/tests/carservice_unit_test/src/com/android/car/internal/property/InputSanitizationUtilsUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/internal/property/InputSanitizationUtilsUnitTest.java
@@ -194,6 +194,8 @@
assertThat(InputSanitizationUtils.sanitizeResolution(featureFlags,
config, 0.0f)).isEqualTo(0.0f);
assertThat(InputSanitizationUtils.sanitizeResolution(featureFlags,
+ config, 0.1f)).isEqualTo(0.1f);
+ assertThat(InputSanitizationUtils.sanitizeResolution(featureFlags,
config, 1.0f)).isEqualTo(1.0f);
assertThrows(IllegalArgumentException.class,
() -> InputSanitizationUtils.sanitizeResolution(featureFlags,
diff --git a/tests/carservice_unit_test/src/com/android/car/internal/user/UserHelperTest.java b/tests/carservice_unit_test/src/com/android/car/internal/user/UserHelperTest.java
index a82ca98..0218ac3 100644
--- a/tests/carservice_unit_test/src/com/android/car/internal/user/UserHelperTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/internal/user/UserHelperTest.java
@@ -70,7 +70,7 @@
/* enable= */ true);
verify(mUserManager).setUserRestriction(
- UserManager.DISALLOW_FACTORY_RESET, /* enable= */ true, UserHandle.of(userId));
+ UserManager.DISALLOW_FACTORY_RESET, /* value= */ true, UserHandle.of(userId));
}
@Test
diff --git a/tests/carservice_unit_test/src/com/android/car/internal/util/ArrayUtilsTest.java b/tests/carservice_unit_test/src/com/android/car/internal/util/ArrayUtilsTest.java
index 6439d03..ca613ac 100644
--- a/tests/carservice_unit_test/src/com/android/car/internal/util/ArrayUtilsTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/internal/util/ArrayUtilsTest.java
@@ -259,9 +259,9 @@
new Integer[]{1, 2, 3});
assertThat(ArrayUtils.appendElement(Integer.class, (Integer[]) null, 3)).isEqualTo(
new Integer[]{3});
- assertThat(ArrayUtils.appendElement(Integer.class, array, 2, /* allowDuplicate=*/ true))
+ assertThat(ArrayUtils.appendElement(Integer.class, array, 2, /* allowDuplicates=*/ true))
.isEqualTo(new Integer[]{1, 2, 2});
- assertThat(ArrayUtils.appendElement(Integer.class, array, 2, /* allowDuplicate=*/ false))
+ assertThat(ArrayUtils.appendElement(Integer.class, array, 2, /* allowDuplicates=*/ false))
.isEqualTo(new Integer[]{1, 2});
}
@@ -283,9 +283,9 @@
assertThat(ArrayUtils.appendInt(array, 3)).isEqualTo(new int[]{1, 2, 3});
assertThat(ArrayUtils.appendInt((int[]) null, 3)).isEqualTo(new int[]{3});
- assertThat(ArrayUtils.appendInt(array, 2, /* allowDuplicate=*/ true))
+ assertThat(ArrayUtils.appendInt(array, 2, /* allowDuplicates=*/ true))
.isEqualTo(new int[]{1, 2, 2});
- assertThat(ArrayUtils.appendInt(array, 2, /* allowDuplicate=*/ false))
+ assertThat(ArrayUtils.appendInt(array, 2, /* allowDuplicates=*/ false))
.isEqualTo(new int[]{1, 2});
assertThat(ArrayUtils.appendInt(null, 3)).isEqualTo(new int[]{3});
}
@@ -318,9 +318,9 @@
assertThat(ArrayUtils.appendLong(array, 3L)).isEqualTo(new long[]{1L, 2L, 3L});
assertThat(ArrayUtils.appendLong((long[]) null, 3)).isEqualTo(new long[]{3L});
- assertThat(ArrayUtils.appendLong(array, 2L, /* allowDuplicate=*/ true))
+ assertThat(ArrayUtils.appendLong(array, 2L, /* allowDuplicates=*/ true))
.isEqualTo(new long[]{1L, 2L, 2L});
- assertThat(ArrayUtils.appendLong(array, 2L, /* allowDuplicate=*/ false))
+ assertThat(ArrayUtils.appendLong(array, 2L, /* allowDuplicates=*/ false))
.isEqualTo(new long[]{1L, 2L});
assertThat(ArrayUtils.appendLong(null, 3L)).isEqualTo(new long[]{3L});
}
diff --git a/tests/carservice_unit_test/src/com/android/car/internal/util/ConcurrentUtilsTest.java b/tests/carservice_unit_test/src/com/android/car/internal/util/ConcurrentUtilsTest.java
index a6a09bf..f851161 100644
--- a/tests/carservice_unit_test/src/com/android/car/internal/util/ConcurrentUtilsTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/internal/util/ConcurrentUtilsTest.java
@@ -43,7 +43,7 @@
@Test
public void testWaitForFutureNoInterruptInterrupted() throws Exception {
- ExecutorService service = ConcurrentUtils.newFixedThreadPool(1, "test pool",
+ ConcurrentUtils.newFixedThreadPool(1, "test pool",
/* linuxThreadPriority= */ 0);
Future<Boolean> mockFuture = mock(Future.class);
when(mockFuture.get()).thenThrow(new InterruptedException());
diff --git a/tests/carservice_unit_test/src/com/android/car/occupantconnection/CarRemoteDeviceServiceTest.java b/tests/carservice_unit_test/src/com/android/car/occupantconnection/CarRemoteDeviceServiceTest.java
index b44f256..24a670b 100644
--- a/tests/carservice_unit_test/src/com/android/car/occupantconnection/CarRemoteDeviceServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/occupantconnection/CarRemoteDeviceServiceTest.java
@@ -34,9 +34,11 @@
import static android.car.VehicleAreaSeat.SEAT_ROW_2_RIGHT;
import static android.car.test.mocks.AndroidMockitoHelper.mockContextCreateContextAsUser;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_INVISIBLE;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
+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.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_VISIBLE;
-import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING;
import static com.android.car.occupantconnection.CarRemoteDeviceService.INITIAL_APP_STATE;
import static com.android.car.occupantconnection.CarRemoteDeviceService.INITIAL_OCCUPANT_ZONE_STATE;
@@ -212,7 +214,7 @@
OccupantZoneInfo peerZone = new OccupantZoneInfo(/* zoneId= */ 0,
OCCUPANT_TYPE_DRIVER, SEAT_ROW_1_LEFT);
- PerUserInfo peerUserInfo = mockPerUserInfo(USER_ID, peerZone);
+ mockPerUserInfo(USER_ID, peerZone);
// The BroadcastReceiver in the peerUserInfo is a mock and will do nothing when calling
// peerUserInfo.receiver.onReceive(), so remove it from the map. When mService.init() is
// called, because the map doesn't have the PerUserInfo, it will create a real
@@ -226,7 +228,7 @@
mService.init();
mService.registerStateCallback(PACKAGE_NAME, mCallback);
// Get the PerUserInfo containing the real BroadcastReceiver.
- peerUserInfo = mPerUserInfoMap.get(USER_ID);
+ PerUserInfo peerUserInfo = mPerUserInfoMap.get(USER_ID);
// Pretend that the peer app is installed in the beginning.
ClientId peerClient = new ClientId(peerZone, USER_ID, PACKAGE_NAME);
@@ -265,7 +267,7 @@
OccupantZoneInfo peerZone = new OccupantZoneInfo(/* zoneId= */ 0,
OCCUPANT_TYPE_DRIVER, SEAT_ROW_1_LEFT);
- PerUserInfo peerUserInfo = mockPerUserInfo(USER_ID, peerZone);
+ mockPerUserInfo(USER_ID, peerZone);
// The BroadcastReceiver in the peerUserInfo is a mock and will do nothing when calling
// peerUserInfo.receiver.onReceive(), so remove it from the map. When mService.init() is
// called, because the map doesn't have the PerUserInfo, it will create a real
@@ -279,7 +281,7 @@
mService.init();
mService.registerStateCallback(PACKAGE_NAME, mCallback);
// Get the PerUserInfo containing the real BroadcastReceiver.
- peerUserInfo = mPerUserInfoMap.get(USER_ID);
+ PerUserInfo peerUserInfo = mPerUserInfoMap.get(USER_ID);
// Pretend that the peer app is installed in the beginning.
ClientId peerClient = new ClientId(peerZone, USER_ID, PACKAGE_NAME);
@@ -685,6 +687,52 @@
}
@Test
+ public void testUserBecameVisible() {
+ UserLifecycleListener[] userLifecycleListeners = new UserLifecycleListener[1];
+ doAnswer((invocation) -> {
+ Object[] args = invocation.getArguments();
+ userLifecycleListeners[0] = (UserLifecycleListener) args[1];
+ return null;
+ }).when(mUserService).addUserLifecycleListener(any(), any());
+
+ mService.init();
+ mOccupantZoneStateMap.put(mOccupantZone, FLAG_OCCUPANT_ZONE_POWER_ON);
+
+ mockPerUserInfo(USER_ID, mOccupantZone);
+ // Remove the item added by previous line, then check whether it can be added back
+ // after onEvent().
+ mPerUserInfoMap.remove(USER_ID);
+ UserLifecycleEvent event = new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_VISIBLE,
+ /* from= */ USER_ID, /* to= */ USER_ID);
+ userLifecycleListeners[0].onEvent(event);
+
+ assertThat(mPerUserInfoMap.get(USER_ID).zone).isEqualTo(mOccupantZone);
+ }
+
+ @Test
+ public void testUserSwitching() {
+ UserLifecycleListener[] userLifecycleListeners = new UserLifecycleListener[1];
+ doAnswer((invocation) -> {
+ Object[] args = invocation.getArguments();
+ userLifecycleListeners[0] = (UserLifecycleListener) args[1];
+ return null;
+ }).when(mUserService).addUserLifecycleListener(any(), any());
+
+ mService.init();
+ mOccupantZoneStateMap.put(mOccupantZone, FLAG_OCCUPANT_ZONE_POWER_ON);
+
+ mockPerUserInfo(USER_ID, mOccupantZone);
+ // Remove the item added by previous line, then check whether it can be added back
+ // after onEvent().
+ mPerUserInfoMap.remove(USER_ID);
+ UserLifecycleEvent event = new UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+ /* from= */ USER_ID, /* to= */ USER_ID);
+ userLifecycleListeners[0].onEvent(event);
+
+ assertThat(mPerUserInfoMap.get(USER_ID).zone).isEqualTo(mOccupantZone);
+ }
+
+ @Test
public void testUserAssigned() {
UserLifecycleListener[] userLifecycleListeners = new UserLifecycleListener[1];
doAnswer((invocation) -> {
diff --git a/tests/carservice_unit_test/src/com/android/car/oem/CarOemProxyServiceHelperTest.java b/tests/carservice_unit_test/src/com/android/car/oem/CarOemProxyServiceHelperTest.java
index 60071f9..1b3b741 100644
--- a/tests/carservice_unit_test/src/com/android/car/oem/CarOemProxyServiceHelperTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/oem/CarOemProxyServiceHelperTest.java
@@ -93,14 +93,14 @@
mCarOemProxyServiceHelper.doBinderTimedCallWithTimeout(CALLER_TAG, () -> {
Thread.sleep(1000); // test will not wait for this timeout
return 42;
- }, /* timeout= */ 10);
+ }, /* timeoutMs= */ 10);
});
}
@Test
public void testDoBinderTimedCall_returnCalculatedValue() throws Exception {
assertThat(mCarOemProxyServiceHelper.doBinderTimedCallWithTimeout(CALLER_TAG, () -> 42,
- /* timeout= */ 1000)).isEqualTo(42);
+ /* timeoutMs= */ 1000)).isEqualTo(42);
}
@Test
diff --git a/tests/carservice_unit_test/src/com/android/car/oem/CarOemProxyServiceTest.java b/tests/carservice_unit_test/src/com/android/car/oem/CarOemProxyServiceTest.java
index 859e395..df3019b 100644
--- a/tests/carservice_unit_test/src/com/android/car/oem/CarOemProxyServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/oem/CarOemProxyServiceTest.java
@@ -273,7 +273,7 @@
mTestOemCarService.mockServiceReady();
}
- private final class TestOemCarService extends IOemCarService.Stub {
+ private static final class TestOemCarService extends IOemCarService.Stub {
private final Object mLock = new Object();
diff --git a/tests/carservice_unit_test/src/com/android/car/pm/CarPackageManagerServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/pm/CarPackageManagerServiceUnitTest.java
index 0e5e479..bd7ae8a 100644
--- a/tests/carservice_unit_test/src/com/android/car/pm/CarPackageManagerServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/pm/CarPackageManagerServiceUnitTest.java
@@ -17,10 +17,12 @@
package com.android.car.pm;
import static android.Manifest.permission.QUERY_ALL_PACKAGES;
+import static android.car.Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY;
import static android.car.content.pm.CarPackageManager.ERROR_CODE_NO_PACKAGE;
import static android.car.content.pm.CarPackageManager.MANIFEST_METADATA_TARGET_CAR_VERSION;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.os.Process.INVALID_UID;
+import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -34,11 +36,15 @@
import static org.mockito.ArgumentMatchers.eq;
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.when;
+import android.app.ActivityManager;
import android.app.PendingIntent;
import android.car.CarVersion;
import android.car.builtin.app.ActivityManagerHelper;
+import android.car.content.pm.ICarBlockingUiCommandListener;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -46,7 +52,9 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Binder;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Process;
+import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
@@ -107,7 +115,7 @@
@Before
public void setUp() {
- mSpiedContext = spy(InstrumentationRegistry.getInstrumentation().getTargetContext());
+ mSpiedContext = spy(InstrumentationRegistry.getInstrumentation().getContext());
doReturn(mUserContext).when(mSpiedContext).createContextAsUser(mUserHandle, /* flags= */ 0);
@@ -337,4 +345,155 @@
when(pm.getApplicationInfo(eq(packageName), any())).thenReturn(info);
return info;
}
+
+ @Test
+ public void registerBlockingUiCommandListenerThrowsException_withoutPermission() {
+ applyPermission(PackageManager.PERMISSION_DENIED);
+ ICarBlockingUiCommandListener carBlockingUiCommandListener =
+ setupBlockingUiCommandListener();
+
+ assertThrows(SecurityException.class,
+ () -> mService.registerBlockingUiCommandListener(
+ carBlockingUiCommandListener, DEFAULT_DISPLAY));
+ }
+
+ @Test
+ public void unregisterBlockingUiCommandListenerThrowsException_withoutPermission() {
+ applyPermission(PackageManager.PERMISSION_DENIED);
+ ICarBlockingUiCommandListener carBlockingUiCommandListener =
+ setupBlockingUiCommandListener();
+
+ assertThrows(SecurityException.class,
+ () -> mService.unregisterBlockingUiCommandListener(carBlockingUiCommandListener));
+ }
+
+ @Test
+ public void registerBlockingUiCommandListener() {
+ applyPermission(PackageManager.PERMISSION_GRANTED);
+ ICarBlockingUiCommandListener carBlockingUiCommandListener =
+ setupBlockingUiCommandListener();
+
+ mService.registerBlockingUiCommandListener(carBlockingUiCommandListener, DEFAULT_DISPLAY);
+
+ assertThat(mService.getCarBlockingUiCommandListenerRegisteredCallbacksForDisplay(
+ DEFAULT_DISPLAY)).isEqualTo(1);
+ }
+
+ @Test
+ public void unregisterBlockingUiCommandListener() {
+ applyPermission(PackageManager.PERMISSION_GRANTED);
+ ICarBlockingUiCommandListener carBlockingUiCommandListener =
+ setupBlockingUiCommandListener();
+
+ mService.registerBlockingUiCommandListener(carBlockingUiCommandListener, DEFAULT_DISPLAY);
+ mService.unregisterBlockingUiCommandListener(carBlockingUiCommandListener);
+
+ assertThat(mService.getCarBlockingUiCommandListenerRegisteredCallbacksForDisplay(
+ DEFAULT_DISPLAY)).isEqualTo(0);
+ }
+
+ @Test
+ public void registerBlockingUiCommandListener_sameListenerNotRegisteredAgain() {
+ applyPermission(PackageManager.PERMISSION_GRANTED);
+ ICarBlockingUiCommandListener carBlockingUiCommandListener =
+ setupBlockingUiCommandListener();
+
+ mService.registerBlockingUiCommandListener(carBlockingUiCommandListener, DEFAULT_DISPLAY);
+ mService.registerBlockingUiCommandListener(carBlockingUiCommandListener, DEFAULT_DISPLAY);
+
+ assertThat(mService.getCarBlockingUiCommandListenerRegisteredCallbacksForDisplay(
+ DEFAULT_DISPLAY)).isEqualTo(1);
+ }
+
+ @Test
+ public void registerBlockingUiCommandListener_registerMultipleListeners() {
+ applyPermission(PackageManager.PERMISSION_GRANTED);
+ ICarBlockingUiCommandListener carBlockingUiCommandListener1 =
+ setupBlockingUiCommandListener();
+ ICarBlockingUiCommandListener carBlockingUiCommandListener2 =
+ setupBlockingUiCommandListener();
+
+ mService.registerBlockingUiCommandListener(carBlockingUiCommandListener1, DEFAULT_DISPLAY);
+ mService.registerBlockingUiCommandListener(carBlockingUiCommandListener2, DEFAULT_DISPLAY);
+
+ assertThat(mService.getCarBlockingUiCommandListenerRegisteredCallbacksForDisplay(
+ DEFAULT_DISPLAY)).isEqualTo(2);
+ }
+
+ @Test
+ public void registerMultipleListeners_finishBlockingUiInvoked()
+ throws RemoteException {
+ applyPermission(PackageManager.PERMISSION_GRANTED);
+ ICarBlockingUiCommandListener carBlockingUiCommandListener1 =
+ setupBlockingUiCommandListener();
+ ICarBlockingUiCommandListener carBlockingUiCommandListener2 =
+ setupBlockingUiCommandListener();
+ ActivityManager.RunningTaskInfo taskInfo = createTask();
+
+ mService.registerBlockingUiCommandListener(carBlockingUiCommandListener1, DEFAULT_DISPLAY);
+ mService.registerBlockingUiCommandListener(carBlockingUiCommandListener2, DEFAULT_DISPLAY);
+ mService.finishBlockingUi(taskInfo);
+
+ verify(carBlockingUiCommandListener1).finishBlockingUi();
+ verify(carBlockingUiCommandListener2).finishBlockingUi();
+ }
+
+ @Test
+ public void registerListenerForOtherDisplay_finishBlockingUiNotInvoked()
+ throws RemoteException {
+ applyPermission(PackageManager.PERMISSION_GRANTED);
+ ICarBlockingUiCommandListener carBlockingUiCommandListener =
+ setupBlockingUiCommandListener();
+ ActivityManager.RunningTaskInfo taskInfo = createTask();
+ int tempDisplayId = 1;
+
+ mService.registerBlockingUiCommandListener(carBlockingUiCommandListener, tempDisplayId);
+ mService.finishBlockingUi(taskInfo);
+
+ verify(carBlockingUiCommandListener, times(0)).finishBlockingUi();
+ }
+
+ @Test
+ public void registerMultipleListenersForDifferentDisplay_finishBlockingUiInvokedForSomeDisplay()
+ throws RemoteException {
+ applyPermission(PackageManager.PERMISSION_GRANTED);
+ ICarBlockingUiCommandListener carBlockingUiCommandListener1 =
+ setupBlockingUiCommandListener();
+ ICarBlockingUiCommandListener carBlockingUiCommandListener2 =
+ setupBlockingUiCommandListener();
+ ICarBlockingUiCommandListener carBlockingUiCommandListener3 =
+ setupBlockingUiCommandListener();
+ ActivityManager.RunningTaskInfo taskInfo = createTask();
+ int tempDisplayId = 1;
+
+ mService.registerBlockingUiCommandListener(carBlockingUiCommandListener1, DEFAULT_DISPLAY);
+ mService.registerBlockingUiCommandListener(carBlockingUiCommandListener2, DEFAULT_DISPLAY);
+ mService.registerBlockingUiCommandListener(carBlockingUiCommandListener3, tempDisplayId);
+ mService.finishBlockingUi(taskInfo);
+
+ verify(carBlockingUiCommandListener1).finishBlockingUi();
+ verify(carBlockingUiCommandListener2).finishBlockingUi();
+ verify(carBlockingUiCommandListener3, times(0)).finishBlockingUi();
+ }
+
+ private ActivityManager.RunningTaskInfo createTask() {
+ ActivityManager.RunningTaskInfo taskInfo =
+ new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = 1;
+ taskInfo.displayId = DEFAULT_DISPLAY;
+ return taskInfo;
+ }
+
+ private ICarBlockingUiCommandListener setupBlockingUiCommandListener() {
+ ICarBlockingUiCommandListener carBlockingUiCommandListener =
+ mock(ICarBlockingUiCommandListener.class);
+ IBinder tempToken = new Binder();
+ when(carBlockingUiCommandListener.asBinder()).thenReturn(tempToken);
+ return carBlockingUiCommandListener;
+ }
+
+ private void applyPermission(int permissionValue) {
+ doReturn(permissionValue).when(mSpiedContext).checkCallingOrSelfPermission(
+ PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY);
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
index 8339968..f877642 100644
--- a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
@@ -16,41 +16,42 @@
package com.android.car.pm;
-import static android.car.test.mocks.CarArgumentMatchers.isUserHandle;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doReturn;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
import android.car.hardware.power.CarPowerManager;
import android.car.hardware.power.ICarPowerStateListener;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.AbstractExpectableTestCase;
import android.car.testapi.BlockingUserLifecycleListener;
import android.car.user.CarUserManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.IContentProvider;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.location.LocationManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.Nullable;
-import androidx.test.core.app.ApplicationProvider;
import com.android.car.CarLocalServices;
import com.android.car.CarOccupantZoneService;
@@ -59,27 +60,35 @@
import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
import com.android.car.power.CarPowerManagementService;
import com.android.car.user.CarUserService;
+import com.android.car.user.CurrentUserFetcher;
+import com.android.car.user.UserHandleHelper;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
-public final class VendorServiceControllerTest extends AbstractExtendedMockitoTestCase {
+// TODO(b/348680063): Enable this on host once we can mock LocationManager on host. Currently it
+// throws ClassNotFound error possibly due to some static methods using unsupported APIs.
+@RunWith(MockitoJUnitRunner.class)
+public final class VendorServiceControllerTest extends AbstractExpectableTestCase {
private static final String TAG = VendorServiceControllerTest.class.getSimpleName();
// TODO(b/152069895): decrease value once refactored. In fact, it should not even use
@@ -118,54 +127,91 @@
@Mock
private Resources mResources;
-
@Mock
private UserManager mUserManager;
-
@Mock
private UserHalService mUserHal;
-
@Mock
private CarUxRestrictionsManagerService mUxRestrictionService;
-
@Mock
private CarPackageManagerService mCarPackageManagerService;
-
@Mock
private CarPowerManagementService mCarPowerManagementService;
-
@Mock
private CarOccupantZoneService mCarOccupantZoneService;
+ @Mock
+ private CurrentUserFetcher mCurrentUserFetcher;
+ @Mock
+ private DevicePolicyManager mDevicePolicyManager;
+ @Mock
+ private ActivityManager mActivityManager;
+ @Mock
+ private LocationManager mLocationManager;
+ @Mock
+ private Context mBaseContext;
+ @Mock
+ private ContentResolver mContentResolver;
+ @Mock
+ private IContentProvider mContentProvider;
private ServiceLauncherContext mContext;
private CarUserService mCarUserService;
private VendorServiceController mController;
- public VendorServiceControllerTest() {
- super(VendorServiceController.TAG);
- }
-
- @Override
- protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
- session.spyStatic(ActivityManager.class);
- }
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private int mUnlockingOrUnlockedUserId;
+ @GuardedBy("mLock")
+ private Set<UserHandle> mVisibleUsers = new ArraySet<UserHandle>();
@Before
public void setUp() throws Exception {
- mContext = new ServiceLauncherContext(ApplicationProvider.getApplicationContext());
+ mContext = new ServiceLauncherContext(mBaseContext);
+
+ // Required for getting/setting user ID in Settings.Global in InitialUserSetter.
+ when(mContentResolver.acquireProvider(anyString())).thenReturn(mContentProvider);
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+
+ // No visible users by default.
+ synchronized (mLock) {
+ mVisibleUsers.clear();
+ }
+ when(mUserManager.getVisibleUsers()).thenAnswer((inv) -> {
+ synchronized (mLock) {
+ return new ArraySet<UserHandle>(mVisibleUsers);
+ }
+ });
+
+ synchronized (mLock) {
+ // Reset unlocking or unlocked user Id for each test case.
+ mUnlockingOrUnlockedUserId = 123456;
+ }
+ when(mUserManager.isUserUnlockingOrUnlocked(anyInt())).thenAnswer((inv) -> {
+ int userId = inv.getArgument(0);
+ synchronized (mLock) {
+ return userId == mUnlockingOrUnlockedUserId;
+ }
+ });
+ when(mUserManager.isUserUnlockingOrUnlocked(any(UserHandle.class))).thenAnswer((inv) -> {
+ UserHandle userHandle = inv.getArgument(0);
+ synchronized (mLock) {
+ return userHandle.getIdentifier() == mUnlockingOrUnlockedUserId;
+ }
+ });
mCarUserService = new CarUserService(mContext, mUserHal, mUserManager,
- /* maxRunningUsers= */ 2, mUxRestrictionService, mCarPackageManagerService,
- mCarOccupantZoneService);
- spyOn(mCarUserService);
+ new UserHandleHelper(mContext, mUserManager),
+ mDevicePolicyManager, mActivityManager, /* maxRunningUsers= */ 2,
+ /* initialUserSetter= */ null, mUxRestrictionService, /* handler= */ null,
+ mCarPackageManagerService, mCarOccupantZoneService, mCurrentUserFetcher);
+
CarLocalServices.removeServiceForTest(CarUserService.class);
CarLocalServices.addService(CarUserService.class, mCarUserService);
CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
CarLocalServices.addService(CarPowerManagementService.class, mCarPowerManagementService);
- // No visible users by default.
- doReturn(false).when(mCarUserService).isUserVisible(anyInt());
- mController = new VendorServiceController(mContext, Looper.getMainLooper());
+ mController = new VendorServiceController(mContext, Looper.getMainLooper(),
+ mCurrentUserFetcher);
UserInfo persistentFgUser = new UserInfo(FG_USER_ID, "persistent user", /* flags= */ 0);
when(mUserManager.getUserInfo(FG_USER_ID)).thenReturn(persistentFgUser);
@@ -180,6 +226,10 @@
CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
}
+ private void mockGetCurrentUser(int userHandle) {
+ when(mCurrentUserFetcher.getCurrentUser()).thenReturn(userHandle);
+ }
+
@Test
public void init_nothingConfigured() {
when(mResources.getStringArray(com.android.car.R.array.config_earlyStartupServices))
@@ -235,6 +285,8 @@
mContext.reset();
mContext.expectServices(SERVICE_START_SYSTEM_UNLOCKED, SERVICE_BIND_SYSTEM_USER_RESUME);
+ // Current user is a regular user.
+ mockGetCurrentUser(10);
// Unlock system user
mockUserUnlock(UserHandle.USER_SYSTEM);
sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED,
@@ -277,6 +329,16 @@
mContext.expectNoMoreServiceLaunches();
}
+ private void setVisibleUsers(Set<Integer> visibleUserIds) {
+ Set<UserHandle> userHandles = new ArraySet<>();
+ for (int userId : visibleUserIds) {
+ userHandles.add(UserHandle.of(userId));
+ }
+ synchronized (mLock) {
+ mVisibleUsers = userHandles;
+ }
+ }
+
@Test
public void testVisibleUsers() throws Exception {
// No visible users yet.
@@ -287,7 +349,7 @@
// A background user becomes visible.
mContext.expectServices(SERVICE_BIND_ALL_USERS_ASAP, SERVICE_BIND_BG_VISIBLE_USER_ASAP,
SERVICE_START_VISIBLE_USER_ASAP);
- mockIsUserVisible(VISIBLE_BG_USER1_ID, true);
+ setVisibleUsers(Set.of(VISIBLE_BG_USER1_ID));
sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_VISIBLE,
VISIBLE_BG_USER1_ID);
@@ -299,7 +361,7 @@
// Unlock another visible background user.
mContext.expectServices(SERVICE_BIND_ALL_USERS_ASAP, SERVICE_BIND_BG_VISIBLE_USER_ASAP,
SERVICE_START_VISIBLE_USER_ASAP, SERVICE_START_VISIBLE_USER_UNLOCKED);
- mockIsUserVisible(VISIBLE_BG_USER2_ID, true);
+ setVisibleUsers(Set.of(VISIBLE_BG_USER1_ID, VISIBLE_BG_USER2_ID));
mockUserUnlock(VISIBLE_BG_USER2_ID);
sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED,
VISIBLE_BG_USER2_ID);
@@ -323,7 +385,7 @@
// Unlock foreground user. This triggers "visible", but not "backgroundVisible".
mContext.expectServices(SERVICE_BIND_FG_USER_UNLOCKED, SERVICE_START_VISIBLE_USER_ASAP,
SERVICE_START_VISIBLE_USER_UNLOCKED);
- mockIsUserVisible(FG_USER_ID, true);
+ setVisibleUsers(Set.of(VISIBLE_BG_USER1_ID, VISIBLE_BG_USER2_ID, FG_USER_ID));
mockUserUnlock(FG_USER_ID);
sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, FG_USER_ID);
@@ -336,7 +398,7 @@
// A background user becomes invisible.
mContext.expectServicesToUnbindOrStop(SERVICE_BIND_BG_VISIBLE_USER_ASAP,
SERVICE_START_VISIBLE_USER_ASAP, SERVICE_START_VISIBLE_USER_UNLOCKED);
- mockIsUserVisible(VISIBLE_BG_USER2_ID, false);
+ setVisibleUsers(Set.of(VISIBLE_BG_USER1_ID, FG_USER_ID));
sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_INVISIBLE,
VISIBLE_BG_USER2_ID);
@@ -440,15 +502,10 @@
listener.onStateChanged(state, /* expirationTimeMs= */ 3000L);
}
- // TODO: Replace this with AndroidMockitoHelper#mockUmIsUserUnlockingOrUnlocked
- // We need to figure out why we get WrongTypeOfReturnValue error with when()..thenReturn().
private void mockUserUnlock(@UserIdInt int userId) {
- doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(isUserHandle(userId));
- doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(userId);
- }
-
- private void mockIsUserVisible(@UserIdInt int userId, boolean visible) throws Exception {
- doReturn(visible).when(mCarUserService).isUserVisible(userId);
+ synchronized (mLock) {
+ mUnlockingOrUnlockedUserId = userId;
+ }
}
private static void runOnMainThreadAndWaitForIdle(Runnable r) {
@@ -680,7 +737,22 @@
if (Context.USER_SERVICE.equals(name)) {
return mUserManager;
}
- return super.getSystemService(name);
+ // Used in CarUserService.setSystemUserRestrictions
+ if (Context.LOCATION_SERVICE.equals(name)) {
+ return mLocationManager;
+ }
+ return null;
+ }
+
+ @Override
+ public String getSystemServiceName(Class<?> clazz) {
+ if (clazz.equals(UserManager.class)) {
+ return Context.USER_SERVICE;
+ }
+ if (clazz.equals(LocationManager.class)) {
+ return Context.LOCATION_SERVICE;
+ }
+ return "";
}
@Nullable
diff --git a/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java
index 78f679a..b88dfd5 100644
--- a/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java
@@ -47,7 +47,9 @@
import android.automotive.powerpolicy.internal.ICarPowerPolicyDelegate;
import android.car.Car;
import android.car.ICarResultReceiver;
+import android.car.builtin.app.ActivityManagerHelper;
import android.car.builtin.app.VoiceInteractionHelper;
+import android.car.builtin.os.UserManagerHelper;
import android.car.feature.FakeFeatureFlagsImpl;
import android.car.feature.Flags;
import android.car.hardware.power.CarPowerManager;
@@ -59,7 +61,6 @@
import android.car.remoteaccess.CarRemoteAccessManager;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.car.test.mocks.JavaMockitoHelper;
-import android.car.test.util.TemporaryFile;
import android.car.testapi.FakeRefactoredCarPowerPolicyDaemon;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -115,11 +116,14 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.mockito.Mock;
import org.mockito.Spy;
import java.io.File;
+import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.annotation.ElementType;
@@ -180,6 +184,8 @@
private static final String POWER_POLICY_GROUP_VALID = "policy_group_id_valid";
private static final String POWER_POLICY_GROUP_INVALID = "policy_group_id_invalid";
private static final String POWER_POLICY_GROUP_1 = "policy_group_1";
+ private static final String PROCESS_TEST_NAME_1 = "test.process.name";
+ private static final String PROCESS_TEST_NAME_2 = "test.process.name2";
public static final int CUSTOM_COMPONENT_1000 = 1000;
public static final int CUSTOM_COMPONENT_1001 = 1001;
@@ -206,6 +212,9 @@
PowerComponent.AUDIO, PowerComponent.DISPLAY, PowerComponent.VISUAL_INTERACTION,
CUSTOM_COMPONENT_1002, CUSTOM_COMPONENT_1003);
+ @Rule
+ public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
private final FakeFeatureFlagsImpl mFeatureFlags = new FakeFeatureFlagsImpl();
private final MockDisplayInterface mDisplayInterface = new MockDisplayInterface();
private final MockSystemStateInterface mSystemStateInterface = new MockSystemStateInterface();
@@ -214,7 +223,6 @@
private final PowerSignalListener mPowerSignalListener = new PowerSignalListener();
@Spy
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
- private final TemporaryFile mComponentStateFile;
private final HalPropValueBuilder mHalPropValueBuilder = new HalPropValueBuilder(
/* isAidl= */ true);
@@ -223,8 +231,9 @@
private PowerComponentHandler mPowerComponentHandler;
private CarPowerManagementService mService;
private CompletableFuture<Void> mFuture;
- private TemporaryFile mFileHwStateMonitoring;
- private TemporaryFile mFileKernelSilentMode;
+ private File mComponentStateFile;
+ private File mFileHwStateMonitoring;
+ private File mFileKernelSilentMode;
private FakeCarPowerPolicyDaemon mPowerPolicyDaemon;
private FakeRefactoredCarPowerPolicyDaemon mRefactoredPowerPolicyDaemon;
private boolean mVoiceInteractionEnabled;
@@ -240,22 +249,28 @@
private WifiManager mWifiManager;
@Mock
private TetheringManager mTetheringManager;
+ @Mock
+ private ActivityManager mMockActivityManager;
+ @Mock
+ private ActivityManager.RunningAppProcessInfo mRunningProcess1;
+ @Mock
+ private ActivityManager.RunningAppProcessInfo mRunningProcess2;
public CarPowerManagementServiceUnitTest() throws Exception {
super(CarPowerManagementService.TAG);
-
- mComponentStateFile = new TemporaryFile("COMPONENT_STATE_FILE");
}
@Override
protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
session
.spyStatic(ActivityManager.class)
+ .spyStatic(ActivityManagerHelper.class)
.spyStatic(VoiceInteractionHelper.class);
}
@Before
public void setUp() throws Exception {
+ mComponentStateFile = temporaryFolder.newFile("COMPONENT_STATE_FILE");
mPowerHal = new MockedPowerHalService(/*isPowerStateSupported=*/true,
/*isDeepSleepAllowed=*/true,
/*isHibernationAllowed=*/true,
@@ -271,6 +286,7 @@
setCurrentUser(CURRENT_USER_ID, /* isGuest= */ false);
setService();
+ setCarPowerCancelShellCommand(true);
}
@After
@@ -278,13 +294,24 @@
if (mService != null) {
mService.release();
}
+ CarServiceUtils.quitHandlerThreads();
CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
- CarServiceUtils.finishAllHandlerTasks();
mIOInterface.tearDown();
}
@Test
public void testShutdown() throws Exception {
+ shutdownTest_withCarPowerCancelShellCommand(/* carShellCommandFlag= */ true);
+ }
+
+ @Test
+ public void testShutdown_carPowerShellCommandDisabled() throws Exception {
+ shutdownTest_withCarPowerCancelShellCommand(/* carShellCommandFlag= */ false);
+ }
+
+ private void shutdownTest_withCarPowerCancelShellCommand(boolean carShellCommandFlag)
+ throws Exception {
+ setCarPowerCancelShellCommand(carShellCommandFlag);
mPowerSignalListener.addEventListener(PowerHalService.SET_ON);
mPowerSignalListener.addEventListener(PowerHalService.SET_SHUTDOWN_START);
// Transition to ON state
@@ -306,6 +333,7 @@
@Test
public void testCanHibernate() throws Exception {
+ setStopProcessBeforeSuspendToDisk(false);
mPowerSignalListener.addEventListener(PowerHalService.SET_ON);
mPowerSignalListener.addEventListener(PowerHalService.SET_HIBERNATION_ENTRY);
mPowerSignalListener.addEventListener(PowerHalService.SET_HIBERNATION_EXIT);
@@ -329,6 +357,166 @@
@Test
public void testHibernateImmediately() throws Exception {
+ setStopProcessBeforeSuspendToDisk(true);
+ when(mResources.getString(R.string.config_suspend_to_disk_memory_savings))
+ .thenReturn("none");
+ hibernateImmediately();
+ }
+
+ @Test
+ public void testHibernateFreeMemory() throws Exception {
+ when(mResources.getString(R.string.config_suspend_to_disk_memory_savings))
+ .thenReturn("high");
+ doReturn(List.of(mRunningProcess1)).when(
+ () -> ActivityManagerHelper.getRunningAppProcesses());
+ setStopProcessBeforeSuspendToDisk(true);
+ mRunningProcess1.pkgList = new String[]{PROCESS_TEST_NAME_1};
+ mRunningProcess1.importance = ActivityManager.RunningAppProcessInfo
+ .IMPORTANCE_FOREGROUND_SERVICE;
+
+ hibernateImmediately();
+
+ verify(mMockActivityManager).forceStopPackageAsUser(PROCESS_TEST_NAME_1,
+ UserManagerHelper.USER_ALL);
+ }
+
+ @Test
+ public void testHibernateFreeMemory_multipleProcesses() throws Exception {
+ when(mResources.getString(R.string.config_suspend_to_disk_memory_savings))
+ .thenReturn("high");
+ doReturn(List.of(mRunningProcess1, mRunningProcess2)).when(
+ () -> ActivityManagerHelper.getRunningAppProcesses());
+ setStopProcessBeforeSuspendToDisk(true);
+ mRunningProcess1.pkgList = new String[]{PROCESS_TEST_NAME_1};
+ mRunningProcess1.importance = ActivityManager.RunningAppProcessInfo
+ .IMPORTANCE_FOREGROUND_SERVICE;
+ mRunningProcess1.uid = 1;
+ mRunningProcess2.pkgList = new String[]{PROCESS_TEST_NAME_2};
+ mRunningProcess2.importance = ActivityManager.RunningAppProcessInfo
+ .IMPORTANCE_FOREGROUND_SERVICE;
+ mRunningProcess2.uid = 2;
+
+ hibernateImmediately();
+
+ verify(mMockActivityManager).forceStopPackageAsUser(PROCESS_TEST_NAME_1,
+ UserManagerHelper.USER_ALL);
+ verify(mMockActivityManager).forceStopPackageAsUser(PROCESS_TEST_NAME_2,
+ UserManagerHelper.USER_ALL);
+ }
+
+ @Test
+ public void testHibernateFreeMemory_multipleProcessesWithOneProcessTooLow()
+ throws Exception {
+ when(mResources.getString(R.string.config_suspend_to_disk_memory_savings))
+ .thenReturn("medium");
+ doReturn(List.of(mRunningProcess1, mRunningProcess2)).when(
+ () -> ActivityManagerHelper.getRunningAppProcesses());
+ setStopProcessBeforeSuspendToDisk(true);
+ mRunningProcess1.pkgList = new String[]{PROCESS_TEST_NAME_1};
+ mRunningProcess1.importance = ActivityManager.RunningAppProcessInfo
+ .IMPORTANCE_FOREGROUND_SERVICE;
+ mRunningProcess1.uid = 1;
+ mRunningProcess2.pkgList = new String[]{PROCESS_TEST_NAME_2};
+ mRunningProcess2.importance = ActivityManager.RunningAppProcessInfo
+ .IMPORTANCE_SERVICE;
+ mRunningProcess2.uid = 2;
+
+ hibernateImmediately();
+
+ verify(mMockActivityManager, never()).forceStopPackageAsUser(PROCESS_TEST_NAME_1,
+ UserManagerHelper.USER_ALL);
+ verify(mMockActivityManager).forceStopPackageAsUser(PROCESS_TEST_NAME_2,
+ UserManagerHelper.USER_ALL);
+ }
+
+ @Test
+ public void testHibernateFreeMemory_multipleProcessesWithOneProcessNotInAllowList()
+ throws Exception {
+ when(mResources.getString(R.string.config_suspend_to_disk_memory_savings))
+ .thenReturn("high");
+ when(mResources.getStringArray(R.array.config_packages_not_to_stop_during_suspend))
+ .thenReturn(new String[] {PROCESS_TEST_NAME_1});
+ doReturn(List.of(mRunningProcess1, mRunningProcess2)).when(
+ () -> ActivityManagerHelper.getRunningAppProcesses());
+ setStopProcessBeforeSuspendToDisk(true);
+ mRunningProcess1.pkgList = new String[]{PROCESS_TEST_NAME_1};
+ mRunningProcess1.importance = ActivityManager.RunningAppProcessInfo
+ .IMPORTANCE_FOREGROUND_SERVICE;
+ mRunningProcess1.processName = PROCESS_TEST_NAME_1;
+ mRunningProcess1.uid = 1;
+ mRunningProcess2.pkgList = new String[]{PROCESS_TEST_NAME_2};
+ mRunningProcess2.processName = PROCESS_TEST_NAME_2;
+ mRunningProcess2.importance = ActivityManager.RunningAppProcessInfo
+ .IMPORTANCE_SERVICE;
+ mRunningProcess2.uid = 2;
+
+ hibernateImmediately();
+
+ verify(mMockActivityManager, never()).forceStopPackageAsUser(PROCESS_TEST_NAME_1,
+ UserManagerHelper.USER_ALL);
+ verify(mMockActivityManager).forceStopPackageAsUser(PROCESS_TEST_NAME_2,
+ UserManagerHelper.USER_ALL);
+ }
+
+ @Test
+ public void testHibernateFreeMemory_multipleProcessesSameUid()
+ throws Exception {
+ when(mResources.getString(R.string.config_suspend_to_disk_memory_savings))
+ .thenReturn("high");
+ when(mResources.getStringArray(R.array.config_packages_not_to_stop_during_suspend))
+ .thenReturn(new String[] {PROCESS_TEST_NAME_1});
+ doReturn(List.of(mRunningProcess1, mRunningProcess2)).when(
+ () -> ActivityManagerHelper.getRunningAppProcesses());
+ setStopProcessBeforeSuspendToDisk(true);
+ mRunningProcess1.pkgList = new String[]{PROCESS_TEST_NAME_1};
+ mRunningProcess1.importance = ActivityManager.RunningAppProcessInfo
+ .IMPORTANCE_FOREGROUND_SERVICE;
+ mRunningProcess1.processName = PROCESS_TEST_NAME_1;
+ mRunningProcess1.uid = 42;
+ mRunningProcess2.pkgList = new String[]{PROCESS_TEST_NAME_2};
+ mRunningProcess2.processName = PROCESS_TEST_NAME_2;
+ mRunningProcess2.importance = ActivityManager.RunningAppProcessInfo
+ .IMPORTANCE_SERVICE;
+ mRunningProcess2.uid = 42;
+
+ hibernateImmediately();
+
+ verify(mMockActivityManager, never()).forceStopPackageAsUser(PROCESS_TEST_NAME_1,
+ UserManagerHelper.USER_ALL);
+ verify(mMockActivityManager, never()).forceStopPackageAsUser(PROCESS_TEST_NAME_2,
+ UserManagerHelper.USER_ALL);
+ }
+
+ @Test
+ public void testHibernateFreeMemory_multipleProcessesPersistentProcess()
+ throws Exception {
+ when(mResources.getString(R.string.config_suspend_to_disk_memory_savings))
+ .thenReturn("high");
+ doReturn(List.of(mRunningProcess1, mRunningProcess2)).when(
+ () -> ActivityManagerHelper.getRunningAppProcesses());
+ setStopProcessBeforeSuspendToDisk(true);
+ mRunningProcess1.pkgList = new String[]{PROCESS_TEST_NAME_1};
+ mRunningProcess1.importance = ActivityManager.RunningAppProcessInfo
+ .IMPORTANCE_FOREGROUND_SERVICE;
+ mRunningProcess1.processName = PROCESS_TEST_NAME_1;
+ mRunningProcess1.flags = ActivityManagerHelper.PROCESS_INFO_PERSISTENT_FLAG;
+ mRunningProcess1.uid = 42;
+ mRunningProcess2.pkgList = new String[]{PROCESS_TEST_NAME_2};
+ mRunningProcess2.processName = PROCESS_TEST_NAME_2;
+ mRunningProcess2.importance = ActivityManager.RunningAppProcessInfo
+ .IMPORTANCE_SERVICE;
+ mRunningProcess2.uid = 42;
+
+ hibernateImmediately();
+
+ verify(mMockActivityManager, never()).forceStopPackageAsUser(PROCESS_TEST_NAME_1,
+ UserManagerHelper.USER_ALL);
+ verify(mMockActivityManager, never()).forceStopPackageAsUser(PROCESS_TEST_NAME_2,
+ UserManagerHelper.USER_ALL);
+ }
+
+
+ private void hibernateImmediately() throws Exception {
mPowerSignalListener.addEventListener(PowerHalService.SET_ON);
mPowerSignalListener.addEventListener(PowerHalService.SET_HIBERNATION_ENTRY);
mPowerSignalListener.addEventListener(PowerHalService.SET_HIBERNATION_EXIT);
@@ -488,32 +676,19 @@
}
}
-
- private String stateToString(int state) {
- String result;
- switch (state) {
- case CarPowerManager.STATE_INVALID -> result = "Invalid";
- case CarPowerManager.STATE_WAIT_FOR_VHAL -> result = "WaitForVHAL";
- case CarPowerManager.STATE_SUSPEND_ENTER -> result = "SuspendEnter";
- case CarPowerManager.STATE_SUSPEND_EXIT -> result = "SuspendExit";
- case CarPowerManager.STATE_SHUTDOWN_ENTER -> result = "ShutdownEnter";
- case CarPowerManager.STATE_ON -> result = "ON";
- case CarPowerManager.STATE_SHUTDOWN_PREPARE -> result = "ShutdownPrepare";
- case CarPowerManager.STATE_SHUTDOWN_CANCELLED -> result = "ShutdownCancelled";
- case CarPowerManager.STATE_HIBERNATION_ENTER -> result = "HibernationEnter";
- case CarPowerManager.STATE_HIBERNATION_EXIT -> result = "HibernationExit";
- case CarPowerManager.STATE_PRE_SHUTDOWN_PREPARE -> result = "PreShutdownPrepare";
- case CarPowerManager.STATE_POST_SUSPEND_ENTER -> result = "PostSuspendEnter";
- case CarPowerManager.STATE_POST_SHUTDOWN_ENTER -> result = "PostShutdownEnter";
- case CarPowerManager.STATE_POST_HIBERNATION_ENTER -> result = "PostHibernationEnter";
- default -> result = "Unknown";
- }
- return result;
- }
-
-
@Test
public void testSuspend() throws Exception {
+ suspendTest_withCarPowerCancelShellCommand(/* carShellCommandFlag= */ true);
+ }
+
+ @Test
+ public void testSuspend_carPowerCancelShellDisabled() throws Exception {
+ suspendTest_withCarPowerCancelShellCommand(/* carShellCommandFlag= */ false);
+ }
+
+ private void suspendTest_withCarPowerCancelShellCommand(boolean carShellCommandFlag)
+ throws Exception {
+ setCarPowerCancelShellCommand(carShellCommandFlag);
mPowerSignalListener.addEventListener(PowerHalService.SET_ON);
// Start in the ON state
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
@@ -920,6 +1095,39 @@
}
@Test
+ public void testInitializePowerPolicy_invalidPowerState_powerPolicyRefactorFlagEnabled()
+ throws Exception {
+ mRefactoredPowerPolicyDaemon = new FakeRefactoredCarPowerPolicyDaemon(mFileKernelSilentMode,
+ new int[]{CUSTOM_COMPONENT_1000, CUSTOM_COMPONENT_1001, CUSTOM_COMPONENT_1002,
+ CUSTOM_COMPONENT_1003});
+ setCarPowerPolicyRefactoringFeatureFlag(true);
+ mService = new CarPowerManagementService.Builder()
+ .setContext(mContext).setResources(mResources)
+ .setPowerHalService(mPowerHal).setSystemInterface(mSystemInterface)
+ .setUserManager(mUserManager).setCarUserService(mUserService)
+ .setPowerPolicyDaemon(mRefactoredPowerPolicyDaemon)
+ .setPowerComponentHandler(mPowerComponentHandler).setFeatureFlags(mFeatureFlags)
+ .setScreenOffHandler(mScreenOffHandler)
+ .setSilentModeHwStatePath(mFileHwStateMonitoring.getPath())
+ .setSilentModeKernelStatePath(mFileKernelSilentMode.getPath())
+ .setBootReason(NORMAL_BOOT).build();
+ CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
+ CarLocalServices.addService(CarPowerManagementService.class, mService);
+ mService.init();
+ int invalidPowerState = -1;
+ int shutdownParam = 0;
+ mPowerHal.setCurrentPowerState(new PowerState(invalidPowerState, shutdownParam));
+ assertWithMessage("Power HAL current power state").that(
+ mPowerHal.getCurrentPowerState().mState).isEqualTo(invalidPowerState);
+
+ mService.initializePowerPolicy();
+
+ assertWithMessage("Power policy daemon last notified power state").that(
+ mRefactoredPowerPolicyDaemon.getLastNotifiedPowerState()).isNotEqualTo(
+ invalidPowerState);
+ }
+
+ @Test
public void testDefineValidPowerPolicy_powerPolicyRefactorFlagDisabled() {
int status = mService.definePowerPolicy(POWER_POLICY_VALID_1,
new String[]{"AUDIO", "BLUETOOTH"}, new String[]{"WIFI"});
@@ -1727,11 +1935,16 @@
.thenReturn(true);
when(mWifiManager.isWifiEnabled()).thenReturn(true);
when(mWifiManager.isWifiApEnabled()).thenReturn(true);
- mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
- mSystemInterface, mUserManager, mUserService, mPowerPolicyDaemon,
- mPowerComponentHandler, mFeatureFlags, mScreenOffHandler,
- mFileHwStateMonitoring.getFile().getPath(),
- mFileKernelSilentMode.getFile().getPath(), NORMAL_BOOT);
+ mService = new CarPowerManagementService.Builder()
+ .setContext(mContext).setResources(mResources)
+ .setPowerHalService(mPowerHal).setSystemInterface(mSystemInterface)
+ .setUserManager(mUserManager).setCarUserService(mUserService)
+ .setPowerPolicyDaemon(mPowerPolicyDaemon)
+ .setPowerComponentHandler(mPowerComponentHandler).setFeatureFlags(mFeatureFlags)
+ .setScreenOffHandler(mScreenOffHandler)
+ .setSilentModeHwStatePath(mFileHwStateMonitoring.getPath())
+ .setSilentModeKernelStatePath(mFileKernelSilentMode.getPath())
+ .setBootReason(NORMAL_BOOT).build();
CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
CarLocalServices.addService(CarPowerManagementService.class, mService);
mService.init();
@@ -2179,12 +2392,16 @@
when(mockVehicleStub.isAidlVhal()).thenReturn(true);
var vehicleHal = new VehicleHal(mContext, mockVehicleStub);
- var service = new CarPowerManagementService(mContext, mResources,
- new PowerHalService(mContext, vehicleHal),
- mSystemInterface, mUserManager, mUserService, mRefactoredPowerPolicyDaemon,
- mPowerComponentHandler, mFeatureFlags, mScreenOffHandler,
- mFileHwStateMonitoring.getFile().getPath(),
- mFileKernelSilentMode.getFile().getPath(), NORMAL_BOOT);
+ var service = new CarPowerManagementService.Builder()
+ .setContext(mContext).setResources(mResources)
+ .setPowerHalService(new PowerHalService(mContext, mFeatureFlags, vehicleHal))
+ .setSystemInterface(mSystemInterface).setUserManager(mUserManager)
+ .setCarUserService(mUserService).setPowerPolicyDaemon(mRefactoredPowerPolicyDaemon)
+ .setPowerComponentHandler(mPowerComponentHandler).setFeatureFlags(mFeatureFlags)
+ .setScreenOffHandler(mScreenOffHandler)
+ .setSilentModeHwStatePath(mFileHwStateMonitoring.getPath())
+ .setSilentModeKernelStatePath(mFileKernelSilentMode.getPath())
+ .setBootReason(NORMAL_BOOT).build();
HalPropValue vehicleInUseRequest = mHalPropValueBuilder.build(
VehicleProperty.VEHICLE_IN_USE, /* areaId= */ 0);
@@ -2215,12 +2432,16 @@
when(mockVehicleStub.isAidlVhal()).thenReturn(true);
var vehicleHal = new VehicleHal(mContext, mockVehicleStub);
- var service = new CarPowerManagementService(mContext, mResources,
- new PowerHalService(mContext, vehicleHal),
- mSystemInterface, mUserManager, mUserService, mRefactoredPowerPolicyDaemon,
- mPowerComponentHandler, mFeatureFlags, mScreenOffHandler,
- mFileHwStateMonitoring.getFile().getPath(),
- mFileKernelSilentMode.getFile().getPath(), NORMAL_BOOT);
+ var service = new CarPowerManagementService.Builder()
+ .setContext(mContext).setResources(mResources)
+ .setPowerHalService(new PowerHalService(mContext, mFeatureFlags, vehicleHal))
+ .setSystemInterface(mSystemInterface).setUserManager(mUserManager)
+ .setCarUserService(mUserService).setPowerPolicyDaemon(mRefactoredPowerPolicyDaemon)
+ .setPowerComponentHandler(mPowerComponentHandler).setFeatureFlags(mFeatureFlags)
+ .setScreenOffHandler(mScreenOffHandler)
+ .setSilentModeHwStatePath(mFileHwStateMonitoring.getPath())
+ .setSilentModeKernelStatePath(mFileKernelSilentMode.getPath())
+ .setBootReason(NORMAL_BOOT).build();
service.onInitComplete();
}
@@ -2235,17 +2456,20 @@
when(mockVehicleStub.isAidlVhal()).thenReturn(true);
var vehicleHal = new VehicleHal(mContext, mockVehicleStub);
- var service = new CarPowerManagementService(mContext, mResources,
- new PowerHalService(mContext, vehicleHal),
- mSystemInterface, mUserManager, mUserService, mRefactoredPowerPolicyDaemon,
- mPowerComponentHandler, mFeatureFlags, mScreenOffHandler,
- mFileHwStateMonitoring.getFile().getPath(),
- mFileKernelSilentMode.getFile().getPath(), NORMAL_BOOT);
+ var service = new CarPowerManagementService.Builder()
+ .setContext(mContext).setResources(mResources)
+ .setPowerHalService(new PowerHalService(mContext, mFeatureFlags, vehicleHal))
+ .setSystemInterface(mSystemInterface).setUserManager(mUserManager)
+ .setCarUserService(mUserService).setPowerPolicyDaemon(mRefactoredPowerPolicyDaemon)
+ .setPowerComponentHandler(mPowerComponentHandler).setFeatureFlags(mFeatureFlags)
+ .setScreenOffHandler(mScreenOffHandler)
+ .setSilentModeHwStatePath(mFileHwStateMonitoring.getPath())
+ .setSilentModeKernelStatePath(mFileKernelSilentMode.getPath())
+ .setBootReason(NORMAL_BOOT).build();
HalPropValue bootupReasonRequest = mHalPropValueBuilder.build(
VehicleProperty.AP_POWER_BOOTUP_REASON, /* areaId= */ 0);
- HalPropValue bootupReasonResponse = mHalPropValueBuilder.build(
- VehicleProperty.AP_POWER_BOOTUP_REASON,
+ mHalPropValueBuilder.build(VehicleProperty.AP_POWER_BOOTUP_REASON,
/* areaId= */ 0, VehicleApPowerBootupReason.SYSTEM_ENTER_GARAGE_MODE);
when(mockVehicleStub.get(eq(bootupReasonRequest))).thenThrow(
new IllegalArgumentException());
@@ -2265,12 +2489,16 @@
when(mockVehicleStub.isAidlVhal()).thenReturn(true);
var vehicleHal = new VehicleHal(mContext, mockVehicleStub);
- var service = new CarPowerManagementService(mContext, mResources,
- new PowerHalService(mContext, vehicleHal),
- mSystemInterface, mUserManager, mUserService, mRefactoredPowerPolicyDaemon,
- mPowerComponentHandler, mFeatureFlags, mScreenOffHandler,
- mFileHwStateMonitoring.getFile().getPath(),
- mFileKernelSilentMode.getFile().getPath(), NORMAL_BOOT);
+ var service = new CarPowerManagementService.Builder()
+ .setContext(mContext).setResources(mResources)
+ .setPowerHalService(new PowerHalService(mContext, mFeatureFlags, vehicleHal))
+ .setSystemInterface(mSystemInterface).setUserManager(mUserManager)
+ .setCarUserService(mUserService).setPowerPolicyDaemon(mRefactoredPowerPolicyDaemon)
+ .setPowerComponentHandler(mPowerComponentHandler).setFeatureFlags(mFeatureFlags)
+ .setScreenOffHandler(mScreenOffHandler)
+ .setSilentModeHwStatePath(mFileHwStateMonitoring.getPath())
+ .setSilentModeKernelStatePath(mFileKernelSilentMode.getPath())
+ .setBootReason(NORMAL_BOOT).build();
HalPropValue vehicleInUseRequest = mHalPropValueBuilder.build(
VehicleProperty.VEHICLE_IN_USE, /* areaId= */ 0);
@@ -2301,12 +2529,16 @@
when(mockVehicleStub.isAidlVhal()).thenReturn(true);
var vehicleHal = new VehicleHal(mContext, mockVehicleStub);
- var service = new CarPowerManagementService(mContext, mResources,
- new PowerHalService(mContext, vehicleHal),
- mSystemInterface, mUserManager, mUserService, mRefactoredPowerPolicyDaemon,
- mPowerComponentHandler, mFeatureFlags, mScreenOffHandler,
- mFileHwStateMonitoring.getFile().getPath(),
- mFileKernelSilentMode.getFile().getPath(), NORMAL_BOOT);
+ var service = new CarPowerManagementService.Builder()
+ .setContext(mContext).setResources(mResources)
+ .setPowerHalService(new PowerHalService(mContext, mFeatureFlags, vehicleHal))
+ .setSystemInterface(mSystemInterface).setUserManager(mUserManager)
+ .setCarUserService(mUserService).setPowerPolicyDaemon(mRefactoredPowerPolicyDaemon)
+ .setPowerComponentHandler(mPowerComponentHandler).setFeatureFlags(mFeatureFlags)
+ .setScreenOffHandler(mScreenOffHandler)
+ .setSilentModeHwStatePath(mFileHwStateMonitoring.getPath())
+ .setSilentModeKernelStatePath(mFileKernelSilentMode.getPath())
+ .setBootReason(NORMAL_BOOT).build();
HalPropValue vehicleInUseRequest = mHalPropValueBuilder.build(
VehicleProperty.VEHICLE_IN_USE, /* areaId= */ 0);
@@ -2327,6 +2559,23 @@
verify(mockVehicleStub, never()).set(any());
}
+ @Test
+ public void testApplyNonPreemptivePolicyOverPreemptivePolicy() throws Exception {
+ setRefactoredService();
+ grantPowerPolicyPermission();
+ String policyId = SYSTEM_POWER_POLICY_NO_USER_INTERACTION;
+ boolean isSuccess = applyPowerPolicyFromCommand(policyId);
+ assertWithMessage("Apply power policy from command status").that(
+ isSuccess).isTrue();
+ waitForPowerPolicy(policyId);
+
+ MockedPowerPolicyListener listenerToWait = setUpPowerPolicyAudioInvert();
+
+ mService.applyPowerPolicy(POWER_POLICY_AUDIO_INVERT);
+
+ assertPowerPolicyNotApplied(POWER_POLICY_AUDIO_INVERT, listenerToWait);
+ }
+
private void setCarPowerPolicyRefactoringFeatureFlag(boolean flagValue) {
mFeatureFlags.setFlag(Flags.FLAG_CAR_POWER_POLICY_REFACTORING, flagValue);
}
@@ -2335,6 +2584,14 @@
mFeatureFlags.setFlag(Flags.FLAG_SERVERLESS_REMOTE_ACCESS, flagValue);
}
+ private void setCarPowerCancelShellCommand(boolean flagValue) {
+ mFeatureFlags.setFlag(Flags.FLAG_CAR_POWER_CANCEL_SHELL_COMMAND, flagValue);
+ }
+
+ private void setStopProcessBeforeSuspendToDisk(boolean flagValue) {
+ mFeatureFlags.setFlag(Flags.FLAG_STOP_PROCESS_BEFORE_SUSPEND_TO_DISK, flagValue);
+ }
+
/**
* Helper method to create mService and initialize a test case
*/
@@ -2344,6 +2601,7 @@
// to timeout. Also, we don't want to actually change Wifi state.
doReturn(mWifiManager).when(mContext).getSystemService(WifiManager.class);
doReturn(mTetheringManager).when(mContext).getSystemService(TetheringManager.class);
+ doReturn(mMockActivityManager).when(mContext).getSystemService(ActivityManager.class);
when(mResources.getInteger(R.integer.maxGarageModeRunningDurationInSecs))
.thenReturn(900);
when(mResources.getInteger(R.integer.config_maxSuspendWaitDuration))
@@ -2356,18 +2614,23 @@
return null;
}).when(() -> VoiceInteractionHelper.setEnabled(anyBoolean()));
- mFileHwStateMonitoring = new TemporaryFile("HW_STATE_MONITORING");
- mFileKernelSilentMode = new TemporaryFile("KERNEL_SILENT_MODE");
- mFileHwStateMonitoring.write(NONSILENT_STRING);
+ mFileHwStateMonitoring = temporaryFolder.newFile("HW_STATE_MONITORING");
+ mFileKernelSilentMode = temporaryFolder.newFile("KERNEL_SILENT_MODE");
+ writeToTempFile(mFileHwStateMonitoring, NONSILENT_STRING);
mPowerComponentHandler = new PowerComponentHandler(mContext, mSystemInterface,
- new AtomicFile(mComponentStateFile.getFile()));
+ new AtomicFile(mComponentStateFile));
mPowerPolicyDaemon = new FakeCarPowerPolicyDaemon();
setCarPowerPolicyRefactoringFeatureFlag(false);
- mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
- mSystemInterface, mUserManager, mUserService, mPowerPolicyDaemon,
- mPowerComponentHandler, mFeatureFlags, mScreenOffHandler,
- mFileHwStateMonitoring.getFile().getPath(),
- mFileKernelSilentMode.getFile().getPath(), NORMAL_BOOT);
+ mService = new CarPowerManagementService.Builder()
+ .setContext(mContext).setResources(mResources)
+ .setPowerHalService(mPowerHal).setSystemInterface(mSystemInterface)
+ .setUserManager(mUserManager).setCarUserService(mUserService)
+ .setPowerPolicyDaemon(mPowerPolicyDaemon)
+ .setPowerComponentHandler(mPowerComponentHandler).setFeatureFlags(mFeatureFlags)
+ .setScreenOffHandler(mScreenOffHandler)
+ .setSilentModeHwStatePath(mFileHwStateMonitoring.getPath())
+ .setSilentModeKernelStatePath(mFileKernelSilentMode.getPath())
+ .setBootReason(NORMAL_BOOT).build();
CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
CarLocalServices.addService(CarPowerManagementService.class, mService);
mService.init();
@@ -2385,11 +2648,16 @@
new int[]{CUSTOM_COMPONENT_1000, CUSTOM_COMPONENT_1001, CUSTOM_COMPONENT_1002,
CUSTOM_COMPONENT_1003});
setCarPowerPolicyRefactoringFeatureFlag(true);
- mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
- mSystemInterface, mUserManager, mUserService, mRefactoredPowerPolicyDaemon,
- mPowerComponentHandler, mFeatureFlags, mScreenOffHandler,
- mFileHwStateMonitoring.getFile().getPath(),
- mFileKernelSilentMode.getFile().getPath(), NORMAL_BOOT);
+ mService = new CarPowerManagementService.Builder()
+ .setContext(mContext).setResources(mResources)
+ .setPowerHalService(mPowerHal).setSystemInterface(mSystemInterface)
+ .setUserManager(mUserManager).setCarUserService(mUserService)
+ .setPowerPolicyDaemon(mRefactoredPowerPolicyDaemon)
+ .setPowerComponentHandler(mPowerComponentHandler).setFeatureFlags(mFeatureFlags)
+ .setScreenOffHandler(mScreenOffHandler)
+ .setSilentModeHwStatePath(mFileHwStateMonitoring.getPath())
+ .setSilentModeKernelStatePath(mFileKernelSilentMode.getPath())
+ .setBootReason(NORMAL_BOOT).build();
CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
CarLocalServices.addService(CarPowerManagementService.class, mService);
mService.init();
@@ -2399,6 +2667,12 @@
assertStateReceived(MockedPowerHalService.SET_WAIT_FOR_VHAL, 0);
}
+ private void writeToTempFile(File file, String content) throws IOException {
+ try (FileWriter fw = new FileWriter(file)) {
+ fw.write(content);
+ }
+ }
+
private void suspendDevice() throws Exception {
mService.handleOn();
mPowerSignalListener.addEventListener(PowerHalService.SET_DEEP_SLEEP_ENTRY);
@@ -2465,7 +2739,7 @@
SYSTEM_POWER_POLICY_INITIAL_ON);
assertThat(mDisplayInterface.isAnyDisplayEnabled()).isTrue();
- mFileHwStateMonitoring.write(NONSILENT_STRING); // Wake non-silently
+ writeToTempFile(mFileHwStateMonitoring, NONSILENT_STRING); // Wake non-silently
mService.setStateForWakeUp();
mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
assertVoiceInteractionEnabled();
@@ -2645,13 +2919,35 @@
() -> listenerToWait.getCurrentPowerPolicy() != null);
if (mFeatureFlags.carPowerPolicyRefactoring()) {
assertWithMessage("Power policy daemon last notified policy ID").that(
- mRefactoredPowerPolicyDaemon.getLastAppliedPowerPolicyId()).isEqualTo(policyId);
+ mRefactoredPowerPolicyDaemon.getCurrentPowerPolicyId()).isEqualTo(policyId);
} else {
assertWithMessage("Power policy daemon last notified policy ID").that(
mPowerPolicyDaemon.getLastNotifiedPolicyId()).isEqualTo(policyId);
}
}
+ private void assertPowerPolicyNotApplied(String policyId,
+ MockedPowerPolicyListener listenerToCheck) throws Exception {
+ CarPowerPolicy policy = mService.getCurrentPowerPolicy();
+ if (policy != null) {
+ assertWithMessage("Current policy ID").that(policy.getPolicyId())
+ .isNotEqualTo(policyId);
+ }
+ policy = listenerToCheck.getCurrentPowerPolicy();
+ if (policy != null) {
+ assertWithMessage("Notified policy ID").that(policy.getPolicyId())
+ .isNotEqualTo(policyId);
+ }
+ if (mFeatureFlags.carPowerPolicyRefactoring()) {
+ assertWithMessage("Power policy daemon last notified policy ID")
+ .that(mRefactoredPowerPolicyDaemon.getCurrentPowerPolicyId())
+ .isNotEqualTo(policyId);
+ } else {
+ assertWithMessage("Power policy daemon last notified policy ID").that(
+ mPowerPolicyDaemon.getLastNotifiedPolicyId()).isNotEqualTo(policyId);
+ }
+ }
+
private void assertPowerPolicyGroupSet(String policyGroupId) {
assertWithMessage("Current power policy group id").that(
mService.getCurrentPowerPolicyGroupId()).isEqualTo(policyGroupId);
@@ -2981,17 +3277,15 @@
@Override
void init() {}
- private boolean isAutoPowerSaving() {
- return mIsAutoPowerSaving;
- }
-
private void setIsAutoPowerSaving(boolean isPowerSaving) {
mIsAutoPowerSaving = isPowerSaving;
}
private void setDisplayPowerInfo(int displayId, @FakeDisplayPowerMode int powerMode) {
FakeDisplayPowerInfo info = new FakeDisplayPowerInfo(powerMode);
- mDisplayPowerInfos.put(displayId, info);
+ synchronized (sLock) {
+ mDisplayPowerInfos.put(displayId, info);
+ }
}
boolean canTurnOnDisplay(int displayId) {
@@ -3058,10 +3352,6 @@
mMode = mode;
}
- private void setMode(@FakeDisplayPowerMode int mode) {
- mMode = mode;
- }
-
private @FakeDisplayPowerMode int getMode() {
return mMode;
}
diff --git a/tests/carservice_unit_test/src/com/android/car/power/SilentModeHandlerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/power/SilentModeHandlerUnitTest.java
index 3fad1e2..50faf19 100644
--- a/tests/carservice_unit_test/src/com/android/car/power/SilentModeHandlerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/power/SilentModeHandlerUnitTest.java
@@ -174,7 +174,7 @@
handler.init();
writeStringToFile(mFileHwStateMonitoring.getFile(), VALUE_SILENT_MODE);
- assertSilentMode(handler, /* isForcedMode= */ false, /* expectedSilentMode= */ true);
+ assertSilentMode(handler, /* isForcedMode= */ false, /* expectedMode= */ true);
handler.release();
boolean currentSilentMode = handler.isSilentMode();
diff --git a/tests/carservice_unit_test/src/com/android/car/remoteaccess/CarRemoteAccessServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/remoteaccess/CarRemoteAccessServiceUnitTest.java
index 2c0f667..c4ed215 100644
--- a/tests/carservice_unit_test/src/com/android/car/remoteaccess/CarRemoteAccessServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/remoteaccess/CarRemoteAccessServiceUnitTest.java
@@ -304,9 +304,9 @@
}
@After
- public void tearDown() {
+ public void tearDown() throws Exception {
mService.release();
- CarServiceUtils.finishAllHandlerTasks();
+ CarServiceUtils.quitHandlerThreads();
CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
CarLocalServices.addService(CarPowerManagementService.class, mOldCarPowerManagementService);
@@ -1254,7 +1254,10 @@
@Test
public void testPendingRequestNotTimeout() throws Exception {
- mService.setMaxTaskPendingMs(1000);
+ // Set unbind delay to be 10s so that we don't unbind before the test finishes.
+ mService.setTaskUnbindDelayMs(10000);
+ // A task will be stored in the queue for 10s.
+ mService.setMaxTaskPendingMs(10000);
mService.init();
runBootComplete();
RemoteAccessHalCallback halCallback = prepareCarRemoteTaskClient();
diff --git a/tests/carservice_unit_test/src/com/android/car/storagemonitoring/IoStatsTrackerTest.java b/tests/carservice_unit_test/src/com/android/car/storagemonitoring/IoStatsTrackerTest.java
index 06fa01a..58e8853 100644
--- a/tests/carservice_unit_test/src/com/android/car/storagemonitoring/IoStatsTrackerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/storagemonitoring/IoStatsTrackerTest.java
@@ -295,7 +295,7 @@
}
}
- private final class MockSystemStateInterface implements SystemStateInterface {
+ private static final class MockSystemStateInterface implements SystemStateInterface {
private final Object mLock = new Object();
@@ -360,12 +360,5 @@
mIoRecords.put(record.uid, record);
}
}
-
- void clear() {
- synchronized (mLock) {
- mProcesses.clear();
- mIoRecords.clear();
- }
- }
}
}
diff --git a/tests/carservice_unit_test/src/com/android/car/systeminterface/SystemStateInterfaceTest.java b/tests/carservice_unit_test/src/com/android/car/systeminterface/SystemStateInterfaceTest.java
index c9c3872..2449f66 100644
--- a/tests/carservice_unit_test/src/com/android/car/systeminterface/SystemStateInterfaceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/systeminterface/SystemStateInterfaceTest.java
@@ -177,16 +177,16 @@
public void testGetRandomizedDelay() throws Exception {
assertThat(getRandomizedDelay(Duration.ZERO, Duration.ZERO)).isEqualTo(Duration.ZERO);
- Duration randomizedDelay = getRandomizedDelay(Duration.ZERO, Duration.ofMillis(1000));
+ Duration randomizedDelay = getRandomizedDelay(Duration.ZERO, Duration.ofSeconds(1));
assertWithMessage("Minimum delay duration").that(randomizedDelay).isAtLeast(Duration.ZERO);
assertWithMessage("Maximum delay duration").that(randomizedDelay)
- .isLessThan(Duration.ofMillis(1000));
+ .isLessThan(Duration.ofSeconds(1));
- randomizedDelay = getRandomizedDelay(/* delay= */ Duration.ofMillis(2000),
- /* delayRange= */ Duration.ofMillis(1000));
+ randomizedDelay = getRandomizedDelay(/* delay= */ Duration.ofSeconds(2),
+ /* delayRange= */ Duration.ofSeconds(1));
assertWithMessage("Minimum delay duration").that(randomizedDelay)
- .isAtLeast(Duration.ofMillis(1000));
+ .isAtLeast(Duration.ofSeconds(1));
assertWithMessage("Maximum delay duration").that(randomizedDelay)
- .isLessThan(Duration.ofMillis(3000));
+ .isLessThan(Duration.ofSeconds(3));
}
}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java
index af41fdc..2374e18 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java
@@ -48,8 +48,6 @@
import android.util.Log;
import com.android.car.CarLog;
-import com.android.car.CarPropertyService;
-import com.android.car.internal.property.CarPropertyConfigList;
import com.android.car.telemetry.ResultStore;
import com.android.car.telemetry.publisher.AbstractPublisher;
import com.android.car.telemetry.publisher.PublisherFactory;
@@ -72,7 +70,6 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
@@ -133,8 +130,6 @@
@Mock
private PackageManager mMockPackageManager;
@Mock
- private CarPropertyService mMockCarPropertyService;
- @Mock
private DataBroker.DataBrokerListener mMockDataBrokerListener;
@Mock
private IBinder mMockScriptExecutorBinder;
@@ -153,9 +148,6 @@
@Before
public void setUp() throws Exception {
- when(mMockCarPropertyService.getPropertyList())
- .thenReturn(new CarPropertyConfigList(
- Collections.singletonList(PROP_CONFIG)));
mockPackageManager();
mFakeScriptExecutor = new FakeScriptExecutor();
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
index bc9e826..fd57a1e 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
@@ -288,8 +288,6 @@
@Captor
private ArgumentCaptor<ICarPropertyEventListener> mCarPropertyCallbackCaptor;
@Captor
- private ArgumentCaptor<PersistableBundle> mBundleCaptor;
- @Captor
private ArgumentCaptor<List<PersistableBundle>> mBundleListCaptor;
private VehiclePropertyPublisher mVehiclePropertyPublisher;
@@ -529,13 +527,13 @@
mVehiclePropertyPublisher.addDataSubscriber(mMockStringDataSubscriber);
ICarPropertyEventListener eventListener = mCarPropertyCallbackCaptor.getValue();
CarPropertyEvent propEvent1 = new CarPropertyEvent(PROPERTY_EVENT_PROPERTY_CHANGE,
- new CarPropertyValue<>(PROP_STRING_ID, AREA_ID, STATUS, /* timestamp= */ 0L,
+ new CarPropertyValue<>(PROP_STRING_ID, AREA_ID, STATUS, /* timestampNanos= */ 0L,
"first"));
CarPropertyEvent propEvent2 = new CarPropertyEvent(PROPERTY_EVENT_PROPERTY_CHANGE,
- new CarPropertyValue<>(PROP_STRING_ID, AREA_ID, STATUS, /* timestamp= */ 5L,
+ new CarPropertyValue<>(PROP_STRING_ID, AREA_ID, STATUS, /* timestampNanos= */ 5L,
"second"));
CarPropertyEvent propEvent3 = new CarPropertyEvent(PROPERTY_EVENT_PROPERTY_CHANGE,
- new CarPropertyValue<>(PROP_STRING_ID, AREA_ID, STATUS, /* timestamp= */ 7L,
+ new CarPropertyValue<>(PROP_STRING_ID, AREA_ID, STATUS, /* timestampNanos= */ 7L,
"third"));
eventListener.onEvent(Collections.singletonList(propEvent1));
diff --git a/tests/carservice_unit_test/src/com/android/car/user/BaseCarUserServiceTestCase.java b/tests/carservice_unit_test/src/com/android/car/user/BaseCarUserServiceTestCase.java
index e6060da..f75f736 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/BaseCarUserServiceTestCase.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/BaseCarUserServiceTestCase.java
@@ -635,7 +635,8 @@
mCarUxRestrictionService,
mHandler,
mCarPackageManagerService,
- mCarOccupantZoneService);
+ mCarOccupantZoneService,
+ new ActivityManagerCurrentUserFetcher());
}
/**
@@ -1032,7 +1033,7 @@
i++;
}
Preconditions.checkArgument(foundCurrentUser,
- "no user with id " + currentUserId + " on " + mExistingUsers);
+ "no user with id %d on %s", currentUserId, mExistingUsers);
return infos;
}
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index d7fda7b..ceb2b7a 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -65,7 +65,6 @@
import android.app.ActivityManager;
import android.car.Car;
import android.car.CarOccupantZoneManager;
-import android.car.CarVersion;
import android.car.ICarResultReceiver;
import android.car.PlatformVersion;
import android.car.SyncResultCallback;
@@ -612,34 +611,6 @@
}
@Test
- public void testOnUserLifecycleEvent_notifyReceiver_targetVersionCheck() throws Exception {
- // Arrange: add receivers.
- mCarUserService.setLifecycleListenerForApp("package1",
- new UserLifecycleEventFilter.Builder()
- .addEventType(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_CREATED).build(),
- mLifecycleEventReceiver);
- mCarUserService.setLifecycleListenerForApp("package2",
- new UserLifecycleEventFilter.Builder()
- .addEventType(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_CREATED).build(),
- mAnotherLifecycleEventReceiver);
-
- when(mCarPackageManagerService.getTargetCarVersion("package1"))
- .thenReturn(CarVersion.VERSION_CODES.TIRAMISU_0);
- when(mCarPackageManagerService.getTargetCarVersion("package2"))
- .thenReturn(CarVersion.VERSION_CODES.TIRAMISU_1);
-
- // Act: User created event occurs.
- sendUserLifecycleEvent(/* fromUser */ 0, mRegularUserId,
- CarUserManager.USER_LIFECYCLE_EVENT_TYPE_CREATED);
- waitForHandlerThreadToFinish();
-
- // Verify: receivers are called or not depending on whether the target version meets
- // requirement.
- verify(mLifecycleEventReceiver, never()).send(anyInt(), any());
- verify(mAnotherLifecycleEventReceiver).send(anyInt(), any());
- }
-
- @Test
public void testOnUserLifecycleEvent_notifyReceiver_singleReceiverWithMultipleFilters()
throws Exception {
// Arrange: add one receiver with multiple filters.
@@ -1007,7 +978,6 @@
List<UserHandle> existingUsers = Arrays.asList(mAdminUser, mRegularUser);
mockExistingUsersAndCurrentUser(existingUsers, currentUser);
UserHandle removeUser = mAdminUser;
- int removedUserId = removeUser.getIdentifier();
mockRemoveUserNoCallback(removeUser, UserManager.REMOVE_RESULT_DEFERRED);
removeUser(mAdminUserId, NO_CALLER_RESTRICTIONS, mUserRemovalResultCallbackImpl);
@@ -1984,7 +1954,7 @@
public void testCreateUser_internalHalFailure() throws Exception {
UserHandle newUser = UserHandle.of(42);
mockUmCreateUser(mMockedUserManager, "dude", "TypeONegative", 108, newUser);
- mockHalCreateUser(HalCallback.STATUS_INVALID, /* not_used_status= */ -1);
+ mockHalCreateUser(HalCallback.STATUS_INVALID, /* responseStatus= */ -1);
mockRemoveUser(newUser);
createUser("dude", "TypeONegative", 108, ASYNC_CALL_TIMEOUT_MS, mUserCreationResultCallback,
@@ -3014,7 +2984,7 @@
}
}
- protected void userOpFlagTest(int carConstant, int amConstant) {
+ private void userOpFlagTest(int carConstant, int amConstant) {
assertWithMessage("Constant %s",
DebugUtils.constantToString(CarUserService.class, "USER_OP_", carConstant))
.that(carConstant).isEqualTo(amConstant);
diff --git a/tests/carservice_unit_test/src/com/android/car/user/InitialUserSetterTest.java b/tests/carservice_unit_test/src/com/android/car/user/InitialUserSetterTest.java
index 9a4b6bb..2e96a66 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/InitialUserSetterTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/InitialUserSetterTest.java
@@ -60,8 +60,6 @@
import android.provider.Settings;
import com.android.car.internal.os.CarSystemProperties;
-import com.android.car.user.InitialUserSetter.Builder;
-import com.android.car.user.InitialUserSetter.InitialUserInfo;
import org.junit.Before;
import org.junit.Test;
@@ -81,8 +79,8 @@
private static final int NEW_USER_ID = 101;
private static final int CURRENT_USER_ID = 102;
- private static final InitialUserInfo INITIAL_USER_INFO_RESUME = new Builder(
- InitialUserSetter.TYPE_CREATE)
+ private static final InitialUserSetter.InitialUserInfo INITIAL_USER_INFO_RESUME =
+ new InitialUserSetter.Builder(InitialUserSetter.TYPE_CREATE)
.setRequestType(InitialUserInfoRequestType.RESUME)
.build();
@@ -169,7 +167,7 @@
UserHandle user = expectRegularUserExists(mMockedUserHandleHelper, USER_ID);
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_SWITCH)
.setSwitchUserId(USER_ID)
.build());
@@ -184,7 +182,7 @@
UserHandle user = expectSystemUserExists(mMockedUserHandleHelper, UserHandle.USER_SYSTEM);
expectSwitchUser(UserHandle.USER_SYSTEM);
- mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_SWITCH)
.setSwitchUserId(UserHandle.USER_SYSTEM)
.build());
@@ -205,7 +203,7 @@
expectGuestReplaced(USER_ID, newGuest);
expectSwitchUser(NEW_USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_SWITCH)
.setSwitchUserId(USER_ID)
.setReplaceGuest(true)
.build());
@@ -225,7 +223,7 @@
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_SWITCH)
.setSwitchUserId(USER_ID)
.setReplaceGuest(false)
.build());
@@ -242,7 +240,7 @@
expectGuestUserExists(mMockedUserHandleHelper, USER_ID, /* isEphemeral= */ true);
expectGuestReplaced(USER_ID, /* newGuest= */ null);
- mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_SWITCH)
.setSwitchUserId(USER_ID)
.setReplaceGuest(true)
.build());
@@ -257,7 +255,7 @@
expectRegularUserExists(mMockedUserHandleHelper, USER_ID);
expectSwitchUserFails(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_SWITCH)
.setSwitchUserId(USER_ID)
.build());
@@ -270,7 +268,7 @@
public void testSwitchUser_fail_userDoesntExist() throws Exception {
// No need to set user exists expectation / will return null by default
- mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_SWITCH)
.setSwitchUserId(USER_ID)
.build());
@@ -284,7 +282,7 @@
expectRegularUserExists(mMockedUserHandleHelper, USER_ID);
expectSwitchUserThrowsException(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_SWITCH)
.setSwitchUserId(USER_ID)
.build());
@@ -298,7 +296,7 @@
mockGetCurrentUser(CURRENT_USER_ID);
UserHandle currentUser = expectRegularUserExists(mMockedUserHandleHelper, CURRENT_USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_SWITCH)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_SWITCH)
.setSwitchUserId(CURRENT_USER_ID)
.build());
@@ -402,7 +400,7 @@
expectCreateFullUser("TheDude", NO_FLAGS, newUser);
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_CREATE)
.setNewUserName("TheDude")
.setNewUserFlags(0)
.build());
@@ -420,7 +418,7 @@
expectCreateFullUser("TheDude", UserManagerHelper.FLAG_ADMIN, newUser);
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_CREATE)
.setNewUserName("TheDude")
.setNewUserFlags(UserInfo.USER_FLAG_ADMIN)
.build());
@@ -438,7 +436,7 @@
expectCreateFullUser("TheDude", UserManagerHelper.FLAG_ADMIN, newUser);
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_CREATE)
.setNewUserName("TheDude")
.setNewUserFlags(UserInfo.USER_FLAG_ADMIN)
.setUserLocales("LOL")
@@ -458,7 +456,7 @@
expectCreateGuestUser("TheDude", UserManagerHelper.FLAG_EPHEMERAL, newGuest);
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_CREATE)
.setNewUserName("TheDude")
.setNewUserFlags(UserInfo.USER_FLAG_EPHEMERAL | UserInfo.USER_FLAG_GUEST)
.build());
@@ -472,7 +470,7 @@
@Test
public void testCreateUser_fail_systemUser() throws Exception {
// No need to mock createUser() expectation - it shouldn't be called
- mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_CREATE)
.setNewUserName("TheDude")
.setNewUserFlags(UserInfo.USER_FLAG_SYSTEM)
.build());
@@ -485,7 +483,7 @@
@Test
public void testCreateUser_fail_guestAdmin() throws Exception {
// No need to set createUser() expectation - it shouldn't be called
- mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_CREATE)
.setNewUserName("TheDude")
.setNewUserFlags(UserInfo.USER_FLAG_GUEST | UserInfo.USER_FLAG_ADMIN)
.build());
@@ -497,7 +495,7 @@
@Test
public void testCreateUser_fail_ephemeralAdmin() throws Exception {
// No need to set createUser() expectation - it shouldn't be called
- mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_CREATE)
.setNewUserName("TheDude")
.setNewUserFlags(UserInfo.USER_FLAG_EPHEMERAL | UserInfo.USER_FLAG_ADMIN)
.build());
@@ -509,7 +507,7 @@
@Test
public void testCreateUser_fail_createFail() throws Exception {
// No need to set createUser() expectation - it will return false by default
- mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_CREATE)
.setNewUserName("TheDude")
.setNewUserFlags(0)
.build());
@@ -522,7 +520,7 @@
public void testCreateUser_fail_createThrowsException() throws Exception {
expectCreateUserThrowsException("TheDude", 0);
- mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_CREATE)
.setNewUserName("TheDude")
.setNewUserFlags(0)
.build());
@@ -537,7 +535,7 @@
expectCreateFullUser("TheDude", NO_FLAGS, user);
expectSwitchUserFails(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_CREATE)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_CREATE)
.setNewUserName("TheDude")
.setNewUserFlags(0)
.build());
@@ -557,7 +555,7 @@
expectGuestReplaced(CURRENT_USER_ID, newGuest);
expectSwitchUser(NEW_USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_REPLACE_GUEST)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_REPLACE_GUEST)
.build());
verifyUserSwitched(NEW_USER_ID);
@@ -573,7 +571,7 @@
expectGuestUserExists(mMockedUserHandleHelper, CURRENT_USER_ID, /* isEphemeral= */ true);
expectGuestReplaced(CURRENT_USER_ID, null);
- mSetter.set(new Builder(InitialUserSetter.TYPE_REPLACE_GUEST)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_REPLACE_GUEST)
.build());
verifyFallbackDefaultBehaviorCalledFromReaplceUser();
@@ -586,7 +584,7 @@
/* isEphemeral= */ true);
expectGuestReplaced(CURRENT_USER_ID, guest);
- mSetter.set(new Builder(InitialUserSetter.TYPE_REPLACE_GUEST)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_REPLACE_GUEST)
.build());
verifyUserNeverSwitched();
@@ -602,7 +600,7 @@
expectCreateFullUser(OWNER_NAME, UserManagerHelper.FLAG_ADMIN, newUser);
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
verifyUserSwitched(USER_ID);
verifyFallbackDefaultBehaviorNeverCalled();
@@ -617,7 +615,7 @@
expectCreateFullUser(OWNER_NAME, UserManagerHelper.FLAG_ADMIN, newUser);
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
.setUserLocales("LOL")
.build());
@@ -635,7 +633,7 @@
expectCreateFullUser(OWNER_NAME, UserManagerHelper.FLAG_ADMIN, newUser);
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
.setUserLocales("")
.build());
@@ -653,7 +651,7 @@
expectCreateFullUser(OWNER_NAME, UserManagerHelper.FLAG_ADMIN, newUser);
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
.setUserLocales(" ")
.build());
@@ -669,7 +667,7 @@
// no need to mock hasInitialUser(), it will return false by default
// no need to mock createUser(), it will return null by default
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
verifyUserNeverSwitched();
verifyFallbackDefaultBehaviorCalledFromDefaultBehavior();
@@ -683,7 +681,7 @@
expectCreateFullUser(OWNER_NAME, UserManagerHelper.FLAG_ADMIN, user);
expectSwitchUserFails(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
verifyFallbackDefaultBehaviorCalledFromDefaultBehavior();
verifySystemUserUnlocked();
@@ -697,7 +695,7 @@
expectCreateFullUser(OWNER_NAME, UserManagerHelper.FLAG_ADMIN, user);
expectSwitchUserFails(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
.setUserLocales("LOL")
.build());
@@ -712,7 +710,7 @@
UserHandle existingUser = expectHasInitialUser(USER_ID);
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
verifyUserSwitched(USER_ID);
verifyFallbackDefaultBehaviorNeverCalled();
@@ -726,7 +724,7 @@
UserHandle currentUser = expectHasInitialUser(CURRENT_USER_ID);
expectSwitchUser(CURRENT_USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
verifyUserNeverSwitched();
verifyFallbackDefaultBehaviorNeverCalled();
@@ -744,7 +742,7 @@
expectCreateFullUser(OWNER_NAME, UserManagerHelper.FLAG_ADMIN, newUser);
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
verifyUserSwitched(USER_ID);
verifyFallbackDefaultBehaviorNeverCalled();
@@ -757,7 +755,7 @@
expectHasInitialUser(USER_ID);
expectSwitchUserFails(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
verifyFallbackDefaultBehaviorCalledFromDefaultBehavior();
verifyUserNeverCreated();
@@ -775,7 +773,7 @@
expectGuestReplaced(USER_ID, newGuest);
expectSwitchUser(NEW_USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
.setReplaceGuest(true)
.build());
@@ -793,7 +791,7 @@
expectGuestUserExists(mMockedUserHandleHelper, USER_ID, /* isEphemeral= */ true);
expectGuestReplaced(USER_ID, /* newGuest= */ null);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
.setReplaceGuest(true)
.build());
@@ -809,7 +807,7 @@
UserHandle user = expectHasInitialUser(USER_ID, supportsOverrideUserIdProperty);
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
.setSupportsOverrideUserIdProperty(true)
.build());
@@ -826,7 +824,7 @@
UserHandle existingGuest = expectHasInitialGuest(USER_ID);
expectSwitchUser(USER_ID);
- mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
+ mSetter.set(new InitialUserSetter.Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
.setReplaceGuest(false)
.build());
@@ -1152,11 +1150,6 @@
doReturn(toBeReturned).when(() -> ActivityManagerHelper.startUserInForeground(userId));
}
- private static void expectAmStartBgUser(@UserIdInt int userId, boolean toBeReturned)
- throws Exception {
- doReturn(toBeReturned).when(() -> ActivityManagerHelper.startUserInBackground(userId));
- }
-
private void verifyUserSwitched(@UserIdInt int userId) throws Exception {
verify(mSetter).startForegroundUser(any(), eq(userId));
verify(mSetter).setLastActiveUser(userId);
@@ -1207,7 +1200,8 @@
isInitialInfo(supportsOverrideUserIdProperty), anyBoolean(), anyString());
}
- private static InitialUserInfo isInitialInfo(boolean supportsOverrideUserIdProperty) {
+ private static InitialUserSetter.InitialUserInfo isInitialInfo(
+ boolean supportsOverrideUserIdProperty) {
return argThat((info) -> {
return info.supportsOverrideUserIdProperty == supportsOverrideUserIdProperty;
});
@@ -1244,7 +1238,7 @@
assertThat(mMockSettings.getString(Settings.System.SYSTEM_LOCALES)).isNull();
}
- private final class MyListener implements Consumer<UserHandle> {
+ private static final class MyListener implements Consumer<UserHandle> {
public int numberCalls;
public UserHandle initialUser;
diff --git a/tests/carservice_unit_test/src/com/android/car/user/MockedUserHandleBuilder.java b/tests/carservice_unit_test/src/com/android/car/user/MockedUserHandleBuilder.java
index 718fdea..3fe9c62 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/MockedUserHandleBuilder.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/MockedUserHandleBuilder.java
@@ -59,11 +59,6 @@
return this;
}
- private MockedUserHandleBuilder setInitialized() {
- when(mUserHandleHelper.isInitializedUser(mUser)).thenReturn(true);
- return this;
- }
-
private MockedUserHandleBuilder expectGettersFail() {
RuntimeException exception = new RuntimeException("D'OH!");
when(mUserHandleHelper.isAdminUser(mUser)).thenThrow(exception);
diff --git a/tests/carservice_unit_test/src/com/android/car/util/BrightnessUtilsTest.java b/tests/carservice_unit_test/src/com/android/car/util/BrightnessUtilsTest.java
index 50c98ef..9d719e2 100644
--- a/tests/carservice_unit_test/src/com/android/car/util/BrightnessUtilsTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/util/BrightnessUtilsTest.java
@@ -32,7 +32,6 @@
private static final float MAX_FLOAT = 1.0f;
private static final int MIN_BACKLIGHT = 10; // config_screenBrightnessSettingMinimum
private static final int MAX_BACKLIGHT = 255; // config_screenBrightnessSettingMaximum
- private static final int INVALID_BRIGHTNESS = -1;
@Test
public void linearToGamma_minValue_shouldReturnMin() {
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogManagerUnitTest.java
index f48ea87..f65823d 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogManagerUnitTest.java
@@ -740,7 +740,7 @@
/* systemWideThresholds= */new ArrayList<>()).build();
ResourceOveruseConfiguration.Builder configBuilder =
new ResourceOveruseConfiguration.Builder(
- COMPONENT_TYPE_SYSTEM, /* safeTiKillPackages= */new ArrayList<>(),
+ COMPONENT_TYPE_SYSTEM, /* safeToKillPackages= */new ArrayList<>(),
/* vendorPackagePrefixes= */new ArrayList<>(),
/* packagesToAppCategoryTypes= */ new ArrayMap<>());
configBuilder.setIoOveruseConfiguration(ioOveruseConfig);
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
index ac03f99..76ca1fd 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
@@ -79,7 +79,6 @@
import android.automotive.watchdog.internal.ApplicationCategoryType;
import android.automotive.watchdog.internal.ComponentType;
import android.automotive.watchdog.internal.GarageMode;
-import android.automotive.watchdog.internal.ICarWatchdog;
import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
import android.automotive.watchdog.internal.PackageIdentifier;
import android.automotive.watchdog.internal.PackageInfo;
@@ -113,7 +112,6 @@
import android.net.Uri;
import android.os.Binder;
import android.os.FileUtils;
-import android.os.IBinder;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -164,8 +162,6 @@
*/
@RunWith(MockitoJUnitRunner.class)
public final class CarWatchdogServiceUnitTest extends AbstractExtendedMockitoTestCase {
- private static final String CAR_WATCHDOG_DAEMON_INTERFACE =
- "android.automotive.watchdog.internal.ICarWatchdog/default";
private static final String SYSTEM_PACKAGE_NAME = "system_package";
private static final int MAX_WAIT_TIME_MS = 3000;
private static final long OVERUSE_HANDLING_DELAY_MILLS = 1000;
@@ -189,8 +185,6 @@
@Mock private CarUserService mMockCarUserService;
@Mock private CarUxRestrictionsManagerService mMockCarUxRestrictionsManagerService;
@Mock private Resources mMockResources;
- @Mock private IBinder mMockBinder;
- @Mock private ICarWatchdog mMockCarWatchdogDaemon;
@Mock private NotificationHelper mMockNotificationHelper;
@Mock private ICarServiceHelper.Stub mMockCarServiceHelper;
@Mock private WatchdogProcessHandler mMockWatchdogProcessHandler;
@@ -228,7 +222,6 @@
private final ArrayMap<String, android.content.pm.PackageInfo> mPmPackageInfoByUserPackage =
new ArrayMap<>();
private final ArraySet<String> mDisabledUserPackages = new ArraySet<>();
- private final SparseArray<String> mDisabledPackagesSettingsStringByUserid = new SparseArray<>();
private final Set<WatchdogStorage.UserPackageSettingsEntry> mUserPackageSettingsEntries =
new ArraySet<>();
private final List<WatchdogStorage.IoUsageStatsEntry> mIoUsageStatsEntries = new ArrayList<>();
@@ -1351,7 +1344,7 @@
private void setCarPowerState(int powerState) throws Exception {
when(mMockCarPowerManagementService.getPowerState()).thenReturn(powerState);
- mCarPowerStateListener.onStateChanged(powerState, /* timeoutMs= */ -1);
+ mCarPowerStateListener.onStateChanged(powerState, /* expirationTimeMs= */ -1);
}
private void injectPackageInfos(List<android.content.pm.PackageInfo> packageInfos) {
@@ -1430,9 +1423,9 @@
mTimeSource.getCurrentDate());
for (int i = 1; i < 8; ++i) {
summaries.add(constructCarWatchdogDailyIoUsageSummary(
- /* fgWrBytes= */ 100 * i * weekMultiplier * sysOrUidMultiplier,
- /* bgWrBytes= */ 200 * i * weekMultiplier * sysOrUidMultiplier,
- /* gmWrBytes= */ 300 * i * weekMultiplier * sysOrUidMultiplier,
+ /* fgWrBytes= */ weekMultiplier * sysOrUidMultiplier * 100 * i ,
+ /* bgWrBytes= */ weekMultiplier * sysOrUidMultiplier * 200 * i,
+ /* gmWrBytes= */ weekMultiplier * sysOrUidMultiplier * 300 * i,
/* overuseCount= */ 2 * i));
}
return summaries;
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogPerfHandlerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogPerfHandlerUnitTest.java
index c7033f8..f38df1f 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogPerfHandlerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogPerfHandlerUnitTest.java
@@ -161,6 +161,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -492,7 +493,7 @@
new WatchdogStorage.UserPackageSettingsEntry(/* userId= */ 101,
"third_party_package",
/* killableState= */ PackageKillableState.KILLABLE_STATE_NO,
- /* lastModifiedKillableStateEpoch= */ 123456789));
+ /* killableStateLastModifiedEpochSeconds= */ 123456789));
mWatchdogPerfHandler.writeToDatabase();
@@ -3248,11 +3249,11 @@
new WatchdogStorage.UserPackageSettingsEntry(/* userId= */ 100,
"system_package",
/* killableState= */ PackageKillableState.KILLABLE_STATE_YES,
- /* lastModifiedKillableStateEpoch= */ 123456789),
+ /* killableStateLastModifiedEpochSeconds= */ 123456789),
new WatchdogStorage.UserPackageSettingsEntry(/* userId= */ 100,
"third_party_package",
/* killableState= */ PackageKillableState.KILLABLE_STATE_YES,
- /* lastModifiedKillableStateEpoch= */ 123456789));
+ /* killableStateLastModifiedEpochSeconds= */ 123456789));
List<WatchdogStorage.IoUsageStatsEntry> expectedSavedIoUsageEntries = Arrays.asList(
new WatchdogStorage.IoUsageStatsEntry(/* userId= */ 100, "system_package",
@@ -4715,9 +4716,9 @@
mTimeSource.getCurrentDate());
for (int i = 1; i < 8; ++i) {
summaries.add(CarWatchdogServiceUnitTest.constructCarWatchdogDailyIoUsageSummary(
- /* fgWrBytes= */ 100 * i * weekMultiplier * sysOrUidMultiplier,
- /* bgWrBytes= */ 200 * i * weekMultiplier * sysOrUidMultiplier,
- /* gmWrBytes= */ 300 * i * weekMultiplier * sysOrUidMultiplier,
+ /* fgWrBytes= */ weekMultiplier * sysOrUidMultiplier * 100 * i,
+ /* bgWrBytes= */ weekMultiplier * sysOrUidMultiplier * 200 * i,
+ /* gmWrBytes= */ weekMultiplier * sysOrUidMultiplier * 300 * i,
/* overuseCount= */ 2 * i));
}
return summaries;
@@ -4747,7 +4748,8 @@
private static android.automotive.watchdog.internal.ResourceOveruseConfiguration
sampleInternalResourceOveruseConfiguration(@ComponentType int componentType,
android.automotive.watchdog.internal.IoOveruseConfiguration ioOveruseConfig) {
- String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType).toLowerCase();
+ String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType)
+ .toLowerCase(Locale.US);
android.automotive.watchdog.internal.ResourceOveruseConfiguration config =
new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
config.componentType = componentType;
@@ -4794,7 +4796,8 @@
private static android.automotive.watchdog.internal.IoOveruseConfiguration
sampleInternalIoOveruseConfiguration(@ComponentType int componentType) {
- String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType).toLowerCase();
+ String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType)
+ .toLowerCase(Locale.US);
android.automotive.watchdog.internal.IoOveruseConfiguration config =
new android.automotive.watchdog.internal.IoOveruseConfiguration();
config.componentLevelThresholds = constructPerStateIoOveruseThreshold(
@@ -4861,7 +4864,8 @@
private static IoOveruseConfiguration.Builder sampleIoOveruseConfigurationBuilder(
@ComponentType int componentType) {
- String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType).toLowerCase();
+ String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType)
+ .toLowerCase(Locale.US);
PerStateBytes componentLevelThresholds = new PerStateBytes(
/* foregroundModeBytes= */ componentType * 10L,
/* backgroundModeBytes= */ componentType * 20L,
@@ -4894,7 +4898,8 @@
private static ResourceOveruseConfiguration.Builder sampleResourceOveruseConfigurationBuilder(
@ComponentType int componentType, IoOveruseConfiguration ioOveruseConfig) {
- String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType).toLowerCase();
+ String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType)
+ .toLowerCase(Locale.US);
List<String> safeToKill = Arrays.asList(prefix + "_package.non_critical.A",
prefix + "_pkg.non_critical.B",
"shared:" + prefix + "_shared_package.non_critical.B",
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
index 00a6e8f..605cc90 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
@@ -166,20 +166,20 @@
List<WatchdogStorage.UserPackageSettingsEntry> expected = Arrays.asList(
new WatchdogStorage.UserPackageSettingsEntry(
/* userId= */ 100, "system_package.non_critical.A", KILLABLE_STATE_YES,
- /* lastModifiedKillableStateEpoch= */ 123456789),
+ /* killableStateLastModifiedEpochSeconds= */ 123456789),
new WatchdogStorage.UserPackageSettingsEntry(
/* userId= */ 100, "system_package.non_critical.B", KILLABLE_STATE_NO,
- /* lastModifiedKillableStateEpoch= */ 123456789));
+ /* killableStateLastModifiedEpochSeconds= */ 123456789));
assertThat(mService.saveUserPackageSettings(expected)).isTrue();
expected = Arrays.asList(
new WatchdogStorage.UserPackageSettingsEntry(
/* userId= */ 100, "system_package.non_critical.A", KILLABLE_STATE_NEVER,
- /* lastModifiedKillableStateEpoch= */ 123456789),
+ /* killableStateLastModifiedEpochSeconds= */ 123456789),
new WatchdogStorage.UserPackageSettingsEntry(
/* userId= */ 100, "system_package.non_critical.B", KILLABLE_STATE_NO,
- /* lastModifiedKillableStateEpoch= */ 123456789));
+ /* killableStateLastModifiedEpochSeconds= */ 123456789));
assertThat(mService.saveUserPackageSettings(expected)).isTrue();
@@ -848,15 +848,15 @@
while (cursor.moveToNext()) {
actual.add(new WatchdogStorage.UserPackageSettingsEntry(cursor.getInt(0),
cursor.getString(1), cursor.getInt(2),
- /* lastModifiedKillableStateEpoch= */ 123456789));
+ /* killableStateLastModifiedEpochSeconds= */ 123456789));
}
}
List<WatchdogStorage.UserPackageSettingsEntry> expected =
Arrays.asList(new WatchdogStorage.UserPackageSettingsEntry(100, "package_A",
- 1, /* lastModifiedKillableStateEpoch= */ 123456789),
+ 1, /* killableStateLastModifiedEpochSeconds= */ 123456789),
new WatchdogStorage.UserPackageSettingsEntry(101, "package_B",
- 2, /* lastModifiedKillableStateEpoch= */ 123456789));
+ 2, /* killableStateLastModifiedEpochSeconds= */ 123456789));
assertWithMessage("User package settings").that(actual).containsExactlyElementsIn(expected);
}
@@ -948,22 +948,24 @@
return new ArrayList<>(Arrays.asList(
new WatchdogStorage.UserPackageSettingsEntry(
/* userId= */ 100, "system_package.non_critical.A",
- KILLABLE_STATE_YES, /* lastModifiedKillableStateEpoch= */ 123456789),
+ KILLABLE_STATE_YES, /* killableStateLastModifiedEpochSeconds= */ 123456789),
new WatchdogStorage.UserPackageSettingsEntry(
/* userId= */ 100, "system_package.non_critical.B",
- KILLABLE_STATE_NO, /* lastModifiedKillableStateEpoch= */ 123456789),
+ KILLABLE_STATE_NO, /* killableStateLastModifiedEpochSeconds= */ 123456789),
new WatchdogStorage.UserPackageSettingsEntry(
/* userId= */ 100, "vendor_package.critical.C",
- KILLABLE_STATE_NEVER, /* lastModifiedKillableStateEpoch= */ 123456789),
+ KILLABLE_STATE_NEVER,
+ /* killableStateLastModifiedEpochSeconds= */ 123456789),
new WatchdogStorage.UserPackageSettingsEntry(
/* userId= */ 101, "system_package.non_critical.A",
- KILLABLE_STATE_NO, /* lastModifiedKillableStateEpoch= */ 123456789),
+ KILLABLE_STATE_NO, /* killableStateLastModifiedEpochSeconds= */ 123456789),
new WatchdogStorage.UserPackageSettingsEntry(
/* userId= */ 101, "system_package.non_critical.B",
- KILLABLE_STATE_YES, /* lastModifiedKillableStateEpoch= */ 123456789),
+ KILLABLE_STATE_YES, /* killableStateLastModifiedEpochSeconds= */ 123456789),
new WatchdogStorage.UserPackageSettingsEntry(
/* userId= */ 101, "vendor_package.critical.C",
- KILLABLE_STATE_NEVER, /* lastModifiedKillableStateEpoch= */ 123456789)));
+ KILLABLE_STATE_NEVER,
+ /* killableStateLastModifiedEpochSeconds= */ 123456789)));
}
private ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsBetweenDates(
@@ -1059,7 +1061,7 @@
WatchdogStorage.WatchdogDbHelper dbHelper =
new WatchdogStorage.WatchdogDbHelper(mContext, /* useDataSystemCarDir= */ false,
mTimeSource);
- dbHelper.onUpgrade(db, /*oldVersion=*/ 1, /*newVersion=*/ 2);
+ dbHelper.onUpgrade(db, /* oldVersion= */ 1, /* currentVersion= */ 2);
if (version < 3) {
return db;
@@ -1083,7 +1085,7 @@
insertIoUsageStats(db, /*userPackageId=*/ 1, /*overuses=*/ 3, /*forgivenOveruses=*/ 2,
/*timesKilled=*/ 1, writtenBytes, remainingWriteBytes, forgivenWriteBytes);
- dbHelper.onUpgrade(db, /* oldVersion= */ 2, /* newVersion= */ 3);
+ dbHelper.onUpgrade(db, /* oldVersion= */ 2, /* currentVersion= */ 3);
return db;
}
diff --git a/tests/carservice_unit_test/src/com/android/car/wifi/CarWifiServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/wifi/CarWifiServiceUnitTest.java
index 2e2987e..377f44e 100644
--- a/tests/carservice_unit_test/src/com/android/car/wifi/CarWifiServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/wifi/CarWifiServiceUnitTest.java
@@ -16,31 +16,58 @@
package com.android.car.wifi;
-import static com.google.common.truth.Truth.assertThat;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.car.feature.Flags;
+import android.car.hardware.power.CarPowerManager;
+import android.car.hardware.power.ICarPowerStateListener;
import android.car.settings.CarSettings;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.car.test.mocks.MockSettings;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.SharedPreferences;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.net.TetheringManager;
+import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.SoftApCallback;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import com.android.car.CarLocalServices;
+import com.android.car.CarServiceUtils;
import com.android.car.R;
import com.android.car.power.CarPowerManagementService;
import com.android.car.user.CarUserService;
+import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.util.concurrent.Executor;
+
@RunWith(MockitoJUnitRunner.class)
public class CarWifiServiceUnitTest extends AbstractExtendedMockitoTestCase {
+ private static final SoftApConfiguration AP_CONFIG = new SoftApConfiguration.Builder()
+ .build();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private Context mContext;
@Mock
@@ -55,9 +82,13 @@
private TetheringManager mTetheringManager;
@Mock
private CarPowerManagementService mCarPowerManagementService;
-
+ @Mock
+ private SharedPreferences mSharedPreferences;
+ @Mock
+ private SharedPreferences.Editor mSharedPreferencesEditor;
private MockSettings mMockSettings;
-
+ private CarPowerManagementService mOriginalCarPowerManagementService;
+ private CarUserService mOriginalCarUserService;
private CarWifiService mCarWifiService;
@Override
@@ -67,22 +98,199 @@
@Before
public void setUp() {
+ mOriginalCarUserService = CarLocalServices.getService(CarUserService.class);
+ CarLocalServices.removeServiceForTest(CarUserService.class);
+ CarLocalServices.addService(CarUserService.class, mCarUserService);
+
+ mOriginalCarPowerManagementService = CarLocalServices.getService(
+ CarPowerManagementService.class);
+ CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
+ CarLocalServices.addService(CarPowerManagementService.class, mCarPowerManagementService);
+
when(mContext.getResources()).thenReturn(mResources);
when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ when(mContext.getSharedPreferences(anyString(), anyInt())).thenReturn(mSharedPreferences);
+ when(mSharedPreferences.edit()).thenReturn(mSharedPreferencesEditor);
+ when(mSharedPreferencesEditor.putBoolean(anyString(), anyBoolean())).thenReturn(
+ mSharedPreferencesEditor);
when(mContext.getSystemService(WifiManager.class)).thenReturn(mWifiManager);
when(mContext.getSystemService(TetheringManager.class)).thenReturn(mTetheringManager);
+ when(mResources.getBoolean(R.bool.config_enablePersistTetheringCapabilities)).thenReturn(
+ true);
+ when(mWifiManager.getSoftApConfiguration()).thenReturn(AP_CONFIG);
mMockSettings.putString(CarSettings.Global.ENABLE_PERSISTENT_TETHERING, "false");
- mCarWifiService = new CarWifiService(mContext, mCarPowerManagementService, mCarUserService);
+ mSetFlagsRule.enableFlags(Flags.FLAG_PERSIST_AP_SETTINGS);
+
+ mCarWifiService = new CarWifiService(mContext);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mCarWifiService.release();
+ CarServiceUtils.quitHandlerThreads();
+
+ CarLocalServices.removeServiceForTest(CarUserService.class);
+ CarLocalServices.addService(CarUserService.class, mOriginalCarUserService);
+ CarLocalServices.removeServiceForTest(CarPowerManagementService.class);
+ CarLocalServices.addService(CarPowerManagementService.class,
+ mOriginalCarPowerManagementService);
}
@Test
- public void testCanControlPersistTetheringSettings_returnsTrue() {
- when(mResources.getBoolean(R.bool.config_enablePersistTetheringCapabilities)).thenReturn(
- true);
- mCarWifiService = new CarWifiService(mContext, mCarPowerManagementService, mCarUserService);
+ public void testCanControlPersistTetheringSettings_capabilityTrue_returnsTrue() {
+ mCarWifiService.init();
boolean result = mCarWifiService.canControlPersistTetheringSettings();
+ expectWithMessage("Can control persist tethering settings").that(result).isTrue();
+ }
- assertThat(result).isTrue();
+ @Test
+ public void testCanControlPersistTetheringSettings_capabilityFalse_returnsFalse() {
+ when(mResources.getBoolean(R.bool.config_enablePersistTetheringCapabilities)).thenReturn(
+ false);
+
+ mCarWifiService = new CarWifiService(mContext);
+ mCarWifiService.init();
+
+ boolean result = mCarWifiService.canControlPersistTetheringSettings();
+ expectWithMessage("Can control persist tethering settings").that(result).isFalse();
+ }
+
+ @Test
+ public void testPersistCarSettingOn_userUnlockBeforePowerOn_tetheringOn() throws Exception {
+ mMockSettings.putString(CarSettings.Global.ENABLE_PERSISTENT_TETHERING, "true");
+ when(mSharedPreferences.getBoolean(anyString(), anyBoolean())).thenReturn(true);
+
+ mCarWifiService = new CarWifiService(mContext);
+ mCarWifiService.init();
+ getUserLifecycleListener().run();
+ getCarPowerStateListener().onStateChanged(CarPowerManager.STATE_ON, 0);
+ getSoftApCallback().onStateChanged(WIFI_AP_STATE_ENABLED, 0);
+
+ verify(mTetheringManager).startTethering(eq(TetheringManager.TETHERING_WIFI), any(
+ Executor.class), any(TetheringManager.StartTetheringCallback.class));
+ expectWithMessage("Autoshutdown Enabled").that(
+ getApConfig().isAutoShutdownEnabled()).isFalse();
+ }
+
+ @Test
+ public void testPersistCarSettingOn_notOnLast_noTethering() throws Exception {
+ mMockSettings.putString(CarSettings.Global.ENABLE_PERSISTENT_TETHERING, "true");
+ when(mSharedPreferences.getBoolean(anyString(), anyBoolean())).thenReturn(false);
+
+ mCarWifiService = new CarWifiService(mContext);
+ mCarWifiService.init();
+ getUserLifecycleListener().run();
+ getCarPowerStateListener().onStateChanged(CarPowerManager.STATE_ON, 0);
+
+ verify(mTetheringManager, never()).startTethering(eq(TetheringManager.TETHERING_WIFI), any(
+ Executor.class), any(TetheringManager.StartTetheringCallback.class));
+ }
+
+ @Test
+ public void testPersistCarSettingOn_tetheringAlreadyEnabled_noTethering() throws Exception {
+ mMockSettings.putString(CarSettings.Global.ENABLE_PERSISTENT_TETHERING, "true");
+ when(mSharedPreferences.getBoolean(anyString(), anyBoolean())).thenReturn(false);
+ when(mWifiManager.isWifiApEnabled()).thenReturn(true);
+
+ mCarWifiService = new CarWifiService(mContext);
+ mCarWifiService.init();
+ getUserLifecycleListener().run();
+ getCarPowerStateListener().onStateChanged(CarPowerManager.STATE_ON, 0);
+
+ verify(mTetheringManager, never()).startTethering(eq(TetheringManager.TETHERING_WIFI), any(
+ Executor.class), any(TetheringManager.StartTetheringCallback.class));
+ }
+
+ @Test
+ public void testPersistCarSettingOn_powerOnBeforeUserUnlock_tetheringOn() throws Exception {
+ mMockSettings.putString(CarSettings.Global.ENABLE_PERSISTENT_TETHERING, "true");
+ when(mSharedPreferences.getBoolean(anyString(), anyBoolean())).thenReturn(true);
+ when(mCarPowerManagementService.getPowerState()).thenReturn(CarPowerManager.STATE_ON);
+
+ mCarWifiService = new CarWifiService(mContext);
+ mCarWifiService.init();
+ getCarPowerStateListener().onStateChanged(CarPowerManager.STATE_ON, 0);
+ getUserLifecycleListener().run();
+ getSoftApCallback().onStateChanged(WIFI_AP_STATE_ENABLED, 0);
+
+ verify(mTetheringManager).startTethering(eq(TetheringManager.TETHERING_WIFI), any(
+ Executor.class), any(TetheringManager.StartTetheringCallback.class));
+ expectWithMessage("Autoshutdown Enabled").that(
+ getApConfig().isAutoShutdownEnabled()).isFalse();
+ }
+
+ @Test
+ public void testPersistCarSettingOff_powerOnBeforeUserUnlock_noTethering() throws Exception {
+ when(mCarPowerManagementService.getPowerState()).thenReturn(CarPowerManager.STATE_ON);
+
+ mCarWifiService = new CarWifiService(mContext);
+ mCarWifiService.init();
+ getCarPowerStateListener().onStateChanged(CarPowerManager.STATE_ON, 0);
+ getUserLifecycleListener().run();
+
+ verify(mTetheringManager, never()).startTethering(eq(TetheringManager.TETHERING_WIFI), any(
+ Executor.class), any(TetheringManager.StartTetheringCallback.class));
+ }
+
+ @Test
+ public void testPersistCarSettingOff_userUnlockBeforePowerOn_noTethering() throws Exception {
+ mCarWifiService.init();
+ getUserLifecycleListener().run();
+ getCarPowerStateListener().onStateChanged(CarPowerManager.STATE_ON, 0);
+
+ verify(mTetheringManager, never()).startTethering(eq(TetheringManager.TETHERING_WIFI), any(
+ Executor.class), any(TetheringManager.StartTetheringCallback.class));
+ }
+
+ @Test
+ public void testPersistCarSettingChange_withCapability_autoShutdownFalse() throws Exception {
+ when(mSharedPreferences.getBoolean(anyString(), anyBoolean())).thenReturn(true);
+
+ mCarWifiService = new CarWifiService(mContext);
+ mCarWifiService.init();
+
+ mMockSettings.putString(CarSettings.Global.ENABLE_PERSISTENT_TETHERING, "true");
+ getSettingsObserver().onChange(/* selfChange= */ false);
+ expectWithMessage("Autoshutdown Enabled").that(
+ getApConfig().isAutoShutdownEnabled()).isFalse();
+ }
+
+ private ICarPowerStateListener getCarPowerStateListener() {
+ ArgumentCaptor<ICarPowerStateListener> internalListenerCaptor =
+ ArgumentCaptor.forClass(ICarPowerStateListener.class);
+ verify(mCarPowerManagementService).registerListener(
+ internalListenerCaptor.capture());
+ return internalListenerCaptor.getValue();
+ }
+
+ private Runnable getUserLifecycleListener() {
+ ArgumentCaptor<Runnable> internalListenerCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ verify(mCarUserService).runOnUser0Unlock(
+ internalListenerCaptor.capture());
+ return internalListenerCaptor.getValue();
+ }
+
+ private ContentObserver getSettingsObserver() {
+ ArgumentCaptor<ContentObserver> captor = ArgumentCaptor.forClass(ContentObserver.class);
+ verify(mContentResolver).registerContentObserver(
+ eq(Settings.Global.getUriFor(CarSettings.Global.ENABLE_PERSISTENT_TETHERING)),
+ eq(false), captor.capture());
+ return captor.getValue();
+ }
+
+ private SoftApConfiguration getApConfig() {
+ ArgumentCaptor<SoftApConfiguration> captor = ArgumentCaptor.forClass(
+ SoftApConfiguration.class);
+ verify(mWifiManager).setSoftApConfiguration(captor.capture());
+ return captor.getValue();
+ }
+
+ private SoftApCallback getSoftApCallback() {
+ ArgumentCaptor<SoftApCallback> captor = ArgumentCaptor.forClass(
+ SoftApCallback.class);
+ verify(mWifiManager).registerSoftApCallback(any(Executor.class), captor.capture());
+ return captor.getValue();
}
}
diff --git a/tests/common_utils/Android.bp b/tests/common_utils/Android.bp
index 6712c2c..6bb7c90 100644
--- a/tests/common_utils/Android.bp
+++ b/tests/common_utils/Android.bp
@@ -14,6 +14,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -29,4 +30,14 @@
],
libs: ["android.car-system-stubs"],
+
+ visibility: ["//packages/services/Car:__subpackages__"],
+}
+
+filegroup {
+ name: "com.android.car.test.lib-srcs",
+
+ srcs: ["src/**/*.java"],
+
+ visibility: ["//packages/services/Car:__subpackages__"],
}
diff --git a/tests/obd2_test/Android.bp b/tests/obd2_test/Android.bp
index d96aa47..bb06d70 100644
--- a/tests/obd2_test/Android.bp
+++ b/tests/obd2_test/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/usb/AoapHostApp/Android.bp b/tests/usb/AoapHostApp/Android.bp
index 5607a04..a8e534b 100644
--- a/tests/usb/AoapHostApp/Android.bp
+++ b/tests/usb/AoapHostApp/Android.bp
@@ -17,6 +17,7 @@
//#################################################
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tools/telemetry/OWNERS b/tools/telemetry/OWNERS
index ae74273..dea80b1 100644
--- a/tools/telemetry/OWNERS
+++ b/tools/telemetry/OWNERS
@@ -1,4 +1,3 @@
sgurun@google.com
hhhao@google.com
ruiqiu@google.com
-pranitjaiswal@google.com
diff --git a/tools/vehiclepropertyidsparser/Android.bp b/tools/vehiclepropertyidsparser/Android.bp
index ecb1d0f..baff26c 100644
--- a/tools/vehiclepropertyidsparser/Android.bp
+++ b/tools/vehiclepropertyidsparser/Android.bp
@@ -15,6 +15,7 @@
*/
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tools/watchdog/Android.bp b/tools/watchdog/Android.bp
index cf1ed48..e79f322 100644
--- a/tools/watchdog/Android.bp
+++ b/tools/watchdog/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}