add basic audio focus handling
- added skeleton CarAudioManager (not plubmed yet)
- added external vs internal radio distinction
- basic external radio use case working with test
- basic focus handling verified with added test cases
bug: 25331859
Change-Id: I4a4439336673b695f38eb11e9aa0d5a6090ee1b2
(cherry picked from commit 2a5c2eed85832331f9baa74569b3dc735a7c61ff)
diff --git a/carsupport-lib/src/android/support/car/Car.java b/carsupport-lib/src/android/support/car/Car.java
index eb0ebd5..1027091 100644
--- a/carsupport-lib/src/android/support/car/Car.java
+++ b/carsupport-lib/src/android/support/car/Car.java
@@ -51,6 +51,8 @@
/** Service name for {@link CarInfoManager}, to be used in {@link #getCarManager(String)}. */
public static final String INFO_SERVICE = "info";
+ public static final String APP_CONTEXT_SERVICE = "app_context";
+
/** Type of car connection: car emulator, not physical connection. */
public static final int CONNECTION_TYPE_EMULATOR = 0;
/** Type of car connection: connected to a car via USB. */
diff --git a/carsupport-lib/src/android/support/car/IAppContext.aidl b/carsupport-lib/src/android/support/car/IAppContext.aidl
new file mode 100644
index 0000000..5228580
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/IAppContext.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.car;
+
+/** @hide */
+interface IAppContext {
+ int getVersion() = 0;
+}
\ No newline at end of file
diff --git a/carsupport-lib/src/android/support/car/media/CarAudioManager.java b/carsupport-lib/src/android/support/car/media/CarAudioManager.java
new file mode 100644
index 0000000..281d841
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/media/CarAudioManager.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.car.media;
+
+import android.media.AudioAttributes;
+import android.support.car.CarManagerBase;
+
+public class CarAudioManager implements CarManagerBase {
+
+ /**
+ * TODO Fix this. Needs new type or custom range in AudioAttribures.
+ */
+ public static final int AUDIO_ATTRIBUTES_USAGE_RADIO = AudioAttributes.USAGE_VIRTUAL_SOURCE;
+
+ private final ICarAudio mService;
+
+ @Override
+ public void onCarDisconnected() {
+ // TODO Auto-generated method stub
+ }
+
+ /** @hide */
+ public CarAudioManager(ICarAudio service) {
+ mService = service;
+ }
+}
diff --git a/carsupport-lib/src/android/support/car/media/ICarAudio.aidl b/carsupport-lib/src/android/support/car/media/ICarAudio.aidl
new file mode 100644
index 0000000..3875087
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/media/ICarAudio.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.car.media;
+
+/** @hide */
+interface ICarAudio {
+ int getVersion() = 0;
+}
\ No newline at end of file
diff --git a/carsystemtest-lib/src/com/android/car/VehicleHalEmulator.java b/carsystemtest-lib/src/com/android/car/VehicleHalEmulator.java
index 037e8b0..a1e0142 100644
--- a/carsystemtest-lib/src/com/android/car/VehicleHalEmulator.java
+++ b/carsystemtest-lib/src/com/android/car/VehicleHalEmulator.java
@@ -201,13 +201,13 @@
if (changeModes != null) {
changeMode = changeModes[0];
}
- int access = VehicleNetworkConsts.getVehicleAccess(property);
- if (access == 0) { // invalid
+ int[] accesses = VehicleNetworkConsts.getVehicleAccess(property);
+ if (accesses == null) { // invalid
continue;
}
VehiclePropConfig config = VehiclePropConfig.newBuilder().
setProp(property).
- setAccess(access).
+ setAccess(accesses[0]).
setChangeMode(changeMode).
setValueType(valueType).
setPermissionModel(
diff --git a/libvehiclenetwork/include/vehicle-internal.h b/libvehiclenetwork/include/vehicle-internal.h
index 266bfcc..5cd2065 100644
--- a/libvehiclenetwork/include/vehicle-internal.h
+++ b/libvehiclenetwork/include/vehicle-internal.h
@@ -49,6 +49,10 @@
VEHICLE_AUDIO_STREAM_STATE_STARTED = 1,
};
+enum vehicle_audio_stream_state_index {
+ VEHICLE_AUDIO_STREAM_STATE_INDEX_STATE = 0,
+ VEHICLE_AUDIO_STREAM_STATE_INDEX_STREAM = 1,
+};
__END_DECLS
diff --git a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java
index 73fa6da..1ed295e 100644
--- a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java
+++ b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java
@@ -52,6 +52,7 @@
public static final int VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY = 0x00000903;
public static final int VEHICLE_PROPERTY_AUDIO_HW_VARIANT = 0x00000904;
public static final int VEHICLE_PROPERTY_AP_POWER_STATE = 0x00000A00;
+public static final int VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS = 0x00000A01;
public static final int VEHICLE_PROPERTY_APP_CONTEXT = 0x00000B00;
public static final int VEHICLE_PROPERTY_CUSTOM_START = 0xf0000000;
public static final int VEHICLE_PROPERTY_CUSTOM_END = 0xf7ffffff;
@@ -84,12 +85,13 @@
case VEHICLE_PROPERTY_HVAC_AC_ON: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_BOOLEAN;
case VEHICLE_PROPERTY_ENV_OUTSIDE_TEMP: return VehicleValueType.VEHICLE_VALUE_TYPE_FLOAT;
case VEHICLE_PROPERTY_RADIO_PRESET: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC4;
-case VEHICLE_PROPERTY_AUDIO_FOCUS: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2;
+case VEHICLE_PROPERTY_AUDIO_FOCUS: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC3;
case VEHICLE_PROPERTY_AUDIO_VOLUME: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC3;
case VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2;
case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2;
case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32;
case VEHICLE_PROPERTY_AP_POWER_STATE: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2;
+case VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32;
case VEHICLE_PROPERTY_APP_CONTEXT: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32;
case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2;
default: return VehicleValueType.VEHICLE_VALUE_TYPE_SHOUD_NOT_USE;
@@ -128,6 +130,7 @@
case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return "VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY";
case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return "VEHICLE_PROPERTY_AUDIO_HW_VARIANT";
case VEHICLE_PROPERTY_AP_POWER_STATE: return "VEHICLE_PROPERTY_AP_POWER_STATE";
+case VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS: return "VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS";
case VEHICLE_PROPERTY_APP_CONTEXT: return "VEHICLE_PROPERTY_APP_CONTEXT";
case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return "VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE";
default: return "UNKNOWN_PROPERTY";
@@ -166,47 +169,49 @@
case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE };
case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC };
case VEHICLE_PROPERTY_AP_POWER_STATE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE };
+case VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE };
case VEHICLE_PROPERTY_APP_CONTEXT: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE };
case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE };
default: return null;
}
}
-public static int getVehicleAccess(int property) {
+public static int[] getVehicleAccess(int property) {
switch (property) {
-case VEHICLE_PROPERTY_INFO_VIN: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_INFO_MAKE: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_INFO_MODEL: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_INFO_MODEL_YEAR: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_INFO_FUEL_CAPACITY: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_PERF_ODOMETER: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_PERF_VEHICLE_SPEED: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_ENGINE_COOLANT_TEMP: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_ENGINE_OIL_TEMP: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_ENGINE_RPM: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_GEAR_SELECTION: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_CURRENT_GEAR: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_PARKING_BRAKE_ON: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_DRIVING_STATUS: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_FUEL_LEVEL_LOW: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_NIGHT_MODE: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_HVAC_FAN_SPEED: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_HVAC_FAN_DIRECTION: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_HVAC_TEMPERATURE_CURRENT: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_HVAC_DEFROSTER: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_HVAC_AC_ON: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_ENV_OUTSIDE_TEMP: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_RADIO_PRESET: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_AUDIO_FOCUS: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_AUDIO_VOLUME: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE;
-case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_AP_POWER_STATE: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_APP_CONTEXT: return VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE;
-case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-default: return 0;
+case VEHICLE_PROPERTY_INFO_VIN: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_INFO_MAKE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_INFO_MODEL: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_INFO_MODEL_YEAR: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_INFO_FUEL_CAPACITY: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_PERF_ODOMETER: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_PERF_VEHICLE_SPEED: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_ENGINE_COOLANT_TEMP: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_ENGINE_OIL_TEMP: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_ENGINE_RPM: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_GEAR_SELECTION: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_CURRENT_GEAR: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_PARKING_BRAKE_ON: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_DRIVING_STATUS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_FUEL_LEVEL_LOW: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_NIGHT_MODE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_HVAC_FAN_SPEED: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_HVAC_FAN_DIRECTION: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_HVAC_TEMPERATURE_CURRENT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_HVAC_DEFROSTER: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_HVAC_AC_ON: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_ENV_OUTSIDE_TEMP: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_RADIO_PRESET: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_AUDIO_FOCUS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_AUDIO_VOLUME: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE };
+case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_AP_POWER_STATE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ , VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_APP_CONTEXT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE };
+case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+default: return null;
}
}
@@ -282,13 +287,31 @@
}
}
+public static class VehicleAudioExtFocusFlag {
+public static final int VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG = 0x0;
+public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG = 0x1;
+public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG = 0x2;
+public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG = 0x4;
+public static String enumToString(int v) {
+switch(v) {
+case VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG: return "VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG";
+case VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG: return "VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG";
+case VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG: return "VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG";
+case VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG: return "VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG";
+default: return "UNKNOWN";
+}
+}
+}
+
public static class VehicleAudioFocusIndex {
public static final int VEHICLE_AUDIO_FOCUS_INDEX_FOCUS = 0;
public static final int VEHICLE_AUDIO_FOCUS_INDEX_STREAMS = 1;
+public static final int VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE = 2;
public static String enumToString(int v) {
switch(v) {
case VEHICLE_AUDIO_FOCUS_INDEX_FOCUS: return "VEHICLE_AUDIO_FOCUS_INDEX_FOCUS";
case VEHICLE_AUDIO_FOCUS_INDEX_STREAMS: return "VEHICLE_AUDIO_FOCUS_INDEX_STREAMS";
+case VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE: return "VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE";
default: return "UNKNOWN";
}
}
@@ -344,6 +367,16 @@
}
}
+public static class VehicleAudioHwVariantConfigFlag {
+public static final int VEHICLE_AUDIO_HW_VARIANT_FLAG_PASS_RADIO_AUDIO_FOCUS_FLAG = 0x1;
+public static String enumToString(int v) {
+switch(v) {
+case VEHICLE_AUDIO_HW_VARIANT_FLAG_PASS_RADIO_AUDIO_FOCUS_FLAG: return "VEHICLE_AUDIO_HW_VARIANT_FLAG_PASS_RADIO_AUDIO_FOCUS_FLAG";
+default: return "UNKNOWN";
+}
+}
+}
+
public static class VehicleApPowerStateConfigFlag {
public static final int VEHICLE_AP_POWER_STATE_CONFIG_ENABLE_DEEP_SLEEP_FLAG = 0x1;
public static final int VEHICLE_AP_POWER_STATE_CONFIG_SUPPORT_TIMER_POWER_ON_FLAG = 0x2;
@@ -815,6 +848,18 @@
}
}
+public static class VehicleAudioStreamStateIndex {
+public static final int VEHICLE_AUDIO_STREAM_STATE_INDEX_STATE = 0;
+public static final int VEHICLE_AUDIO_STREAM_STATE_INDEX_STREAM = 1;
+public static String enumToString(int v) {
+switch(v) {
+case VEHICLE_AUDIO_STREAM_STATE_INDEX_STATE: return "VEHICLE_AUDIO_STREAM_STATE_INDEX_STATE";
+case VEHICLE_AUDIO_STREAM_STATE_INDEX_STREAM: return "VEHICLE_AUDIO_STREAM_STATE_INDEX_STREAM";
+default: return "UNKNOWN";
+}
+}
+}
+
}
diff --git a/libvehiclenetwork/tool/vehiclehal_code_gen.py b/libvehiclenetwork/tool/vehiclehal_code_gen.py
index 7a0f623..d96a87e 100755
--- a/libvehiclenetwork/tool/vehiclehal_code_gen.py
+++ b/libvehiclenetwork/tool/vehiclehal_code_gen.py
@@ -147,13 +147,17 @@
"""
#now implement getVehicleAccess
print \
-"""public static int getVehicleAccess(int property) {
+"""public static int[] getVehicleAccess(int property) {
switch (property) {"""
for p in props:
if p.access != "":
- print "case " + p.name + ": return VehiclePropAccess." + p.access + ";"
+ accesses = p.access.split('|')
+ accessesString = []
+ for a in accesses:
+ accessesString.append("VehiclePropAccess." + a)
+ print "case " + p.name + ": return new int[] { " + " , ".join(accessesString) + " };"
print \
-"""default: return 0;
+"""default: return null;
}
}
"""
diff --git a/service/src/com/android/car/AppContextService.java b/service/src/com/android/car/AppContextService.java
new file mode 100644
index 0000000..7f12ca1
--- /dev/null
+++ b/service/src/com/android/car/AppContextService.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car;
+
+import android.content.Context;
+import android.support.car.IAppContext;
+
+import java.io.PrintWriter;
+
+public class AppContextService extends IAppContext.Stub implements CarServiceBase {
+ private static final int VERSION = 1;
+
+ public AppContextService(Context context) {
+ //TODO
+ }
+
+ @Override
+ public int getVersion() {
+ return VERSION;
+ }
+
+ @Override
+ public void init() {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void release() {
+ // TODO Auto-generated method stub
+ }
+
+ public void handleCallStateChange(boolean callActive) {
+ //TODO
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ // TODO Auto-generated method stub
+ }
+
+}
diff --git a/service/src/com/android/car/AudioRoutingPolicy.java b/service/src/com/android/car/AudioRoutingPolicy.java
index 0cbf96d..8d53cfc 100644
--- a/service/src/com/android/car/AudioRoutingPolicy.java
+++ b/service/src/com/android/car/AudioRoutingPolicy.java
@@ -20,8 +20,13 @@
import android.content.Context;
import android.content.res.Resources;
+import android.media.AudioAttributes;
import android.util.Log;
+/**
+ * Holds audio routing policy from config.xml. R.array.audioRoutingPolicy can contain
+ * multiple policies and VEHICLE_PROPERTY_AUDIO_HW_VARIANT decide which one to use.
+ */
public class AudioRoutingPolicy {
/**
* Type of logical stream defined in res/values/config.xml. This definition should be
@@ -74,6 +79,10 @@
final int nPhysicalStreams = streamPolicies.length;
mLogicalStreams = new int[nPhysicalStreams][];
mPhisicalStreamForLogicalStream = new int[STREAM_TYPE_MAX + 1];
+ for (int i = 0; i < mPhisicalStreamForLogicalStream.length; i++) {
+ mPhisicalStreamForLogicalStream[i] = STREAM_TYPE_INVALID;
+ }
+ int defaultStreamType = STREAM_TYPE_INVALID;
for (String streamPolicy : streamPolicies) {
String[] numberVsStreams = streamPolicy.split(":");
int physicalStream = Integer.parseInt(numberVsStreams[0]);
@@ -81,12 +90,25 @@
int[] logicalStreamsInt = new int[logicalStreams.length];
for (int i = 0; i < logicalStreams.length; i++) {
int logicalStreamNumber = getStreamType(logicalStreams[i]);
+ if (logicalStreamNumber == STREAM_TYPE_UNKNOWN) {
+ defaultStreamType = physicalStream;
+ }
logicalStreamsInt[i] = logicalStreamNumber;
mPhisicalStreamForLogicalStream[logicalStreamNumber] = physicalStream;
}
Arrays.sort(logicalStreamsInt);
mLogicalStreams[physicalStream] = logicalStreamsInt;
}
+ if (defaultStreamType == STREAM_TYPE_INVALID) {
+ Log.e(CarLog.TAG_AUDIO, "Audio routing policy did not include unknown");
+ defaultStreamType = 0;
+ }
+ for (int i = 0; i < mPhisicalStreamForLogicalStream.length; i++) {
+ if (mPhisicalStreamForLogicalStream[i] == STREAM_TYPE_INVALID) {
+ Log.w(CarLog.TAG_AUDIO, "Audio routing policy did not cover logical stream " + i);
+ mPhisicalStreamForLogicalStream[i] = defaultStreamType;
+ }
+ }
}
public int getPhysicalStreamsCount() {
@@ -101,6 +123,48 @@
return mPhisicalStreamForLogicalStream[logicalStream];
}
+ public int getLogicalStreamFromAudioAttributes(AudioAttributes attrib) {
+ if (attrib == null) {
+ return STREAM_TYPE_UNKNOWN;
+ }
+ int routingLogicalStream = STREAM_TYPE_UNKNOWN;
+ switch (attrib.getUsage()) {
+ case AudioAttributes.USAGE_MEDIA:
+ routingLogicalStream = STREAM_TYPE_MEDIA;
+ break;
+ case AudioAttributes.USAGE_VOICE_COMMUNICATION:
+ case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
+ routingLogicalStream = STREAM_TYPE_CALL;
+ break;
+ case AudioAttributes.USAGE_ALARM:
+ routingLogicalStream = STREAM_TYPE_ALARM;
+ break;
+ case AudioAttributes.USAGE_NOTIFICATION:
+ routingLogicalStream = STREAM_TYPE_NOTIFICATION;
+ break;
+ case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
+ routingLogicalStream = STREAM_TYPE_CALL;
+ break;
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+ case AudioAttributes.USAGE_NOTIFICATION_EVENT:
+ case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
+ routingLogicalStream = STREAM_TYPE_NOTIFICATION;
+ break;
+ case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+ routingLogicalStream = STREAM_TYPE_NAV_GUIDANCE;
+ break;
+ case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
+ routingLogicalStream = STREAM_TYPE_NOTIFICATION;
+ break;
+ case AudioAttributes.USAGE_GAME:
+ routingLogicalStream = STREAM_TYPE_MEDIA;
+ break;
+ }
+ return routingLogicalStream;
+ }
+
public void dump(PrintWriter writer) {
writer.println("*AudioRoutingPolicy*");
writer.println("**Logical Streams**");
diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/CarAudioService.java
index a7b0737..77a8ef2 100644
--- a/service/src/com/android/car/CarAudioService.java
+++ b/service/src/com/android/car/CarAudioService.java
@@ -16,6 +16,7 @@
package com.android.car;
import android.content.Context;
+import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.media.AudioManager;
import android.media.audiopolicy.AudioPolicy;
@@ -24,6 +25,8 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.support.car.media.CarAudioManager;
+import android.support.car.media.ICarAudio;
import android.util.Log;
import com.android.car.hal.AudioHalService;
@@ -32,51 +35,99 @@
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
+import java.util.LinkedList;
-public class CarAudioService implements CarServiceBase, AudioHalListener {
+public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, AudioHalListener {
- // support only two streams, default and media for now.
- private static final int NUMBER_OF_STREAMS = 2;
+ private static final long FOCUS_RESPONSE_WAIT_TIMEOUT_MS = 500;
- private static final int FOCUS_STACK_DEPTH_TO_MONITOR = 2;
+ private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS";
+
+ private static final boolean DBG = true;
+
+ private static final int VERSION = 1;
private final AudioHalService mAudioHal;
private final Context mContext;
- private final HandlerThread mHandlerThread;
- private final CarAudioChangeHandler mHandler;
+ private final HandlerThread mFocusHandlerThread;
+ private final CarAudioFocusChangeHandler mFocusHandler;
+ private final CarAudioVolumeHandler mVolumeHandler;
private final SystemFocusListener mSystemFocusListener;
private AudioPolicy mAudioPolicy;
private final Object mLock = new Object();
@GuardedBy("mLock")
- private int mCurrentFocusState;
+ private FocusState mCurrentFocusState = FocusState.STATE_LOSS;
+ /** Focus state received, but not handled yet. Once handled, this will be set to null. */
@GuardedBy("mLock")
- private int mAllowedStreams;
+ private FocusState mFocusReceived = null;
@GuardedBy("mLock")
- private int mLastFocusRequest;
+ private FocusRequest mLastFocusRequestToCar = null;
@GuardedBy("mLock")
- private int mLastFocusRequestStreams;
+ private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>();
@GuardedBy("mLock")
- private final AudioFocusInfo[] mFocusInfos = new AudioFocusInfo[FOCUS_STACK_DEPTH_TO_MONITOR];
- private AudioRoutingPolicy mAudioRoutingPolicy;
+ private AudioFocusInfo mTopFocusInfo = null;
- public CarAudioService(Context context) {
+ private AudioRoutingPolicy mAudioRoutingPolicy;
+ private final AudioManager mAudioManager;
+ private final BottomAudioFocusListener mBottomAudioFocusHandler =
+ new BottomAudioFocusListener();
+ private final CarProxyAndroidFocusListener mCarProxyAudioFocusHandler =
+ new CarProxyAndroidFocusListener();
+ @GuardedBy("mLock")
+ private int mBottomFocusState;
+ @GuardedBy("mLock")
+ private boolean mRadioActive = false;
+ @GuardedBy("mLock")
+ private boolean mCallActive = false;
+
+ private final AppContextService mAppContextService;
+
+ private final AudioAttributes mAttributeBottom = (new AudioAttributes.Builder()).
+ setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN).
+ setUsage(AudioAttributes.USAGE_UNKNOWN).build();
+ private final AudioAttributes mAttributeCarExternal = (new AudioAttributes.Builder()).
+ setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN).
+ setUsage(AudioAttributes.USAGE_UNKNOWN).build();
+
+ public CarAudioService(Context context, AppContextService appContextService) {
mAudioHal = VehicleHal.getInstance().getAudioHal();
mContext = context;
- mHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
+ mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
mSystemFocusListener = new SystemFocusListener();
- mHandlerThread.start();
- AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
- builder.setLooper(mHandlerThread.getLooper()).
- setAudioPolicyFocusListener(mSystemFocusListener);
- mAudioPolicy = builder.build();
- mHandler = new CarAudioChangeHandler(mHandlerThread.getLooper());
+ mFocusHandlerThread.start();
+ mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
+ mVolumeHandler = new CarAudioVolumeHandler(Looper.getMainLooper());
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mAppContextService = appContextService;
+ }
+
+ @Override
+ public int getVersion() {
+ return VERSION;
}
@Override
public void init() {
- AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- int r = am.registerAudioPolicy(mAudioPolicy);
+ AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
+ builder.setLooper(Looper.getMainLooper());
+ boolean isFocusSuported = mAudioHal.isFocusSupported();
+ if (isFocusSuported) {
+ builder.setAudioPolicyFocusListener(mSystemFocusListener);
+ }
+ mAudioPolicy = builder.build();
+ if (isFocusSuported) {
+ int r = mAudioManager.requestAudioFocus(mBottomAudioFocusHandler, mAttributeBottom,
+ AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
+ synchronized (mLock) {
+ if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN;
+ } else {
+ mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
+ }
+ }
+ }
+ int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
if (r != 0) {
throw new RuntimeException("registerAudioPolicy failed " + r);
}
@@ -84,84 +135,198 @@
int audioHwVariant = mAudioHal.getHwVariant();
mAudioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
mAudioHal.setAudioRoutingPolicy(mAudioRoutingPolicy);
+ //TODO set routing policy with new AudioPolicy API. This will control which logical stream
+ // goes to which physical stream.
}
@Override
public void release() {
- AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- am.unregisterAudioPolicyAsync(mAudioPolicy);
+ mAudioManager.unregisterAudioPolicyAsync(mAudioPolicy);
+ mAudioManager.abandonAudioFocus(mBottomAudioFocusHandler);
+ mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
+ mFocusHandler.cancelAll();
+ synchronized (mLock) {
+ mCurrentFocusState = FocusState.STATE_LOSS;
+ mLastFocusRequestToCar = null;
+ mTopFocusInfo = null;
+ mPendingFocusChanges.clear();
+ mRadioActive = false;
+ }
}
@Override
public void dump(PrintWriter writer) {
- // TODO Auto-generated method stub
+ writer.println("*CarAudioService*");
+ writer.println(" mCurrentFocusState:" + mCurrentFocusState +
+ " mLastFocusRequestToCar:" + mLastFocusRequestToCar);
+ writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive);
}
@Override
- public void onFocusChange(int focusState, int streams) {
- mHandler.handleFocusChange(focusState, streams);
+ public void onFocusChange(int focusState, int streams, int externalFocus) {
+ synchronized (mLock) {
+ mFocusReceived = FocusState.create(focusState, streams, externalFocus);
+ // wake up thread waiting for focus response.
+ mLock.notifyAll();
+ }
+ mFocusHandler.handleFocusChange();
}
@Override
public void onVolumeChange(int streamNumber, int volume, int volumeState) {
- mHandler.handleVolumeChange(new VolumeStateChangeEvent(streamNumber, volume, volumeState));
+ mVolumeHandler.handleVolumeChange(new VolumeStateChangeEvent(streamNumber, volume,
+ volumeState));
+ }
+
+ @Override
+ public void onVolumeLimitChange(int streamNumber, int volume) {
+ //TODO
}
@Override
public void onStreamStatusChange(int state, int streamNumber) {
- mHandler.handleStreamStateChange(state, streamNumber);
+ mFocusHandler.handleStreamStateChange(state, streamNumber);
}
- private void doHandleCarFocusChange(int focusState, int streams) {
- boolean needsFocusUpdate = false;
+ private void doHandleCarFocusChange() {
+ int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID;
+ FocusState currentState;
+ AudioFocusInfo topInfo;
synchronized (mLock) {
- mCurrentFocusState = focusState;
- mAllowedStreams = streams;
+ if (mFocusReceived == null) {
+ // already handled
+ return;
+ }
+ if (mFocusReceived.equals(mCurrentFocusState)) {
+ // no change
+ mFocusReceived = null;
+ return;
+ }
+ if (DBG) {
+ Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived);
+ }
+ topInfo = mTopFocusInfo;
+ if (!mFocusReceived.equals(mCurrentFocusState.focusState)) {
+ newFocusState = mFocusReceived.focusState;
+ }
+ mCurrentFocusState = mFocusReceived;
+ currentState = mFocusReceived;
+ mFocusReceived = null;
+ if (mLastFocusRequestToCar != null &&
+ (mLastFocusRequestToCar.focusRequest ==
+ AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN ||
+ mLastFocusRequestToCar.focusRequest ==
+ AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT ||
+ mLastFocusRequestToCar.focusRequest ==
+ AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) &&
+ (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) !=
+ mLastFocusRequestToCar.streams) {
+ Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString(
+ mLastFocusRequestToCar.streams) + " got:0x" +
+ Integer.toHexString(mCurrentFocusState.streams));
+ // treat it as focus loss as requested streams are not there.
+ newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
+ }
+ mLastFocusRequestToCar = null;
+ if (mRadioActive &&
+ (mCurrentFocusState.externalFocus &
+ AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) {
+ // radio flag dropped
+ newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
+ mRadioActive = false;
+ }
}
- switch (focusState) {
+ switch (newFocusState) {
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
- doHandleFocusGainFromCar();
+ doHandleFocusGainFromCar(currentState, topInfo);
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
- doHandleFocusGainTransientFromCar();
+ doHandleFocusGainTransientFromCar(currentState, topInfo);
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
- doHandleFocusLossFromCar();
+ doHandleFocusLossFromCar(currentState, topInfo);
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
- doHandleFocusLossTransientFromCar();
+ doHandleFocusLossTransientFromCar(currentState);
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
- doHandleFocusLossTransientCanDuckFromCar();
+ doHandleFocusLossTransientCanDuckFromCar(currentState);
break;
case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
- doHandleFocusLossTransientExclusiveFromCar();
+ doHandleFocusLossTransientExclusiveFromCar(currentState);
break;
}
}
- private void doHandleFocusGainFromCar() {
- //TODO
+ private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo) {
+ if (isFocusFromCarServiceBottom(topInfo)) {
+ Log.w(TAG_FOCUS, "focus gain from car:" + currentState +
+ " while bottom listener is top");
+ mFocusHandler.handleFocusReleaseRequest();
+ } else {
+ mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
+ }
}
- private void doHandleFocusGainTransientFromCar() {
- //TODO
+ private void doHandleFocusGainTransientFromCar(FocusState currentState,
+ AudioFocusInfo topInfo) {
+ if ((currentState.externalFocus &
+ (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
+ AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
+ mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
+ } else {
+ if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) {
+ Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState +
+ " while bottom listener or car proxy is top");
+ mFocusHandler.handleFocusReleaseRequest();
+ }
+ }
}
- private void doHandleFocusLossFromCar() {
- //TODO
+ private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) {
+ if (DBG) {
+ Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState +
+ " top:" + dumpAudioFocusInfo(topInfo));
+ }
+ boolean shouldRequestProxyFocus = false;
+ if ((currentState.externalFocus &
+ AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) {
+ shouldRequestProxyFocus = true;
+ }
+ if (isFocusFromCarProxy(topInfo)) {
+ if ((currentState.externalFocus &
+ (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
+ AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
+ // CarProcy in top, but no external focus: Drop it so that some other app
+ // may pick up focus.
+ mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
+ return;
+ }
+ } else if (!isFocusFromCarServiceBottom(topInfo)) {
+ shouldRequestProxyFocus = true;
+ }
+ if (shouldRequestProxyFocus) {
+ requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0);
+ }
}
- private void doHandleFocusLossTransientFromCar() {
- //TODO
+ private void doHandleFocusLossTransientFromCar(FocusState currentState) {
+ requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
}
- private void doHandleFocusLossTransientCanDuckFromCar() {
- //TODO
+ private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
+ requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
}
- private void doHandleFocusLossTransientExclusiveFromCar() {
- //TODO
+ private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
+ requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
+ AudioManager.AUDIOFOCUS_FLAG_LOCK);
+ }
+
+ private void requestCarProxyFocus(int androidFocus, int flags) {
+ mAudioManager.requestAudioFocus(mCarProxyAudioFocusHandler, mAttributeCarExternal,
+ androidFocus,
+ flags);
}
private void doHandleVolumeChange(VolumeStateChangeEvent event) {
@@ -172,93 +337,392 @@
//TODO
}
- private void lockSystemAudioFocus() {
- //TODO use AUDIOFOCUS_FLAG_LOCK
- }
-
- private void unlockSystemAudioFocus() {
- //TODO
- }
-
- private void sendCarAudioFocusRequestIfNecessary() {
- //TODO
- }
-
- private void doHandleSystemAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
- //TODO distinguish car service's own focus request from others
- if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
- mFocusInfos[0] = afi;
- sendCarAudioFocusRequestIfNecessary();
+ private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) {
+ if (info == null) {
+ return false;
}
+ AudioAttributes attrib = info.getAttributes();
+ if (info.getPackageName().equals(mContext.getPackageName()) &&
+ info.getClientId().contains(BottomAudioFocusListener.class.getName()) &&
+ attrib != null &&
+ attrib.getContentType() == mAttributeBottom.getContentType() &&
+ attrib.getUsage() == mAttributeBottom.getUsage()) {
+ return true;
+ }
+ return false;
}
- private void doHandleSystemAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
- if (!wasNotified) {
- // app released focus by itself. Remove from stack if it is there.
- boolean mayNeedsFocusChange = false;
- for (int i = 0; i < mFocusInfos.length; i++) {
- AudioFocusInfo info = mFocusInfos[i];
- if (info == null) {
- continue;
+ private boolean isFocusFromCarProxy(AudioFocusInfo info) {
+ if (info == null) {
+ return false;
+ }
+ AudioAttributes attrib = info.getAttributes();
+ if (info.getPackageName().equals(mContext.getPackageName()) &&
+ info.getClientId().contains(CarProxyAndroidFocusListener.class.getName()) &&
+ attrib != null &&
+ attrib.getContentType() == mAttributeCarExternal.getContentType() &&
+ attrib.getUsage() == mAttributeCarExternal.getUsage()) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isFocusFromRadio(AudioFocusInfo info) {
+ if (!mAudioHal.isRadioExternal()) {
+ // if radio is not external, no special handling of radio is necessary.
+ return false;
+ }
+ if (info == null) {
+ return false;
+ }
+ AudioAttributes attrib = info.getAttributes();
+ //TODO remove content type check?
+ if (attrib != null &&
+ attrib.getContentType() == AudioAttributes.CONTENT_TYPE_MUSIC &&
+ attrib.getUsage() == CarAudioManager.AUDIO_ATTRIBUTES_USAGE_RADIO) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Re-evaluate current focus state and send focus request to car if new focus was requested.
+ * @return true if focus change was requested to car.
+ */
+ private boolean reevaluateCarAudioFocusLocked() {
+ if (mTopFocusInfo == null) {
+ // should not happen
+ Log.w(TAG_FOCUS, "reevaluateCarAudioFocusLocked, top focus info null");
+ return false;
+ }
+ if (mTopFocusInfo.getLossReceived() != 0) {
+ // top one got loss. This should not happen.
+ Log.e(TAG_FOCUS, "Top focus holder got loss " + dumpAudioFocusInfo(mTopFocusInfo));
+ return false;
+ }
+ if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) {
+ switch (mCurrentFocusState.focusState) {
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
+ // should not have focus. So enqueue release
+ mFocusHandler.handleFocusReleaseRequest();
+ break;
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
+ doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo);
+ break;
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
+ doHandleFocusLossTransientFromCar(mCurrentFocusState);
+ break;
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
+ doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState);
+ break;
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
+ doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
+ break;
+ }
+ if (mRadioActive) { // radio is no longer active.
+ mRadioActive = false;
+ }
+ return false;
+ }
+ mFocusHandler.cancelFocusReleaseRequest();
+ AudioAttributes attrib = mTopFocusInfo.getAttributes();
+ int logicalStreamTypeForTop = mAudioRoutingPolicy.getLogicalStreamFromAudioAttributes(
+ attrib);
+ int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
+ logicalStreamTypeForTop);
+ if (logicalStreamTypeForTop == AudioRoutingPolicy.STREAM_TYPE_CALL) {
+ if (!mCallActive) {
+ mCallActive = true;
+ mAppContextService.handleCallStateChange(mCallActive);
+ }
+ } else {
+ if (mCallActive) {
+ mCallActive = false;
+ mAppContextService.handleCallStateChange(mCallActive);
+ }
+ }
+ // other apps having focus
+ int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
+ int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
+ int streamsToRequest = 0x1 << physicalStreamTypeForTop;
+ switch (mTopFocusInfo.getGainRequest()) {
+ case AudioManager.AUDIOFOCUS_GAIN:
+ if (isFocusFromRadio(mTopFocusInfo)) {
+ mRadioActive = true;
+ } else {
+ mRadioActive = false;
}
- if (info.getClientId().equals(afi.getClientId())) {
- if (i == 0) { // this is top component releasing focus
- // clear bottom one as well. This can lead into sending focus request
- // if there is a focus holder other than this one.
- // But that cannot be distinguished. So release it now, and request
- // again if necessary.
- mFocusInfos[1] = null;
+ focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
+ break;
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
+ // radio cannot be active
+ mRadioActive = false;
+ focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
+ break;
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
+ focusToRequest =
+ AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
+ switch (mCurrentFocusState.focusState) {
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
+ streamsToRequest |= mCurrentFocusState.streams;
+ focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
break;
- }
- mFocusInfos[i] = null;
- mayNeedsFocusChange = true;
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
+ streamsToRequest |= mCurrentFocusState.streams;
+ //TODO is there a need to change this to GAIN_TRANSIENT?
+ break;
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
+ break;
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
+ doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
+ return false;
+ }
+ break;
+ default:
+ streamsToRequest = 0;
+ break;
+ }
+ if (mRadioActive) {
+ extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
+ // TODO any need to keep media stream while radio is active?
+ // Most cars do not allow that, but if mixing is possible, it can take media stream.
+ // For now, assume no mixing capability.
+ int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
+ AudioRoutingPolicy.STREAM_TYPE_MEDIA);
+ streamsToRequest &= ~(0x1 << radioPhysicalStream);
+ } else if (streamsToRequest == 0) {
+ mFocusHandler.handleFocusReleaseRequest();
+ return false;
+ }
+ return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus);
+ }
+
+ private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
+ int streamsToRequest, int extFocus) {
+ if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus)) {
+ mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
+ extFocus);
+ if (DBG) {
+ Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar);
+ }
+ mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus);
+ try {
+ mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ //ignore
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest,
+ int extFocus) {
+ if (streamsToRequest != mCurrentFocusState.streams) {
+ return true;
+ }
+ if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) {
+ return true;
+ }
+ switch (focusToRequest) {
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
+ if (mCurrentFocusState.focusState ==
+ AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) {
+ return false;
+ }
+ break;
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
+ if (mCurrentFocusState.focusState ==
+ AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN ||
+ mCurrentFocusState.focusState ==
+ AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) {
+ return false;
+ }
+ break;
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
+ if (mCurrentFocusState.focusState ==
+ AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
+ mCurrentFocusState.focusState ==
+ AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
+ return false;
+ }
+ break;
+ }
+ return true;
+ }
+
+ private void doHandleAndroidFocusChange() {
+ boolean focusRequested = false;
+ synchronized (mLock) {
+ if (mPendingFocusChanges.isEmpty()) {
+ // no entry. It was handled already.
+ if (DBG) {
+ Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty");
+ }
+ return;
+ }
+ AudioFocusInfo newTopInfo = mPendingFocusChanges.getFirst();
+ mPendingFocusChanges.clear();
+ if (mTopFocusInfo != null &&
+ newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) &&
+ newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() &&
+ isAudioAttributesSame(
+ newTopInfo.getAttributes(), mTopFocusInfo.getAttributes())) {
+ if (DBG) {
+ Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" +
+ dumpAudioFocusInfo(mTopFocusInfo));
+ }
+ // already in top somehow, no need to make any change
+ return;
+ }
+ if (DBG) {
+ Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo));
+ }
+ mTopFocusInfo = newTopInfo;
+ focusRequested = reevaluateCarAudioFocusLocked();
+ if (DBG) {
+ if (!focusRequested) {
+ Log.i(TAG_FOCUS, "focus not requested for top focus:" +
+ dumpAudioFocusInfo(newTopInfo) + " currentState:" + mCurrentFocusState);
}
}
- if (mayNeedsFocusChange) {
- sendCarAudioFocusRequestIfNecessary();
+ if (focusRequested && mFocusReceived == null) {
+ Log.w(TAG_FOCUS, "focus response timed out, request sent" +
+ mLastFocusRequestToCar);
+ // no response. so reset to loss.
+ mFocusReceived = FocusState.STATE_LOSS;
}
- } else { // there will be a separate grant soon
- mFocusInfos[1] = afi;
}
+ // handle it if there was response or force handle it for timeout.
+ if (focusRequested) {
+ doHandleCarFocusChange();
+ }
+ }
+
+ private void doHandleFocusRelease() {
+ //TODO Is there a need to wait for the stopping of streams?
+ boolean sent = false;
+ synchronized (mLock) {
+ if (mCurrentFocusState != FocusState.STATE_LOSS) {
+ if (DBG) {
+ Log.d(TAG_FOCUS, "focus release to car");
+ }
+ mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
+ sent = true;
+ mAudioHal.requestAudioFocusChange(
+ AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0);
+ try {
+ mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ //ignore
+ }
+ } else if (DBG) {
+ Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss");
+ }
+ }
+ // handle it if there was response.
+ if (sent) {
+ doHandleCarFocusChange();
+ }
+ }
+
+ private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
+ if (one.getContentType() != two.getContentType()) {
+ return false;
+ }
+ if (one.getUsage() != two.getUsage()) {
+ return false;
+ }
+ return true;
+ }
+
+ private static String dumpAudioFocusInfo(AudioFocusInfo info) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("afi package:" + info.getPackageName());
+ builder.append("client id:" + info.getClientId());
+ builder.append(",gain:" + info.getGainRequest());
+ builder.append(",loss:" + info.getLossReceived());
+ builder.append(",flag:" + info.getFlags());
+ AudioAttributes attrib = info.getAttributes();
+ if (attrib != null) {
+ builder.append("," + attrib.toString());
+ }
+ return builder.toString();
}
private class SystemFocusListener extends AudioPolicyFocusListener {
@Override
public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
- Log.i(CarLog.TAG_AUDIO, "onAudioFocusGrant " + afi + " result:" + requestResult +
- " clientId:" + afi.getClientId() + " loss received:" + afi.getLossReceived());
- doHandleSystemAudioFocusGrant(afi, requestResult);
+ if (afi == null) {
+ return;
+ }
+ if (DBG) {
+ Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) +
+ " result:" + requestResult);
+ }
+ if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ synchronized (mLock) {
+ mPendingFocusChanges.addFirst(afi);
+ }
+ mFocusHandler.handleAndroidFocusChange();
+ }
}
@Override
public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
- Log.i(CarLog.TAG_AUDIO, "onAudioFocusLoss " + afi + " notified:" + wasNotified +
- " clientId:" + afi.getClientId() + " loss received:" + afi.getLossReceived());
- doHandleSystemAudioFocusLoss(afi, wasNotified);
+ if (DBG) {
+ Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) +
+ " notified:" + wasNotified);
+ }
+ // ignore loss as tracking gain is enough. At least bottom listener will be
+ // always there and getting focus grant. So it is safe to ignore this here.
}
}
/**
- * Focus listener to take focus away from android apps.
+ * Focus listener to take focus away from android apps as a proxy to car.
*/
- private class AndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
+ private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
@Override
public void onAudioFocusChange(int focusChange) {
- // nothing to do as system focus listener will get all necessary information.
+ // Do not need to handle car's focus loss or gain separately. Focus monitoring
+ // through system focus listener will take care all cases.
}
}
- private class CarAudioChangeHandler extends Handler {
+ /**
+ * Focus listener kept at the bottom to check if there is any focus holder.
+ *
+ */
+ private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ synchronized (mLock) {
+ mBottomFocusState = focusChange;
+ }
+ }
+ }
+
+ private class CarAudioFocusChangeHandler extends Handler {
private static final int MSG_FOCUS_CHANGE = 0;
private static final int MSG_STREAM_STATE_CHANGE = 1;
- private static final int MSG_VOLUME_CHANGE = 2;
+ private static final int MSG_ANDROID_FOCUS_CHANGE = 2;
+ private static final int MSG_FOCUS_RELEASE = 3;
- private CarAudioChangeHandler(Looper looper) {
+ /** Focus release is always delayed this much to handle repeated acquire / release. */
+ private static final long FOCUS_RELEASE_DELAY_MS = 500;
+
+ private CarAudioFocusChangeHandler(Looper looper) {
super(looper);
}
- private void handleFocusChange(int focusState, int streams) {
- Message msg = obtainMessage(MSG_FOCUS_CHANGE, focusState, streams);
+ private void handleFocusChange() {
+ Message msg = obtainMessage(MSG_FOCUS_CHANGE);
sendMessage(msg);
}
@@ -267,6 +731,57 @@
sendMessage(msg);
}
+ private void handleAndroidFocusChange() {
+ Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
+ sendMessage(msg);
+ }
+
+ private void handleFocusReleaseRequest() {
+ if (DBG) {
+ Log.d(TAG_FOCUS, "handleFocusReleaseRequest");
+ }
+ cancelFocusReleaseRequest();
+ Message msg = obtainMessage(MSG_FOCUS_RELEASE);
+ sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS);
+ }
+
+ private void cancelFocusReleaseRequest() {
+ removeMessages(MSG_FOCUS_RELEASE);
+ }
+
+ private void cancelAll() {
+ removeMessages(MSG_FOCUS_CHANGE);
+ removeMessages(MSG_STREAM_STATE_CHANGE);
+ removeMessages(MSG_ANDROID_FOCUS_CHANGE);
+ removeMessages(MSG_FOCUS_RELEASE);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_FOCUS_CHANGE:
+ doHandleCarFocusChange();
+ break;
+ case MSG_STREAM_STATE_CHANGE:
+ doHandleStreamStatusChange(msg.arg1, msg.arg2);
+ break;
+ case MSG_ANDROID_FOCUS_CHANGE:
+ doHandleAndroidFocusChange();
+ break;
+ case MSG_FOCUS_RELEASE:
+ doHandleFocusRelease();
+ break;
+ }
+ }
+ }
+
+ private class CarAudioVolumeHandler extends Handler {
+ private static final int MSG_VOLUME_CHANGE = 0;
+
+ private CarAudioVolumeHandler(Looper looper) {
+ super(looper);
+ }
+
private void handleVolumeChange(VolumeStateChangeEvent event) {
Message msg = obtainMessage(MSG_VOLUME_CHANGE, event);
sendMessage(msg);
@@ -275,12 +790,6 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_FOCUS_CHANGE:
- doHandleCarFocusChange(msg.arg1, msg.arg2);
- break;
- case MSG_STREAM_STATE_CHANGE:
- doHandleStreamStatusChange(msg.arg1, msg.arg2);
- break;
case MSG_VOLUME_CHANGE:
doHandleVolumeChange((VolumeStateChangeEvent) msg.obj);
break;
@@ -299,4 +808,88 @@
this.state = state;
}
}
+
+ /** Wrapper class for holding the current focus state from car. */
+ private static class FocusState {
+ public final int focusState;
+ public final int streams;
+ public final int externalFocus;
+
+ private FocusState(int focusState, int streams, int externalFocus) {
+ this.focusState = focusState;
+ this.streams = streams;
+ this.externalFocus = externalFocus;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof FocusState)) {
+ return false;
+ }
+ FocusState that = (FocusState) o;
+ return this.focusState == that.focusState && this.streams == that.streams &&
+ this.externalFocus == that.externalFocus;
+ }
+
+ @Override
+ public String toString() {
+ return "FocusState, state:" + focusState +
+ " streams:0x" + Integer.toHexString(streams) +
+ " externalFocus:0x" + Integer.toHexString(externalFocus);
+ }
+
+ public static FocusState create(int focusState, int streams, int externalAudios) {
+ return new FocusState(focusState, streams, externalAudios);
+ }
+
+ public static FocusState STATE_LOSS =
+ new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0);
+ }
+
+ /** Wrapper class for holding the focus requested to car. */
+ private static class FocusRequest {
+ public final int focusRequest;
+ public final int streams;
+ public final int externalFocus;
+
+ private FocusRequest(int focusRequest, int streams, int externalFocus) {
+ this.focusRequest = focusRequest;
+ this.streams = streams;
+ this.externalFocus = externalFocus;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof FocusRequest)) {
+ return false;
+ }
+ FocusRequest that = (FocusRequest) o;
+ return this.focusRequest == that.focusRequest && this.streams == that.streams &&
+ this.externalFocus == that.externalFocus;
+ }
+
+ @Override
+ public String toString() {
+ return "FocusRequest, request:" + focusRequest +
+ " streams:0x" + Integer.toHexString(streams) +
+ " externalFocus:0x" + Integer.toHexString(externalFocus);
+ }
+
+ public static FocusRequest create(int focusRequest, int streams, int externalFocus) {
+ switch (focusRequest) {
+ case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
+ return STATE_RELEASE;
+ }
+ return new FocusRequest(focusRequest, streams, externalFocus);
+ }
+
+ public static FocusRequest STATE_RELEASE =
+ new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
+ }
}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 604b0e1..72ef0c8 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -45,6 +45,8 @@
private final CarInfoService mCarInfoService;
private final CarAudioService mCarAudioService;
private final CarRadioService mCarRadioService;
+ private final AppContextService mAppContextService;
+
/** Test only service. Populate it only when necessary. */
@GuardedBy("this")
private CarTestService mCarTestService;
@@ -70,15 +72,19 @@
mContext = serviceContext;
mHal = VehicleHal.getInstance();
mCarInfoService = new CarInfoService(serviceContext);
+ mAppContextService = new AppContextService(serviceContext);
mCarSensorService = new CarSensorService(serviceContext);
- mCarAudioService = new CarAudioService(serviceContext);
+ mCarAudioService = new CarAudioService(serviceContext, mAppContextService);
mCarRadioService = new CarRadioService(serviceContext);
+
// Be careful with order. Service depending on other service should be inited later.
mAllServices = new CarServiceBase[] {
mCarInfoService,
+ mAppContextService,
mCarSensorService,
mCarAudioService,
- mCarRadioService };
+ mCarRadioService,
+ };
}
private void init() {
diff --git a/service/src/com/android/car/hal/AudioHalService.java b/service/src/com/android/car/hal/AudioHalService.java
index 7e1df39..94c0ce2 100644
--- a/service/src/com/android/car/hal/AudioHalService.java
+++ b/service/src/com/android/car/hal/AudioHalService.java
@@ -22,20 +22,27 @@
import com.android.car.vehiclenetwork.VehicleNetwork;
import com.android.car.vehiclenetwork.VehicleNetworkConsts;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAppContextFlag;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioExtFocusFlag;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusIndex;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusRequest;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusState;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioHwVariantConfigFlag;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioRoutingPolicyIndex;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioStreamState;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioStreamStateIndex;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioVolumeIndex;
import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfigs;
import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
import java.io.PrintWriter;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
public class AudioHalService extends HalServiceBase {
+ public static final int VEHICLE_AUDIO_FOCUS_REQUEST_INVALID = -1;
public static final int VEHICLE_AUDIO_FOCUS_REQUEST_GAIN =
VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
public static final int VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT =
@@ -49,6 +56,7 @@
return VehicleAudioFocusRequest.enumToString(request);
}
+ public static final int VEHICLE_AUDIO_FOCUS_STATE_INVALID = -1;
public static final int VEHICLE_AUDIO_FOCUS_STATE_GAIN =
VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN;
public static final int VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT =
@@ -75,23 +83,55 @@
return VehicleAudioStreamState.enumToString(state);
}
+ public static final int VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG =
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
+ public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG =
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG;
+ public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG =
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG;
+ public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG =
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
+
public static final int STREAM_NUM_DEFAULT = 0;
public interface AudioHalListener {
- void onFocusChange(int focusState, int streams);
+ /**
+ * Audio focus change from car.
+ * @param focusState
+ * @param streams
+ * @param externalFocus Flags of active external audio focus.
+ * 0 means no external audio focus.
+ */
+ void onFocusChange(int focusState, int streams, int externalFocus);
+ /**
+ * Audio volume change from car.
+ * @param streamNumber
+ * @param volume
+ * @param volumeState
+ */
void onVolumeChange(int streamNumber, int volume, int volumeState);
+ /**
+ * Volume limit change from car.
+ * @param streamNumber
+ * @param volume
+ */
+ void onVolumeLimitChange(int streamNumber, int volume);
+ /**
+ * Stream state change (start / stop) from android
+ * @param streamNumber
+ * @param state
+ */
void onStreamStatusChange(int streamNumber, int state);
}
private final VehicleHal mVehicleHal;
private AudioHalListener mListener;
- private boolean mFocusSupported = false;
- private boolean mVolumeSupported = false;
- private boolean mVolumeLimitSupported = false;
private int mVariant;
private List<VehiclePropValue> mQueuedEvents;
+ private final HashMap<Integer, VehiclePropConfig> mProperties = new HashMap<>();
+
public AudioHalService(VehicleHal vehicleHal) {
mVehicleHal = vehicleHal;
}
@@ -157,7 +197,11 @@
}
public synchronized void requestAudioFocusChange(int request, int streams) {
- int[] payload = { request, streams };
+ requestAudioFocusChange(request, streams, VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+ }
+
+ public synchronized void requestAudioFocusChange(int request, int streams, int extFocus) {
+ int[] payload = { request, streams, extFocus };
mVehicleHal.getVehicleNetwork().setIntVectorProperty(
VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, payload);
}
@@ -166,20 +210,31 @@
return mVariant;
}
+ public synchronized boolean isRadioExternal() {
+ VehiclePropConfig config = mProperties.get(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT);
+ if (config == null) {
+ return true;
+ }
+ return (config.getConfigFlags() &
+ VehicleAudioHwVariantConfigFlag.VEHICLE_AUDIO_HW_VARIANT_FLAG_PASS_RADIO_AUDIO_FOCUS_FLAG)
+ == 0;
+ }
+
+ public synchronized boolean isFocusSupported() {
+ return isPropertySupportedLocked(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS);
+ }
+
+ private boolean isPropertySupportedLocked(int property) {
+ VehiclePropConfig config = mProperties.get(property);
+ return config != null;
+ }
+
@Override
public synchronized void init() {
- if (mFocusSupported) {
- mVehicleHal.subscribeProperty(this, VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS,
- 0);
- mVehicleHal.subscribeProperty(this,
- VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE, 0);
- }
- if (mVolumeSupported) {
- mVehicleHal.subscribeProperty(this, VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME,
- 0);
- if (mVolumeLimitSupported) {
- mVehicleHal.subscribeProperty(this,
- VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT, 0);
+ for (VehiclePropConfig config : mProperties.values()) {
+ if (VehicleHal.isPropertySubscribable(config)) {
+ mVehicleHal.subscribeProperty(this, config.getProp(), 0);
}
}
try {
@@ -193,52 +248,29 @@
@Override
public synchronized void release() {
- if (mFocusSupported) {
- mVehicleHal.unsubscribeProperty(this,
- VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS);
- mVehicleHal.unsubscribeProperty(this,
- VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE);
- mFocusSupported = false;
- }
- if (mVolumeSupported) {
- mVehicleHal.unsubscribeProperty(this,
- VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME);
- mVolumeSupported = false;
- if (mVolumeLimitSupported) {
- mVehicleHal.unsubscribeProperty(this,
- VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT);
- mVolumeLimitSupported = false;
+ for (VehiclePropConfig config : mProperties.values()) {
+ if (VehicleHal.isPropertySubscribable(config)) {
+ mVehicleHal.unsubscribeProperty(this, config.getProp());
}
}
+ mProperties.clear();
}
@Override
public synchronized List<VehiclePropConfig> takeSupportedProperties(
List<VehiclePropConfig> allProperties) {
- List<VehiclePropConfig> taken = new LinkedList<VehiclePropConfig>();
for (VehiclePropConfig p : allProperties) {
switch (p.getProp()) {
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS:
- mFocusSupported = true;
- taken.add(p);
- break;
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME:
- mVolumeSupported = true;
- taken.add(p);
- break;
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT:
- mVolumeLimitSupported = true;
- taken.add(p);
- break;
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT:
- taken.add(p);
- break;
case VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE:
- taken.add(p);
+ mProperties.put(p.getProp(), p);
break;
}
}
- return taken;
+ return new LinkedList<VehiclePropConfig>(mProperties.values());
}
@Override
@@ -262,22 +294,31 @@
for (VehiclePropValue v : values) {
switch (v.getProp()) {
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS: {
- int focusState = v.getInt32Values(0);
- int streams = v.getInt32Values(1);
- listener.onFocusChange(focusState, streams);
+ int focusState = v.getInt32Values(
+ VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_FOCUS);
+ int streams = v.getInt32Values(
+ VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_STREAMS);
+ int externalFocus = v.getInt32Values(
+ VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE);
+ listener.onFocusChange(focusState, streams, externalFocus);
} break;
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME: {
- int volume = v.getInt32Values(0);
- int streamNum = v.getInt32Values(1);
- int volumeState = v.getInt32Values(2);
+ int volume = v.getInt32Values(
+ VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_VOLUME);
+ int streamNum = v.getInt32Values(
+ VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_STREAM);
+ int volumeState = v.getInt32Values(
+ VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_STATE);
listener.onVolumeChange(streamNum, volume, volumeState);
} break;
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: {
//TODO
} break;
case VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: {
- int state = v.getInt32Values(0);
- int streamNum = v.getInt32Values(1);
+ int state = v.getInt32Values(
+ VehicleAudioStreamStateIndex.VEHICLE_AUDIO_STREAM_STATE_INDEX_STATE);
+ int streamNum = v.getInt32Values(
+ VehicleAudioStreamStateIndex.VEHICLE_AUDIO_STREAM_STATE_INDEX_STREAM);
listener.onStreamStatusChange(streamNum, state);
} break;
}
@@ -288,8 +329,8 @@
public void dump(PrintWriter writer) {
writer.println("*Audio HAL*");
writer.println(" audio H/W variant:" + mVariant);
- writer.println(" focus supported:" + mFocusSupported +
- " volume supported:" + mVolumeSupported);
+ writer.println(" Supported properties");
+ VehicleHal.dumpProperties(writer, mProperties.values());
}
}
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 8f9bd67..0506b6a 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -27,6 +27,9 @@
import com.android.car.CarLog;
import com.android.car.vehiclenetwork.VehicleNetwork;
import com.android.car.vehiclenetwork.VehicleNetwork.VehicleNetworkListener;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropAccess;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropChangeMode;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType;
import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfigs;
@@ -34,6 +37,7 @@
import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValues;
import java.io.PrintWriter;
+import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@@ -208,6 +212,22 @@
return mVehicleNetwork;
}
+ public static boolean isPropertySubscribable(VehiclePropConfig config) {
+ if (config.hasAccess() & VehiclePropAccess.VEHICLE_PROP_ACCESS_READ == 0 ||
+ config.getChangeMode() ==
+ VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC) {
+ return false;
+ }
+ return true;
+ }
+
+ public static void dumpProperties(PrintWriter writer, Collection<VehiclePropConfig> configs) {
+ for (VehiclePropConfig config : configs) {
+ writer.println("property " +
+ VehicleNetworkConsts.getVehiclePropertyName(config.getProp()));
+ }
+ }
+
private final ArraySet<HalServiceBase> mServicesToDispatch = new ArraySet<HalServiceBase>();
@Override
public void onVehicleNetworkEvents(VehiclePropValues values) {
diff --git a/tests/carservice_test/src/com/android/support/car/test/AudioRoutingPolicyTest.java b/tests/carservice_test/src/com/android/support/car/test/AudioRoutingPolicyTest.java
index ddb7910..fcafbde 100644
--- a/tests/carservice_test/src/com/android/support/car/test/AudioRoutingPolicyTest.java
+++ b/tests/carservice_test/src/com/android/support/car/test/AudioRoutingPolicyTest.java
@@ -35,7 +35,8 @@
private static final long TIMEOUT_MS = 3000;
- private VehicleHalPropertyHandler mAudioRoutingPolicyHandler = new VehicleHalPropertyHandler() {
+ private final VehicleHalPropertyHandler mAudioRoutingPolicyHandler =
+ new VehicleHalPropertyHandler() {
@Override
public void onPropertySet(VehiclePropValue value) {
@@ -123,7 +124,7 @@
}
private void checkPolicy1() throws Exception {
- // write should be twice.
+ // write happens twice.
assertTrue(mWaitSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(mWaitSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
VehiclePropValue v = mEvents.get(0);
diff --git a/tests/carservice_test/src/com/android/support/car/test/CarAudioFocusTest.java b/tests/carservice_test/src/com/android/support/car/test/CarAudioFocusTest.java
new file mode 100644
index 0000000..c88d046
--- /dev/null
+++ b/tests/carservice_test/src/com/android/support/car/test/CarAudioFocusTest.java
@@ -0,0 +1,555 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.support.car.test;
+
+import java.util.LinkedList;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import com.android.car.VehicleHalEmulator.VehicleHalPropertyHandler;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioExtFocusFlag;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusIndex;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusRequest;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusState;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioStream;
+import com.android.car.vehiclenetwork.VehiclePropConfigUtil;
+import com.android.car.vehiclenetwork.VehiclePropValueUtil;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePermissionModel;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropAccess;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropChangeMode;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType;
+import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.SystemClock;
+import android.support.car.media.CarAudioManager;
+import android.test.AndroidTestCase;
+
+public class CarAudioFocusTest extends MockedCarTestBase {
+
+ private static final long TIMEOUT_MS = 3000;
+
+ private final VehicleHalPropertyHandler mAudioRoutingPolicyPropertyHandler =
+ new VehicleHalPropertyHandler() {
+ @Override
+ public void onPropertySet(VehiclePropValue value) {
+ //TODO
+ }
+
+ @Override
+ public VehiclePropValue onPropertyGet(VehiclePropValue value) {
+ fail("cannot get");
+ return null;
+ }
+
+ @Override
+ public void onPropertySubscribe(int property, int sampleRate) {
+ fail("cannot subscribe");
+ }
+
+ @Override
+ public void onPropertyUnsubscribe(int property) {
+ fail("cannot unsubscribe");
+ }
+ };
+
+ private final FocusPropertyHandler mAudioFocusPropertyHandler =
+ new FocusPropertyHandler();
+
+ private final VehicleHalPropertyHandler mAppContextPropertyHandler =
+ new VehicleHalPropertyHandler() {
+
+ @Override
+ public void onPropertySet(VehiclePropValue value) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public VehiclePropValue onPropertyGet(VehiclePropValue value) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void onPropertySubscribe(int property, int sampleRate) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void onPropertyUnsubscribe(int property) {
+ // TODO Auto-generated method stub
+
+ }
+ };
+
+ private final Semaphore mWaitSemaphore = new Semaphore(0);
+ private final LinkedList<VehiclePropValue> mEvents = new LinkedList<VehiclePropValue>();
+ private AudioManager mAudioManager;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // AudioManager should be created in main thread to get focus event. :(
+ runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ }
+ });
+
+ getVehicleHalEmulator().addProperty(
+ VehiclePropConfigUtil.getBuilder(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY,
+ VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE,
+ VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
+ VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2,
+ VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
+ 0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(),
+ mAudioRoutingPolicyPropertyHandler);
+ getVehicleHalEmulator().addProperty(
+ VehiclePropConfigUtil.getBuilder(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS,
+ VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE,
+ VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
+ VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC3,
+ VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
+ 0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(),
+ mAudioFocusPropertyHandler);
+ getVehicleHalEmulator().addProperty(
+ VehiclePropConfigUtil.getBuilder(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_APP_CONTEXT,
+ VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE,
+ VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
+ VehicleValueType.VEHICLE_VALUE_TYPE_INT32,
+ VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
+ 0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(),
+ mAppContextPropertyHandler);
+ getVehicleHalEmulator().addStaticProperty(
+ VehiclePropConfigUtil.createStaticStringProperty(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT),
+ VehiclePropValueUtil.createIntValue(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT, 1, 0));
+ getVehicleHalEmulator().start();
+ }
+
+ public void testMediaGainFocus() throws Exception {
+ //TODO update this to check config
+ checkSingleRequestRelease(
+ AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN,
+ VehicleAudioStream.VEHICLE_AUDIO_STREAM0);
+ }
+
+ public void testMediaGainTransientFocus() throws Exception {
+ checkSingleRequestRelease(
+ AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
+ VehicleAudioStream.VEHICLE_AUDIO_STREAM0);
+ }
+
+ public void testMediaGainTransientMayDuckFocus() throws Exception {
+ checkSingleRequestRelease(
+ AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+ VehicleAudioStream.VEHICLE_AUDIO_STREAM0);
+ }
+
+ public void testAlarmGainTransientFocus() throws Exception {
+ checkSingleRequestRelease(
+ AudioManager.STREAM_ALARM,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
+ VehicleAudioStream.VEHICLE_AUDIO_STREAM1);
+ }
+
+ public void testAlarmGainTransientMayDuckFocus() throws Exception {
+ checkSingleRequestRelease(
+ AudioManager.STREAM_ALARM,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+ VehicleAudioStream.VEHICLE_AUDIO_STREAM1);
+ }
+
+ public void testMediaNavFocus() throws Exception {
+ //music start
+ AudioFocusListener listenerMusic = new AudioFocusListener();
+ int res = mAudioManager.requestAudioFocus(listenerMusic,
+ AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+ assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+ assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]);
+ assertEquals(0, request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+ // nav guidance start
+ AudioFocusListener listenerNav = new AudioFocusListener();
+ AudioAttributes navAttrib = (new AudioAttributes.Builder()).
+ setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
+ setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
+ build();
+ res = mAudioManager.requestAudioFocus(listenerNav, navAttrib,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+ assertEquals(0x3, request[1]);
+ assertEquals(0, request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+ // nav guidance done
+ mAudioManager.abandonAudioFocus(listenerNav);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+ assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]);
+ assertEquals(0, request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+ // music done
+ mAudioManager.abandonAudioFocus(listenerMusic);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]);
+ assertEquals(0, request[1]);
+ assertEquals(0, request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS, request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+ }
+
+ public void testMediaExternalMediaNavFocus() throws Exception {
+ // android music
+ AudioFocusListener listenerMusic = new AudioFocusListener();
+ int res = mAudioManager.requestAudioFocus(listenerMusic,
+ AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+ assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+ assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]);
+ assertEquals(0, request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+ // car plays external media (=outside Android)
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+ 0,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG);
+ int focusChange = listenerMusic.waitAndGetFocusChange(TIMEOUT_MS);
+ assertEquals(AudioManager.AUDIOFOCUS_LOSS, focusChange);
+
+ // nav guidance start
+ AudioFocusListener listenerNav = new AudioFocusListener();
+ AudioAttributes navAttrib = (new AudioAttributes.Builder()).
+ setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
+ setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
+ build();
+ res = mAudioManager.requestAudioFocus(listenerNav, navAttrib,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK,
+ request[0]);
+ assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1, request[1]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT,
+ 0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG);
+
+ // nav guidance ends
+ mAudioManager.abandonAudioFocus(listenerNav);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]);
+ assertEquals(0, request[1]);
+ assertEquals(0, request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+ 0,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG);
+
+ // now ends external play
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+ 0,
+ 0);
+ // music picks up
+ listenerMusic.waitForFocus(TIMEOUT_MS, AudioManager.AUDIOFOCUS_GAIN);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+ assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]);
+ assertEquals(0, request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+ // now ends music
+ mAudioManager.abandonAudioFocus(listenerMusic);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]);
+ assertEquals(0, request[1]);
+ assertEquals(0, request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+ 0,
+ 0);
+ }
+
+ public void testMediaExternalRadioNavMediaFocus() throws Exception {
+ // android music
+ AudioFocusListener listenerMusic = new AudioFocusListener();
+ int res = mAudioManager.requestAudioFocus(listenerMusic,
+ AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+ assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+ assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]);
+ assertEquals(0, request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+ // android radio
+ AudioFocusListener listenerRadio = new AudioFocusListener();
+ AudioAttributes radioAttributes = (new AudioAttributes.Builder()).
+ setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).
+ setUsage(CarAudioManager.AUDIO_ATTRIBUTES_USAGE_RADIO).build();
+ res = mAudioManager.requestAudioFocus(listenerRadio,
+ radioAttributes, AudioManager.AUDIOFOCUS_GAIN, 0);
+ assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+ assertEquals(0, request[1]);
+ assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+ request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ 0,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG);
+
+ // nav guidance start
+ AudioFocusListener listenerNav = new AudioFocusListener();
+ AudioAttributes navAttrib = (new AudioAttributes.Builder()).
+ setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
+ setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
+ build();
+ res = mAudioManager.requestAudioFocus(listenerNav, navAttrib,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN,
+ request[0]);
+ assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1, request[1]);
+ assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+ request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ 0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG);
+
+ // nav guidance ends
+ mAudioManager.abandonAudioFocus(listenerNav);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN,
+ request[0]);
+ assertEquals(0, request[1]);
+ assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+ request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ 0,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG);
+
+ // ends radio. music will get the focus GAIN.
+ // Music app is supposed to stop and release focus when it has lost focus, but here just
+ // check if focus is working.
+ mAudioManager.abandonAudioFocus(listenerRadio);
+ listenerMusic.waitForFocus(TIMEOUT_MS, AudioManager.AUDIOFOCUS_GAIN);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+ assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]);
+ assertEquals(0, request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+ 0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0,
+ 0);
+
+ // now music release focus.
+ mAudioManager.abandonAudioFocus(listenerMusic);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]);
+ assertEquals(0, request[1]);
+ assertEquals(0, request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+ 0,
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+ }
+
+ private void checkSingleRequestRelease(int streamType, int androidFocus, int streamNumber)
+ throws Exception {
+ AudioFocusListener lister = new AudioFocusListener();
+ int res = mAudioManager.requestAudioFocus(lister,
+ streamType,
+ androidFocus);
+ assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+ int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ int expectedRequest = VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
+ int response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
+ switch (androidFocus) {
+ case AudioManager.AUDIOFOCUS_GAIN:
+ expectedRequest = VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
+ response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN;
+ break;
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
+ expectedRequest =
+ VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
+ response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT;
+ break;
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
+ expectedRequest =
+ VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
+ response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT;
+ break;
+ }
+ assertEquals(expectedRequest, request[0]);
+ assertEquals(0x1 << streamNumber, request[1]);
+ assertEquals(0, request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ response,
+ request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+ mAudioManager.abandonAudioFocus(lister);
+ request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+ assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]);
+ assertEquals(0, request[1]);
+ assertEquals(0, request[2]);
+ mAudioFocusPropertyHandler.sendAudioFocusState(
+ VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+ request[1],
+ VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+ }
+
+ private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
+ private final Semaphore mFocusChangeWait = new Semaphore(0);
+ private int mLastFocusChange;
+
+ public int waitAndGetFocusChange(long timeoutMs) throws Exception {
+ if (!mFocusChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
+ fail("timeout waiting for focus change");
+ }
+ return mLastFocusChange;
+ }
+
+ public void waitForFocus(long timeoutMs, int expectedFocus) throws Exception {
+ while (mLastFocusChange != expectedFocus) {
+ if (!mFocusChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
+ fail("timeout waiting for focus change");
+ }
+ }
+ }
+
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ mLastFocusChange = focusChange;
+ mFocusChangeWait.release();
+ }
+ }
+
+ private class FocusPropertyHandler implements VehicleHalPropertyHandler {
+
+ private int mState = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
+ private int mStreams = 0;
+ private int mExtFocus = 0;
+ private int mRequest;
+ private int mRequestedStreams;
+ private int mRequestedExtFocus;
+
+ private final Semaphore mSetWaitSemaphore = new Semaphore(0);
+
+ public void sendAudioFocusState(int state, int streams, int extFocus) {
+ synchronized (this) {
+ mState = state;
+ mStreams = streams;
+ mExtFocus = extFocus;
+ }
+ int[] values = { state, streams, extFocus };
+ getVehicleHalEmulator().injectEvent(VehiclePropValueUtil.createIntVectorValue(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
+ SystemClock.elapsedRealtimeNanos()));
+ }
+
+ public int[] waitForAudioFocusRequest(long timeoutMs) throws Exception {
+ if (!mSetWaitSemaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
+ fail("timeout");
+ }
+ synchronized (this) {
+ return new int[] { mRequest, mRequestedStreams, mRequestedExtFocus };
+ }
+ }
+
+ @Override
+ public void onPropertySet(VehiclePropValue value) {
+ assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, value.getProp());
+ synchronized (this) {
+ mRequest = value.getInt32Values(
+ VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_FOCUS);
+ mRequestedStreams = value.getInt32Values(
+ VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_STREAMS);
+ mRequestedExtFocus = value.getInt32Values(
+ VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE);
+ }
+ mSetWaitSemaphore.release();
+ }
+
+ @Override
+ public VehiclePropValue onPropertyGet(VehiclePropValue value) {
+ assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, value.getProp());
+ int state, streams, extFocus;
+ synchronized (this) {
+ state = mState;
+ streams = mStreams;
+ extFocus = mExtFocus;
+ }
+ int[] values = { state, streams, extFocus };
+ return VehiclePropValueUtil.createIntVectorValue(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
+ SystemClock.elapsedRealtimeNanos());
+ }
+
+ @Override
+ public void onPropertySubscribe(int property, int sampleRate) {
+ assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, property);
+ }
+
+ @Override
+ public void onPropertyUnsubscribe(int property) {
+ assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, property);
+ }
+ }
+}
diff --git a/tests/carservice_test/src/com/android/support/car/test/CarAudioTest.java b/tests/carservice_test/src/com/android/support/car/test/CarAudioTest.java
deleted file mode 100644
index 3b6c870..0000000
--- a/tests/carservice_test/src/com/android/support/car/test/CarAudioTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.support.car.test;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.test.AndroidTestCase;
-
-public class CarAudioTest extends AndroidTestCase {
- public void testFocusChange() throws Exception {
- //TODO evolve this into full features test using testmanager / mocking.
- AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- AudioFocusListener lister1 = new AudioFocusListener();
- AudioFocusListener lister2 = new AudioFocusListener();
- AudioFocusListener lister3 = new AudioFocusListener();
- int res = am.requestAudioFocus(lister1,
- AudioManager.STREAM_MUSIC,
- AudioManager.AUDIOFOCUS_GAIN);
- Thread.sleep(1000);
- res = am.requestAudioFocus(lister2,
- AudioManager.STREAM_NOTIFICATION,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
- Thread.sleep(1000);
- res = am.requestAudioFocus(lister3,
- AudioManager.STREAM_TTS,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
- Thread.sleep(1000);
- am.abandonAudioFocus(lister3);
- Thread.sleep(1000);
- am.abandonAudioFocus(lister2);
- Thread.sleep(1000);
- am.abandonAudioFocus(lister1);
- Thread.sleep(1000);
- res = am.requestAudioFocus(lister1,
- AudioManager.STREAM_MUSIC,
- AudioManager.AUDIOFOCUS_GAIN);
- Thread.sleep(1000);
- res = am.requestAudioFocus(lister2,
- AudioManager.STREAM_NOTIFICATION,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
- Thread.sleep(1000);
- res = am.requestAudioFocus(lister3,
- AudioManager.STREAM_TTS,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
- Thread.sleep(1000);
- am.abandonAudioFocus(lister2);
- Thread.sleep(1000);
- am.abandonAudioFocus(lister3);
- Thread.sleep(1000);
- am.abandonAudioFocus(lister1);
- }
-
- private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
-
- @Override
- public void onAudioFocusChange(int arg0) {
- // TODO Auto-generated method stub
- }
- }
-}
diff --git a/tests/carservice_test/src/com/android/support/car/test/MockedCarTestBase.java b/tests/carservice_test/src/com/android/support/car/test/MockedCarTestBase.java
index 502f806..abecdec 100644
--- a/tests/carservice_test/src/com/android/support/car/test/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/support/car/test/MockedCarTestBase.java
@@ -21,7 +21,9 @@
import com.android.car.VehicleHalEmulator;
import android.content.ComponentName;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.support.car.Car;
import android.support.car.ServiceConnectionListener;
import android.test.AndroidTestCase;
@@ -40,6 +42,8 @@
private VehicleHalEmulator mVehicleHalEmulator;
private final Semaphore mConnectionWait = new Semaphore(0);
+ private final Semaphore mWaitForMain = new Semaphore(0);
+ private final Handler mMainHalder = new Handler(Looper.getMainLooper());
private final ServiceConnectionListener mConnectionListener = new ServiceConnectionListener() {
@@ -84,6 +88,21 @@
return mCar;
}
+ protected void runOnMain(final Runnable r) {
+ mMainHalder.post(r);
+ }
+
+ protected void runOnMainSync(final Runnable r) throws Exception {
+ mMainHalder.post(new Runnable() {
+ @Override
+ public void run() {
+ r.run();
+ mWaitForMain.release();
+ }
+ });
+ mWaitForMain.acquire();
+ }
+
protected synchronized VehicleHalEmulator getVehicleHalEmulator() {
return mVehicleHalEmulator;
}