Merge "Updated CarSensorManager documentation and annotation."
diff --git a/car-lib/Android.bp b/car-lib/Android.bp
index 287949e..c037284 100644
--- a/car-lib/Android.bp
+++ b/car-lib/Android.bp
@@ -110,6 +110,17 @@
],
}
+genrule {
+ name: "android-car-last-released-test-api",
+ srcs: [
+ "api/test-released/*.txt",
+ ],
+ cmd: "cp -f $$(echo $(in) | tr \" \" \"\\n\" | sort -n | tail -1) $(genDir)/last-released-test-api.txt",
+ out: [
+ "last-released-test-api.txt",
+ ],
+}
+
droiddoc {
name: "android.car-stubs-docs",
defaults: ["android.car-docs-default"],
@@ -164,6 +175,26 @@
}
droiddoc {
+ name: "android.car-test-stubs-docs",
+ defaults: ["android.car-docs-default"],
+ api_tag_name: "ANDROID_CAR_SYSTEM",
+ api_filename: "api.txt",
+ removed_api_filename: "removed.txt",
+ args: "-hide 113 -hide 110 -nodocs -stubpackages android.car* " +
+ "-showAnnotation android.annotation.TestApi ",
+ installable: false,
+ check_api: {
+ 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 ",
+ },
+ },
+}
+
+droiddoc {
name: "android.car-stub-docs",
srcs: [
"src/**/*.java",
@@ -214,3 +245,19 @@
},
compile_dex: true,
}
+
+java_library_static {
+ name: "android.car-test-stubs",
+ srcs: [
+ ":android.car-test-stubs-docs",
+ ],
+ libs: [
+ "android.car",
+ ],
+ product_variables: {
+ pdk: {
+ enabled: false,
+ },
+ },
+ compile_dex: true,
+}
diff --git a/car-lib/api/test-current.txt b/car-lib/api/test-current.txt
new file mode 100644
index 0000000..c5c41b6
--- /dev/null
+++ b/car-lib/api/test-current.txt
@@ -0,0 +1,8 @@
+package android.car.content.pm {
+
+ public final class CarPackageManager {
+ method public void setEnableActivityBlocking(boolean);
+ }
+
+}
+
diff --git a/car-lib/api/test-removed.txt b/car-lib/api/test-removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/car-lib/api/test-removed.txt
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index 3e41d70..1e67b47 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -127,5 +127,8 @@
PRODUCT_HIDDENAPI_STUBS_SYSTEM := \
android.car-system-stubs
+PRODUCT_HIDDENAPI_STUBS_TEST := \
+ android.car-test-stubs
+
INCLUDED_ANDROID_CAR_TO_PRODUCT_BOOT_JARS := yes
endif
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/drawable/ic_backspace.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/drawable/ic_backspace.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/drawable/ic_backspace.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/drawable/ic_backspace.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/drawable/ic_done.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/drawable/ic_done.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/drawable/ic_done.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/drawable/ic_done.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/drawable/keyguard_button_background.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/drawable/keyguard_button_background.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/drawable/keyguard_button_background.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/drawable/keyguard_button_background.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_bouncer.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_bouncer.xml
diff --git a/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_message_area.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_message_area.xml
new file mode 100644
index 0000000..c230414
--- /dev/null
+++ b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_message_area.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.keyguard.KeyguardMessageArea
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ style="@style/Keyguard.TextView"
+ android:id="@+id/keyguard_message_area"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:focusable="true"
+ android:layout_marginBottom="@dimen/car_padding_4"
+ android:textSize="@dimen/car_body2_size" />
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_pattern_view.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_pattern_view.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_pin_view.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_pin_view.xml
diff --git a/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml
new file mode 100644
index 0000000..ca0595d
--- /dev/null
+++ b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml
@@ -0,0 +1,83 @@
+<?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
+ -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <!-- Row 1 -->
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key1"
+ style="@style/NumPadKeyButton"
+ app:digit="@string/one" />
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key2"
+ style="@style/NumPadKeyButton.MiddleColumn"
+ app:digit="@string/two" />
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key3"
+ style="@style/NumPadKeyButton"
+ app:digit="@string/three" />
+
+ <!-- Row 2 -->
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key4"
+ style="@style/NumPadKeyButton"
+ app:digit="@string/four" />
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key5"
+ style="@style/NumPadKeyButton.MiddleColumn"
+ app:digit="@string/five" />
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key6"
+ style="@style/NumPadKeyButton"
+ app:digit="@string/six" />
+
+ <!-- Row 3 -->
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key7"
+ style="@style/NumPadKeyButton"
+ app:digit="@string/seven" />
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key8"
+ style="@style/NumPadKeyButton.MiddleColumn"
+ app:digit="@string/eight" />
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key9"
+ style="@style/NumPadKeyButton"
+ app:digit="@string/nine" />
+
+ <!-- Row 4 -->
+ <ImageButton
+ android:id="@+id/delete_button"
+ style="@style/NumPadKeyButton.LastRow"
+ android:gravity="center_vertical"
+ android:src="@drawable/ic_backspace"
+ android:clickable="true"
+ android:tint="@android:color/white"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/keyboardview_keycode_delete" />
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key0"
+ style="@style/NumPadKeyButton.LastRow.MiddleColumn"
+ app:digit="@string/zero" />
+ <ImageButton
+ android:id="@+id/key_enter"
+ style="@style/NumPadKeyButton.LastRow"
+ android:src="@drawable/ic_done"
+ android:tint="@android:color/white"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/keyboardview_keycode_enter" />
+</merge>
+
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values-h1000dp/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values-h1000dp/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values-h1000dp/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values-h1000dp/dimens.xml
diff --git a/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values-land/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values-land/dimens.xml
new file mode 100644
index 0000000..805a134
--- /dev/null
+++ b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values-land/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2018, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License")
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <dimen name="num_pad_key_margin_horizontal">@dimen/car_padding_5</dimen>
+ <dimen name="num_pad_key_margin_bottom">@dimen/car_padding_4</dimen>
+</resources>
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/colors.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/colors.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/colors.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/colors.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/dimens.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/integers.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/integers.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/integers.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/integers.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/strings.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/strings.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/strings.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/strings.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/styles.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/styles.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/styles.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/styles.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values-h600dp/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-h600dp/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res/values-h600dp/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-h600dp/dimens.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values-sw600dp/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-sw600dp/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res/values-sw600dp/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-sw600dp/dimens.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values-w1024dp/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-w1024dp/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res/values-w1024dp/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-w1024dp/dimens.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values-w550dp-land/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-w550dp-land/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res/values-w550dp-land/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-w550dp-land/dimens.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values/config.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/config.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res/values/config.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/config.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res/values/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/dimens.xml
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 641ead9..1f221ac 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -23,7 +23,7 @@
<original-package android:name="com.android.car" />
<permission-group
android:name="android.car.permission-group.CAR_MONITORING"
- android:icon="@drawable/car_ic_mode"
+ android:icon="@drawable/perm_group_car"
android:description="@string/car_permission_desc"
android:label="@string/car_permission_label" />
<permission
diff --git a/service/res/drawable-hdpi/car_ic_mode.png b/service/res/drawable-hdpi/car_ic_mode.png
deleted file mode 100644
index a8f719f..0000000
--- a/service/res/drawable-hdpi/car_ic_mode.png
+++ /dev/null
Binary files differ
diff --git a/service/res/drawable-mdpi/car_ic_mode.png b/service/res/drawable-mdpi/car_ic_mode.png
deleted file mode 100644
index 38a9747..0000000
--- a/service/res/drawable-mdpi/car_ic_mode.png
+++ /dev/null
Binary files differ
diff --git a/service/res/drawable-xhdpi/car_ic_mode.png b/service/res/drawable-xhdpi/car_ic_mode.png
deleted file mode 100644
index 58a1aca..0000000
--- a/service/res/drawable-xhdpi/car_ic_mode.png
+++ /dev/null
Binary files differ
diff --git a/service/res/drawable/perm_group_car.xml b/service/res/drawable/perm_group_car.xml
new file mode 100644
index 0000000..79e3f03
--- /dev/null
+++ b/service/res/drawable/perm_group_car.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="192.0"
+ android:viewportWidth="192.0">
+
+ <path android:fillColor="#FFFFFFFF"
+ android:pathData="M36.9,180.4l5.6,5.6L96,164l53.4,22l5.6,-5.6L96,73L36.9,180.4zM184,145.8L105.7,11.6C103.7,8.1 100,6 96,6c-4,0 -7.7,2.1 -9.8,5.6L7.5,146.5c-2,3.5 -2.1,7.3 0,10.8c2,3.5 5.8,5.7 9.8,5.7H38l54.6,-99h6.8l54.6,99h20.3c6.2,0 11.7,-4.6 11.7,-10.8C186,149.8 185.3,147.6 184,145.8z"/>
+</vector>
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 1d72dca..3b05e29 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -24,6 +24,14 @@
dynamic audio routing is disabled and audio works in legacy mode. It may be useful
during initial development where audio hal does not support bus based addressing yet. -->
<bool name="audioUseDynamicRouting">false</bool>
+
+ <!-- Configuration to use the unified audio configuration.
+ This flag has no effect if audioUseDynamicRouting is set to false.
+ When audioUseDynamicRouting is enabled
+ - car_volume_groups.xml will be picked if this flag is false
+ - car_audio_configuration.xml will be used otherwise. -->
+ <bool name="audioUseUnifiedConfiguration">false</bool>
+
<!-- Configuration to persist master mute state. If this is set to true,
Android will restore the master mute state on boot. -->
<bool name="audioPersistMasterMuteState">true</bool>
diff --git a/service/res/xml/car_audio_configuration.xml b/service/res/xml/car_audio_configuration.xml
index 2f82233..e3fd8f6 100644
--- a/service/res/xml/car_audio_configuration.xml
+++ b/service/res/xml/car_audio_configuration.xml
@@ -44,7 +44,7 @@
</group>
<group>
<device car:address="bus1_navigation_out">
- <context car:context="notification"/>
+ <context car:context="navigation"/>
</device>
<device car:address="bus2_voice_command_out">
<context car:context="voice_command"/>
@@ -70,7 +70,7 @@
<group>
<device car:address="bus100_rear_seat">
<context car:context="music"/>
- <context car:context="notification"/>
+ <context car:context="navigation"/>
<context car:context="voice_command"/>
<context car:context="call_ring"/>
<context car:context="call"/>
diff --git a/service/res/xml/car_volume_groups.xml b/service/res/xml/car_volume_groups.xml
index e2fa6b2..c900329 100644
--- a/service/res/xml/car_volume_groups.xml
+++ b/service/res/xml/car_volume_groups.xml
@@ -18,13 +18,12 @@
This configuration is replaced by car_audio_configuration.xml
Notes on backward compatibility
- - A new isDeprecated=true attr is added to the stock car_volume_groups.xml
- - Existing car_volume_groups.xml overlays from OEM, without isDeprecated
- attr being set, should default to isDeprecated=false
- - If isDeprecated=true, CarAudioService loads the new
- car_audio_configuration.xml
- - Otherwise, CarAudioService loads car_volume_groups.xml queries
- IAudioControl HAL (getBusForContext)
+ - A new audioUseUnifiedConfiguration flag is added, and the default value
+ is false
+ - If OEM does not explicitly set audioUseUnifiedConfiguration to be true,
+ car_volume_groups.xml will be used, CarAudioService also queries
+ IAudioControl HAL (getBusForContext)
+ - Otherwise, CarAudioService loads the new car_audio_configuration.xml
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/service/src/com/android/car/CarProjectionService.java b/service/src/com/android/car/CarProjectionService.java
index f47362c..5bc8352 100644
--- a/service/src/com/android/car/CarProjectionService.java
+++ b/service/src/com/android/car/CarProjectionService.java
@@ -140,7 +140,7 @@
public void onServiceDisconnected(ComponentName className) {
// Service has crashed.
Log.w(CarLog.TAG_PROJECTION, "Service disconnected: " + className);
- synchronized (CarProjectionService.this) {
+ synchronized (mLock) {
mRegisteredService = null;
}
unbindServiceIfBound();
@@ -163,7 +163,7 @@
public void registerProjectionRunner(Intent serviceIntent) {
// We assume one active projection app running in the system at one time.
synchronized (mLock) {
- if (serviceIntent.filterEquals(mRegisteredService)) {
+ if (serviceIntent.filterEquals(mRegisteredService) && mBound) {
return;
}
if (mRegisteredService != null) {
@@ -203,6 +203,7 @@
return;
}
mBound = false;
+ mRegisteredService = null;
}
mContext.unbindService(mConnection);
}
@@ -529,6 +530,8 @@
writer.println("Wireless clients: " + mWirelessClients.size());
writer.println("Current wifi mode: " + mWifiMode);
writer.println("SoftApCallback: " + mSoftApCallback);
+ writer.println("Bound to projection app: " + mBound);
+ writer.println("Registered Service: " + mRegisteredService);
}
}
diff --git a/service/src/com/android/car/audio/CarAudioDeviceInfo.java b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
index 2e35c56..f45f48e 100644
--- a/service/src/com/android/car/audio/CarAudioDeviceInfo.java
+++ b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
@@ -38,6 +38,26 @@
*/
/* package */ class CarAudioDeviceInfo {
+ /**
+ * Parse device address. Expected format is BUS%d_%s, address, usage hint
+ * @return valid address (from 0 to positive) or -1 for invalid address.
+ */
+ static int parseDeviceAddress(String address) {
+ String[] words = address.split("_");
+ int addressParsed = -1;
+ if (words[0].toLowerCase().startsWith("bus")) {
+ try {
+ addressParsed = Integer.parseInt(words[0].substring(3));
+ } catch (NumberFormatException e) {
+ //ignore
+ }
+ }
+ if (addressParsed < 0) {
+ return -1;
+ }
+ return addressParsed;
+ }
+
private final AudioDeviceInfo mAudioDeviceInfo;
private final int mBusNumber;
private final int mSampleRate;
@@ -143,26 +163,6 @@
}
}
- /**
- * Parse device address. Expected format is BUS%d_%s, address, usage hint
- * @return valid address (from 0 to positive) or -1 for invalid address.
- */
- private int parseDeviceAddress(String address) {
- String[] words = address.split("_");
- int addressParsed = -1;
- if (words[0].toLowerCase().startsWith("bus")) {
- try {
- addressParsed = Integer.parseInt(words[0].substring(3));
- } catch (NumberFormatException e) {
- //ignore
- }
- }
- if (addressParsed < 0) {
- return -1;
- }
- return addressParsed;
- }
-
private int getMaxSampleRate(AudioDeviceInfo info) {
int[] sampleRates = info.getSampleRates();
if (sampleRates == null || sampleRates.length == 0) {
@@ -247,12 +247,12 @@
+ " minGain: " + mMinGain;
}
- void dump(PrintWriter writer) {
- writer.printf("Bus Number (%d) / address (%s)\n ",
- mBusNumber, mAudioDeviceInfo.getAddress());
- writer.printf("\tsample rate / encoding format / channel count: %d %d %d\n",
- getSampleRate(), getEncodingFormat(), getChannelCount());
- writer.printf("\tGain in millibel (min / max / default/ current): %d %d %d %d\n",
- mMinGain, mMaxGain, mDefaultGain, mCurrentGain);
+ void dump(String indent, PrintWriter writer) {
+ writer.printf("%sCarAudioDeviceInfo Bus(%d: %s)\n ",
+ indent, mBusNumber, mAudioDeviceInfo.getAddress());
+ writer.printf("%s\tsample rate / encoding format / channel count: %d %d %d\n",
+ indent, getSampleRate(), getEncodingFormat(), getChannelCount());
+ writer.printf("%s\tGain values (min / max / default/ current): %d %d %d %d\n",
+ indent, mMinGain, mMaxGain, mDefaultGain, mCurrentGain);
}
}
diff --git a/service/src/com/android/car/audio/CarAudioDynamicRouting.java b/service/src/com/android/car/audio/CarAudioDynamicRouting.java
new file mode 100644
index 0000000..73b810c
--- /dev/null
+++ b/service/src/com/android/car/audio/CarAudioDynamicRouting.java
@@ -0,0 +1,162 @@
+/*
+ * 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.android.car.audio;
+
+import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioPolicy;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.car.CarLog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Builds dynamic audio routing in a car from audio zone configuration.
+ */
+/* package */ class CarAudioDynamicRouting {
+
+ static final int[] CONTEXT_NUMBERS = new int[] {
+ ContextNumber.MUSIC,
+ ContextNumber.NAVIGATION,
+ ContextNumber.VOICE_COMMAND,
+ ContextNumber.CALL_RING,
+ ContextNumber.CALL,
+ ContextNumber.ALARM,
+ ContextNumber.NOTIFICATION,
+ ContextNumber.SYSTEM_SOUND
+ };
+
+ static final SparseIntArray USAGE_TO_CONTEXT = new SparseIntArray();
+
+ static final int DEFAULT_AUDIO_USAGE = AudioAttributes.USAGE_MEDIA;
+
+ // For legacy stream type based volume control.
+ // Values in STREAM_TYPES and STREAM_TYPE_USAGES should be aligned.
+ static final int[] STREAM_TYPES = new int[] {
+ AudioManager.STREAM_MUSIC,
+ AudioManager.STREAM_ALARM,
+ AudioManager.STREAM_RING
+ };
+ static final int[] STREAM_TYPE_USAGES = new int[] {
+ AudioAttributes.USAGE_MEDIA,
+ AudioAttributes.USAGE_ALARM,
+ AudioAttributes.USAGE_NOTIFICATION_RINGTONE
+ };
+
+ static {
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_UNKNOWN, ContextNumber.MUSIC);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_MEDIA, ContextNumber.MUSIC);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION, ContextNumber.CALL);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
+ ContextNumber.CALL);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ALARM, ContextNumber.ALARM);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION, ContextNumber.NOTIFICATION);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, ContextNumber.CALL_RING);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
+ ContextNumber.NOTIFICATION);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
+ ContextNumber.NOTIFICATION);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
+ ContextNumber.NOTIFICATION);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, ContextNumber.NOTIFICATION);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
+ ContextNumber.VOICE_COMMAND);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+ ContextNumber.NAVIGATION);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
+ ContextNumber.SYSTEM_SOUND);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_GAME, ContextNumber.MUSIC);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VIRTUAL_SOURCE, ContextNumber.INVALID);
+ USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANT, ContextNumber.VOICE_COMMAND);
+ }
+
+ private final CarAudioZone[] mCarAudioZones;
+
+ CarAudioDynamicRouting(CarAudioZone[] carAudioZones) {
+ mCarAudioZones = carAudioZones;
+ }
+
+ void setupAudioDynamicRouting(AudioPolicy.Builder builder) {
+ for (CarAudioZone zone : mCarAudioZones) {
+ for (CarVolumeGroup group : zone.getVolumeGroups()) {
+ setupAudioDynamicRoutingForGroup(group, builder);
+ }
+ }
+ }
+
+ /**
+ * Enumerates all physical buses in a given volume group and attach the mixing rules.
+ * @param group {@link CarVolumeGroup} instance to enumerate the buses with
+ * @param builder {@link AudioPolicy.Builder} to attach the mixing rules
+ */
+ private void setupAudioDynamicRoutingForGroup(CarVolumeGroup group,
+ AudioPolicy.Builder builder) {
+ // Note that one can not register audio mix for same bus more than once.
+ for (int busNumber : group.getBusNumbers()) {
+ boolean hasContext = false;
+ CarAudioDeviceInfo info = group.getCarAudioDeviceInfoForBus(busNumber);
+ AudioFormat mixFormat = new AudioFormat.Builder()
+ .setSampleRate(info.getSampleRate())
+ .setEncoding(info.getEncodingFormat())
+ .setChannelMask(info.getChannelCount())
+ .build();
+ AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
+ for (int contextNumber : group.getContextsForBus(busNumber)) {
+ hasContext = true;
+ int[] usages = getUsagesForContext(contextNumber);
+ for (int usage : usages) {
+ mixingRuleBuilder.addRule(
+ new AudioAttributes.Builder().setUsage(usage).build(),
+ AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
+ }
+ Log.d(CarLog.TAG_AUDIO, "Bus number: " + busNumber
+ + " contextNumber: " + contextNumber
+ + " sampleRate: " + info.getSampleRate()
+ + " channels: " + info.getChannelCount()
+ + " usages: " + Arrays.toString(usages));
+ }
+ if (hasContext) {
+ // It's a valid case that an audio output bus 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.
+ AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
+ .setFormat(mixFormat)
+ .setDevice(info.getAudioDeviceInfo())
+ .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
+ .build();
+ builder.addMix(audioMix);
+ }
+ }
+ }
+
+ private int[] getUsagesForContext(int contextNumber) {
+ final List<Integer> usages = new ArrayList<>();
+ for (int i = 0; i < CarAudioDynamicRouting.USAGE_TO_CONTEXT.size(); i++) {
+ if (CarAudioDynamicRouting.USAGE_TO_CONTEXT.valueAt(i) == contextNumber) {
+ usages.add(CarAudioDynamicRouting.USAGE_TO_CONTEXT.keyAt(i));
+ }
+ }
+ return usages.stream().mapToInt(i -> i).toArray();
+ }
+}
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index b3743dc..324c5ac 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -27,7 +27,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
import android.hardware.automotive.audiocontrol.V1_0.IAudioControl;
import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
@@ -40,8 +39,6 @@
import android.media.AudioPlaybackConfiguration;
import android.media.AudioPortConfig;
import android.media.AudioSystem;
-import android.media.audiopolicy.AudioMix;
-import android.media.audiopolicy.AudioMixingRule;
import android.media.audiopolicy.AudioPolicy;
import android.os.IBinder;
import android.os.Looper;
@@ -51,7 +48,6 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import android.view.KeyEvent;
import com.android.car.BinderInterfaceContainer;
@@ -63,7 +59,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
@@ -81,35 +76,6 @@
// Search for "DUCK_VSHAPE" in PLaybackActivityMonitor.java to see where this happens.
private static boolean sUseCarAudioFocus = true;
- private static final int DEFAULT_AUDIO_USAGE = AudioAttributes.USAGE_MEDIA;
-
- private static final int[] CONTEXT_NUMBERS = new int[] {
- ContextNumber.MUSIC,
- ContextNumber.NAVIGATION,
- ContextNumber.VOICE_COMMAND,
- ContextNumber.CALL_RING,
- ContextNumber.CALL,
- ContextNumber.ALARM,
- ContextNumber.NOTIFICATION,
- ContextNumber.SYSTEM_SOUND
- };
-
- private static final SparseIntArray USAGE_TO_CONTEXT = new SparseIntArray();
-
- // For legacy stream type based volume control.
- // Values in STREAM_TYPES and STREAM_TYPE_USAGES should be aligned.
- private static final int[] STREAM_TYPES = new int[] {
- AudioManager.STREAM_MUSIC,
- AudioManager.STREAM_ALARM,
- AudioManager.STREAM_RING
- };
- private static final int[] STREAM_TYPE_USAGES = new int[] {
- AudioAttributes.USAGE_MEDIA,
- AudioAttributes.USAGE_ALARM,
- AudioAttributes.USAGE_NOTIFICATION_RINGTONE
- };
-
-
// Key to persist master mute state in system settings
private static final String VOLUME_SETTINGS_KEY_MASTER_MUTE = "android.car.MASTER_MUTE";
@@ -118,15 +84,6 @@
private static final String VOLUME_SETTINGS_KEY_FOR_GROUP_PREFIX = "android.car.VOLUME_GROUP/";
/**
- * Gets the key to persist volume for a volume group in settings, in primary zone
- *
- * @see {@link #getVolumeSettingsKeyForGroup(int, int)}
- */
- static String getVolumeSettingsKeyForGroup(int groupId) {
- return getVolumeSettingsKeyForGroup(CarAudioManager.PRIMARY_AUDIO_ZONE, groupId);
- }
-
- /**
* Gets the key to persist volume for a volume group in settings
*
* @param zoneId The audio zone id
@@ -138,42 +95,14 @@
return VOLUME_SETTINGS_KEY_FOR_GROUP_PREFIX + maskedGroupId;
}
- static {
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_UNKNOWN, ContextNumber.MUSIC);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_MEDIA, ContextNumber.MUSIC);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION, ContextNumber.CALL);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
- ContextNumber.CALL);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ALARM, ContextNumber.ALARM);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION, ContextNumber.NOTIFICATION);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, ContextNumber.CALL_RING);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
- ContextNumber.NOTIFICATION);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
- ContextNumber.NOTIFICATION);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
- ContextNumber.NOTIFICATION);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, ContextNumber.NOTIFICATION);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
- ContextNumber.VOICE_COMMAND);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
- ContextNumber.NAVIGATION);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
- ContextNumber.SYSTEM_SOUND);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_GAME, ContextNumber.MUSIC);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VIRTUAL_SOURCE, ContextNumber.INVALID);
- USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANT, ContextNumber.VOICE_COMMAND);
- }
-
private final Object mImplLock = new Object();
private final Context mContext;
private final TelephonyManager mTelephonyManager;
private final AudioManager mAudioManager;
private final boolean mUseDynamicRouting;
+ private final boolean mUseUnifiedConfiguration;
private final boolean mPersistMasterMuteState;
- private final SparseIntArray mContextToBus = new SparseIntArray();
- private final SparseArray<CarAudioDeviceInfo> mCarAudioDeviceInfos = new SparseArray<>();
private final AudioPolicy.AudioPolicyVolumeCallback mAudioPolicyVolumeCallback =
new AudioPolicy.AudioPolicyVolumeCallback() {
@@ -226,6 +155,7 @@
private final BroadcastReceiver mLegacyVolumeChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ final int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
switch (intent.getAction()) {
case AudioManager.VOLUME_CHANGED_ACTION:
int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
@@ -233,11 +163,11 @@
if (groupId == -1) {
Log.w(CarLog.TAG_AUDIO, "Unknown stream type: " + streamType);
} else {
- callbackGroupVolumeChange(CarAudioManager.PRIMARY_AUDIO_ZONE, groupId, 0);
+ callbackGroupVolumeChange(zoneId, groupId, 0);
}
break;
case AudioManager.MASTER_MUTE_CHANGED_ACTION:
- callbackMasterMuteChange(CarAudioManager.PRIMARY_AUDIO_ZONE, 0);
+ callbackMasterMuteChange(zoneId, 0);
break;
}
}
@@ -245,13 +175,15 @@
private AudioPolicy mAudioPolicy;
private CarAudioFocus mFocusHandler;
- private CarVolumeGroup[] mCarVolumeGroups;
+ private CarAudioZone[] mCarAudioZones;
public CarAudioService(Context context) {
mContext = context;
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
+ mUseUnifiedConfiguration = mContext.getResources().getBoolean(
+ R.bool.audioUseUnifiedConfiguration);
mPersistMasterMuteState = mContext.getResources().getBoolean(
R.bool.audioPersistMasterMuteState);
}
@@ -263,12 +195,32 @@
@Override
public void init() {
synchronized (mImplLock) {
- if (!mUseDynamicRouting) {
- Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not configured, run in legacy mode");
- setupLegacyVolumeChangedListener();
+ if (mUseDynamicRouting) {
+ // Enumerate all output bus device ports
+ AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
+ AudioManager.GET_DEVICES_OUTPUTS);
+ if (deviceInfos.length == 0) {
+ Log.e(CarLog.TAG_AUDIO, "No output device available, ignore");
+ return;
+ }
+ SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo = new SparseArray<>();
+ for (AudioDeviceInfo info : deviceInfos) {
+ Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
+ info.getId(), info.getAddress(), info.getType()));
+ if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
+ final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
+ // See also the audio_policy_configuration.xml,
+ // the bus number should be no less than zero.
+ if (carInfo.getBusNumber() >= 0) {
+ busToCarAudioDeviceInfo.put(carInfo.getBusNumber(), carInfo);
+ Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
+ }
+ }
+ }
+ setupDynamicRouting(busToCarAudioDeviceInfo);
} else {
- setupDynamicRouting();
- setupVolumeGroups();
+ Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not enabled, run in legacy mode");
+ setupLegacyVolumeChangedListener();
}
// Restore master mute state if applicable
@@ -302,13 +254,14 @@
public void dump(PrintWriter writer) {
writer.println("*CarAudioService*");
writer.println("\tRun in legacy mode? " + (!mUseDynamicRouting));
+ writer.println("\tUse unified configuration? " + mUseUnifiedConfiguration);
writer.println("\tPersist master mute state? " + mPersistMasterMuteState);
writer.println("\tMaster muted? " + mAudioManager.isMasterMute());
// Empty line for comfortable reading
writer.println();
if (mUseDynamicRouting) {
- for (CarVolumeGroup group : mCarVolumeGroups) {
- group.dump(writer);
+ for (CarAudioZone zone : mCarAudioZones) {
+ zone.dump("\t", writer);
}
}
}
@@ -324,11 +277,12 @@
callbackGroupVolumeChange(zoneId, groupId, flags);
// For legacy stream type based volume control
if (!mUseDynamicRouting) {
- mAudioManager.setStreamVolume(STREAM_TYPES[groupId], index, flags);
+ mAudioManager.setStreamVolume(
+ CarAudioDynamicRouting.STREAM_TYPES[groupId], index, flags);
return;
}
- CarVolumeGroup group = getCarVolumeGroup(groupId);
+ CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
group.setCurrentGainIndex(index);
}
}
@@ -382,10 +336,11 @@
// For legacy stream type based volume control
if (!mUseDynamicRouting) {
- return mAudioManager.getStreamMaxVolume(STREAM_TYPES[groupId]);
+ return mAudioManager.getStreamMaxVolume(
+ CarAudioDynamicRouting.STREAM_TYPES[groupId]);
}
- CarVolumeGroup group = getCarVolumeGroup(groupId);
+ CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
return group.getMaxGainIndex();
}
}
@@ -400,10 +355,11 @@
// For legacy stream type based volume control
if (!mUseDynamicRouting) {
- return mAudioManager.getStreamMinVolume(STREAM_TYPES[groupId]);
+ return mAudioManager.getStreamMinVolume(
+ CarAudioDynamicRouting.STREAM_TYPES[groupId]);
}
- CarVolumeGroup group = getCarVolumeGroup(groupId);
+ CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
return group.getMinGainIndex();
}
}
@@ -418,19 +374,20 @@
// For legacy stream type based volume control
if (!mUseDynamicRouting) {
- return mAudioManager.getStreamVolume(STREAM_TYPES[groupId]);
+ return mAudioManager.getStreamVolume(
+ CarAudioDynamicRouting.STREAM_TYPES[groupId]);
}
- CarVolumeGroup group = getCarVolumeGroup(groupId);
+ CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
return group.getCurrentGainIndex();
}
}
- private CarVolumeGroup getCarVolumeGroup(int groupId) {
- Preconditions.checkNotNull(mCarVolumeGroups);
- Preconditions.checkArgument(groupId >= 0 && groupId < mCarVolumeGroups.length,
- "groupId out of range: " + groupId);
- return mCarVolumeGroups[groupId];
+ private CarVolumeGroup getCarVolumeGroup(int zoneId, int groupId) {
+ Preconditions.checkNotNull(mCarAudioZones);
+ Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
+ "zoneId out of range: " + zoneId);
+ return mCarAudioZones[zoneId].getVolumeGroup(groupId);
}
private void setupLegacyVolumeChangedListener() {
@@ -440,174 +397,37 @@
mContext.registerReceiver(mLegacyVolumeChangedReceiver, intentFilter);
}
- private void setupDynamicRouting() {
- final IAudioControl audioControl = getAudioControl();
- if (audioControl == null) {
- throw new RuntimeException(
- "Dynamic routing requested but audioControl HAL not available");
- }
-
- AudioPolicy audioPolicy = getDynamicAudioPolicy(audioControl);
- int r = mAudioManager.registerAudioPolicy(audioPolicy);
- if (r != AudioManager.SUCCESS) {
- throw new RuntimeException("registerAudioPolicy failed " + r);
- }
-
- mAudioPolicy = audioPolicy;
- }
-
- private void setupVolumeGroups() {
- Preconditions.checkArgument(mCarAudioDeviceInfos.size() > 0,
- "No bus device is configured to setup volume groups");
- final CarVolumeGroupsHelper helper = new CarVolumeGroupsHelper(
- mContext, R.xml.car_volume_groups);
- mCarVolumeGroups = helper.loadVolumeGroups();
- for (CarVolumeGroup group : mCarVolumeGroups) {
- for (int contextNumber : group.getContexts()) {
- int busNumber = mContextToBus.get(contextNumber);
- group.bind(contextNumber, busNumber, mCarAudioDeviceInfos.get(busNumber));
- }
-
- // Now that we have all our contexts, ensure the HAL gets our intial value
- group.setCurrentGainIndex(group.getCurrentGainIndex());
-
- Log.v(CarLog.TAG_AUDIO, "Processed volume group: " + group);
- }
- // Perform validation after all volume groups are processed
- if (!validateVolumeGroups()) {
- throw new RuntimeException("Invalid volume groups configuration");
- }
- }
-
- /**
- * Constraints applied here:
- *
- * - One context should not appear in two groups
- * - All contexts are assigned
- * - One bus should not appear in two groups
- * - All gain controllers in the same group have same step value
- *
- * Note that it is fine that there are buses not appear in any group, those buses may be
- * reserved for other usages.
- * Step value validation is done in {@link CarVolumeGroup#bind(int, int, CarAudioDeviceInfo)}
- *
- * See also the car_volume_groups.xml configuration
- */
- private boolean validateVolumeGroups() {
- Set<Integer> contextSet = new HashSet<>();
- Set<Integer> busNumberSet = new HashSet<>();
- for (CarVolumeGroup group : mCarVolumeGroups) {
- // One context should not appear in two groups
- for (int context : group.getContexts()) {
- if (contextSet.contains(context)) {
- Log.e(CarLog.TAG_AUDIO, "Context appears in two groups: " + context);
- return false;
- }
- contextSet.add(context);
- }
-
- // One bus should not appear in two groups
- for (int busNumber : group.getBusNumbers()) {
- if (busNumberSet.contains(busNumber)) {
- Log.e(CarLog.TAG_AUDIO, "Bus appears in two groups: " + busNumber);
- return false;
- }
- busNumberSet.add(busNumber);
- }
- }
-
- // All contexts are assigned
- if (contextSet.size() != CONTEXT_NUMBERS.length) {
- Log.e(CarLog.TAG_AUDIO, "Some contexts are not assigned to group");
- Log.e(CarLog.TAG_AUDIO, "Assigned contexts "
- + Arrays.toString(contextSet.toArray(new Integer[contextSet.size()])));
- Log.e(CarLog.TAG_AUDIO, "All contexts " + Arrays.toString(CONTEXT_NUMBERS));
- return false;
- }
-
- return true;
- }
-
- @Nullable
- private AudioPolicy getDynamicAudioPolicy(@NonNull IAudioControl audioControl) {
- AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
+ private void setupDynamicRouting(SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
+ final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
builder.setLooper(Looper.getMainLooper());
- // Enumerate all output bus device ports
- AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
- if (deviceInfos.length == 0) {
- Log.e(CarLog.TAG_AUDIO, "getDynamicAudioPolicy, no output device available, ignore");
- return null;
- }
- for (AudioDeviceInfo info : deviceInfos) {
- Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
- info.getId(), info.getAddress(), info.getType()));
- if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
- final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
- // See also the audio_policy_configuration.xml and getBusForContext in
- // audio control HAL, the bus number should be no less than zero.
- if (carInfo.getBusNumber() >= 0) {
- mCarAudioDeviceInfos.put(carInfo.getBusNumber(), carInfo);
- Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
- }
+ final CarAudioZonesLoader zonesLoader;
+ if (mUseUnifiedConfiguration) {
+ zonesLoader = new CarAudioZonesHelper(mContext, R.xml.car_audio_configuration,
+ busToCarAudioDeviceInfo);
+ } else {
+ // In legacy mode, context -> bus mapping is done by querying IAudioControl HAL.
+ final IAudioControl audioControl = getAudioControl();
+ if (audioControl == null) {
+ throw new RuntimeException(
+ "Dynamic routing requested but audioControl HAL not available");
}
+ zonesLoader = new CarAudioZonesHelperLegacy(mContext, R.xml.car_volume_groups,
+ busToCarAudioDeviceInfo, audioControl);
+ }
+ mCarAudioZones = zonesLoader.loadAudioZones();
+ for (CarAudioZone zone : mCarAudioZones) {
+ if (!zone.validateVolumeGroups()) {
+ throw new RuntimeException("Invalid volume groups configuration");
+ }
+ // Ensure HAL gets our initial value
+ zone.synchronizeCurrentGainIndex();
+ Log.v(CarLog.TAG_AUDIO, "Processed audio zone: " + zone);
}
- // Map context to physical bus
- try {
- for (int contextNumber : CONTEXT_NUMBERS) {
- int busNumber = audioControl.getBusForContext(contextNumber);
- mContextToBus.put(contextNumber, busNumber);
- CarAudioDeviceInfo info = mCarAudioDeviceInfos.get(busNumber);
- if (info == null) {
- Log.w(CarLog.TAG_AUDIO, "No bus configured for context: " + contextNumber);
- }
- }
- } catch (RemoteException e) {
- Log.e(CarLog.TAG_AUDIO, "Error mapping context to physical bus", e);
- }
-
- // Enumerate all physical buses and build the routing policy.
- // Note that one can not register audio mix for same bus more than once.
- for (int i = 0; i < mCarAudioDeviceInfos.size(); i++) {
- int busNumber = mCarAudioDeviceInfos.keyAt(i);
- boolean hasContext = false;
- CarAudioDeviceInfo info = mCarAudioDeviceInfos.valueAt(i);
- AudioFormat mixFormat = new AudioFormat.Builder()
- .setSampleRate(info.getSampleRate())
- .setEncoding(info.getEncodingFormat())
- .setChannelMask(info.getChannelCount())
- .build();
- AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
- for (int j = 0; j < mContextToBus.size(); j++) {
- if (mContextToBus.valueAt(j) == busNumber) {
- hasContext = true;
- int contextNumber = mContextToBus.keyAt(j);
- int[] usages = getUsagesForContext(contextNumber);
- for (int usage : usages) {
- mixingRuleBuilder.addRule(
- new AudioAttributes.Builder().setUsage(usage).build(),
- AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
- }
- Log.i(CarLog.TAG_AUDIO, "Bus number: " + busNumber
- + " contextNumber: " + contextNumber
- + " sampleRate: " + info.getSampleRate()
- + " channels: " + info.getChannelCount()
- + " usages: " + Arrays.toString(usages));
- }
- }
- if (hasContext) {
- // It's a valid case that an audio output bus 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.
- AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
- .setFormat(mixFormat)
- .setDevice(info.getAudioDeviceInfo())
- .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
- .build();
- builder.addMix(audioMix);
- }
- }
+ // Setup dynamic routing rules by usage
+ final CarAudioDynamicRouting dynamicRouting = new CarAudioDynamicRouting(mCarAudioZones);
+ dynamicRouting.setupAudioDynamicRouting(builder);
// Attach the {@link AudioPolicyVolumeCallback}
builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
@@ -621,34 +441,23 @@
builder.setIsAudioFocusPolicy(true);
}
- // Instantiate the AudioPolicy
- final AudioPolicy audioPolicy = builder.build();
-
+ mAudioPolicy = builder.build();
if (sUseCarAudioFocus) {
// Connect the AudioPolicy and the focus listener
- mFocusHandler.setOwningPolicy(this, audioPolicy);
+ mFocusHandler.setOwningPolicy(this, mAudioPolicy);
}
- // Send the completed AudioPolicy back for use
- return audioPolicy;
- }
-
- // This is public so it can be used by our CarAudioFocus policy implementation, but since
- // "context" is a HAL level concept, this API should not be visible through the
- // CarAudioManager interface.
- // Returns 0 (INVALID) context if an unrecognized audio usage is passed in.
- public int getContextForUsage(int audioUsage) {
- return USAGE_TO_CONTEXT.get(audioUsage);
- }
-
- private int[] getUsagesForContext(int contextNumber) {
- final List<Integer> usages = new ArrayList<>();
- for (int i = 0; i < USAGE_TO_CONTEXT.size(); i++) {
- if (USAGE_TO_CONTEXT.valueAt(i) == contextNumber) {
- usages.add(USAGE_TO_CONTEXT.keyAt(i));
- }
+ int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
+ if (r != AudioManager.SUCCESS) {
+ throw new RuntimeException("registerAudioPolicy failed " + r);
}
- return usages.stream().mapToInt(i -> i).toArray();
+ }
+
+ /**
+ * @return Context number for a given audio usage, 0 if the given usage is unrecognized.
+ */
+ int getContextForUsage(int audioUsage) {
+ return CarAudioDynamicRouting.USAGE_TO_CONTEXT.get(audioUsage);
}
@Override
@@ -721,7 +530,7 @@
}
}
- return sourceAddresses.toArray(new String[sourceAddresses.size()]);
+ return sourceAddresses.toArray(new String[0]);
}
}
@@ -836,11 +645,12 @@
public int getVolumeGroupCount(int zoneId) {
synchronized (mImplLock) {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
-
// For legacy stream type based volume control
- if (!mUseDynamicRouting) return STREAM_TYPES.length;
+ if (!mUseDynamicRouting) return CarAudioDynamicRouting.STREAM_TYPES.length;
- return mCarVolumeGroups == null ? 0 : mCarVolumeGroups.length;
+ Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
+ "zoneId out of range: " + zoneId);
+ return mCarAudioZones[zoneId].getVolumeGroupCount();
}
}
@@ -848,13 +658,12 @@
public int getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage) {
synchronized (mImplLock) {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
+ Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
+ "zoneId out of range: " + zoneId);
- if (mCarVolumeGroups == null) {
- return -1;
- }
-
- for (int i = 0; i < mCarVolumeGroups.length; i++) {
- int[] contexts = mCarVolumeGroups[i].getContexts();
+ CarVolumeGroup[] groups = mCarAudioZones[zoneId].getVolumeGroups();
+ for (int i = 0; i < groups.length; i++) {
+ int[] contexts = groups[i].getContexts();
for (int context : contexts) {
if (getContextForUsage(usage) == context) {
return i;
@@ -872,16 +681,16 @@
// For legacy stream type based volume control
if (!mUseDynamicRouting) {
- return new int[] { STREAM_TYPE_USAGES[groupId] };
+ return new int[] { CarAudioDynamicRouting.STREAM_TYPE_USAGES[groupId] };
}
- CarVolumeGroup group = getCarVolumeGroup(groupId);
+ CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
Set<Integer> contexts =
Arrays.stream(group.getContexts()).boxed().collect(Collectors.toSet());
final List<Integer> usages = new ArrayList<>();
- for (int i = 0; i < USAGE_TO_CONTEXT.size(); i++) {
- if (contexts.contains(USAGE_TO_CONTEXT.valueAt(i))) {
- usages.add(USAGE_TO_CONTEXT.keyAt(i));
+ for (int i = 0; i < CarAudioDynamicRouting.USAGE_TO_CONTEXT.size(); i++) {
+ if (contexts.contains(CarAudioDynamicRouting.USAGE_TO_CONTEXT.valueAt(i))) {
+ usages.add(CarAudioDynamicRouting.USAGE_TO_CONTEXT.keyAt(i));
}
}
return usages.stream().mapToInt(i -> i).toArray();
@@ -918,8 +727,10 @@
* Multiple usages may share one {@link AudioDevicePort}
*/
private @Nullable AudioDevicePort getAudioPort(@AudioAttributes.AttributeUsage int usage) {
- final int groupId = getVolumeGroupIdForUsage(CarAudioManager.PRIMARY_AUDIO_ZONE, usage);
- final CarVolumeGroup group = Preconditions.checkNotNull(mCarVolumeGroups[groupId],
+ int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
+ final int groupId = getVolumeGroupIdForUsage(zoneId, usage);
+ final CarVolumeGroup group = Preconditions.checkNotNull(
+ mCarAudioZones[zoneId].getVolumeGroup(groupId),
"Can not find CarVolumeGroup by usage: "
+ AudioAttributes.usageToString(usage));
return group.getAudioDevicePortForContext(getContextForUsage(usage));
@@ -945,7 +756,7 @@
return playbacks.get(playbacks.size() - 1).getAudioAttributes().getUsage();
} else {
// TODO(b/72695246): Otherwise, get audio usage from foreground activity/window
- return DEFAULT_AUDIO_USAGE;
+ return CarAudioDynamicRouting.DEFAULT_AUDIO_USAGE;
}
}
}
@@ -957,8 +768,8 @@
*/
private int getVolumeGroupIdForStreamType(int streamType) {
int groupId = -1;
- for (int i = 0; i < STREAM_TYPES.length; i++) {
- if (streamType == STREAM_TYPES[i]) {
+ for (int i = 0; i < CarAudioDynamicRouting.STREAM_TYPES.length; i++) {
+ if (streamType == CarAudioDynamicRouting.STREAM_TYPES[i]) {
groupId = i;
break;
}
@@ -977,4 +788,8 @@
}
return null;
}
+
+ interface CarAudioZonesLoader {
+ CarAudioZone[] loadAudioZones();
+ }
}
diff --git a/service/src/com/android/car/audio/CarAudioZone.java b/service/src/com/android/car/audio/CarAudioZone.java
new file mode 100644
index 0000000..b827906
--- /dev/null
+++ b/service/src/com/android/car/audio/CarAudioZone.java
@@ -0,0 +1,146 @@
+/*
+ * 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.android.car.audio;
+
+import android.car.media.CarAudioManager;
+import android.util.Log;
+
+import com.android.car.CarLog;
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A class encapsulates an audio zone in car.
+ *
+ * An audio zone can contain multiple {@link CarVolumeGroup}s, and each zone has its own
+ * {@link CarAudioFocus} instance. Additionally, there may be dedicated hardware volume keys
+ * attached to each zone.
+ *
+ * See also the unified car_audio_configuration.xml
+ */
+/* package */ class CarAudioZone {
+
+ private final int mId;
+ private final String mName;
+ private final List<CarVolumeGroup> mVolumeGroups;
+
+ CarAudioZone(int id, String name) {
+ mId = id;
+ mName = name;
+ mVolumeGroups = new ArrayList<>();
+ }
+
+ int getId() {
+ return mId;
+ }
+
+ String getName() {
+ return mName;
+ }
+
+ boolean isPrimaryZone() {
+ return mId == CarAudioManager.PRIMARY_AUDIO_ZONE;
+ }
+
+ void addVolumeGroup(CarVolumeGroup volumeGroup) {
+ mVolumeGroups.add(volumeGroup);
+ }
+
+ CarVolumeGroup getVolumeGroup(int groupId) {
+ Preconditions.checkArgumentInRange(groupId, 0, mVolumeGroups.size() - 1,
+ "groupId(" + groupId + ") is out of range");
+ return mVolumeGroups.get(groupId);
+ }
+
+ int getVolumeGroupCount() {
+ return mVolumeGroups.size();
+ }
+
+ /**
+ * @return Snapshot of available {@link CarVolumeGroup}s in array.
+ */
+ CarVolumeGroup[] getVolumeGroups() {
+ return mVolumeGroups.toArray(new CarVolumeGroup[0]);
+ }
+
+ /**
+ * Constraints applied here:
+ *
+ * - One context should not appear in two groups
+ * - All contexts are assigned
+ * - One bus should not appear in two groups
+ * - All gain controllers in the same group have same step value
+ *
+ * Note that it is fine that there are buses not appear in any group, those buses may be
+ * reserved for other usages.
+ * Step value validation is done in {@link CarVolumeGroup#bind(int, int, CarAudioDeviceInfo)}
+ */
+ boolean validateVolumeGroups() {
+ Set<Integer> contextSet = new HashSet<>();
+ Set<Integer> busNumberSet = new HashSet<>();
+ for (CarVolumeGroup group : mVolumeGroups) {
+ // One context should not appear in two groups
+ for (int context : group.getContexts()) {
+ if (contextSet.contains(context)) {
+ Log.e(CarLog.TAG_AUDIO, "Context appears in two groups: " + context);
+ return false;
+ }
+ contextSet.add(context);
+ }
+
+ // One bus should not appear in two groups
+ for (int busNumber : group.getBusNumbers()) {
+ if (busNumberSet.contains(busNumber)) {
+ Log.e(CarLog.TAG_AUDIO, "Bus appears in two groups: " + busNumber);
+ return false;
+ }
+ busNumberSet.add(busNumber);
+ }
+ }
+
+ // All contexts are assigned
+ if (contextSet.size() != CarAudioDynamicRouting.CONTEXT_NUMBERS.length) {
+ Log.e(CarLog.TAG_AUDIO, "Some contexts are not assigned to group");
+ Log.e(CarLog.TAG_AUDIO, "Assigned contexts "
+ + Arrays.toString(contextSet.toArray(new Integer[0])));
+ Log.e(CarLog.TAG_AUDIO,
+ "All contexts " + Arrays.toString(CarAudioDynamicRouting.CONTEXT_NUMBERS));
+ return false;
+ }
+
+ return true;
+ }
+
+ void synchronizeCurrentGainIndex() {
+ for (CarVolumeGroup group : mVolumeGroups) {
+ group.setCurrentGainIndex(group.getCurrentGainIndex());
+ }
+ }
+
+ void dump(String indent, PrintWriter writer) {
+ writer.printf("%sCarAudioZone(%s:%d) isPrimary? %b\n", indent, mName, mId, isPrimaryZone());
+ for (CarVolumeGroup group : mVolumeGroups) {
+ group.dump(indent + "\t", writer);
+ }
+ writer.println();
+ }
+}
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
new file mode 100644
index 0000000..ba7b93c
--- /dev/null
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -0,0 +1,202 @@
+/*
+ * 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.android.car.audio;
+
+import android.annotation.NonNull;
+import android.annotation.XmlRes;
+import android.car.media.CarAudioManager;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.car.CarLog;
+import com.android.car.R;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class loads all audio zones from the configuration XML file.
+ */
+/* package */ class CarAudioZonesHelper implements CarAudioService.CarAudioZonesLoader {
+
+ private static final String TAG_ROOT = "carAudioConfiguration";
+ private static final String TAG_AUDIO_ZONES = "zones";
+ private static final String TAG_AUDIO_ZONE = "zone";
+ private static final String TAG_VOLUME_GROUPS = "volumeGroups";
+ 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 int SUPPORTED_VERSION = 1;
+
+ private final Context mContext;
+ private final int mXmlConfiguration;
+ private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo;
+
+ private int mNextSecondaryZoneId;
+
+ CarAudioZonesHelper(Context context, @XmlRes int xmlConfiguration,
+ @NonNull SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
+ mContext = context;
+ mXmlConfiguration = xmlConfiguration;
+ mBusToCarAudioDeviceInfo = busToCarAudioDeviceInfo;
+
+ mNextSecondaryZoneId = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
+ }
+
+ @Override
+ public CarAudioZone[] loadAudioZones() {
+ List<CarAudioZone> carAudioZones = new ArrayList<>();
+ try (XmlResourceParser parser = mContext.getResources().getXml(mXmlConfiguration)) {
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+ int type;
+ // Traverse to the first start tag, <carAudioConfiguration> in this case
+ while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+ && type != XmlResourceParser.START_TAG) {
+ // ignored
+ }
+ if (!TAG_ROOT.equals(parser.getName())) {
+ throw new RuntimeException("Meta-data does not start with " + TAG_ROOT);
+ }
+
+ // Version check
+ TypedArray c = mContext.getResources().obtainAttributes(
+ attrs, R.styleable.carAudioConfiguration);
+ final int versionNumber = c.getInt(R.styleable.carAudioConfiguration_version, -1);
+ if (versionNumber != SUPPORTED_VERSION) {
+ throw new RuntimeException("Support version:"
+ + SUPPORTED_VERSION + " only, got version:" + versionNumber);
+ }
+ c.recycle();
+
+ // And follows with the <zones> tag
+ while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+ && type != XmlResourceParser.START_TAG) {
+ // ignored
+ }
+ if (!TAG_AUDIO_ZONES.equals(parser.getName())) {
+ throw new RuntimeException("Configuration should begin with a <zones> tag");
+ }
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+ && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlResourceParser.END_TAG) {
+ continue;
+ }
+ if (TAG_AUDIO_ZONE.equals(parser.getName())) {
+ carAudioZones.add(parseAudioZone(attrs, parser));
+ }
+ }
+ } catch (Exception e) {
+ Log.e(CarLog.TAG_AUDIO, "Error parsing unified car audio configuration", e);
+
+ }
+ return carAudioZones.toArray(new CarAudioZone[0]);
+ }
+
+ private CarAudioZone parseAudioZone(AttributeSet attrs, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ TypedArray c = mContext.getResources().obtainAttributes(
+ attrs, R.styleable.carAudioConfiguration);
+ final boolean isPrimary = c.getBoolean(R.styleable.carAudioConfiguration_isPrimary, false);
+ final String zoneName = c.getString(R.styleable.carAudioConfiguration_name);
+ c.recycle();
+
+ CarAudioZone zone = new CarAudioZone(
+ isPrimary ? CarAudioManager.PRIMARY_AUDIO_ZONE : getNextSecondaryZoneId(),
+ zoneName);
+ int type;
+ // Traverse to the first start tag, <volumeGroups> in this case
+ while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+ && type != XmlResourceParser.START_TAG) {
+ // ignored
+ }
+
+ if (!TAG_VOLUME_GROUPS.equals(parser.getName())) {
+ throw new RuntimeException("Audio zone does not start with <volumeGroups> tag");
+ }
+ int outerDepth = parser.getDepth();
+ int groupId = 0;
+ while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+ && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlResourceParser.END_TAG) {
+ continue;
+ }
+ if (TAG_VOLUME_GROUP.equals(parser.getName())) {
+ zone.addVolumeGroup(parseVolumeGroup(zone.getId(), groupId, attrs, parser));
+ groupId += 1;
+ }
+ }
+ return zone;
+ }
+
+ private CarVolumeGroup parseVolumeGroup(
+ int zoneId, int groupId, AttributeSet attrs, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ final CarVolumeGroup group = new CarVolumeGroup(mContext, zoneId, groupId);
+ int type;
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+ && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlResourceParser.END_TAG) {
+ continue;
+ }
+ if (TAG_AUDIO_DEVICE.equals(parser.getName())) {
+ TypedArray c = mContext.getResources().obtainAttributes(
+ attrs, R.styleable.carAudioConfiguration);
+ final String address = c.getString(R.styleable.carAudioConfiguration_address);
+ parseVolumeGroupContexts(group,
+ CarAudioDeviceInfo.parseDeviceAddress(address), attrs, parser);
+ c.recycle();
+ }
+ }
+ return group;
+ }
+
+ private void parseVolumeGroupContexts(
+ CarVolumeGroup group, int busNumber, AttributeSet attrs, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ int type;
+ int innerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+ && (type != XmlResourceParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlResourceParser.END_TAG) {
+ continue;
+ }
+ if (TAG_CONTEXT.equals(parser.getName())) {
+ TypedArray c = mContext.getResources().obtainAttributes(
+ attrs, R.styleable.volumeGroups_context);
+ final int contextNumber = c.getInt(
+ R.styleable.volumeGroups_context_context, -1);
+ c.recycle();
+ group.bind(contextNumber, busNumber, mBusToCarAudioDeviceInfo.get(busNumber));
+ }
+ }
+ }
+
+ private int getNextSecondaryZoneId() {
+ int zoneId = mNextSecondaryZoneId;
+ mNextSecondaryZoneId += 1;
+ return zoneId;
+ }
+}
diff --git a/service/src/com/android/car/audio/CarVolumeGroupsHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
similarity index 63%
rename from service/src/com/android/car/audio/CarVolumeGroupsHelper.java
rename to service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
index eddcd4f..fb5481d 100644
--- a/service/src/com/android/car/audio/CarVolumeGroupsHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
@@ -15,12 +15,18 @@
*/
package com.android.car.audio;
+import android.annotation.NonNull;
import android.annotation.XmlRes;
+import android.car.media.CarAudioManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.hardware.automotive.audiocontrol.V1_0.IAudioControl;
+import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.util.Xml;
import com.android.car.CarLog;
@@ -33,9 +39,12 @@
import java.util.List;
/**
- * A helper class loads all volume groups from the configuration XML file.
+ * A helper class loads volume groups from car_volume_groups.xml configuration into one zone.
+ *
+ * @deprecated This is replaced by {@link CarAudioZonesHelper}.
*/
-/* package */ class CarVolumeGroupsHelper {
+@Deprecated
+/* package */ class CarAudioZonesHelperLegacy implements CarAudioService.CarAudioZonesLoader {
private static final String TAG_VOLUME_GROUPS = "volumeGroups";
private static final String TAG_GROUP = "group";
@@ -43,16 +52,47 @@
private final Context mContext;
private final @XmlRes int mXmlConfiguration;
+ private final SparseIntArray mContextToBus;
+ private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo;
- CarVolumeGroupsHelper(Context context, @XmlRes int xmlConfiguration) {
+ CarAudioZonesHelperLegacy(Context context, @XmlRes int xmlConfiguration,
+ @NonNull SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo,
+ @NonNull IAudioControl audioControl) {
mContext = context;
mXmlConfiguration = xmlConfiguration;
+ mBusToCarAudioDeviceInfo = busToCarAudioDeviceInfo;
+
+ // Initialize context => bus mapping once.
+ mContextToBus = new SparseIntArray();
+ try {
+ for (int contextNumber : CarAudioDynamicRouting.CONTEXT_NUMBERS) {
+ mContextToBus.put(contextNumber, audioControl.getBusForContext(contextNumber));
+ }
+ } catch (RemoteException e) {
+ Log.e(CarLog.TAG_AUDIO, "Failed to query IAudioControl HAL", e);
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public CarAudioZone[] loadAudioZones() {
+ final CarAudioZone zone = new CarAudioZone(CarAudioManager.PRIMARY_AUDIO_ZONE,
+ "Primary zone");
+ for (CarVolumeGroup group : loadVolumeGroups()) {
+ zone.addVolumeGroup(group);
+ // Binding audio device to volume group.
+ for (int contextNumber : group.getContexts()) {
+ int busNumber = mContextToBus.get(contextNumber);
+ group.bind(contextNumber, busNumber, mBusToCarAudioDeviceInfo.get(busNumber));
+ }
+ }
+ return new CarAudioZone[] { zone };
}
/**
* @return all {@link CarVolumeGroup} read from configuration.
*/
- CarVolumeGroup[] loadVolumeGroups() {
+ private List<CarVolumeGroup> loadVolumeGroups() {
List<CarVolumeGroup> carVolumeGroups = new ArrayList<>();
try (XmlResourceParser parser = mContext.getResources().getXml(mXmlConfiguration)) {
AttributeSet attrs = Xml.asAttributeSet(parser);
@@ -81,14 +121,13 @@
} catch (Exception e) {
Log.e(CarLog.TAG_AUDIO, "Error parsing volume groups configuration", e);
}
- return carVolumeGroups.toArray(new CarVolumeGroup[carVolumeGroups.size()]);
+ return carVolumeGroups;
}
private CarVolumeGroup parseVolumeGroup(int id, AttributeSet attrs, XmlResourceParser parser)
throws XmlPullParserException, IOException {
- int type;
-
List<Integer> contexts = new ArrayList<>();
+ int type;
int innerDepth = parser.getDepth();
while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
&& (type != XmlResourceParser.END_TAG || parser.getDepth() > innerDepth)) {
@@ -103,7 +142,7 @@
}
}
- return new CarVolumeGroup(mContext, id,
+ return new CarVolumeGroup(mContext, CarAudioManager.PRIMARY_AUDIO_ZONE, id,
contexts.stream().mapToInt(i -> i).filter(i -> i >= 0).toArray());
}
}
diff --git a/service/src/com/android/car/audio/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
index 5cda3d2..eb3cc4f 100644
--- a/service/src/com/android/car/audio/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -29,7 +29,9 @@
import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
* A class encapsulates a volume group in car.
@@ -41,10 +43,10 @@
/* package */ final class CarVolumeGroup {
private final ContentResolver mContentResolver;
+ private final int mZoneId;
private final int mId;
- private final int[] mContexts;
private final SparseIntArray mContextToBus = new SparseIntArray();
- private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfos = new SparseArray<>();
+ private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo = new SparseArray<>();
private int mDefaultGain = Integer.MIN_VALUE;
private int mMaxGain = Integer.MIN_VALUE;
@@ -53,30 +55,78 @@
private int mStoredGainIndex;
private int mCurrentGainIndex = -1;
- CarVolumeGroup(Context context, int id, @NonNull int[] contexts) {
+ /**
+ * Constructs a {@link CarVolumeGroup} instance
+ * @param context {@link Context} instance
+ * @param zoneId Audio zone this volume group belongs to
+ * @param id ID of this volume group
+ */
+ CarVolumeGroup(Context context, int zoneId, int id) {
mContentResolver = context.getContentResolver();
+ mZoneId = zoneId;
mId = id;
- mContexts = contexts;
-
mStoredGainIndex = Settings.Global.getInt(mContentResolver,
- CarAudioService.getVolumeSettingsKeyForGroup(mId), -1);
+ CarAudioService.getVolumeSettingsKeyForGroup(mZoneId, mId), -1);
}
- int getId() {
- return mId;
+ /**
+ * Constructs a {@link CarVolumeGroup} instance
+ * @param context {@link Context} instance
+ * @param zoneId Audio zone this volume group belongs to
+ * @param id ID of this volume group
+ * @param contexts Pre-populated array of car contexts, for legacy car_volume_groups.xml only
+ * @deprecated In favor of {@link #CarVolumeGroup(Context, int, int)}
+ */
+ @Deprecated
+ CarVolumeGroup(Context context, int zoneId, int id, @NonNull int[] contexts) {
+ this(context, zoneId, id);
+ // Deal with the pre-populated car audio contexts
+ for (int audioContext : contexts) {
+ mContextToBus.put(audioContext, -1);
+ }
}
+ /**
+ * @param busNumber Physical bus number for the audio device port
+ * @return {@link CarAudioDeviceInfo} associated with a given bus number
+ */
+ CarAudioDeviceInfo getCarAudioDeviceInfoForBus(int busNumber) {
+ return mBusToCarAudioDeviceInfo.get(busNumber);
+ }
+
+ /**
+ * @return Array of context numbers in this {@link CarVolumeGroup}
+ */
int[] getContexts() {
- return mContexts;
+ final int[] contextNumbers = new int[mContextToBus.size()];
+ for (int i = 0; i < contextNumbers.length; i++) {
+ contextNumbers[i] = mContextToBus.keyAt(i);
+ }
+ return contextNumbers;
+ }
+
+ /**
+ * @param busNumber Physical bus number for the audio device port
+ * @return Array of context numbers assigned to a given bus number
+ */
+ int[] getContextsForBus(int busNumber) {
+ List<Integer> contextNumbers = new ArrayList<>();
+ for (int i = 0; i < mContextToBus.size(); i++) {
+ int value = mContextToBus.valueAt(i);
+ if (value == busNumber) {
+ contextNumbers.add(mContextToBus.keyAt(i));
+ }
+ }
+ return contextNumbers.stream().mapToInt(i -> i).toArray();
}
/**
* @return Array of bus numbers in this {@link CarVolumeGroup}
*/
int[] getBusNumbers() {
- final int[] busNumbers = new int[mBusToCarAudioDeviceInfos.size()];
+ final int[] busNumbers = new int[mBusToCarAudioDeviceInfo.size()];
for (int i = 0; i < busNumbers.length; i++) {
- busNumbers[i] = mBusToCarAudioDeviceInfos.keyAt(i);
+ busNumbers[i] = mBusToCarAudioDeviceInfo.keyAt(i);
}
return busNumbers;
}
@@ -92,7 +142,7 @@
* @param info {@link CarAudioDeviceInfo} instance relates to the physical bus
*/
void bind(int contextNumber, int busNumber, CarAudioDeviceInfo info) {
- if (mBusToCarAudioDeviceInfos.size() == 0) {
+ if (mBusToCarAudioDeviceInfo.size() == 0) {
mStepSize = info.getAudioGain().stepValue();
} else {
Preconditions.checkArgument(
@@ -101,7 +151,7 @@
}
mContextToBus.put(contextNumber, busNumber);
- mBusToCarAudioDeviceInfos.put(busNumber, info);
+ mBusToCarAudioDeviceInfo.put(busNumber, info);
if (info.getDefaultGain() > mDefaultGain) {
// We're arbitrarily selecting the highest bus default gain as the group's default.
@@ -156,14 +206,14 @@
+ gainInMillibels + "index "
+ gainIndex);
- for (int i = 0; i < mBusToCarAudioDeviceInfos.size(); i++) {
- CarAudioDeviceInfo info = mBusToCarAudioDeviceInfos.valueAt(i);
+ for (int i = 0; i < mBusToCarAudioDeviceInfo.size(); i++) {
+ CarAudioDeviceInfo info = mBusToCarAudioDeviceInfo.valueAt(i);
info.setCurrentGain(gainInMillibels);
}
mCurrentGainIndex = gainIndex;
Settings.Global.putInt(mContentResolver,
- CarAudioService.getVolumeSettingsKeyForGroup(mId), gainIndex);
+ CarAudioService.getVolumeSettingsKeyForGroup(mZoneId, mId), gainIndex);
}
// Given a group level gain index, return the computed gain in millibells
@@ -186,33 +236,35 @@
@Nullable
AudioDevicePort getAudioDevicePortForContext(int contextNumber) {
final int busNumber = mContextToBus.get(contextNumber, -1);
- if (busNumber < 0 || mBusToCarAudioDeviceInfos.get(busNumber) == null) {
+ if (busNumber < 0 || mBusToCarAudioDeviceInfo.get(busNumber) == null) {
return null;
}
- return mBusToCarAudioDeviceInfos.get(busNumber).getAudioDevicePort();
+ return mBusToCarAudioDeviceInfo.get(busNumber).getAudioDevicePort();
}
@Override
public String toString() {
return "CarVolumeGroup id: " + mId
+ " currentGainIndex: " + mCurrentGainIndex
- + " contexts: " + Arrays.toString(mContexts)
+ + " contexts: " + Arrays.toString(getContexts())
+ " buses: " + Arrays.toString(getBusNumbers());
}
/** Writes to dumpsys output */
- void dump(PrintWriter writer) {
- writer.println("CarVolumeGroup " + mId);
- writer.printf("\tGain in millibel (min / max / default/ current): %d %d %d %d\n",
- mMinGain, mMaxGain, mDefaultGain, getGainForIndex(mCurrentGainIndex));
- writer.printf("\tGain in index (min / max / default / current): %d %d %d %d\n",
- getMinGainIndex(), getMaxGainIndex(), getDefaultGainIndex(), mCurrentGainIndex);
+ void dump(String indent, PrintWriter writer) {
+ writer.printf("%sCarVolumeGroup(%d)\n", indent, mId);
+ writer.printf("%sGain values (min / max / default/ current): %d %d %d %d\n",
+ indent, mMinGain, mMaxGain,
+ mDefaultGain, getGainForIndex(mCurrentGainIndex));
+ writer.printf("%sGain indexes (min / max / default / current): %d %d %d %d\n",
+ indent, getMinGainIndex(), getMaxGainIndex(),
+ getDefaultGainIndex(), mCurrentGainIndex);
for (int i = 0; i < mContextToBus.size(); i++) {
- writer.printf("\tContext: %s -> Bus: %d\n",
+ writer.printf("%sContext: %s -> Bus: %d\n", indent,
ContextNumber.toString(mContextToBus.keyAt(i)), mContextToBus.valueAt(i));
}
- for (int i = 0; i < mBusToCarAudioDeviceInfos.size(); i++) {
- mBusToCarAudioDeviceInfos.valueAt(i).dump(writer);
+ for (int i = 0; i < mBusToCarAudioDeviceInfo.size(); i++) {
+ mBusToCarAudioDeviceInfo.valueAt(i).dump(indent, writer);
}
// Empty line for comfortable reading
writer.println();
diff --git a/tests/EmbeddedKitchenSinkApp/Android.mk b/tests/EmbeddedKitchenSinkApp/Android.mk
index e6f457e..db59ddf 100644
--- a/tests/EmbeddedKitchenSinkApp/Android.mk
+++ b/tests/EmbeddedKitchenSinkApp/Android.mk
@@ -43,7 +43,6 @@
LOCAL_STATIC_ANDROID_LIBRARIES += \
car-service-lib-for-test \
car-apps-common \
- androidx.car_car \
androidx.car_car-cluster
LOCAL_STATIC_JAVA_LIBRARIES += \
diff --git a/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_checked.xml b/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_checked.xml
index d64d4fc..0ef8e43 100644
--- a/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_checked.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_checked.xml
@@ -22,5 +22,5 @@
android:viewportHeight="24.0">
<path
android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"
- android:fillColor="#000000"/>
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_unchecked.xml b/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_unchecked.xml
index 96246a3..b05cb48 100644
--- a/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_unchecked.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_unchecked.xml
@@ -22,5 +22,5 @@
android:viewportHeight="24.0">
<path
android:pathData="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"
- android:fillColor="#000000"/>
+ android:fillColor="#FFFFFF"/>
</vector>
diff --git a/tests/EmbeddedKitchenSinkApp/res/drawable/ic_voice_assistant_mic.xml b/tests/EmbeddedKitchenSinkApp/res/drawable/ic_voice_assistant_mic.xml
index 0468870..2171286 100644
--- a/tests/EmbeddedKitchenSinkApp/res/drawable/ic_voice_assistant_mic.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/drawable/ic_voice_assistant_mic.xml
@@ -21,7 +21,7 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="#000000"
+ android:fillColor="#FFFFFF"
android:pathData="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3
3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6
6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z" />
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_content.xml b/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_content.xml
index 45b38dc..2ebc1c6 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_content.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_content.xml
@@ -17,7 +17,6 @@
<!-- We use this container to place kitchen app fragments. It insets the fragment contents -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/kitchen_content"
- android:background="#A8A9AA"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="56dp"
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
index b9b8e8b..1dc40f6 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
@@ -65,4 +65,21 @@
android:layout_height="wrap_content"
android:text="Category: CATEGORY_MESSAGE"
android:textSize="35sp"/>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <Button
+ android:id="@+id/category_car_emerg_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Category: CATEGORY_EMERG"
+ android:textSize="35sp"/>
+ <Button
+ android:id="@+id/category_car_warning_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Category: CATEGORY_WARN"
+ android:textSize="35sp"/>
+ </LinearLayout>
</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml b/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml
index cf98695..52a7763 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml
@@ -23,7 +23,6 @@
android:layout_height="wrap_content"
android:text="Storage Wear Information"
android:textSize="24dp"
- android:textColor="#ff0000"
android:minLines="5"/>
<ListView
android:id="@+id/storage_events_list"
@@ -58,7 +57,6 @@
android:layout_height="wrap_content"
android:text="Free Disk Space: 1 byte"
android:textSize="24dp"
- android:textColor="#ff0000"
android:minLines="4"/>
<ScrollView
android:id="@+id/scroll_view"
@@ -72,7 +70,6 @@
android:layout_height="fill_parent"
android:text="No I/O activity on record"
android:textSize="20dp"
- android:textColor="#ff0000"
android:minLines="10"/>
</ScrollView>
</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/colors.xml b/tests/EmbeddedKitchenSinkApp/res/values/colors.xml
deleted file mode 100644
index 9a1ed89..0000000
--- a/tests/EmbeddedKitchenSinkApp/res/values/colors.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * 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.
-*/
--->
-<resources>
- <color name="car_button_tint">#fffafafa</color>
-</resources>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/styles.xml b/tests/EmbeddedKitchenSinkApp/res/values/styles.xml
index f16d19d..1dcaa55 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/styles.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/styles.xml
@@ -30,13 +30,10 @@
<item name="android:layout_width">@dimen/overview_icon_size</item>
<item name="android:layout_height">@dimen/overview_icon_size</item>
<item name="android:padding">6dp</item>
- <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
- <item name="android:tint">@color/car_button_tint</item>
<item name="android:scaleType">fitCenter</item>
<item name="android:clickable">true</item>
</style>
- <style name="KitchenSinkActivityTheme" parent="Theme.Car.Light.NoActionBar.Drawer">
- <item name="android:colorPrimary">@android:color/transparent</item>
+ <style name="KitchenSinkActivityTheme" parent="Theme.NoActionBar.Drawer">
</style>
</resources>
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 eb425bd..acc651d 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -33,11 +33,12 @@
import android.os.IBinder;
import android.util.Log;
-import androidx.car.drawer.CarDrawerActivity;
import androidx.car.drawer.CarDrawerAdapter;
import androidx.car.drawer.DrawerItemViewHolder;
import androidx.fragment.app.Fragment;
+import com.android.car.apps.common.DrawerActivity;
+
import com.google.android.car.kitchensink.activityview.ActivityViewTestFragment;
import com.google.android.car.kitchensink.alertdialog.AlertDialogTestFragment;
import com.google.android.car.kitchensink.assistant.CarAssistantFragment;
@@ -66,7 +67,8 @@
import java.util.ArrayList;
import java.util.List;
-public class KitchenSinkActivity extends CarDrawerActivity {
+
+public class KitchenSinkActivity extends DrawerActivity {
private static final String TAG = "KitchenSinkActivity";
private interface ClickHandler {
@@ -209,14 +211,14 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setToolbarElevation(0f);
- setMainContent(R.layout.kitchen_content);
- getDrawerController().setRootAdapter(new DrawerAdapter());
+ setContentView(R.layout.kitchen_content);
+
// Connection to Car Service does not work for non-automotive yet.
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
initCarApi();
}
Log.i(TAG, "onCreate");
+ getDrawerController().setRootAdapter(new DrawerAdapter());
}
private void initCarApi() {
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/displayinfo/DisplayInfoFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/displayinfo/DisplayInfoFragment.java
index ac0bdae..e199821 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/displayinfo/DisplayInfoFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/displayinfo/DisplayInfoFragment.java
@@ -32,7 +32,7 @@
import com.google.android.car.kitchensink.R;
/**
- * Shows alert dialogs
+ * Displays info about the display this is run on.
*/
public class DisplayInfoFragment extends Fragment {
@@ -95,11 +95,18 @@
}
private void addDimenText(String dimenName) {
- addTextView(dimenName + " : " + convertPixelsToDp(
- getResources().getDimensionPixelSize(
- getResources().getIdentifier(
- dimenName, "dimen", getContext().getPackageName())),
- getContext()));
+ String value;
+ try {
+ float dimen = convertPixelsToDp(
+ getResources().getDimensionPixelSize(
+ getResources().getIdentifier(
+ dimenName, "dimen", getContext().getPackageName())),
+ getContext());
+ value = Float.toString(dimen);
+ } catch (Resources.NotFoundException e) {
+ value = "Resource Not Found";
+ }
+ addTextView(dimenName + " : " + value);
}
private void addTextView(String text) {
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java
index 5128816..34ea1cf 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java
@@ -30,7 +30,6 @@
import android.hardware.automotive.vehicle.V2_0.VehicleArea;
import android.hardware.automotive.vehicle.V2_0.VehicleDisplay;
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
import android.hardware.automotive.vehicle.V2_0.VehiclePropertyStatus;
import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
index 7a26f93..d7230fc 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
@@ -45,6 +45,8 @@
Button importanceDefaultButton = view.findViewById(R.id.importance_default_button);
Button ongoingButton = view.findViewById(R.id.ongoing_button);
Button messageButton = view.findViewById(R.id.category_message_button);
+ Button emerg = view.findViewById(R.id.category_car_emerg_button);
+ Button warn = view.findViewById(R.id.category_car_warning_button);
NotificationManager manager =
(NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
@@ -184,6 +186,28 @@
manager.notify(3, notification);
});
+ emerg.setOnClickListener(v -> {
+
+ Notification notification = new Notification.Builder(getActivity(), CHANNEL_ID_1)
+ .setContentTitle("OMG")
+ .setContentText("This is of top importance")
+ .setCategory(Notification.CATEGORY_CAR_EMERGENCY)
+ .setSmallIcon(R.drawable.car_ic_mode)
+ .build();
+ manager.notify(10, notification);
+ });
+
+ warn.setOnClickListener(v -> {
+
+ Notification notification = new Notification.Builder(getActivity(), CHANNEL_ID_1)
+ .setContentTitle("OMG -ish ")
+ .setContentText("This is of less importance but still")
+ .setCategory(Notification.CATEGORY_CAR_WARNING)
+ .setSmallIcon(R.drawable.car_ic_mode)
+ .build();
+ manager.notify(11, notification);
+ });
+
return view;
}
}
diff --git a/tests/GarageModeTestApp/res/layout/activity_content.xml b/tests/GarageModeTestApp/res/layout/activity_content.xml
deleted file mode 100644
index 4ae4c8d..0000000
--- a/tests/GarageModeTestApp/res/layout/activity_content.xml
+++ /dev/null
@@ -1,21 +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.
--->
-<!-- We use this container to place app fragments. It insets the fragment contents -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/activity_content"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-</FrameLayout>
\ No newline at end of file
diff --git a/tests/GarageModeTestApp/res/layout/main_activity.xml b/tests/GarageModeTestApp/res/layout/main_activity.xml
new file mode 100644
index 0000000..b76e5b7
--- /dev/null
+++ b/tests/GarageModeTestApp/res/layout/main_activity.xml
@@ -0,0 +1,61 @@
+<?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.
+-->
+<androidx.drawerlayout.widget.DrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:background="@android:color/black"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <!-- The min height of the Toolbar needs to be set to ensure that the icons in it
+ are vertically centered. -->
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/car_toolbar"
+ android:background="@android:color/black"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_app_bar_height"
+ android:layout_gravity="center_vertical"
+ android:minHeight="@dimen/car_app_bar_height"
+ style="?attr/toolbarStyle" />
+
+ <FrameLayout
+ android:id="@+id/activity_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </LinearLayout>
+
+ <!-- Using a CosntraintLayout to specify a maxWidth. -->
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:layout_marginEnd="@dimen/car_margin"
+ android:maxWidth="@dimen/car_drawer_max_width">
+
+ <include
+ layout="@layout/car_drawer"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</androidx.drawerlayout.widget.DrawerLayout>
diff --git a/tests/GarageModeTestApp/res/values/strings.xml b/tests/GarageModeTestApp/res/values/strings.xml
index 7ad5d70..4d713c5 100644
--- a/tests/GarageModeTestApp/res/values/strings.xml
+++ b/tests/GarageModeTestApp/res/values/strings.xml
@@ -51,4 +51,7 @@
<item>5 minutes</item>
<item>1 hour</item>
</string-array>
+
+ <string name="car_drawer_open" translatable="false">Open drawer</string>
+ <string name="car_drawer_close" translatable="false">Close drawer</string>
</resources>
diff --git a/tests/GarageModeTestApp/res/values/styles.xml b/tests/GarageModeTestApp/res/values/styles.xml
index 92cb1c5..642e7ea 100644
--- a/tests/GarageModeTestApp/res/values/styles.xml
+++ b/tests/GarageModeTestApp/res/values/styles.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
<resources>
- <style name="GarageModeTheme" parent="Theme.Car.Light.NoActionBar.Drawer">
+ <style name="GarageModeTheme" parent="Theme.Car.Dark.NoActionBar.Drawer">
<item name="android:textSize">24sp</item>
</style>
<style name="SectionContainer">
@@ -54,4 +54,4 @@
<item name="android:layout_margin">5dp</item>
<item name="android:padding">3dp</item>
</style>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/tests/GarageModeTestApp/src/com/google/android/car/garagemode/testapp/MainActivity.java b/tests/GarageModeTestApp/src/com/google/android/car/garagemode/testapp/MainActivity.java
index 55b7c03..2019b7e 100644
--- a/tests/GarageModeTestApp/src/com/google/android/car/garagemode/testapp/MainActivity.java
+++ b/tests/GarageModeTestApp/src/com/google/android/car/garagemode/testapp/MainActivity.java
@@ -15,19 +15,28 @@
*/
package com.google.android.car.garagemode.testapp;
+import android.content.res.Configuration;
import android.os.Bundle;
+import android.view.MenuItem;
-import androidx.car.drawer.CarDrawerActivity;
+import androidx.appcompat.app.ActionBarDrawerToggle;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
import androidx.car.drawer.CarDrawerAdapter;
+import androidx.car.drawer.CarDrawerController;
import androidx.car.drawer.DrawerItemViewHolder;
+import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import java.util.ArrayList;
import java.util.List;
-public class MainActivity extends CarDrawerActivity {
+public class MainActivity extends AppCompatActivity {
private static final Logger LOG = new Logger("MainActivity");
+ private CarDrawerController mDrawerController;
+ private Toolbar mToolbar;
+
private final List<MenuEntry> mMenuEntries = new ArrayList<MenuEntry>() {
{
add("Offcar testing", OffcarTestingFragment.class);
@@ -47,9 +56,47 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setMainContent(R.layout.activity_content);
+ setContentView(R.layout.main_activity);
+
+ DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
+ ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(
+ /* activity= */ this,
+ drawerLayout,
+ R.string.car_drawer_open,
+ R.string.car_drawer_close);
+
+ mToolbar = findViewById(R.id.car_toolbar);
+ setSupportActionBar(mToolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setHomeButtonEnabled(true);
+
mMenuEntries.get(0).onClick();
- getDrawerController().setRootAdapter(new DrawerAdapter());
+
+ mDrawerController = new CarDrawerController(drawerLayout, drawerToggle);
+ mDrawerController.setRootAdapter(new DrawerAdapter());
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ mDrawerController.syncState();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mDrawerController.closeDrawer();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDrawerController.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ return mDrawerController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
}
private interface ClickHandler {
@@ -155,8 +202,7 @@
}
mMenuEntries.get(position).onClick();
-
- getDrawerController().closeDrawer();
+ mDrawerController.closeDrawer();
}
}
}